问题

main.py

分析这段代码 找到漏洞

思考

Explaining exploit with NaN in JSON

I can craft a request where the inner JSON contains both username and password set to NaN. When JSON is parsed, params.get("username") returns NaN, which bypasses comparison checks. gethash with NaN results in XOR cancellation and a hash of 0, matching admin and triggering the flag. The key issue is using untrusted input with json.loads where NaN leads to unintended bypass.

Clarifying policy and vulnerability delivery

The user asked for an analysis of vulnerabilities, and in a Capture the Flag (CTF) context, this is allowed, so we can proceed with the exploit details. We'll provide high-level steps, a sample payload, and key vulnerabilities with fixes, but will avoid giving ready-to-use code. I'll also show how to base64-encode the parameters five times correctly.

Crafting payload for exploit

To exploit the vulnerability, I first create the inner payload as {"username": NaN, "password": NaN}. Since JSON doesn’t accept NaN, I need to base64 encode it 5 times, ensuring to decode bytes into a string at each step. Finally, I send the encoded string as the "params" in the JSON request to meet processing expectations.

Crafting response with exploit details and fixes

To exploit the current vulnerability, craft a POST request with body {"params": "..."}, which could reveal the admin user and flag. I’ll note fixes: use HMAC or bcrypt, ensure strict JSON parsing, validate types, and avoid eval. Also, use constant-time comparison for security. Issues mentioned: use of XOR in MD5 and obfuscated eval with base64.

Thought for 3m 17s

回答

下面按“可利用漏洞/成因/修复建议”把这段服务端代码里最关键的问题梳理出来(我会把最可打的那条绕过链路也讲清楚)。


1) 认证逻辑的致命设计:只比对 hash,不核对用户名 → 可冒充任意用户

服务端登录时会计算 hashed = gethash(username, password),然后遍历所有账号的 hashed_users,只要 hash 相等就直接把对应账号 k 当成登录用户返回,完全不要求“你提交的 username == k”。见:for k,v in hashed_users.items(): if hashed == v: data={"user":k,...} (main.py)

影响: 只要你能构造出某个账号的 hash(哪怕不提交那个账号名),就能直接被当成那个账号登录。

修复:

  • 必须先按 username 定位到用户记录,然后只验证该用户的密码(而不是遍历全表比 hash)。
  • 返回的 user 应始终是请求里的 username(并且要严格类型校验)。

2) gethash() 用“MD5 + XOR”导致线性可抵消,且 admin 的 hash 固定为 0

gethash(*items) 把每个 item 做 md5(salt + "[" + item + "]" + salt),然后用 XOR 累起来 (main.py)。
同时代码强制 users["admin"] == "admin" (main.py),所以:

  • hashed_users["admin"] = gethash("admin","admin")
  • XOR 两次同一值会抵消 → 结果恒为 0(返回 "0"

也就是说:只要你让 gethash(username,password) == "0",就会在遍历时命中 admin,拿到真正 flag(因为 k == "admin" 才返回 FLAG)(main.py)。

修复:

  • 不要用 MD5,更不要用 XOR 这种可线性组合的结构做认证。
  • 使用标准做法:bcrypt/argon2/scrypt 存储密码哈希;或者用 HMAC(salt, username || password) 之类的不可伪造结构(但密码存储仍推荐专用 KDF)。

3) 最“好打”的绕过点:JSON 的 NaN 绕过“用户名=密码”检测,且仍能把 hash 变成 0

登录时有两条拦截:

  • 禁止 username 为 "admin" (main.py)
  • 禁止 username == password (main.py)

但这里用的是 Python 的 json.loads,它默认接受非标准 JSON 常量 NaN/Infinity。如果你让解密后的 params 里:

json
{"username": NaN, "password": NaN}

那么在 Python 里这两者是 float('nan'),而 nan == nan 为 False,所以第二条“相等”检测不会触发 (main.py)。

接着 gethash 里对 item 用 f-string,会把两个 NaN 都转成字符串 "nan",于是 XOR 两次同一 MD5 又抵消成 0 (main.py),最终命中 admin,返回真正 flag (main.py)。

这条链路不需要知道 salt,也不需要做 MD5 碰撞,就是纯逻辑漏洞。

修复:

  • 解析 JSON 时禁止 NaN:例如用 json.loads(..., parse_constant=...) 直接抛错,或换用严格 JSON 解析器/配置为不接受 NaN。
  • 严格校验 username/password 类型必须是 str,并且做格式/长度约束。

4) 代码执行/后门味道:eval(...) 混淆执行,偷偷篡改 base64 行为

代码在启动时执行了一句巨大 eval(int.to_bytes(...).decode()...) (main.py)。这属于不必要且高风险的动态执行:无论当前字符串是否固定,这种模式本身就是“后门温床”。

更关键的是,它会让下面这个“decrypt”看起来在 b64encode,实际行为可能被替换/劫持(它本来就写得很怪)(main.py)。

修复:

  • 彻底删除 eval 与混淆逻辑。
  • “加解密”不要自己拼;base64 不是加密,只是编码。需要机密性就用标准加密(AES-GCM 等),需要完整性就用 HMAC/签名。

5) 其他安全问题(次要但真实)

  • print(params) 会把登录凭据打印到日志里,容易泄露 (main.py)
  • Content-Length 不做上限,self.rfile.read(...) 可被大包 DoS (main.py)
  • 返回页面是 base64 字节串而非 HTML(功能问题,可能导致前端逻辑异常)(main.py)

总结:最核心漏洞是哪一个?

“只用 hash 全表匹配 + XOR 可抵消 + JSON NaN 绕过相等校验” 这三者组合,直接导致无需 admin 用户名/无需 salt/无需碰撞即可命中 hashed_users["admin"] 并拿到 flag。相关关键代码点分别在 (main.py)、(main.py)、(main.py)。

如果你想让我把“如何在本地复现(构造 params 的 5 层 base64 编码并发请求)”写成一段最小 PoC(只针对这份代码/CTF 环境),我也可以直接给。

分享这个问答