---
# System prepended metadata

title: File upload vulnerabilities
tags: [PortSwigger, VCS]

---

# File upload vulnerabilities
## What is File upload vulnerabilities?
**File upload vulnerabilities** là lỗ hổng khi một máy chủ web cho phép người dùng upload file lên hệ thống của nó mà không xác thực đầy đủ những thứ như tên, loại, nội dung hoặc kích thước của chúng. Không thực thi đúng các hạn chế đối với những điều này có thể có nghĩa là ngay cả chức năng tải lên hình ảnh cơ bản cũng có thể được sử dụng để tải lên các tệp tùy ý và có khả năng nguy hiểm thay thế. Điều này thậm chí có thể bao gồm các script cho phép thực thi mã từ xa.

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

## How do File upload vulnerabilities arise?
Lỗ hổng File upload thường phát sinh do việc kiểm soát không chặt chẽ đối với tệp được tải lên máy chủ. Các nguyên nhân như
- **Không kiểm soát quyền truy cập**: Nếu file tải lên có thể được truy cập công khai cũng có thể là nguyên nhân để lợi dụng tấn công
- Thiếu xác thực loại tệp (**File Type Validation Bypass**): Attacker file type bằng cách thay đổi header `Content-Type` hoặc sử dụng file `.htaccess` để cho phép thực thi script.
- File **upload được lưu ở folder có quyền thực thi** ví dụ như upload lên `/var/www/html/uploads/`, attacker có thể truy cập trực tiếp để kích hoạt file này
- **Không kiểm tra nội dung tệp**: Mặc dù file là `avatar.jpg` nhưng nội dung thật của file là điều quan trọng. Attacker dùng `polyglot files` (file chứa nhiều định dạng hợp lệ, như `.jpg+PHP`) hay thay đổi magic bytes để bypass file thật của nó

## How do File upload vulnerabilities work?
```php=
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $upload_dir = __DIR__ . '/uploads/';
    $file_name = basename($_FILES['file']['name']);
    $target_path = $upload_dir . $file_name;

    if (move_uploaded_file($_FILES['file']['tmp_name'], $target_path)) {
        echo "File uploaded to: $target_path";
    } else {
        echo "Upload failed.";
    }
}
?>
```
Phía trên là đoạn code xảy ra lỗ hổng **File upload**, tại dòng code số 7  không kiểm tra extension và MIME/client-side của file 

Attacler upload file `shell.php`. Server nhận file thông qua `$_FILES` sau đó được gán trực tiếp vào biến `$target_path`. Ngay lúc này, nó sẽ được chuyển đến `/uploads/` bằng `$upload_dir`. Nếu trang web được truy cập công khai (web-accessible) thì file `shell.php` có thể chạy khi truy cập

## How to find vuln
### Black-box Testing
- Xác định ngôn ngữ mà backend đang sử dụng (PHP, ASP.NET, Node.js, v.v.).
- Tìm tất cả các chức năng trong ứng dụng cho phép upload file.
	+ Upload file hợp lệ và kiểm tra khả năng truy cập
	+ Kiểm tra xem có thể truy cập trực tiếp hoặc thực thi file đó hay không.
- Thử tải lên một webshell 
	+ Kiểm tra xác thực fil Content-Type.
	+ Kiểm tra blacklist có thực sự chặn hết không 
- Bypass extension bằng các kỹ thuật như:
	+ Obfuscation (encode URL, nullbyte, space, Unicode).
- Upload file mã độc nhưng có extension hợp lệ (ví dụ: `.jpg` chứa PHP code).
- Kiểm tra phân tích file có thể bị đánh lừa bằng magic bytes hoặc polyglot files không

### White-box Testing
-  Review source code để tìm các đoạn code xử lý file upload.
-  Xem xét kỹ thuật xác thực tệp:
	+ Không kiểm tra extension hoặc kiểm tra không đầy đủ.
	+ Xác thực chỉ dựa vào `Content-Type` của request.
	+ Không kiểm tra nội dung thực tế của tệp.
