
The developers did call escapeshellarg(), the PHP function made for exactly this job. They still shipped a remote code execution bug, because the escaped values then went inside a string wrapped in double quotes, and a shell still expands $(...) in there. That is CVE-2026-110447.
If you run Nextcloud with the optional "Collabora Online - Built-in CODE Server" app, anyone on the internet can use this to run commands as your web server user. CISA has it in the Known Exploited Vulnerabilities (KEV) catalogue. By the end of this post you'll know how the quoting mistake works, why it only bites at certain moments, how to check your own server, and what to do if you find a hit.
What this is, and who's affected
The vulnerable file is proxy.php inside richdocumentscode, the Nextcloud app that bundles a small Collabora Online server so you can edit documents without running a separate server. Collabora's CODE edition is the "Collabora Online Development Edition". The bug is an unauthenticated OS command injection, which is CWE-78. Versions before 26.4.303 are affected.
The score is CVSS v4.0 9.2, vector AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H. That reads as: over the network, low complexity, no login, no user click, high impact on confidentiality, integrity and availability. The AT:P bit means "attack requirements present", and it matches the odd condition you'll meet below. The fix is 26.4.303, and it needs Nextcloud 30 or later. If you're on Nextcloud 29 or older you can't install it, so the app has to go or Nextcloud has to be upgraded.
Just as useful is who isn't affected. Collabora and the reports say standalone Collabora Online, the Collabora Docker images, the RPM and DEB packages, Nextcloud All-in-One and hosted Collabora services are all fine, so you don't need to worry about those. Only the built-in app is in scope, so your first job is simply to find out if you have it.
Censys counted 19,454 hosts and 103,350 web properties running the app in its advisory dated 16 October. That's a lot of servers for something most admins turned on once and forgot.
First attacks show up in a victim's access logs, according to the wz-it write-up. Censys cites a first malicious request on 16 Sep from one reporter.
26.4.303 is released on 11 Sep with no security note in the changelog. Collabora confirms on 14 Sep that it fixes the flaw.
The CVE and the GitHub advisory are published, a month after the fix shipped.
CISA adds it to KEV, with a federal remediation date of 26 October, according to shattered.io.
The gap in that timeline is the lesson for defenders. The fix landed on 11 September with a changelog that said nothing about security, and the advisory came a month later. If you only patch when a CVE shows up in your scanner, you were exposed the whole time.
The bug: escaped values inside an unescaped string
Here's the part worth getting right, because you'll see this mistake again. According to the wz-it write-up, proxy.php starts the Collabora server with a long command line. That command line includes a font configuration address built from the host and the requested URL, so both are attacker-influenced.
The developers quoted the font URL and the host with escapeshellarg(). That function wraps a value in single quotes, and a shell treats everything inside single quotes as plain text. So far, that's fine. Then the full command was placed inside a bash -c "..." string wrapped in double quotes. And double quotes don't switch off command substitution. A $(...) inside them still runs.
Here's the same shape in a tiny lab you can run on any Linux box. Nothing here touches Nextcloud. It's two one-line shell files, and the command inside is the harmless id -un.
# cmd.sh: the shape of the bug (illustrative, not the real source)
bash -c "echo 'font=x$(id -un)y'"
The value sits in single quotes, but the single quotes themselves sit inside an outer pair of double quotes.
$ sh cmd.sh
font=xrooty
Output from a quick run on a Linux sandbox as root. The shell ran id -un before echo ever started. On a real target, that is the attacker's command.
Read the output once more. The text between the single quotes was never treated as text. The outer double quotes were parsed first, and the shell expanded the $(id -un) there and then, so the single quotes around it protected nothing. That's why adding an escape function at the value level doesn't help when the string around the value is the problem.
Per the same write-up, the fix in 26.4.303 is to put the whole inner command through escapeshellarg(), so the outer layer sees one single-quoted blob. Here's the equivalent in the lab:
# fixed.sh: the whole inner command is single-quoted
bash -c 'echo '\''font=x$(id -un)y'\'''
$ sh fixed.sh
font=x$(id -un)y
Output from the same sandbox run. Now the payload prints as plain text. Nothing was executed.
Escaping a value is only half the job. You also have to know which quotes the shell reads first.
This is the same family as the injection bugs where one layer checks a string and the next layer reads it differently. The usual safer pattern is to skip the shell completely and pass an argument list to the process, so there's nothing to expand.
Why it only works when the server is stopped
The vulnerable code path is the one that starts the built-in Collabora server. Once the server is running, proxy.php just proxies requests to it and never builds that command line. So Collabora's advisory says the attack only works while the built-in CODE server is not running. That happens after a container restart, an app upgrade or a crash.
Don't take too much comfort from that. A condition that sounds rare is usually a timing problem. Servers restart for kernel updates, upgrades and out-of-memory kills, and an attacker who can send a request every few seconds only needs one of those windows. The reported attacks hit real servers in September and October, so the window was clearly reachable. Whether attackers also knew how to force a crash isn't something the public write-ups say, and I'm not going to guess.
What attackers did with it
The first report was a GitHub issue titled "Security issue - Hack" in the richdocumentscode repository, where operators described finding unexpected files. From the wz-it write-up and Censys, the attack looked like this:
- A crafted
GETrequest to/apps/richdocumentscode/proxy.php?status, using encoded path traversal (%2e%2e), shell command substitution ($(...)) and${IFS}to hide spaces. - A Base64-encoded command run through
sh, which creates a PHP file namedshx*.phpin the app directory and calls out to awebhook.siteaddress. - The web shell itself is a one-line PHP script that runs a command passed in a
GETparameter. One example file was namedshrf4645.php. - On one affected system, the attacker searched for password manager databases, wallets, keystores and cryptocurrency folders.
The sources don't name an attacker group, and you won't find file-upload commands in the reporting, only the web shell. A web shell runs with the web server account's rights, and on a Nextcloud box that account can usually read config.php. That file holds the database password and the instance secret, so "just the web user" is a big prize here.
How to check your own server
Do these five checks in order. They come from the wz-it write-up and the Collabora advice, and the commands assume a standard layout, so adjust the paths.
Step 2 matters more than it looks. occ integrity:check-core only covers Nextcloud core, so it won't see files dropped into an app folder. You need the -app variant with the app name. And step 4 will only help if your logs go back to early September, so check the retention first.
A hit on any of these means you should treat the server as compromised. You can't call it patched and done. Collabora also advised on 18 September that leftover shx*.php files should be removed even after updating, which tells you the patch alone doesn't clean a box.
What to do today
Update richdocumentscode to 26.4.303 or later, or remove the app. Disabling and removing are different things. The files stay on disk and stay reachable over the web, so after you remove it, confirm the apps/richdocumentscode folder is really gone. If you're on Nextcloud 29 or older, you can't install the fixed version, so upgrade Nextcloud or remove the app.
If you find evidence of a hit, rotate everything the web server user could read: the database password, the secrets in config.php, object storage keys, SMTP credentials, app passwords and tokens. Then check for outbound connections, new files and new users, and restore from a state before the first attack, or get the clean-up reviewed by someone independent. The wz-it write-up also points out that GDPR notification duties may apply, though it doesn't decide that for you.
For the long term, wz-it suggests a separately run Collabora server, for example in Docker behind a reverse proxy. That fits the pattern: the standalone deployments weren't affected. It also keeps an internet-facing document converter away from the same account that owns your database password.
Common mistakes: the three that leave you exposed
The first mistake: you switch the app off in the admin page and call it done. Disabling leaves the PHP files where they are, and the entry point is a plain file path.
The second is updating and walking away. If an attacker got in between 10 September and your patch, the web shell survives the update. Looking for shx*.php is the step people skip, and it's the one that finds the intruder.
The third: you assume "I use standalone Collabora, so I'm fine" and don't check. Plenty of installs have both, because someone tried the built-in server first and never removed it. Step 1 above takes ten seconds and settles it.
MITRE ATT&CK mapping
- T1190 Exploit Public-Facing Application, the unauthenticated request to
proxy.php. - T1059.004 Unix Shell, the injected command running through
shandbash -c. - T1505.003 Server Software Component: Web Shell, the
shx*.phpfiles. - T1083 File and Directory Discovery and T1552.001 Credentials in Files, the search for password databases, wallets and keystores.
What the public sources don't say
A few details aren't public in the material I read, and I've left them out rather than guess. None of the sources I read give a researcher credit. No source gives a public proof-of-concept, and Censys said none had been confirmed at the time it wrote. I also haven't run this against a real Nextcloud, so the quoting lab above only shows how the shell behaves. It doesn't show the app. If you've reproduced the real chain and my description of the start-up path is off, I'd like to know.
Quick recap
- CVE-2026-110447 is an unauthenticated OS command injection (CWE-78) in
proxy.phpof the Nextcloudrichdocumentscodeapp, CVSS 4.0 9.2, fixed in 26.4.303, which needs Nextcloud 30 or later. - The values were escaped with
escapeshellarg(), but the full command sat in a double-quotedbash -cstring, so$(...)still ran. The fix quotes the whole inner command. - It only works while the built-in server isn't running, such as after a restart, upgrade or crash. Treat it as a timing gap you can't rely on.
- It's exploited in the wild, with
shx*.phpweb shells and secret hunting reported, and CISA lists it in KEV. - Update or fully remove the app, delete any
shx*.phpfiles, runocc integrity:check-app richdocumentscode, and rotate secrets if anything turns up.
Check this today
Run occ app:list | grep richdocumentscode on every Nextcloud you look after, then find for shx*.php under the app folder. If the app is there and the version is below 26.4.303, treat it as urgent today, not next maintenance window. It takes five minutes and gives you a clear yes or no.
If you've worked through this in your own lab, or you run Nextcloud and you've found something I haven't covered, I'd be glad to compare notes on LinkedIn.
References
- wz-it, Nextcloud Built-in CODE Server: Unauthenticated Command Injection (root cause, timeline, indicators, remediation steps)
- Censys advisory, CVE-2026-110447 (exposure counts, Collabora's stopped-server condition, exploitation notes)
- shattered.io, Nextcloud Collabora flaw hits CISA KEV (CVSS 4.0 vector, KEV date and deadline, unaffected products)
- Upwind, CVE-2026-110447 threat post (affected and fixed versions, preconditions)
- CISA Known Exploited Vulnerabilities Catalog (the authoritative KEV record)
FAQ
What is CVE-2026-110447?
An unauthenticated OS command injection in proxy.php, part of the Nextcloud app richdocumentscode (Collabora Online Built-in CODE Server). It affects versions before 26.4.303, scores CVSS 4.0 9.2, and runs commands as the web server user. It is exploited in the wild and listed in CISA's Known Exploited Vulnerabilities catalogue.
Is standalone Collabora Online affected?
No. Collabora and the reports say standalone Collabora Online servers, Collabora Docker images, RPM and DEB packages, Nextcloud All-in-One images and hosted Collabora services are not affected. Only Nextcloud servers running the optional built-in CODE Server app, richdocumentscode, below version 26.4.303 are in scope.
Does disabling the app fix it?
No. Disabling the app leaves its files on disk and they stay reachable over the web. Update richdocumentscode to 26.4.303 or later, or remove the app completely and confirm the apps/richdocumentscode folder is gone. Then check for shx*.php files, because a patch does not remove a web shell.
How do I check whether my Nextcloud was hit?
Look for shx*.php files under apps/richdocumentscode, run occ integrity:check-app richdocumentscode, and search web server logs for proxy.php requests containing %2e%2e, $(, %24%28 or ${IFS}. If you find any of these, assume compromise and rotate every secret the web server user could read.
Related reading
- WordPress Loaded a PHP File From Outside Its Own Theme Folder, Because One Value Skipped the Check (another guard that sat on the wrong value)
- A JSON Key Metabase Never Asked For Became a CVSS 10 SQL Injection (input reaching a sink nobody validated)
- vCenter Wrote a Log Line Into /etc/cron.d, and Cron Ran It as Root (a file write turned into code execution)
- Browse the whole Threats & Exploits track