CVE-2022-31631
Patch Available.
This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.
Overview
PHP is a general-purpose scripting language used mostly for web development. It runs behind web servers through server APIs such as Apache mod_php and PHP-FPM, and powers content management systems and frameworks such as WordPress, Drupal, Moodle and Laravel. Its PDO extension gives applications one database API across drivers, and the pdo_sqlite driver connects it to SQLite databases.
A SQL Injection vulnerability (CVE-2022-31631) has been identified in the PDO::quote() method of PHP's PDO SQLite driver, which allows an attacker who can supply a very long string to an application that quotes it with PDO::quote() to receive back a string that is not properly quoted, and so to inject SQL into the query the application builds from it. PHP tracks this issue as bug #81740.
Per OWASP: A SQL injection attack consists of insertion or "injection" of a SQL query via the input data from the client to the application. A successful SQL injection exploit can read sensitive data from the database, modify database data (Insert/Update/Delete), execute administration operations on the database (such as shutdown the DBMS), recover the content of a given file present on the DBMS file system and in some cases issue commands to the operating system.
This issue affects PHP before 8.0.27, PHP 8.1 before 8.1.15 and PHP 8.2 before 8.2.2, which includes every End-of-Life release line from PHP 5.6 through 7.4, each through its final release.
Details
Module Info
- Product: PHP
- Affected packages:
php-src(ext/pdo_sqlite) - Affected versions: <8.0.27, >=8.1.0 <8.1.15, >=8.2.0 <8.2.2
- GitHub repository: https://github.com/php/php-src
- Published packages: https://www.php.net/downloads
- Package manager: N/A
- Fixed in:
- OSS PHP 8.0.27, 8.1.15, 8.2.2
- NES for PHP 5.6.1 (based on PHP 5.6.40)
- NES for PHP 7.2.1 (based on PHP 7.2.34)
- NES for PHP 7.4.1 (based on PHP 7.4.33)
Vulnerability Info
This Critical-severity vulnerability is found in the pdo_sqlite extension of PHP before 8.0.27, 8.1 before 8.1.15 and 8.2 before 8.2.2, including every End-of-Life release line from PHP 5.6 through 7.4, which never received the upstream fix. The PHP project scores it 9.1 under CVSS 3.1 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N).
PDO::quote() escapes a string so an application can place it inside a SQL statement. For SQLite connections, PHP implements it in sqlite_handle_quoter(), which allocates a buffer twice the input length plus three bytes and lets SQLite's sqlite3_snprintf() fill it with the input wrapped in single quotes and its quotes doubled. In PHP 7.4.33, the function reads:
static int sqlite_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t *quotedlen, enum pdo_param_type paramtype )
{
*quoted = safe_emalloc(2, unquotedlen, 3);
sqlite3_snprintf(2*unquotedlen + 3, *quoted, "'%q'", unquoted);
*quotedlen = strlen(*quoted);
return 1;
}The buffer size is computed as a size_t, but the first parameter of sqlite3_snprintf() is an int. When the input is long enough, 2*unquotedlen + 3 no longer fits in an int and wraps, so SQLite is told the buffer is far smaller than it is and writes a truncated result. The reporter showed that with recent SQLite versions the call can return a single apostrophe. An application that concatenates that result into a query no longer has its value enclosed in quotes, so the rest of the attacker's input is parsed as SQL.
Exploitation needs an application that passes attacker-controlled input to PDO::quote() on an SQLite connection without limiting its length. HeroDevs' analysis puts the threshold at strings of about 2 GiB on PHP 7.2 and 7.4, and about 1 GiB on PHP 5.6. PHP's memory_limit and, for request data, post_max_size must allow a string that large, which common settings do not. Applications that use prepared statements with bound parameters instead of PDO::quote() are not affected.
The fix rejects input whose quoted length cannot fit in an int, so PDO::quote() returns false instead of a broken string:
if (unquotedlen > (INT_MAX - 3) / 2) {
return 0;
}
*quoted = safe_emalloc(2, unquotedlen, 3);
sqlite3_snprintf(2*unquotedlen + 3, *quoted, "'%q'", unquoted);Mitigation
The PHP project publishes security fixes only for its supported release lines. PHP 5.6, 7.0, 7.1, 7.2, 7.3 and 7.4 are End-of-Life and will not receive any public updates to address this issue.
Until an upgrade is possible, use prepared statements with bound parameters instead of PDO::quote(), and keep memory_limit and post_max_size well below the sizes needed to reach the overflow.
Users of the affected components should apply one of the following mitigations:
- Upgrade to PHP 8.0.27, 8.1.15, 8.2.2, or later.
- Leverage a commercial support partner like HeroDevs (NES for PHP) for post-EOL security support.
Credits
- Christoph M. Becker (finder, remediation)