Autopsies

Gavel: Trust Boundary Collapse in Dynamic SQL Logic

A system autopsy examining how dynamic SQL construction in an internal auction platform invalidated PDO security assumptions and enabled credential disclosure.

trust-boundary dynamic-sql sql-injection pdo logic-flaw

System Context

Gavel is an internal auction-style application designed to manage inventory and bidding workflows. The system relied on PDO prepared statements and was assumed to be protected against SQL injection through parameterization.

User-facing features included dynamic sorting and filtering to support operational flexibility.


Failed Design Assumption

The core assumption was that prepared statements alone were sufficient to enforce query safety. However, user-controlled input influenced query structure during runtime, crossing the boundary between data and execution logic.

As a result, the database executed queries whose behavior was partially defined by external input rather than fixed application intent.


Impact

This design flaw enabled disclosure of sensitive credential data, providing initial access to the system. Once access was established, additional weaknesses could be chained to escalate privileges and compromise system integrity.

The failure was rooted in logic design, not missing patches or outdated dependencies.


Why This Was Hard to Detect

The application behaved correctly under normal usage. Query syntax remained valid, and no obvious injection patterns appeared during routine testing.

Because the behavior aligned with legitimate workflows, abnormal execution paths blended into expected activity, allowing the flaw to survive security review.


Design Lessons

This case demonstrates that:

  • Prepared statements protect values, not execution intent
  • Trust boundaries must be explicit and enforced structurally
  • Security guarantees must not depend on runtime interpretation

When execution logic becomes flexible, security becomes fragile.




Technical Proof

→ Technical proof and source analysis available on request.


Vulnerability Analysis

The core failure was dynamic SQL construction that bypassed PDO's parameterization for ORDER BY and sort direction clauses. While PDO correctly parameterized WHERE filters, the sort column and direction were interpolated directly from user input into the query string.

-- Vulnerable pattern: user-controlled sort column injected raw
$query = "SELECT * FROM items WHERE auction_id = ? ORDER BY " .
  $_GET['sort'] . " " . $_GET['dir'];
$stmt = $pdo->prepare($query);
$stmt->execute([$auctionId]);

The application assumed that because parameterization was in use for the WHERE clause, the query was safe. This is a trust boundary collapse: the developer trusted that PDO's protection extended to parts of the query it cannot protect.


Attack Chain

Step 1 — Identify the injection point

The /items endpoint accepted sort and dir GET parameters. A simple test confirmed the injection by appending a SLEEP(2) payload:

GET /items?auction_id=5&sort=price,(SELECT+SLEEP(2))--&dir=ASC

A 2-second delay confirmed blind injection via the ORDER BY clause.

Step 2 — Extract schema information

Using time-based blind injection to enumerate tables:

sort=price,(SELECT SLEEP(2) FROM information_schema.tables
WHERE table_name='admin_credentials' LIMIT 1)--

The 2-second response confirmed admin_credentials existed.

Step 3 — Credential disclosure

Column enumeration revealed username and password_hash fields. Time-based extraction of individual characters allowed full credential recovery.


Design Failure

The root cause is not SQL injection — it is a design assumption failure. The developer correctly knew about parameterization but incorrectly believed it applied to ORDER BY clauses. PDO cannot parameterize identifiers (column names, table names, sort directions). Only literals (values) can be parameterized.

The correct fix is an explicit allowlist:

$allowed_sorts = ['price', 'title', 'created_at', 'end_time'];
$allowed_dirs = ['ASC', 'DESC'];
$sort = in_array($_GET['sort'], $allowed_sorts) ? $_GET['sort'] : 'created_at';
$dir = in_array($_GET['dir'], $allowed_dirs) ? $_GET['dir'] : 'DESC';
$query = "SELECT * FROM items WHERE auction_id = ? ORDER BY {$sort} {$dir}";

MITRE ATT&CK Mapping

  • T1190 — Exploitation of Public-Facing Application
  • T1555 — Credentials from Password Stores
  • T1005 — Data from Local System

Lessons

  • Parameterization protects values, not identifiers. ORDER BY columns must be validated against an allowlist. This is non-negotiable.
  • PDO does not protect you from yourself. Framework usage is not a substitute for understanding what the framework actually does.
  • Blind injection is patient. Even without UNION-based extraction, a time-based approach recovers credentials given enough requests. Rate limiting alone does not prevent this.
  • Trust boundaries must be explicit. Every point where user-controlled data enters a query must be documented and reviewed, regardless of the query builder used.