CodeIgniter, a popular PHP framework used to build web applications quickly and securely, made headlines in 2022 with the exposure of a dangerous deserialization flaw. This vulnerability, officially tracked as CVE-2022-21647, affected CodeIgniter4 applications using the common old() function and several related helpers. Let's break down this vulnerability in plain language, examine the risk with code examples, look at the real exploit, and cover how you can stay safe.
What Is CVE-2022-21647?
In CodeIgniter4, the old() function is used to retrieve old form input data, typically after validation errors. The framework stores this data in the session, and when restoring it, deserializes the session value to reconstruct the original data.
The Problem:
The deserialization in CodeIgniter4 did not sufficiently validate untrusted data from the session. Malicious actors could inject serialized PHP objects via crafted requests, exploiting PHP’s 'autoload' behavior to trigger code execution or, even worse, launch attacks like SQL injection.
Your code uses the old() helper (commonly in forms).
- Your application accepts and echoes user input using form helpers or methods like RedirectResponse::withInput().
How Does the Exploit Work?
Attackers abuse how CodeIgniter handles the session data for old inputs. By crafting a request that includes a maliciously serialized PHP object within the input values, and then ensuring the application deserializes it, the attacker can autoload classes and trigger special __wakeup or __destruct methods in PHP objects.
In some scenarios, if your app uses libraries with “deserialization gadgets” (classes with dangerous magic methods), an attacker can turn this into complete code execution.
Suppose your login form has this logic in the controller
public function submit()
{
// ... process form validation ...
if (!$this->validate()) {
// Redirect back with input
return redirect()->back()->withInput();
}
// ...
}
And your view uses
<input type="text" name="username" value="<?= old('username') ?>">
The framework internally serializes the input values and stores in the session cookie. When returning to the form, CodeIgniter unserializes the session data without restricting what object types it unpacks.
An attacker crafts a POST request
username=O%3A17%3A%22SomeGadgetClass%22%3A%3A%7B%7D
This is a URL-encoded, serialized PHP object:
O:17:"SomeGadgetClass"::{}
After the server deserializes it
- If SomeGadgetClass is autoloaded and has a magic method (such as __wakeup), it executes with attacker-provided data.
- If the attacker uses a gadget from the Database connection, it can result in SQL injection or arbitrary query execution.
Proof-of-Concept Code Snippet
// Simulate deserialization of an untrusted input
$evil = 'O:17:"SomeGadgetClass"::{}';
unserialize($evil); // DANGER: executes __wakeup(), __destruct(), etc.
If SomeGadgetClass exists in your project, with dangerous code in __destruct() or __wakeup(), the attack is successful.
Real-World Gadget
Security advisory and detailed analysis confirmed that a malicious value inside old() could result in the unserialization of a CodeIgniter\Database\PreparedQuery object, which, when destructed, executes a stored SQL statement.
Attacker Input Example
username=O:35:"CodeIgniter\Database\PreparedQuery":1:{s:...}
This can ultimately trigger SQL queries controlled by the attacker!
Version 4.1.6 and later strictly restrict the object types that may be deserialized.
Manually Validate:
If you must keep using old input logic, ensure you do NOT allow objects, only plain text or arrays, and NEVER pass user content to unserialize().
Instead of
value="<?= old('username') ?>"
Use:
value="<?= esc($_POST['username'] ?? '') ?>"
This provides old input safely (from the POST request, not session).
Official Advisory:
GitHub Security Advisory GHSA-p5wf-m82x-g73x
NVD Entry:
Release Notes:
If you can't, don’t use old() or helpers that save user inputs in session.
Stay secure — never unserialize what you don’t control!
Timeline
Published on: 01/04/2022 20:15:00 UTC
Last modified on: 01/20/2022 15:04:00 UTC