Typecast Catastrophe: The EGroupware JSON-to-SQL Pipeline
Jan 28, 2026·6 min read·6 visits
Executive Summary (TL;DR)
EGroupware trusted JSON integers to be safe. They weren't. By sending native integers in a JSON payload, attackers bypass input sanitization that expects strings, injecting unquoted values directly into SQL queries. This allows for database type confusion and unauthorized data access.
A high-severity SQL injection vulnerability in EGroupware's Nextmatch widget allows authenticated attackers to manipulate database queries via JSON type juggling. By leveraging PHP's strict integer handling against a database's implicit casting, attackers can bypass quoting mechanisms and potentially exfiltrate sensitive data or modify records.
The Hook: When Types Don't Match
In the world of web security, we often obsess over strings. We strip tags, we escape quotes, and we panic over apostrophes. But what happens when the input isn't a string? EGroupware, a robust collaboration platform used by enterprises, fell into a classic trap: assuming that if something looks like a number and smells like a number, it can't possibly be a weapon.
The vulnerability resides in the Nextmatch widget, the core component responsible for rendering lists, calendars, and address books. It's the gatekeeper that decides what you see. To handle complex filtering, Nextmatch accepts JSON payloads. And this is where the plot thickens. JSON supports native types—booleans, nulls, and most importantly, integers.
PHP, being the loosely-typed chaotic neutral language we all love (and fear), interacts with these JSON types in interesting ways. When a developer writes code expecting a standard HTTP POST request (where everything is a string), they might not anticipate the behavior of json_decode. This cognitive gap turned a routine filter feature into a high-severity hole, earning it a CVSS score of 8.7.
The Flaw: The Integer Bypass
The root cause of CVE-2026-22243 is a subtle logic error in how filters are sanitized. The application logic followed a seemingly sound principle: strict validation. Before putting a user-supplied value into a SQL WHERE clause, the code checked its type.
Here is the logic in pseudocode: "If the value is an integer, it's safe. Append it. If it's anything else (like a string), quote and escape it." This relies on the PHP function is_int(). In a standard web request (application/x-www-form-urlencoded), every input is a string. Even id=123 arrives as the string "123". In that scenario, is_int("123") returns false, and the value gets safely quoted as '123'.
But JSON is different. When json_decode processes {"id": 123}, it creates a native PHP integer. Now, is_int(123) returns true. The application sees this, says "Trust me, I'm an engineer," and concatenates the value directly into the SQL string without quotes. This removes the protective layer of quotes, exposing the database to Type Confusion attacks or logic manipulation via numeric literals.
The Code: The Smoking Gun
Let's look at the vulnerable pattern. The code likely resembled this simplified logic inside Nextmatch.php:
// VULNERABLE LOGIC
$filter = json_decode($json_input, true);
$query = "SELECT * FROM contacts WHERE ";
foreach ($filter as $col => $val) {
// If it's a native integer, use it directly
if (is_int($val)) {
$query .= $col . " = " . $val;
} else {
// Otherwise, escape and quote it
$query .= $col . " = '" . $db->quote($val) . "'";
}
}The fix implementation (released Jan 13, 2026) forces strict handling regardless of the input type. It stops treating integers as a special "safe" class that deserves unquoted access. The patch ensures that all user inputs, regardless of their JSON data type, are treated with suspicion and properly parameterized or cast explicitly before query construction.
// FIXED LOGIC
// Treat everything as a parameter or force quoting
$query .= $col . " = '" . $db->quote((string)$val) . "'";
// OR better yet, use prepared statements bindingThe Exploit: Boolean Blindness
So how do we exploit this? We can't inject UNION SELECT because is_int() is strict—it won't accept strings containing SQL keywords. However, we can perform Database Type Juggling. This is where the PHP type juggling vulnerability hands the baton to the Database type juggling vulnerability.
Imagine a query filtering by a text column, like username. Normally, the query is WHERE username = 'admin'. If we inject a 0 via JSON ({"username": 0}), the query becomes WHERE username = 0 (no quotes).
In many database configurations (especially older MySQL/MariaDB versions or specific modes), comparing a string column to the integer 0 results in true for every row that doesn't start with a number. This effectively becomes WHERE true.
Attack Scenario:
- Recon: Attacker logs in and intercepts the
Nextmatchfilter request. - Modify: They change a filter for a sensitive text column (e.g.,
statusorowner) to the integer0or a booleantrue(which PHP might cast to 1). - Execute: The server generates
SELECT * FROM secret_table WHERE owner = 0. - Result: The database implicitly casts the
ownerstring column to integers. Strings like "admin" become0. The condition matches. The attacker sees data they shouldn't.
The Impact: Why Panic?
While this isn't a "drop table" RCE (Remote Code Execution) scenario immediately, the impact is severe for confidentiality and integrity. The vulnerability allows an authenticated user—potentially a low-privileged employee—to bypass horizontal privilege escalation controls.
By manipulating filters:
- Data Leakage: A user could view calendar entries, contacts, or internal notes belonging to other users or administrators by nullifying the
owner_idchecks. - Logic Bypass: In workflows where
status=1means "Approved", injecting unquoted integers might allow manipulating workflow states if the application relies on string comparisons that are subverted by the injection.
The CVSS score of 8.7 reflects this high impact on confidentiality and integrity without requiring user interaction.
The Fix: Casting Calls
Mitigation is straightforward but requires code changes. You cannot fix this with a WAF easily because the payload ({"id": 0}) looks perfectly legitimate. The fix must occur in the PHP logic.
For Administrators: Update EGroupware immediately to version 23.1.20260113 or 26.0.20260113. If you are running an older version, you are exposed.
For Developers:
Stop trusting is_int(), is_numeric(), or gettype() for security decisions regarding SQL generation. Always use Prepared Statements (parameterized queries). If you must build dynamic SQL strings (which you shouldn't), treat every input as hostile and ensure it is enclosed in quotes, regardless of whether PHP thinks it's a harmless integer.
Technical Appendix
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
EGroupware EGroupware | < 23.1.20260113 | 23.1.20260113 |
EGroupware EGroupware | < 26.0.20260113 | 26.0.20260113 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-89 (SQL Injection) |
| Attack Vector | Network (Authenticated) |
| CVSS Score | 8.7 (High) |
| Exploit Status | No Public PoC |
| Root Cause | PHP Type Juggling / JSON Decoding |
| Affected Component | Nextmatch Filter Widget |
MITRE ATT&CK Mapping
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.