HackThisSite - JavaScript Mission 7
Challenge
JS Obfuscation. FTW!
Find the password:
索引名
JS Obfuscation. FTW!,难度 moderate。密码比较逻辑藏在一个运行时由十六进制字符串数组加String.fromCharCode(...)生成出来的<button>里。要先把混淆还原出来。
Solution
- 关卡页
https://www.hackthissite.org/missions/javascript/7/,页面源码里只有一行被混淆的脚本(另有 jQuery 的 CDN 标签)。 - 看不到可见的密码框逻辑:那个
Check Password按钮本身是脚本用document.write(...)运行时写进页面的。 - 那一行叠加了两类混淆:
["\x66\x72..."]这种\xNN字符串数组藏标识符名,String.fromCharCode(0x3c,0x62,...)这种十六进制码点数组藏 HTML。
拉取页面源码:
1 | $ curl -s -b "$HTS_COOKIE" \ |
1 | var _0x4e9d=["\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65","\x77\x72\x69\x74\x65"];document[_0x4e9d[0x1]](String[_0x4e9d[0x0]](0x3c,0x62,0x75,0x74,0x74,0x6f,0x6e,0x20,0x6f,0x6e,0x63,0x6c,0x69,0x63,0x6b,0x3d,0x27,0x6a,0x61,0x76,0x61,0x73,0x63,0x72,0x69,0x70,0x74,0x3a,0x69,0x66,0x20,0x28,0x64,0x6f,0x63,0x75,0x6d,0x65,0x6e,0x74,0x2e,0x67,0x65,0x74,0x45,0x6c,0x65,0x6d,0x65,0x6e,0x74,0x42,0x79,0x49,0x64,0x28,0x22,0x70,0x61,0x73,0x73,0x22,0x29,0x2e,0x76,0x61,0x6c,0x75,0x65,0x3d,0x3d,0x22,0x6a,0x30,0x30,0x77,0x31,0x6e,0x22,0x29,0x7b,0x61,0x6c,0x65,0x72,0x74,0x28,0x22,0x59,0x6f,0x75,0x20,0x57,0x49,0x4e,0x21,0x22,0x29,0x3b,0x77,0x69,0x6e,0x64,0x6f,0x77,0x2e,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x2b,0x3d,0x20,0x22,0x3f,0x6c,0x76,0x6c,0x5f,0x70,0x61,0x73,0x73,0x77,0x6f,0x72,0x64,0x3d,0x22,0x2b,0x64,0x6f,0x63,0x75,0x6d,0x65,0x6e,0x74,0x2e,0x67,0x65,0x74,0x45,0x6c,0x65,0x6d,0x65,0x6e,0x74,0x42,0x79,0x49,0x64,0x28,0x22,0x70,0x61,0x73,0x73,0x22,0x29,0x2e,0x76,0x61,0x6c,0x75,0x65,0x7d,0x65,0x6c,0x73,0x65,0x20,0x7b,0x61,0x6c,0x65,0x72,0x74,0x28,0x22,0x57,0x52,0x4f,0x4e,0x47,0x21,0x20,0x54,0x72,0x79,0x20,0x61,0x67,0x61,0x69,0x6e,0x21,0x22,0x29,0x7d,0x27,0x3e,0x43,0x68,0x65,0x63,0x6b,0x20,0x50,0x61,0x73,0x73,0x77,0x6f,0x72,0x64,0x3c,0x2f,0x62,0x75,0x74,0x74,0x6f,0x6e,0x3e)); |
拆开看:
_0x4e9d是一个字符串数组,两项都用\xNN转义写出:\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65解出来是fromCharCode,\x77\x72\x69\x74\x65解出来是write。- 于是
document[_0x4e9d[0x1]]就是document.write,String[_0x4e9d[0x0]]就是String.fromCharCode。 String.fromCharCode(0x3c,0x62,...)的那一串十六进制参数,正是被写进页面的 HTML 每个字符的码点。
把标识符数组和码点数组都换成真实形态,就能拿到
document.write 真正写出的按钮
HTML。脚本对三类字符串分别解码,最后把 HTML 里的比较字面量抽出来:
1 | #!/usr/bin/env python3 |
1 | $ uv run python decode7.py |
还原出的按钮 HTML 里,成功分支是:
1 | if (document.getElementById("pass").value=="j00w1n") { |
比较的字面量即密码(见文末 spoiler)。注意这一关用的是
window.location +=(在当前 URL 上追加 query),而 5、6
关是整体赋值。写法不同,效果一样,都是把密码拼进
lvl_password 参数。
curl 复现必须带关卡 Referer:
1 | $ curl -s -o /dev/null -w '%{http_code}\n' -b "$HTS_COOKIE" \ |
接受证据:个人资料列出
Javascript: (1) (2) (3) (4) (5) (6) (7),积分为通关前的
6501 升到 Master (6669 Points)。
状态:verified(live 通关并已计入 profile)。
Vulnerabilities
这一关只做了混淆:\xNN
字符串数组隐藏标识符名,String.fromCharCode(...) 隐藏
HTML,但两者都是可逆的常量编码,不是加密:脚本在运行时无论如何都要把它们还原成明文才能
write
和比较,所以明文必然在同一份源码里可被还原。在浏览器里把那一行写入控制台、或在页面渲染完后查看
DOM,都能直接看到按钮 HTML 和
j00w1n。混淆只能拖慢人工阅读,挡不住任何自动化或一次性的还原脚本。修复方向:把比较放服务端,客户端最多提交不可逆校验值;如果必须客户端比较,也应使用服务端下发的随机挑战值,而不是一个可离线还原的静态常量。