← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

PDO query() vs prepare()

PHP CWE-89 OWASP A3:2021 CVSS 9.8 PHP 5.1+ Beginner
debt(d5/e3/b3/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep and phpstan as tools, and automated detection is marked yes with a specific code pattern ($pdo->query('...' . $var . '...')). These are specialist SAST tools rather than default linters or compiler errors, placing this firmly at d5.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix states: replace query() with prepare()->execute() whenever a query contains any variable. This is a pattern-level swap — each call site needs updating but the change is local and mechanical, not architectural. Multiple call sites may exist but each fix is self-contained, so e3 is appropriate rather than e5.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3). The issue applies to web and cli contexts broadly, but the burden is per-query: each affected query must be reviewed and potentially rewritten. It doesn't impose a cross-cutting architectural constraint on the codebase — it's a recurring local tax on database interaction code, not a system-shaping decision.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7). The misconception field states explicitly that developers believe escaping (addslashes, manual quoting) makes query() safe — it does not. This contradicts a widely-held mental model where 'sanitising input' is taught as sufficient. The trap is compounded by second-order SQL injection (noted in common_mistakes), where even 'trusted' data can be malicious. This is worse than a minor gotcha and rises to t7.

About DEBT scoring →

Also Known As

PDO query method PDO execute vs query PDO direct query unsafe

TL;DR

PDO query() executes raw SQL immediately — prepare() parameterises it. query() must never include user-controlled values.

Explanation

PDO::query($sql) sends a full SQL string to the database and executes it immediately. Any variables interpolated into that string become part of the SQL syntax itself — the database cannot distinguish data from code, which enables SQL injection. PDO::prepare() separates SQL structure from data at the protocol level: the query is compiled first, then values are bound and transmitted separately. This guarantees that input is always treated as data, never executable SQL. query() is only safe for strictly static queries with zero dynamic input.

How It's Exploited

Attacker supplies email = ' OR 1=1 -- which turns the query into a tautology, returning all users. In authentication queries, this bypasses login checks entirely.

Common Misconception

Escaping input (e.g. addslashes or manual quoting) makes query() safe. It does not — escaping is encoding-dependent, easy to bypass, and fails in edge cases. Only prepared statements enforce separation between SQL and data.

Why It Matters

Using query() with even a single user-controlled value allows attackers to modify the SQL structure — bypassing authentication, dumping entire tables, or executing destructive queries. This is one of the most common causes of full database compromise.

Common Mistakes

  • Using query() with string concatenation for 'simple' lookups — still exploitable.
  • Assuming internal or trusted input cannot be malicious — second-order SQL injection is common.
  • Using query() for convenience instead of switching to prepared statements.

Avoid When

  • Never use query() with user input, even if validated or escaped.
  • Avoid query() in any context where data may originate from a database, API, or user (second-order injection risk).

When To Use

  • Use query() only for completely static SQL with no variables (e.g. SELECT VERSION(), SHOW TABLES).
  • Use prepare() for every query that includes external input, variables, or dynamic conditions.

Code Examples

✗ Vulnerable
// SQL injection — direct interpolation
$email = $_GET['email'];
$user  = $pdo->query("SELECT * FROM users WHERE email = '$email'")->fetch();

// also unsafe — concatenation
$id = $_GET['id'];
$user = $pdo->query('SELECT * FROM users WHERE id = ' . $id)->fetch();
✓ Fixed
// query() — safe only for static SQL
$version = $pdo->query('SELECT VERSION()')->fetchColumn();

// prepare() — always for dynamic input
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute([':email' => $email]);
$user = $stmt->fetch();

Added 31 Mar 2026
Edited 16 Apr 2026
Views 42
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
1 ping T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 1 ping T 2 pings W 1 ping T 0 pings F 2 pings S 0 pings S 2 pings M 0 pings T 0 pings W 0 pings T 2 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 1 ping S 0 pings M 1 ping T 0 pings W
No pings yet today
PetalBot 1
Perplexity 6 Scrapy 5 Google 4 Ahrefs 3 SEMrush 3 Unknown AI 2 ChatGPT 1 Meta AI 1 Claude 1 Bing 1 Majestic 1 PetalBot 1
crawler 28 crawler_json 1
DEV INTEL Tools & Severity
🔴 Critical ⚙ Fix effort: Low
⚡ Quick Fix
Replace query() with prepare()->execute() whenever a query contains any variable. Only use query() for fixed, hardcoded SQL.
📦 Applies To
PHP 5.1+ web cli
🔗 Prerequisites
🔍 Detection Hints
$pdo->query("...{$var}...") or $pdo->query('...' . $var . '...')
Auto-detectable: ✓ Yes semgrep phpstan
⚠ Related Problems
🤖 AI Agent
Confidence: High False Positives: Low ✓ Auto-fixable Fix: Low Context: Line
CWE-89


✓ schema.org compliant