Skenario 19 / 26FAIL
Unrestricted File Upload - Profile Picture - File Type Validation
Lingkungan lab — bukan sistem produksi
Tangkapan layar dan trafik mentah tidak diterjemahkan; teks di dalamnya mungkin menggunakan bahasa lain.
vuln-bank/Evidence/[FAIL] Unrestricted File Upload - Profile Picture - File Type Validation
01 — Catatan
Catatan pengujian
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
1) (Screenshot 1) NORMAL: unggah berkas gambar PNG sebagai foto profil -> diterima.
2) (Screenshot 2) EKSPLOITASI: unggah payload.html berisi tag script melalui endpoint yang sama -> diterima tanpa validasi tipe.
3) (Screenshot 3) DAMPAK CIA: berkas diakses via HTTP -> disajikan dengan Content-Type text/html berisi skrip utuh, dieksekusi pada origin aplikasi.
Lihat catatan sumber (bahasa Indonesia)
Vulnerability:
[FAIL] Unrestricted File Upload - Profile Picture - File Type Validation
Description:
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.
Impact:
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
Scenario:
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
Affected Feature:
Unggah foto profil
Remediation:
- 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.
POC:
1) (Screenshot 1) NORMAL: unggah berkas gambar PNG sebagai foto profil -> diterima.
2) (Screenshot 2) EKSPLOITASI: unggah payload.html berisi tag script melalui endpoint yang sama -> diterima tanpa validasi tipe.
3) (Screenshot 3) DAMPAK CIA: berkas diakses via HTTP -> disajikan dengan Content-Type text/html berisi skrip utuh, dieksekusi pada origin aplikasi.
02 — Tangkapan layar
Tangkapan layar (3)
03 — Request dan response
Rekaman HTTP
Sebagian nilai kredensial disamarkan pada tampilan HTML publik ini. Penyamaran ini tidak menjamin bahwa seluruh data sensitif atau berkas tertaut sudah disamarkan.
REKAMAN 01 / 1
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
\x1a
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
--6e1c16ecc8818d6543fc29ca3d550dce--
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"
}REKAMAN 02 / 2
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/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"
}REKAMAN 03 / 3
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/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>