Jiva| April 6, 2026 During recent independent security research of Dolibarr 23.0.0 — a popular open-source ERP/CRM platform used by thousands of organizations worldwide — I discovered a zero-day vulnerability in the application's expression evaluation engine that allows an authenticated administrator to execute arbitrary PHP code on the server. Chained with the default administrative credentials present on the target installation, this became exploitable on a default deployment without any credential guessing. This is the story of how I found it, what I tried that didn't work, and how a single misplacedifblock and a regex that doesn't understand PHP turned a "hardened" eval sandbox into an open door. If you spend any time reading the Dolibarrcodebase, you will eventually end up indol_eval(). Itlivesin/htdocs/core/lib/functions.lib.php, and it is called from over 100 locations across the entire application. Cron job test conditions. Menu permission checks. Translation string processing. And the one that caught my eye:computed extrafields. Dolibarr has a feature where administrators can define custom fields on any object — companies, invoices, contacts, projects — and set those fields to be "computed." A computed extrafield's value is not stored in the database. Instead, it's a PHP expression that gets evaluated every time a record is displayed. The admin types in something like: And every time a user views a list of companies, that expression runs througheval()for every single row. Yes,eval(). In production. Evaluated for every user who views the page. Now, the Dolibarr developers are not naive. They knew that handingeval()an admin-controlled string is dangerous. So they built a sandbox around it —a functioncalleddol_eval_standard()that validates the expression before it ever reacheseval(). The function is 280 lines long. It has two distinct operating modes. It checks for dangerous strings, dangerous functions, dangerous classes, dangerous characters, dangerous variable access patterns. It is, by any measure, a serious attempt at makingeval()safe. It almost works. When I first openeddol_eval_standard(), the structure jumped out immediately. The function has two completely separate validation paths, controlled by a global configuration variable called$dolibarr_main_restrict_eval_methods. Here's the skeleton: Do you see it? The$forbiddenphpstringsarray is defined atline 12367—beforetheif/elsebranch. It containsSplFileObject,SplTempFileObject, superglobal access patterns, and the critical pattern)((which I'll come back to later). But the code that actuallyappliesthis check — thestr_ireplaceandpreg_replacecalls that scan the input string against these patterns — that code is inside theif (empty($dolibarr_main_restrict_eval_methods))block. The blacklist branch. Lines12428 through 12439. Thewhitelist branch, which is thedefault modeon every standard Dolibarr installation, never touches$forbiddenphpstrings. It defines the array, and then walks right past it. I read those lines three times to make sure I wasn't misunderstanding the control flow. I wasn't. The most dangerous string patterns — includingSplFileObject— are checked in theless securemode and completely ignored in themore securemode. So what does the whitelist branch actually check? Let's look at the exact code: Three things are happening here: That third point was where my pulse started to quicken. The whitelist mode's approach to class instantiation is essentially: "allow everything exceptReflectionFunction." The$forbiddenphpstringsarray that blocksSplFileObjectandSplTempFileObjectnever gets checked. So my first hypothesis was straightforward: can I instantiateSplFileObjectto write arbitrary files? We navigated to the Dolibarr admin panel, went toThird Parties > Extrafields, and created a new computed extrafield with this expression:
This vulnerability (CVE-2026-22666, CVSS 7.2 HIGH) is an authenticated Remote Code Execution flaw in Dolibarr's `dol_eval()` function, where a bypass of the expression whitelist allows an administrator to execute arbitrary PHP code. The issue stems from a logic error where critical security checks are only applied in a disabled blacklist mode, leaving the default whitelist mode unprotected.