HackThisSite - JavaScript Mission 2

Challenge

Disable Javascript. Loading the page immediately kicks you to a fail page; the win link is hidden in the same HTML.

第二关 Disable Javascript:页面加载后立即跳转到失败页,通向通关的链接就在同一份 HTML 里。

Solution

用带登录态的会话取关卡页:

1
2
$ curl -s -b 'HackThisSite=<mission-cookie>' \
'https://www.hackthissite.org/missions/javascript/2/'

服务端返回的正文里,开头只有一条跳转脚本:

1
<script>window.location="http://www.hackthissite.org/missions/javascript/2/fail.php";</script>

同一份 HTML 的下方藏着一个锚点:

1
<a href="/missions/javascript/2/index.php?challengePass=<串>">Click here to win.</a>

为什么这段 JS 能被绕过

  • 跳转是浏览器执行的客户端行为。HTML 已经把完整的页面(包括那个 Click here to win. 链接)发下来了;脚本只是改变用户看到什么,没有改变用户收到什么。用 curl 拿原始响应,跳转脚本根本不会执行,链接直接可见。
  • challengePass 是每次加载 index.php重新生成的(实测三次分别拿到 EK@1%Ij$A9ks6LW*+I)。所以不能用第一次抓到的串过一会儿再提交:抓取和提交必须在同一个会话里、中间不重新加载关卡页,否则服务端记住的是最新那一次生成的串。
  • 关键点:真正的放行动作是那次带 challengePass 的 GET,跳转脚本本身没有做任何校验。

抓页面并立即用同一串提交

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
#!/usr/bin/env python3
"""HackThisSite JavaScript 2 ("Disable Javascript").

The level page only runs `window.location = ".../2/fail.php"`; the win link is
hidden in the same HTML. index.php regenerates the challengePass token on
every load, so fetch and submit must happen back to back in one session:

usage: HTS_COOKIE=<live-cookie> ./solve.py
"""
import os
import re
import sys
import urllib.parse
import urllib.request

BASE = "https://www.hackthissite.org/missions/javascript/2/"
COOKIE = os.environ.get("HTS_COOKIE", "<mission-cookie>")

def get(url, referer):
req = urllib.request.Request(url)
req.add_header("Cookie", "HackThisSite=" + COOKIE)
req.add_header("Referer", referer)
with urllib.request.urlopen(req) as rsp:
return rsp.read().decode("latin-1")

def main():
page = get(BASE, BASE)
match = re.search(r'challengePass=([^"&]+)', page)
if not match:
sys.exit("no challengePass token in page")
token = match.group(1)
print("token:", token)
win_url = BASE + "index.php?" + urllib.parse.urlencode({"challengePass": token})
body = get(win_url, BASE)
print("submitted:", win_url)
print(body[:200])

if __name__ == "__main__":
main()

urllib.parse.urlencode 会把串里的 @%$* 这些字符正确转义进查询串。服务端接受的等价 curl 形式是:

1
2
3
4
$ curl -s -b 'HackThisSite=<mission-cookie>' \
-e 'https://www.hackthissite.org/missions/javascript/2/' \
-G 'https://www.hackthissite.org/missions/javascript/2/index.php' \
--data-urlencode 'challengePass=EK@1%I'

和第 1 关一样,服务端靠 Referer 判断请求来自关卡页:不带 Referer 时不计完成,带上关卡页地址后即计入完成。提交后 profile 的 Javascript 列表出现本关完成标记,账号积分 6501 → 6669(七关合计)。

页面隐藏链接里的 per-load challengePass 串