Full Report
PHP contains a library for prepared statements with PDO and contains prepared statements. In most languages, this is sufficient for preventing SQL injection. However, PDO in PHP isn't a true prepared statement. PDO attempts to do the escaping itself instead of binding the information, like many other languages do, which requires their own custom SQL parser. There's a great quote that sums up the whole challenge: "if we can trick the PDO parser into parsing our input as a bound parameter where it shouldn’t, we can get an SQLi in a situation that would otherwise be impossible." They created this as a CTF challenge that contains a potential injection point within a column name; this is because column names cannot be bound. So, the parameter should be escaped adequately with backticks, but this isn't enough! The parser contains a nice specification for how it parses the data. The type ANYNOEOF is defined as [\001-\377]. When adding a null byte and a question mark - ?\0 - it will see this as a SPECIAL and note a literal now. Naturally, this error occurs because it's trying to bind two things in the query. However, adding a comment - code>?#\0 - can easily remediate this problem. So, are we done? Nope! Still some more trickery for this to work. Now, the first parameterized item in the query is used with OUR question mark and has to have a value. The substitution turns this into 'x'#\0, where the x is a controllable parameter and the parameterization adds the single quotes around the query because it thinks it's a string. There's another issue now: a null byte cannot be in a comment. The problem can be solved by adding a semicolon between the comment and nullbyte to make it a new line. With the stolen parameter x`;# and the same column name, this problem is solved. The column 'x does not exist, though. What now? PDO still thinks that our injection point is in a string! Placing a \ as the first character in the string causes some MAJOR havoc. It will escape the single quote to allow for a context escape. The column name \?#\0 and the stolen parameter with x` FROM... allows us to create a legitimate query to perform SQL injection. Neat! Older versions of PHP are much more susceptible to these types of attacks. This is because the lack of usage of backticks allowed for any injection of a question mark or semicolon, leading to SQL injection. There are numerous other ways to exploit this by creating a desync between the parameterization data. Overall, a pretty slick blog post on a fun SQL injection technique.
Analysis Summary
# Vulnerability: PHP PDO Parser Desynchronization SQL Injection
## CVE Details
- **CVE ID:** Not explicitly cited (Technique-based research/CTF scenario)
- **CVSS Score:** N/A (Likely High/Critical in vulnerable configurations)
- **CWE:** CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
## Affected Systems
- **Products:** PHP (PDO - PHP Data Objects)
- **Versions:** Multiple versions; older versions are noted as significantly more susceptible.
- **Configurations:**
- Emulated prepared statements enabled (default in some drivers).
- Applications using user-controllable input for identifiers that cannot be bound (e.g., column names, table names).
- Inadequate escaping/quoting of identifiers.
## Vulnerability Description
The vulnerability stems from PHP PDO’s implementation of "emulated prepared statements." Unlike native prepared statements, PDO often parses the SQL string locally and performs its own escaping/binding. A desynchronization occurs when the PDO parser can be tricked into misinterpreting user input (specifically within identifiers like column names) as a placeholder (`?`).
The flaw centers on the `ANYNOEOF` type in the parser, defined as `[\001-\377]`. By injecting specific sequences like `?\0`, the parser recognizes a "SPECIAL" token and expects a literal. By further manipulating the parser with comments (`#`), null bytes (`\0`), and semicolons (`;`), an attacker can force the parser to "steal" a parameter intended for another part of the query. Finally, using a backslash (`\`) allows for a context escape by escaping the single quote that PDO automatically adds during parameter substitution, enabling full SQL injection.
## Exploitation
- **Status:** PoC available (demonstrated via CTF challenge).
- **Complexity:** High (Requires precise manipulation of the PDO parser state).
- **Attack Vector:** Network.
## Impact
- **Confidentiality:** High (Full database access).
- **Integrity:** High (Unauthorized data modification).
- **Availability:** High (Potential for database deletion or denial of service).
## Remediation
### Patches
- Ensure PHP is updated to the latest stable version to benefit from improved parser logic and backtick handling.
### Workarounds
- **Disable Emulated Prepares:** Set `PDO::ATTR_EMULATE_PREPARES => false` to force the use of native prepared statements by the database engine.
- **Strict White-listing:** Never allow direct user input to define column or table names. Use a strict allow-list of known-safe identifiers.
- **Identifier Quoting:** Properly quote all identifiers with backticks (for MySQL) or double quotes (for PostgreSQL), though this article notes that backticks alone may not be sufficient against sophisticated parser desync attacks.
## Detection
- **Indicators of Compromise:**
- Queries containing unusual character sequences: `?\0`, `?#\0`, or semicolons within column identifiers.
- Database error logs showing mismatched parameter counts or syntax errors involving backslashes and quotes.
- **Detection Methods:** Static analysis of code to find dynamic identifier concatenation in PDO queries; WAF rules targeting null bytes and SQL comments in unexpected URL parameters.
## References
- PHP PDO Manual: hxxps[://]www[.]php[.]net/manual/en/book[.]pdo[.]php
- OWASP SQL Injection Prevention: hxxps[://]cheatsheetseries[.]owasp[.]org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet[.]html