Unauthenticated SQL injection in the client portal login chains to full administrative takeover
- Target
- portal.whitmorehale.example
- Endpoint
- POST /client-login
- Parameter
- username (form)
- Access required
- None — pre-authentication
- CVSS v3.1 vector
- AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- Time to prove
- 3 min 41 s from first request
- Blast radius
- 1,284 client records reachable
- Status
- Remediated & retested
This is an illustrative report. The target, hostnames and records here are a constructed demonstration — the same scenario you can run yourself in Demo Room 02. The structure, identifiers (CVSS, CWE, OWASP, MITRE ATT&CK) and the safe-proof methodology are exactly what K0G produces against a real engagement.
Executive summary
A K0G agent swarm reached the client portal at portal.whitmorehale.example and
found the login form builds its SQL query by string concatenation, with no parameterisation
on the username field. A single crafted value authenticates as the first row in
the users table — which is the firm's own administrator — with no valid password and no
prior access.
On its own this is one injectable field. Chained, it is a straight line from the public internet to the administrative backend: the agent authenticated as an admin, enumerated the matters dashboard, and confirmed that 1,284 client records — including sealed settlements and privileged correspondence — were reachable from that session. K0G stopped at proof and did not read a single record's contents. The finding is rated Critical (CVSS 9.8) because it is remotely exploitable, needs no privileges or user interaction, and fully compromises confidentiality, integrity and availability of the application's data. Integrity and availability score High because the administrator role reached here confers write and destructive capability over that data — demonstrated as reachable, not exercised (see §4).
Why this matters to the business
- Confidential client data is one request away from the open internet — no credentials, no phishing, no insider.
- Privileged legal material (settlements under seal, strategy memos) sits behind the same broken check.
- Breach-notification exposure — unauthorised access to client PII would trigger contractual and regulatory disclosure obligations.
- Undetectable in logs as written — the request looks like an ordinary failed-then-successful login.
Attack chain
The severity is in the chain, not the single flaw. Five steps take the agent from anonymous reconnaissance to data it was never meant to see. MITRE ATT&CK techniques are noted per hop.
Reconnaissance & surface mapping
Agents crawled the public marketing site, enumerated links and directories, and discovered the client portal on a separate subdomain that is not linked from the primary navigation.
Credential spraying — unsuccessful
The swarm attempted a bounded set of common and firm-themed credentials against the login. All were rejected; lockout did not trigger, but no valid pair was found. This path was abandoned, and the observation ("no rate limiting on failed auth") was recorded as a secondary finding.
Injection discovery
Probing the username field with a single quote returned a 500 with a database error fragment, confirming unsanitised input reaching the SQL layer. The agent fingerprinted the backend as MySQL from the error signature and stopped fuzzing.
Authentication bypass
The canonical tautology ' OR 1=1 -- in username made the WHERE clause always true. The query returned the first user row and the application logged the agent in as that identity — the firm administrator.
Impact — administrative data access
With an admin session the agent loaded the matters dashboard, counted the reachable client records to establish blast radius, captured proof of the privilege level, and halted. No record contents were read or exfiltrated.
Reproduction
Deterministic. Any engineer can replay this in under a minute against the same build.
Confirm the injection point
Submit a single quote as the username. The error fragment confirms input reaches the query unescaped.
POST /client-login HTTP/1.1 Host: portal.whitmorehale.example Content-Type: application/x-www-form-urlencoded username='&password=x
HTTP/1.1 500 Internal Server Error { "error": "SQL syntax error near ''' at line 1", "code": "ER_PARSE_ERROR" }
Bypass authentication
Send the tautology payload. The response is a 302 to the dashboard with a valid admin session cookie — no password required.
POST /client-login HTTP/1.1 Host: portal.whitmorehale.example Content-Type: application/x-www-form-urlencoded username=' OR 1=1 -- &password=x
HTTP/1.1 302 Found Location: /dashboard Set-Cookie: session=a8f2…9c1d; HttpOnly; Path=/ X-Auth-User: admin@whitmorehale.example # first row in users
Confirm the resulting query
The application composed this statement (recovered from the query log). The comment sequence discards the password check entirely.
SELECT * FROM users WHERE username = '' OR 1=1 -- ' AND password = '…ignored…' -- resolves to: WHERE username='' OR 1=1 → every row; LIMIT 1 returns the admin
Proof of impact — captured safely
The point of a proof is to remove all doubt that impact is real, without becoming the breach it warns about. K0G proves reach, then stops. Here is exactly what was and was not done.
| Signal | What K0G captured | Damage done |
|---|---|---|
| Privilege level | Response header X-Auth-User: admin@… and the admin-only nav rendering | None |
| Session validity | Re-used the cookie on a second admin route; got 200 | None |
| Blast radius | Row count only — SELECT COUNT(*) returned 1,284; no rows fetched | None |
| Data sensitivity | Column names from schema metadata (e.g. settlement_amount) — never values | None |
| Screenshot | Dashboard chrome with all record bodies masked client-side before capture | None |
The line K0G does not cross
- Counts, never contents. Reach is proven with
COUNT(*)and schema metadata, not by reading or dumping records. - Read, never write. No
INSERT,UPDATE,DELETEorDROPis ever issued. The database is left byte-for-byte unchanged. - Prove the path, don't walk it. Once admin access is demonstrated, the run halts — it does not pivot deeper or touch other systems.
- Every request is logged in order with timestamps, exportable to your SIEM, so your team sees exactly what the agent did.
Remediation
The root cause is string-built SQL. The fix is a parameterised query so user input can never change the statement's structure. K0G opened this as a pull request against the target repo, in the codebase's own style.
function findUser(username, password) { - const q = `SELECT * FROM users - WHERE username = '${username}' AND password = '${hash(password)}'`; - return db.query(q); + const q = `SELECT * FROM users WHERE username = ? AND password = ?`; + return db.query(q, [username, hash(password)]); // parameterised — input can't alter structure }
Defence in depth, recommended alongside: enforce rate limiting and lockout on failed authentication (the secondary finding from hop 2); adopt a query builder or ORM that parameterises by default across the codebase; and add a WAF rule as a compensating control while the fix ships — noting a WAF is mitigation, not a fix.
| Reference | Identifier |
|---|---|
| Weakness | CWE-89 — Improper Neutralization of Special Elements used in an SQL Command |
| Auth weakness (chain) | CWE-287 — Improper Authentication |
| OWASP Top 10 (2021) | A03:2021 — Injection |
| OWASP test guide | WSTG-INPV-05 — Testing for SQL Injection |
| Compliance touchpoints | PCI DSS v4.0.1 Req. 6.2.4 · SOC 2 CC7.1 · ISO/IEC 27001:2022 A.8.28 |
Verified retest
A finding is not closed because a PR merged — it is closed because the original attack path no longer works. When the fix landed, K0G replayed the exact chain from hop 3.
The tautology payload authenticated as admin. 1,284 records reachable. Critical, exploitable.
Identical payload treated as a literal username, matched no user, returned 401. Injection error gone. Path closed.
POST /client-login → username=' OR 1=1 -- &password=x HTTP/1.1 401 Unauthorized { "error": "invalid credentials" } # payload matched no user; no SQL error ✓ attack path K0G-2026-0417 no longer reproducible — finding closed 2026-08-05
Every K0G finding looks like this
- Point K0G at one environment and we will show you a working attack path within the hour — with a proof exactly this concrete — or tell you plainly that we could not find one.