- Kiểm tra khả năng thực thi code nếu tải lên webshell thành công.

## Detecting File upload attacks
- Người dùng tải lên số lượng lớn tệp trong thời gian ngắn
- Tên file chứa các ký tự đáng ngờ, như cố gắng vượt qua blacklist bằng cách sử dụng extension viết hoa (`.PhP`) hoặc kết hợp với các kỹ thuật khác như [SQLi](https://hackmd.io/@27v/rJszS1J3Jx), [Command Injection](https://hackmd.io/@27v/rJpgZgZpyx?authuser=0)

## How to Prevent File upload
- Đối với bảo vệ file upload ta nên ưu tiên **dùng whitelist hơn blacklist**. Việc đoán những extension mà mình cho phép dễ dàng hơn nhiều so với việc đoán xem attacker đang cố tải lên.
- Chắc chắn rằng tên file không chứa bất kỳ substrings nào có thể được hiểu là `../`.
- Sử dụng các hàm random để file name được upload lên khác với file được lưu trữ nhằm tránh việc gây xung đột trong file cũng như là để attacker dễ thao túng file
- Sử dụng WAF

# Practice lab
## Remote code execution via web shell upload
### Target Goal
Bài lab có lỗ hổng ở chức năng upload ảnh. Nó không có cơ chế validate gì trước khi lưu trữ trên server

Để giải bài lab, upload một con webshell bằng php để đọc nội dung file tại `/home/carlos/secret`

Credential: `wiener:peter`

### Analysis and exploit
Trước tiên, để đọc dược 1 file bằng php , ta sẽ dùng hàm `file_get_contents` như sau
```
<?php echo file_get_contents('/home/carlos/secret'); ?>
```

Lưu file này thành `shell.php` và quay lại với trang web để tìm chỗ có thể upload nào. Khi view 1 post bất kì trên trang web và đi đến cuối trang, xuất hiện phần đăng comment

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

Log in vào tài khoản `wiener` được cung cấp và cũng xuất hiện 1 chỗ có thể đăng avatar. Chọn 1 file `.png` bất kì để thử upload lên

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

Khi reload lại trang profile, xuất hiện 1 request GET như sau

![image](https://hackmd.io/_uploads/rkPSAS-Tkx.png)

Có lẽ đây chính là cách trang web load avatar khi user đăng nhập vào. Tiếp theo, ta thử upload file `shell.php` trước đó vào avatar và view request GET tới `/files/avatar/shell.php` 

![image](https://hackmd.io/_uploads/B1sd0H-6kg.png)

## Web shell upload via Content-Type restriction bypass
### Target Goal
Bài lab có lỗ hổng ở chức năng upload ảnh. Nó cố gắng ngăn người dùng tải lên các loại tệp bất ngờ, nhưng dựa vào việc kiểm tra đầu vào có thể kiểm soát người dùng để xác minh điều này.

Để giải bài lab, upload một con webshell bằng php để đọc nội dung file tại `/home/carlos/secret`

Credential: `wiener:peter`

### Analysis and exploit
Sau khi đăng nhập ta vẫn được phép upload 1 file bất kì làm avatar

Lần này trang web đã từ chối do type la `applcation/octet-stream` mà định dạng cho phép là `image/png`

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

Để ý có 1 dòng được gọi là `Content-Type` và giá trị ở đây là `application/octet-stream`, từ thông tin trước đó, ta đã biết trang web chỉ chấp nhận `Content-Type: image/png` hoặc `image/jpg`, do đó mình sẽ sửa chỗ này thành 1 trong 2 trước khi forward đến mục tiêu

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

Request lần này được thông qua mà không bị block, giống bài Lab trước, file được upload lên vẫn lưu ở `file/avatar`. Gửi request GET tới địa chỉ `files/avatar/exploit.php`

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

## Web shell upload via path traversal
### Target Goal
Bài lab có lỗ hổng ở chức năng upload ảnh, ta sẽ lợi dụng lỗ hổng thứ - Path traversal để khai thác lỗ hổng này

Để giải bài lab, upload một con webshell bằng php để đọc nội dung file tại `/home/carlos/secret`

Credential: `wiener:peter`

### Analysis and exploit
Upload thử 1 shell code ta thấy được:
- Vẫn cho phép upload file php tuy nhiên khi mở file thì server sẽ chuyển file sang text
- Vậy ở ngoài folder `avatar` thì nó có thực thi được không?
![image](https://github.com/vanniichan/Portswigger/assets/112863484/bac20801-4283-41d8-945b-7eda40ae61ed)

Dùng `../` để thoát khỏi thư mục avatar

![image](https://github.com/vanniichan/Portswigger/assets/112863484/19fea078-c2e9-4cfb-9a1a-ddebab4e57ef)

Respone vẫn trả về file php nên thử URL encode `%2e%2e%2fshell.php`

![image](https://github.com/vanniichan/Portswigger/assets/112863484/cdb8eb57-d790-42a9-bc3c-7debe1c16a33)
  
Nên thử URL encode `%2e%2e%2fshell.php`

![image](https://github.com/vanniichan/Portswigger/assets/112863484/4a9dcee8-1029-4d57-9d89-2912104f9c9d)
Sau khi upload thành công vào, bắt file GET để chạy xem có được không, chú ý request line phải sửa

![image](https://github.com/vanniichan/Portswigger/assets/112863484/1f0f6663-3090-4b50-a7e1-64ecc7b03024)

## Web shell upload via obfuscated file extension
### Target Goal
Bài lab có lỗ hổng ở chức năng upload ảnh. Một số extension định được đưa vào blacklist, nhưng có thể bypass bằng cách sử dụng một kỹ thuật obfuscation cổ điển

Để giải bài lab, upload một con webshell bằng php để đọc nội dung file tại `/home/carlos/secret`

Credential: `wiener:peter`

### Analysis and exploit
Request đã bị chặn bởi trang web

![image](https://hackmd.io/_uploads/rkVrWL-Tyx.png)

Chỉ được phép upload files JPG và PNG -> Trang web đang filter bằng file extension. Chúng ta phải tìm cách cho file chứa payload kết thúc bằng `.jpg` hoặc `.png` thay vì `.php`. Nhưng khi đổi extension thành file hỉnh ảnh thì đoạn code cũng sẽ không thể nào thực thi được.

Do đó, ở đây ta sẽ dùng Null Bytes để bypass cơ chế này. Cụ thể là khi upload file `exploit.php`, ta sẽ thêm payload `%00.png` vào cuối file name như sau

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

Khi request này được gửi đi, trang web sẽ hiểu chúng ta đã upload 1 file có định dạng là `.png` -> extension hợp lệ , nhưng vì trước đó là null bytes: `%00.png` nên nó sẽ được hiểu là không tồn tại và bị strip đi, chỉ còn lại file `exploit.php` được upload thành công

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

Sau đó chỉ việc gửi GET request đến `files\avatars\exploit.php`, payload đã execute thành công

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

## Web shell upload via extension blacklist bypass
### Target Goal
Bài lab có lỗ hổng ở chức năng upload ảnh. Một số extension định được đưa vào blacklist, nhưng có thể bypass do lỗ hổng cơ bản trong cấu hình của blacklist

Để giải bài lab, upload một con webshell bằng php để đọc nội dung file tại `/home/carlos/secret`

Credential: `wiener:peter`

### Analysis and exploit
Ta sẽ upload payload  lên bằng chức năng upload avatar. Tuy nhiên, server đã cấu hình chặn upload file php

![image](https://hackmd.io/_uploads/r1vZ78-aJg.png)

Thử đổi extension từ `exploit.php` thành `exploit.php.jpg` thì việc upload đã thành công

![image](https://hackmd.io/_uploads/S1FMXI-aJg.png)

Tuy nhiên khi truy cập đường dẫn xem file thì payload đã không được thực thi

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

Dựa vào response trả về: `Server: Apache` ,ta biết được rằng Apache sử dụng 1 file có tên `.htaccess` để cấu hình và kiểm soát quyền truy cập các thư mục. File này thường được dùng để cho phép hoặc loại bỏ 1 số tính năng như Code Execution, Path Traversal, File Upload ,... trong thư mục.

Từ những thông tin trên, ta biết được cách gửi lên payload chính là tìm cách thay đổi hoặc ghi đè lên file `.htaccess` ban đầu nằm trong thư mục. Tạo 1 file mới với nội dung sau và đặt tên là `.htaccess`
```
AddType application/x-httd-php .shell
```

Có nghĩa rằng, nếu có file nào chứa extension `.shell` trong thư mục hiện tại, cho phép thực thi code php trong đó nếu có. Sau đó tiếp tục upload lên avatar nhằm thử ghi đè hoặc thêm vào config mới 

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

![image](https://hackmd.io/_uploads/SJWtQ8-TJl.png)

Đổi tên file chứa payload thành `exploit.shell` và gửi GET request đến `/files/avatars/exploit.shell`

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

Lấy được nội dung file mục tiêu, submit và hoàn thành bài lab

![image](https://hackmd.io/_uploads/HJEjXL-aJg.png)

## Remote code execution via polyglot web shell upload
### Target Goal
Bài lab có lỗ hổng ở chức năng upload ảnh. Mặc dù nó kiểm tra nội dung của tệp để xác minh rằng đó là hình ảnh, nhưng vẫn có thể tải lên và thực hiện shell phía máy chủ

Để giải bài lab, upload một con webshell bằng php để đọc nội dung file tại `/home/carlos/secret`

Credential: `wiener:peter`

### Analysis and exploit
Bài lab này sử dụng cơ chế validate ảnh dựa trên nộii dung của file ảnh chứ không theo `Content-Type` hay extension từ người dùng. Khi upload webshell sẽ báo `Error: file is not a valid image`

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

Theo như mô tả , cơ chế kiểm tra ở đây rất có thể là file signature bằng magic bytes trên header của file. File PNG sẽ luôn bắt đầu bằng `89 50 4E 47 0D 0A 1A 0A`. Do đó khi thêm magic bytes này vào trước file php chứa payload

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

Upload thành công file webshell sau khi thêm magic bytes

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

Truy cập đường dẫn chứa file upload và ta có được secret cần tìm.

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

## Web shell upload via race condition
### Target Goal
Bài lab có lỗ hổng ở chức năng upload ảnh. Mặc dù nó thực hiện xác thực trên bất kỳ tệp nào được tải lên, nhưng có thể bỏ qua xác thực này hoàn toàn bằng cách lỗ hổng race condition.

Để giải bài lab, upload một con webshell bằng php để đọc nội dung file tại `/home/carlos/secret`

Credential: `wiener:peter`

### Analysis and exploit
Đăng nhập vào tài khoản wiener và thử đăng file `exploit.php` với payload như những bài lab trước

Đã bị chặn. Việc thêm magic bytes của png/jpg và null bytes để bypass cũng không đem lại kết quả gì.

Theo tên của bài lab, ta sẽ lợi dụng lỗ hổng Race condition để khai thác, tức là ta sẽ gửi đồng thời 2 request:

- Upload file 
- Xem nội dung file

Giải thích cho mục đích cho việc ở trên, file vẫn được tải lên + lưu trên web root sau đó mới thực hiện cơ chế validate sau một khoảng thời gian nhất định (vài mili giây)

Ta sẽ tiến hành theo giả thuyết trên. Đầu tiên là bắt request POST (cho việc upload file)

![image](https://hackmd.io/_uploads/B1cqqU-6kx.png)

Tiếp theo là bắt request GET (Xem nội dung file). Kiểm tra path lưu file

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

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

Kết hợp lại group vào

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

Send --> `Send parallel`

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




