# Portswigger DOM-based vulnerabilities 😣 <style>body {text-align: justify}</style> Hi, dưới đây là writeup của 7/7 bài lab về lỗ hổng [DOM-based](https://portswigger.net/web-security/dom-based) mình đã solve trong quá trình ôn tập thi chứng chỉ của Portswigger. ### 1. DOM XSS using web messages ##### Description > This lab demonstrates a simple web message vulnerability. To solve this lab, use the exploit server to post a message to the target site that causes the `print(`) function to be called. ##### Writeup Truy cập trang chủ thì xuất hiện 1 đoạn script tạo event lắng nghe và lấy trực tiếp web message ghi vào tag có chứa `id="ads"` thông qua innerHTML. ![](https://i.imgur.com/I8WxECJ.png) ```javascript <script> window.addEventListener('message', function(e) { document.getElementById('ads').innerHTML = e.data; }) </script> ``` Có thể thấy, đoạn script trên không có check nguồn cũng như validate message. Như vậy ta có thể dùng hàm `postMessage()` với message là XSS payload `<img src=1 onerror=print()` (Do dùng innerHTML). Để trigger được event trên, ta dùng iframe với nội dung như sau: ```htmlembedded <iframe src="<LAB-DOMAIN>" onload="this.contentWindow.postMessage('<img src=1 onerror=print()>','*')"> ``` Lưu vào exploit-server và thử `View exploit`, ta thấy hàm print() đã được trigger thành công. ![](https://i.imgur.com/nKYDoLZ.png) `Deliver exploit to victim` và ta solve được challenge. ![](https://i.imgur.com/Zt1l3oq.png) ### 2. DOM XSS using web messages and a JavaScript URL ##### Description > This lab demonstrates a DOM-based redirection vulnerability that is triggered by web messaging. To solve this lab, construct an HTML page on the exploit server that exploits this vulnerability and calls the `print()` function ##### Writeup Tương tự bài trên, ứng dụng tồn tại một script lắng nghe web message. Khác với bài trên, message lần này yêu cầu là 1 url phải chứa `http:` hoặc `https:` vì dùng hàm indexOf; Đồng thời, nó sẽ được gán vào `location.href` nếu thỏa điều kiện. ```javascript <script> window.addEventListener('message', function(e) { var url = e.data; if (url.indexOf('http:') > -1 || url.indexOf('https:') > -1) { location.href = url; } }, false); </script> ``` Như vậy mình có thể `postMessage()` với message: `javascript:alert("https://a")`. Lúc này, nó thỏa điều kiện check url ở trên vì có chứa `https:`, và vì dùng sink location.href nên có thể trigger các hàm js bằng scheme `javascript:`. ![](https://i.imgur.com/pZDHBSB.png) Tạo payload bằng iframe và lưu vào exploit-server. Message cụ thể: `javascript:print('https://a')` ```htmlembedded <iframe src="<LAB-DOMAIN>" onload="this.contentWindow.postMessage('javascript:print(\'https://a\')','*')"> ``` `View exploit`, ta thấy `print()` thành công. ![](https://i.imgur.com/tL45cs3.png) Gửi cho nạn nhân và ta solve được challenge. ![](https://i.imgur.com/PsUllkV.png) ### 3. DOM XSS using web messages and JSON.parse ##### Description > This lab uses web messaging and parses the message as JSON. To solve the lab, construct an HTML page on the exploit server that exploits this vulnerability and calls the `print()` function. ##### Writeup Ứng dụng tạo 1 iframe với src là `d.url` từ web message `d` sau khi được parse bằng `JSON.parse()` nếu như `d.type=="load-channel".` ```javascript <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> ``` Như vậy ta có tạo payload như sau với url `javascript:print()` để trigger XSS. ```javascript var payload = '{"type":"load-channel", "url": "javascript:print()"}' postMessage(payload) ``` Test thử trên console ta thấy `print()` đã được gọi thành công. ![](https://i.imgur.com/2O2a3DR.png) Sử dụng iframe tương tự các bài trên với payload đã tạo và lưu vào exploit-server. ```htmlembedded <iframe src="<LAB-DOMAIN>" onload='this.contentWindow.postMessage("{\"type\":\"load-channel\",\"url\":\"javascript:print()\"}","*")'> ``` `View exploit` thử thì thấy attack thành công. ![](https://i.imgur.com/jA5bl1G.png) `Deliver exploit to victim` và ta solve được challenge. ![](https://i.imgur.com/7556E8u.png) ### 4. DOM-based open redirection ##### Description > This lab contains a DOM-based open-redirection vulnerability. To solve this lab, exploit this vulnerability and redirect the victim to the exploit server. ##### Writeup Tại chức năng `Back to Blog`, xuất hiện một đoạn xử lí để lấy returnPath trước khi trả user về đường dẫn theo returnPath. ![](https://i.imgur.com/byIoCqO.png) Cụ thể, nếu như `location` chứa `url=https://...` thì returnPath sẽ được set và mình sẽ được trả về chính đường dẫn đó nếu click `Back to Blog`. Nếu không nó sẽ trả về trang chủ tại `/`. ```htmlembedded <a href='#' onclick='returnUrl = /url=(https?:\/\/.+)/.exec(location); if(returnUrl)location.href = returnUrl[1];else location.href = "/"'>Back to Blog</a> ``` Như vậy mình chỉ cần thêm tham số `url` với đường dẫn exploit-server trên url để solve challenge này. ``` https://<LAB-ID>.web-security-academy.net/post?postId=2&url=https://<EXPLOIT-ID>.exploit-server.net/ ``` Click `Back to Blog` và mình được redirect đến exploit-server. Như vậy ta solve được challenge. ![](https://i.imgur.com/1QTudLg.png) ### 5. DOM-based cookie manipulation ##### Description > This lab demonstrates DOM-based client-side cookie manipulation. To solve this lab, inject a cookie that will cause XSS on a different page and call the print() function. You will need to use the exploit server to direct the victim to the correct pages. ##### Writeup Khi xem các post về product thì mình sẽ được set 1 cookie `lastViewedProduct` để lưu đường dẫn của post product vừa xem thông qua `window.location`. ![](https://i.imgur.com/juvJ0ko.png) ![](https://i.imgur.com/vWV0Y7v.png) Sau đó, quay lại trang chủ thì thấy 1 đường link `Last viewed product` với đường dẫn được lấy từ chính cookie `lastViewedProduct`. Có vẻ như mình có thể escape và reflected XSS tại đây. ![](https://i.imgur.com/VrGRyRN.png) GET tới đường dẫn như hình chứa XSS payload để `lastViewProduct` được set. ![](https://i.imgur.com/9oRRzAx.png) Quay lại trang chủ thì thấy mình đã XSS thành công. ![](https://i.imgur.com/V4zhAa6.png) ![](https://i.imgur.com/aAOMXhx.png) Bây giờ chỉ cần tạo iframe query đến post chứa XSS print() payload. Và khi nó load thành công thì quay lại trang chủ để trigger XSS. `window.x=1` ở đây được sử dụng để tránh bị loop load trang chủ. ``` <iframe src="https://LAB-ID.web-security-academy.net/product?productId=1&'><script>print()</script>" onload="if(!window.x)this.src='https://LAB-ID.web-security-academy.net';window.x=1;"> ``` Lưu vào exploit-server và view thử thì thấy print() được thực thi. ![](https://i.imgur.com/cVdb177.png) Như vậy ta solve thành công challenge. ![](https://i.imgur.com/MZcVV3H.png) ### 6. Exploiting DOM clobbering to enable XSS ##### Description > This lab contains a DOM-clobbering vulnerability. The comment functionality allows "safe" HTML. To solve this lab, construct an HTML injection that clobbers a variable and uses XSS to call the `alert()` function. ##### Writeup Chức năng comment của ứng dụng có cho phép input dạng HTML. Test thử với tag `<h1>` ![](https://i.imgur.com/JwL0bhw.png) Có thể thấy thẻ `<h1>` đã được render thành công. ![](https://i.imgur.com/6Vjk2WU.png) Đọc source HTML thì thấy cái file `loadCommentsWithDomClobbering.js` thực hiện load các comments. ![](https://i.imgur.com/qRZWLbX.png) Truy cập vào đọc file js, ta thấy biến `defaultAvatar` được lấy từ kết quả phép OR giữa `window.defaultAvatar` với `{avatar: '/resources/images/avatarDefault.svg'}`. Sau đó nó sẽ được nối với thuộc tính `src` của thẻ `<img>` để render avatar. Có thể thấy, đoạn script này dính DOM Clobbering khi mình có thể clobber `defaultAvatar` object. ```javascript let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'} let avatarImgHTML = '<img class="avatar" src="' + (comment.avatar ? escapeHTML(comment.avatar) : defaultAvatar.avatar) + '">'; ``` Post comment với body như sau để clobber `defaultAvatar` object. ```htmlembedded <a id=defaultAvatar><a id=defaultAvatar name=avatar href='\"onerror=alert(1)//'> ``` Ta cần 2 tag `<a>` với cùng `id=defaultAvatar` và `name=avatar` để tạo 1 DOM HTML collection vì ta cần truy xuất 2 level `window.defaultAvatar.avatar`. Đồng thời `href='\"onerror=alert(1)//'` để escape `src` của tag `<img>` avatar &rarr; `src` sai dẫn đến trigger `onerror`. Gửi payload với comment. Tuy nhiên trường `href` của tag `<a>` đã bị xóa hoàn toàn. ![](https://i.imgur.com/lwlFAY7.png) Đọc kĩ lại source js, có thể thấy ứng dụng dùng DOMpurify để sanitize &rarr; `href` bị xóa. ```javascript commentBodyPElement.innerHTML = DOMPurify.sanitize(comment.body); ``` Tuy nhiên, có 1 trick dùng cho DOMPurify là sử dụng các protocol `cid`, `xmpp`, ... tại các thuộc tính url thì nó sẽ không URL encode kí tự `"`. (Xem tại [link](https://github.com/cure53/DOMPurify/blob/41766f5527233fc378b49a4d11b3ad9dc5775882/dist/purify.js#L98)). Như vậy khi ta encode `"` thành `&quot;` thì nó sẽ được decode tại runtime thành `"` và khi đi qua DOMPurify.sanitize() sẽ không bị encode &rarr; Sử dụng payload sau với trường href dùng 1 trong các scheme `cid` hay `xmpp`, ... ```htmlembedded <a id=defaultAvatar><a id=defaultAvatar name=avatar href=xmpp:&quot;onerror=alert(1)//> ``` Tiếp theo, gửi payload bằng comment 1 trước để nó clobber `defaultAvatar` object trước rồi gửi comment 2 bất kì thì avatar của comment 2 bị dính XSS theo payload đã tạo. ![](https://i.imgur.com/m9TNzfi.png) Kết quả khi load comment thì `alert()` bị trigger. ![](https://i.imgur.com/z8cnDSK.png) Như vậy ta solve được challenge. ![](https://i.imgur.com/AZtlcI7.png) ### 7. Clobbering DOM attributes to bypass HTML filters ##### Description > This lab uses the HTMLJanitor library, which is vulnerable to DOM clobbering. To solve this lab, construct a vector that bypasses the filter and uses DOM clobbering to inject a vector that calls the `print()` function. You may need to use the exploit server in order to make your vector auto-execute in the victim's browser. ##### Writeup Đọc source HTML tại trang các posts thì thấy cái file `loadCommentsWithDomClobbering.js` thực hiện load các comments và cả thư viện HTMLJanitor để sanitize input của người dùng. ![](https://i.imgur.com/bw9iGEu.png) Trong `loadCommentsWithDomClobbering.js`, xuất hiện config các tag và thuộc tính cho phép đối với input nó check. Cụ thể, nó chỉ cho phép sử dụng: - tag `input` với các thuộc tính `name, type, value` - tag `form` với thuộc tính `id` - các tag `i`, `b`, `p` không được sử dụng thuộc tính nào ```javascript let janitor = new HTMLJanitor({ tags: { input: { name: true, type: true, value: true }, form: { id: true }, i: {}, b: {}, p: {} } }); ``` Ví dụ như nội dung body của comment sẽ được clean bởi janitor theo config trên. ```javascript commentBodyPElement.innerHTML = janitor.clean(comment.body); ``` Ta test thử với comment thỏa mãn config của janitor. ``` <form id=test><input name=button type=button value=Click> ``` Có thể thấy 1 form chứa input button được render tại comment. Tuy nhiên khi dùng các thuộc tính ngoài config trên thì sẽ bị janitor filter đi. ![](https://i.imgur.com/ph248Nf.png) Nó thực hiện sanitize thông qua hàm `clean()`. Hàm `clean()` sẽ gọi đến `_sanitize()` ![](https://i.imgur.com/KAGf9dy.png) Tại hàm `_sanitize()`, nó thực hiện sanitize đến node firstChild trước và loop qua các attributes của node, nếu nó không nằm trong whitelist (theo config trên) thì sẽ bị remove. ```javascript // 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; } } // Sanitize children this._sanitize(document, node); ``` Tuy nhiên, mình có thể DOM clobbering thuộc tính `attributes` &rarr; `node.attributes.length` bị undefined &rarr; các thuộc tính ngoài config sẽ không bị filter. Payload sử dụng như sau: ```htmlembedded <form id=exp tabindex=1 onfocus=print()><input id=attributes> ``` Bằng cách sử dụng thẻ `input` là firstChild của `form` và `id=attributes` tại tag `input`, tga sẽ bypass được janitor. Comment với payload. ![](https://i.imgur.com/ciDxEve.png) Focus đến comment bằng `#exp` (`id` của `form`), sự kiện `onfocus` được trigger &rarr; `print()` được gọi thành công. ![](https://i.imgur.com/leBAJzh.png) Sử dụng iframe để gửi đến nạn nhân đường dẫn và đợi đến khi load comments xong thì tự động focus tới `#exp`. ![](https://i.imgur.com/6CNtiKS.png) Gửi cho nạn nhân và ta solve được challenge. ![](https://i.imgur.com/d3sWBMu.png) ## Summary ![](https://i.imgur.com/wOcOmFw.png) - **Source:** Dangerous input data from attacker - `location.search, location.hash, document.referer, document.cookie, ...` - Web message - **Sink:** Dangerous JS function or DOM object - `eval(), document.body.innerHTML, document.write()` Fundamentally, **DOM-based vulnerabilities** arise when a website passes data from a source to a sink, which then handles the data in an unsafe way in the context of the client's session. **&rarr; Taint-flow vulnerabilities** - avoid allowing data from any untrusted source to dynamically alter the value that is transmitted to any sink **Web message** - Impact: depends on the destination document's handling of the incoming message - The `window.postMessage(message, targetOrigin)` method safely enables cross-origin communication between `window objects`; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it. **DOM-based open-redirection vulnerabilities** - avoid dynamically setting redirection targets using data that originated from any untrusted source. **DOM Clobbering** ###### tags: `portswigger`, `dom-based`, `client-side`