Ethical › Module 15 › Lesson 3
Parameterized Queries and Least Privilege DB
The actual fix. ORM pitfalls.
Visual · parameterized_least_privilege
Placeholders bind values off to the side of the SQL text. ORMs still concatenate if you use raw f-strings. Least privilege shrinks blast radius. WAF is not the engine.
Opening
The actual fix is not a smarter filter. It is a query whose grammar cannot be finished by the caller.
Lessons 1–2 named concatenation as the bug and in-band/blind as report words. This lesson names the repair you will write in the lab: parameterized queries (prepared statements) plus a database account that cannot do more than the app needs. A WAF pattern-match, a regex that strips quotes, and “we escape apostrophes by hand” are seatbelts. They are not the engine. Topic 10 goes deeper on prevention labs. Here you install the rule in notes you can hand a teammate: structure in the SQL string, values in the bind list, identifiers that cannot be bound come from an allowlist in code. This is original Cyberlium teaching mapped to the CEH v13 SQL-injection domain — not official EC-Council training, not a certification, not exam dumps. ORM pitfalls belong in this lesson because “we use Django / SQLAlchemy / Prisma” is not a guarantee if someone interpolates into.raw() or text(). Least privilege belongs because a bound query that still runs as a superuser can DROP or read a schema the feature never needed. Next is Lab — Rewrite a Unsafe Query You Wrote: a fictional concatenating snippet becomes sqlite3 placeholders on YOUR disk. You still never point anything at a live foreign DB.
1. Parameterized queries: structure and values travel apart
A parameterized query is a statement with placeholders and a separate list of values. sqlite3 uses ?. Some drivers use %s, $1, or :name. The driver sends the statement text to the engine as a template. The engine plans that template. Values arrive as bound data, not as more source code. A SKU that contains a quote remains a SKU. It does not close a literal. It does not add a clause. That is why this module repeats THE fix until it is boring: stop compiling SQL from untrusted text. Use the parameter API your driver documents. Never f-string, +, .format, or percent-format untrusted bytes into the statement string — even if you “only concatenate the number.” Numbers in strings still change grammar if you paste them into SQL text.
What cannot be a bound value: table names, column names, ASC/DESC, and other identifiers. ORDER BY {column} from a query string is still concatenation. The repair is an allowlist in YOUR code: if the client asked for price, you pick the identifier price from a frozen map; otherwise you ignore it. Stored procedures are safe only when they bind parameters too. Dynamic SQL inside a procedure (building a string, then executing it) reintroduces the Lesson 1 bug behind a fancier door. Topic 10 will say the same on local labs. This lesson needs the rule, not a second engine’s dialect cookbook.
Command guide
Structure and values travel apart — WHAT/WHY
═══ INSTALL ═══
Linux (Debian/Ubuntu):
Command — copy this
sudo apt install python3
macOS:
Command — copy this
brew install python3
Windows: Download https://python.org/downloads/
═══ COMMANDS ═══
Command — copy this
python3 - << 'PY'
import sqlite3
c = sqlite3.connect(':memory:')
c.execute('CREATE TABLE t(id INTEGER)')
c.execute('INSERT INTO t VALUES (?)', (7,))
print(c.execute('SELECT id FROM t WHERE id = ?', (7,)).fetchall())
print('structure in SQL, values in tuple')
PY2. ORM pitfalls: the object layer is not a spell against glue
ORMs and query builders (Django ORM, SQLAlchemy, Prisma, Room, and cousins) parameterize when you stay on their high-level APIs: filter(sku=value), where(column == bind). The pitfall is the escape hatch: Model.objects.raw(f"SELECT ... '{sku}'"), session.execute(text(f"...{sku}...")), prisma.$queryRaw with a template you interpolated yourself. Those calls are concatenating SQL with extra steps. Reviewers who only grep for execute( and miss the f-string still ship the bug. Your notes should say: ORM default APIs yes; raw interpolated SQL no. The lab in Lesson 4 uses sqlite3 so the placeholder is visible. The same bind rule applies when you go back to an ORM.
Second pitfall: copying a “safe” snippet from a chat model that still builds the WHERE clause with plus signs. Third: parameterizing the login lookup but concatenating a report exporter because “only staff use it.” Staff browsers still send strings. Fourth: binding the search term but concatenating LIMIT. If the driver cannot bind LIMIT, clamp it in code to a small integer you parsed yourself — do not paste the query-string into SQL. None of these pitfalls is a reason to test a stranger’s ORM with sqlmap. They are reasons to read YOUR query functions before Lesson 4’s rewrite.
3. Least privilege and defense in depth — still not a substitute for binds
The application’s database user should connect with credentials that can SELECT/INSERT/UPDATE only the tables that feature needs. It should not be a superuser. It should not DROP. It should not read an unrelated schema “in case we need it.” Least privilege does not stop SQLi. It shrinks what a broken statement can reach if someone still concatenated. That is blast radius, the same idea Module 6 used for credentials. Combined with parameterized queries, you get a repair plus a fence. Combined with a WAF only, you get a filter attackers tune around. This course will not teach WAF bypass. It will say: ship binds first; treat WAF as visibility and friction.
Other depth, still not the engine: generic errors to the user (log details server-side) so in-band error-based findings get harder to read — Lesson 2’s channel word, not a reason to skip binds. Short-lived credentials and secret storage so the DB password is not in the git repo. None of that authorizes a live hunt. If you do not own the app, you write the rule for the next app you do own. Module 14’s authorized-testing line still holds. Topic 10 goes deeper on local prevention labs.
4. What you record: bind rule, ORM escape hatch, privilege line — not a bypass list
sqli-parameterized-notes.txt: disclaimer; THE fix (placeholders + bind tuple); driver shape you will use in the lab (sqlite3 ?); identifiers via allowlist; ORM pitfall (raw interpolated SQL); least privilege (app user is not superuser); WAF is not the primary fix; Topic 10 goes deeper; NEVER (no live SQLi, no UNION cheat sheet, no sqlmap against strangers, no pointing the lab at a foreign DB). Path: $HOME/cyberlium-lab/sqli-parameterized-notes.txt, chmod 600. Empty parentheticals fail. Notes that list WAF evasion or UNION recipes fail ethics even if they mention placeholders.
Do not paste production DSNs, cloud DB URLs, or other people’s connection strings. Lesson 4 will create a local catalog.db under cyberlium-lab. That is the only database this module assigns. If you already maintain an app, you may later apply the same bind rule there under YOUR ownership — still not a classmate’s hosted API.
5. Wrong vs right: quote-stripping / WAF-only / raw f-strings vs binds plus least privilege
Worked failure — same word “fix,” opposite engine. Right makes grammar independent of the caller’s bytes.
Wrong
Strip quotes and call it done. Trust a WAF as the primary control. Interpolate into ORM .raw(). Concatenate ORDER BY from the query string. Connect the app as superuser. sqlmap a production URL to “prove the WAF.” Paste UNION lists. Skip chmod. Claim official CEH parameterized labs.
Right
Placeholders + bind tuple as THE fix. Allowlist identifiers. Treat ORM raw interpolation as concatenating SQL. Least-privilege DB user. WAF is a seatbelt. Lock sqli-parameterized-notes.txt chmod 600. Topic 10 goes deeper. Next: Lab — Rewrite a Unsafe Query You Wrote — local sqlite only.
6. Hands-on: lock sqli-parameterized-notes.txt — the fix you will write tomorrow
Fill every heading in YOUR words. Run the checker. chmod 600. Do not add a remote DSN. Windows without chmod: WSL/Git Bash, or restrict the files in your profile.
Command guide
Least privilege DB — WHAT/WHY then lock
═══ COMMANDS ═══
Command — copy this
cat >> "$NOTES" << 'EOF' db_account: least privilege — no extra DROP/FILE rights on MY lab not_a_substitute: least privilege != skip binds EOF
Mission: sqli-parameterized-notes.txt in cyberlium-lab (mode 600)
1) In your own words, state THE fix (placeholders + bind), the sqlite3 shape, an ORM raw-SQL pitfall, identifier allowlists, and least privilege. 2) Fill $HOME/cyberlium-lab/sqli-parameterized-notes.txt; run the checker; chmod 600. 3) WAF is not the primary fix. No live SQLi. No sqlmap against strangers. Topic 10 goes deeper. Not official EC-Council training.
Stuck? Ask Cyberlium AI Mentor
If “the ORM means we cannot have SQLi” or “a WAF is enough for CEH Module 15” still feels true, ask for a hint — not a bypass. Try: "Hint only: why placeholders plus a bind tuple are THE fix, why ORM .raw() f-strings are still concatenation, why ORDER BY needs an allowlist, why least privilege is blast radius not the engine, why Topic 10 goes deeper, why sqli-parameterized-notes.txt lives at $HOME/cyberlium-lab chmod 600, and why this is not official EC-Council training?" You still fill the file. No sqlmap. No foreign DB.
You now treat parameterized queries as the actual fix, ORM raw interpolation as the same old glue, and least privilege as a fence — not a substitute. Topic 10 goes deeper in Cyberlium labs. Notes are locked. This is original Cyberlium material covering the same domain as CEH v13 SQL injection, not official training and not an exam dump. Next — Lab — Rewrite a Unsafe Query You Wrote — takes a fictional concatenating snippet and asks you to write sqlite3 placeholders. NEVER a live foreign DB.
Knowledge Check
APPLY: A teammate says the WAF plus stripping quotes is the CEH fix, and they will sqlmap production to prove it. What is THE fix, and what do you do?
Multiple choice
Knowledge Check
APPLY: True or False: Using an ORM guarantees there is no SQL injection, so concatenating into Model.objects.raw(f"...") is safe.
True or False
Knowledge Check
APPLY: ORDER BY arrives as a query-string column name. What matches this lesson?
Multiple choice
Knowledge Check
APPLY: curl of http://192.168.0.1/ shows a home router login (TP-Link / Netgear / Huawei / "Router Admin"). Is that DEMO in scope as a hacking target?
Multiple choice