Xảy ra khi code JavaScript ở phía client lấy dữ liệu từ một nguồn mà attacker có thể kiểm soát được chẳng hạn như URL và truyền vào một “sink” (lỗ thoát) mà được dùng để thực thi mã tự động chẳng hạn như eval() hoặc innerHTML.
Attacker sẽ gửi URL có chứa mã độc cho người dùng để họ gửi request tương tự với reflected XSS.
Minh họa:
How to Test for DOM-based Cross-site Scripting
Để test DOM XSS trong trường hợp payload được chèn vào HTML thì ta cần gửi một chuỗi ngẫu nhiên gồm chữ cái và số vào một entry point và dùng developer tools của trình duyệt để xem chuỗi đó được chèn vào đâu ở trong HTML.
Trong trường hợp payload được truyền vào một hàm JavaScript để thực thi thì ta cần dùng debugger của trình duyệt để xem payload được xử lý như thế nào.
Khi có được vị trí chèn dữ liệu thì ta sẽ xem xét đến các ngữ cảnh xung quanh. Ví dụ, nếu chuỗi truyền vào được bọc ở trong một cặp nháy đơn thì ta cần dùng cặp nháy kép trong payload để thoát ra khỏi cặp nháy đơn đó.
Attention
Các trình duyệt chẳng hạn như Chrome, Firefox, và Safari sẽ URL encode giá trị của location.search (query string) hoặc location.hash (fragment). Trong khi đó, IE và Edge lại không URL encode các giá trị này. Nếu payload của chúng ta bị URL encode thì sẽ không thực hiện được DOM XSS attack.
Tip
Trình duyệt của Burp có extension giúp tìm và khai thác các lỗ hổng DOM XSS dễ dàng hơn.
Exploiting DOM XSS with Different Sources and Sinks
Sink document.write có thể hoạt động với các thẻ <script> nên ta có thể xây dựng payload như sau:
Lab: DOM XSS in document.write Sink Using Source location.search
Lab này sử dụng sink document.write để ghi dữ liệu từ location.search (có thể được kiểm soát thông qua URL) vào trang web.
Request có truy vấn có dạng như sau:
Response có đoạn script sau:
Dữ liệu được chèn vào HTML như sau:
Dùng payload như sau:
Sau khi script trên được thực thi thì HTML sinh ra có dạng như sau:
Lab: DOM XSS in document.write Sink Using source location.search Inside a Select Element
Lab này cũng tương tự lab trước: sử dụng document.write để ghi dữ liệu vào HTML từ location.search.
Xem thông tin chi tiết của một sản phẩm:
Response có đoạn script sau:
Nếu không dùng query param storeId thì HTML được sinh ra từ đoạn script trên có dạng như sau:
Nếu dùng query param storeId thì nội dung của nó sẽ được chèn vào một thẻ <option selected>. Ví dụ, nếu storeId=Paris thì HTML sinh ra từ script sẽ là:
Thử dùng payload sau cho query param storeId:
Khai thác thành công.
Lab: DOM XSS in innerHTML Sink Using Source location.search
Sink innerHTML không chấp nhận element <script> nên ta cần dùng các element khác chẳng hạn như <img> hoặc <iframe> kết hợp với các sự kiện onload và onerror. Ví dụ:
Lab này tồn tại lỗ hổng DOM-XSS ở chức năng tìm kiếm blog. Nó sử dụng innerHTML để thay đổi nội dung HTML của một thẻ <span> với dữ liệu lấy từ location.search.
Request tìm kiếm:
Response có đoạn script sau:
Chuỗi hello trong query param search sẽ được chèn vào thẻ <span> có id là searchMessage như sau:
Dùng payload sau cho query param search:
Khai thác thành công.
Sources and Sinks in Third-party Dependencies
Các sink và source từ các thư viện của bên thứ ba cũng có thể dùng để tấn công DOM-XSS.
Lab: DOM XSS in jQuery Anchor href Attribute Sink Using location.search Source
Nếu dữ liệu được đọc từ một nguồn mà user có thể kiểm soát chẳng hạn như URL rồi được truyền vào trong hàm thay đổi DOM element (chẳng hạn như hàm attr() của jQuery giúp thay đổi thuộc tính của element) thì ta có thể triển khai DOM-XSS attack. Ví dụ, xét đoạn script sau:
Chúng ta có thể truyền vào query param returnUrl payload như sau:
?returnUrl=javascript:alert(document.domain)
Khi đó, bất cứ khi nào người dùng click vào link có ID là #backLink thì payload sẽ được thực thi.
Info
Cú pháp javascript:<script> là để chạy script thông qua URL. Cụ thể, nếu ta gõ javascript:alert(1) vào URL của trình duyệt thì sẽ có hộp thoại hiện ra. Lưu ý là không thể thực hiện copy và paste chuỗi này để thực thi.
Lab này có lỗ hổng trong DOM-XSS trong chức năng submit feedback.
Truy vấn đến trang gửi feedback với returnPath=/:
Response trả về có đoạn script sau:
Dùng payload sau cho query param returnPath:
javascript:alert(1)
Lab: DOM XSS in jQuery Selector Sink Using a hashchange Event
Attacker cũng có thể tấn công DOM-XSS thông qua hàm $() của jQuery kết hợp với giá trị của location.hash. Cụ thể, ngoài truy vấn element thì hàm $() còn có thể tạo element nếu đối số đầu tiên truyền vào có chứa một chuỗi trông giống như code HTML (có thể có các prefix hoặc suffix xung quanh). Ví dụ:
Trong đoạn code trên, jQuery sẽ cố gắng tạo ra một element <img>. Mà do thuộc tính src của thẻ <img> xảy ra lỗi nên script trong attribute onerror sẽ được thực thi.
Ví dụ, đoạn code bên dưới được dùng để cuộn trang đến vị trí của element tương ứng khi fragment trong URL thay đổi.
Dễ thấy, để tấn công DOM-XSS, attacker sẽ chèn payload vào fragment của URL như sau:
/#<img src=1 onerror=alert(1)/>
Tuy nhiên, attacker cần trigger được sự kiện hashchange thì client-side script mới chạy payload. Để làm được điều này, attacker sẽ sử dụng một cross-site request có thuộc tính onload giúp thay đổi fragment như sau:
Lab này tồn tại lỗ hổng DOM-XSS ở trang chủ. Để hoàn thành, ta cần gọi hàm print() (dùng để in trang web) trong trình duyệt của nạn nhân.
Request đến trang chủ:
Response có đoạn script sau:
Đoạn script sẽ cuộn màn hình đến các element các thẻ <h2> bên trong thẻ <section class="blog-list"> mà có nội dung chứa giá trị của location.hash.
Thử chạy script sau trên console:
Kết quả: có menu in hiện lên. Như vậy, ta có thể khai thác DOM-XSS nếu trigger được sự kiện hashchange với fragment là <img src=1 onerror=print()>.
Dùng payload sau ở trên exploit server:
Lab: DOM XSS in AngularJS Expression with Angle Brackets and Double Quotes HTML-encoded
Đối với trang web sử dụng AngularJS, nếu element có thuộc tính ng-app thì nó có thể được dùng để thực thi script. Ví dụ:
Kết quả sẽ là:
Attacker có thể chèn payload vào cặp dấu ngoặc nhọn ({{}}) để thực thi script.
Lab này tồn tại lỗ hổng trong tính năng tìm kiếm. Mục tiêu là gọi hàm alert.
Dữ liệu của user có trong response (reflected XSS) có thể được chèn vào DOM bởi client-side script. Điều này khiến cho reflected XSS có thể được dùng để khai thác DOM XSS. Ví dụ:
Mục tiêu của lab này là gọi hàm alert.
Gửi request để tìm kiếm với từ khóa là hello:
Response có một đoạn script như sau:
Hàm search trong searchResults.js có một đoạn như sau:
Request gửi đến /search-results thông qua Ajax có response như sau:
Thử dùng payload là "alert(1) thì response là:
Có thể thấy, ký tự " bị escape.
Thêm \ vào trước ký tự " thì ta được response sau:
Tiếp tục thêm ký tự } để đóng object và ký tự ; để ngăn cách câu lệnh. Ngoài ra, ta cũng cần comment chuỗi "} ở cuối. Payload hoàn chỉnh:
\"};alert(1)//
Gửi request và hàm alert được thực thi bởi hàm eval.
Tip
Thực thi thử chuỗi truyền vào hàm eval để xây dựng payload.
Lab: Stored DOM XSS
Client-side script cũng có thể chèn payload được lưu trong database vào DOM. Ví dụ:
Mục tiêu của lab này là thực thi hàm alert.
Đăng một comment với nội dung là hello lên post 1:
Request truy vấn post 1:
Response có đoạn script sau:
Hàm loadComments thực hiện truy vấn danh sách các comment thông qua Ajax:
Request truy vấn danh sách comment:
Response:
Trong hàm loadComments có hàm escapeHTML dùng để filter ký tự < và >:
Tìm được một chỗ mà có sử dụng hàm escapeHTML trước khi gán cho innerHTML:
Kiểm tra tài liệu về hàm replace của kiểu String trong JavaScript thì thấy nó chỉ replace 1 lần duy nhất nếu đối số đầu tiên là kiểu chuỗi:
Xây dựng payload như sau:
<><script>alert(1)</script>
Tuy nhiên, ký tự / bị escape trong kết quả trả về của endpoint /post/comment: