critical · 9.4CVE-2026-63221Aug 7, 2026

CVE-2026-63221: CodeIgniter4 Query Builder SQL Injection via deleteBatch() and where()

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A SQL injection flaw in CodeIgniter4's Query Builder lets attackers inject arbitrary SQL through where() conditions when deleteBatch() is used, because the framework drops its own escaping logic for…

Packagecodeigniter4/framework
Ecosystemcomposer
Affected>= 4.3.0, < 4.7.4
Fixed in4.7.4
CVE-2026-63221: CodeIgniter4 Query Builder SQL Injection via deleteBatch() and where()

The problem

CodeIgniter4 versions 4.3.0 through 4.7.3 contain a SQL injection vulnerability in the Query Builder's deleteBatch() method.

When where() conditions are added before calling deleteBatch(), the bound values are substituted directly into the generated SQL string with their escape flag silently ignored. They are never quoted or escaped. An attacker who controls any value passed to where() in that code path can inject arbitrary SQL, with potential for mass data deletion, unauthorized reads, and full database manipulation.

The regular delete() method handles the same binds correctly. Only the deleteBatch() path is affected.

Proof of concept

A working proof-of-concept for CVE-2026-63221 in codeigniter4/framework, with the exact payload below.

php
<?php
// Attacker controls $userInput, e.g. from a request parameter.
$userInput = "1 OR 1=1 -- ";

// Application code (vulnerable pattern):
$db->table('orders')
   ->where('tenant_id', $userInput)
   ->deleteBatch($batchData);

// SQL generated by CodeIgniter 4.3.0 - 4.7.3 (pre-patch):
// DELETE FROM `orders`
//   WHERE `tenant_id` IN (SELECT `tenant_id` FROM ...)
//   AND tenant_id = 1 OR 1=1 --
//
// The injected "OR 1=1" turns the WHERE into a tautology,
// causing the DELETE to wipe every row the engine can reach.

The Query Builder stores each where() bind as a struct carrying the value and an escape flag. The delete() path reads and respects that flag when splicing binds into SQL. The deleteBatch() path did not: it read only the raw value and wrote it straight into the SQL string, bypassing all quoting and character escaping.

The patch (commit f5e463b) brings deleteBatch() into line with delete() by honouring the escape flag during bind substitution. Any value that should have been quoted now gets the same treatment it would receive in a regular delete call. CWE-89 applies directly: user data reached a SQL command without passing through the escape layer.

The fix

Upgrade to codeigniter4/framework 4.7.4 or later. The fix is in commit f5e463b9a3e986389ce285963e51a7f1fab6559f. If an immediate upgrade is not possible: do not pass user-controlled values to where() before calling deleteBatch(); cast numeric IDs explicitly; or rewrite the operation using a standard delete() with proper Query Builder binds.

Expressing filter conditions through the batch data and onConstraint() instead of a separate where() also avoids the vulnerable path.

Reporter not attributed.

References: [1][2][3][4][5]

Related research