Nextcloud Collabora Built-in CODE Server: CVE-2026-110447 Unauthenticated Command Injection and Web Shells

An unauthenticated OS command injection (CVSS v4 9.2, CWE-78) in proxy.php of the Nextcloud app “Collabora Online - Built-in CODE Server” (richdocumentscode) has been exploited since 2026-09-10 to run commands as the web server account and write PHP web shells, followed on one host by a search for password vaults and cryptocurrency wallets. The activity is unattributed and opportunistic. This is a Linux endpoint hunt for the post-exploitation behavior, plus the patch-or-remove fix. Defensive hunt and harden pack, no exploit code.
Threat
CVE-2026-110447 (GHSA-g3vx-3m76-fghh), unattributed opportunistic exploitation
Severity
EXPLOITED ITW · CVSS v4 9.2 · UNAUTH RCE
Type
OS command injection (CWE-78), web shell, credential and wallet search
Access
Network, no credentials, no user interaction; needs the CODE server to be stopped
Affected
richdocumentscode before 26.4.303 (fixed in 26.4.303)
Timeline
Exploited 2026-09-10 · fix 2026-09-11 · advisory 2026-10-14
Version
v0.1 · 2026-10-16
Author
HuntPack
Confidence
High on the flaw and behavior; low on scale and actor (none attributed)
01

Executive Summary

CVE-2026-110447 is an unauthenticated OS command injection in proxy.php, the script shipped by the Nextcloud app Collabora Online - Built-in CODE Server (richdocumentscode). When a request arrives and no CODE server is listening, the script starts one. In every release before 26.4.303 it placed text from the request, including the URL path, inside a double-quoted bash -c command, so shell command substitution in the path ran as the web server account. The attack only works while the built-in CODE server is stopped: after a container restart, an app upgrade, or a crash. The script is executed directly by the web server without loading Nextcloud, so disabling the app does not help. Standalone Collabora Online servers and Nextcloud Office installs without this app are not affected.

It is being exploited. The first logged attacks are dated 2026-09-10 (UTC+2 server time), one month before the Collabora advisory of 2026-10-14. Requests to proxy.php?status carried a base64-wrapped command that wrote a one-line PHP web shell into the app folder and sent uname -a output to a webhook.site callback. On one server the operator then used the shell to search for *.kdbx, wallet.dat, keystore.json and Bitwarden, 1Password, Bitcoin, Electrum, Monero and Ethereum artifacts. Three administrators reported the same pattern independently; none reported root access, and the reviewed requests showed no archiving or upload step. Nobody is attributed, and no campaign name appears in any source, so this pack gives it none.

What an endpoint sensor can see. The initial request is web-tier traffic and is invisible to an endpoint sensor. What follows is visible if the Nextcloud host carries a Falcon Linux sensor: the web server account starting a shell with command-substitution markers, injection text inside the CODE server launch arguments, the web account running base64 -d, curl, uname or credential searches, and any PHP file written under the app path. The pack hunts those, and leads with the fix.

⚠ Defender priority: (1) Upgrade richdocumentscode to 26.4.303 or later, or remove the app. Disabling it leaves proxy.php reachable. (2) Patching does not remove an existing web shell: search apps/richdocumentscode and other web roots for shx*.php and shrf*.php, and check access logs back to 2026-09-10 for proxy.php?status requests containing %2e%2e, $( or ${IFS}. (3) Run Q1, Q2 and Q4 first; treat anything the web account could read (Nextcloud config, database credentials, password vaults, wallets) as exposed on any confirmed hit.
02

Source Review & Web Hunter Notes

TierSourceKey FindingCarry
1 · VendorCollabora GitHub security advisory GHSA-g3vx-3m76-fghh (snapshot 01)Published 2026-10-14. Critical, CVSS v4 9.2, CWE-78. Affects richdocumentscode < 26.4.303; fixed in 26.4.303. Attack works only while the built-in CODE server is not running (restart, upgrade, crash). Disabling the app is not enough; upgrade or remove. Not the same code path as CVE-2025-66208.yes
1 · Primary reportGitHub issue CollaboraOnline/richdocumentscode #354 (snapshot 02)First-hand access-log lines, web shell filenames, recovered one-line PHP web shell and its SHA-256, the two source IPs, callback IDs, and the credential and wallet search terms. Maintainer replies confirm 26.4.303 is the fix and to remove leftover shx*.php.yes
1 · RegistryCVE.org CVE record CVE-2026-110447 (snapshot 03)Published 2026-10-14. States exploitation in the wild in September and October 2026; affected range 0 up to (not including) 26.4.303.yes
1 · FixFix commit 6cc8dae in richdocumentscode (snapshot 04)Author date 2026-08-20, titled “tidy up how the coolwsd launch command is built”. Wraps the inner command with escapeshellarg() instead of double quotes. Confirms the injection primitive.yes
2 · NewsThe Hacker News, 2026-10-14 (snapshot 07)Best secondary write-up: mechanism, timeline, three administrators, no root, failed sudo mails, fix history, shx file list, defanged IPs, both webhook callback IDs. Re-reports the GitHub issue; not independent of it.yes
2 · NewsIT-Connect, 2026-10-15 (snapshot 05)Mitigation steps, the two IPs, shx*.php search, access-log hunt terms, and the caveat that 26.4.402 fails to start the CODE server on some PHP-FPM setups. Re-reports the GitHub issue; not independent of it.yes
2 · NewsGBHackers, 2026-10-16 (snapshot 06)Restates the advisory and CVSS vector, notes the reporter credit. Its line that no exploitation was in the advisory is superseded by the CVE record and the issue. No indicators.partial
2 · Aggregatorcvefeed.io (snapshot 08)Description and dates only; no affected-product table. Corroborates the CVE record text.partial
2 · CommunityNextcloud help forum thread, 2026-10-03 and 2026-10-14 (snapshot 09)Early user warning that the flaw was already exploited before any CVE; affected and not-affected deployment list.yes

Decisions: (1) Nothing ships as prevent. Every secondary article re-reports the single GitHub issue, so no indicator has two independent sources. The two IPs, the web shell SHA-256, the callback domain and the filenames all ship detect or hunt. (2) proxy.php and the other richdocumentscode files are the vulnerable stock app, not malware: they appear only as a hunt location. (3) The IPs are opportunistic and perishable and carry a short expiry. webhook.site is a public service and is a context signal, not a block. (4) No actor, campaign or malware name is used because no source gives one. (5) No source contained text directed at an AI agent or any instruction to run commands, fetch other URLs or skip checks.

Not used: SC World, BleepingComputer and thehackerwire.com returned HTTP 403 to automated fetch, so no snapshot exists and they are not cited. SecurityOnline and Daily Security Review are single-source derivatives of the same GitHub issue and add nothing independent. A third forum post offering a “free self-test” on a third-party site was left out as promotional and unverified.

03

Hunt Brief & Attack Chain

Working hypothesis: An internet-facing Nextcloud host runs the built-in CODE server app below 26.4.303. When the CODE server is down, an unauthenticated request to /apps/richdocumentscode/proxy.php whose path contains shell command substitution makes the app build and run a bash -c launch command that includes the attacker text, as the web server account. The operator uses that to decode and write a one-line PHP web shell into the app folder, calls back to a webhook.site URL, then drives the web shell over HTTP to search for credential stores and wallets. An endpoint sensor on the host sees everything from the shell onward.

StepBehaviorTelemetryHunt Angle
1 · PreconditionCODE server stopped by a restart, upgrade or crash; the vulnerable window opensProcess creation with command line (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) for coolwsd start and stopContext only; correlate hits with restart times
2 · DeliveryGET to /apps/richdocumentscode/proxy.php?status with encoded traversal, $( and ${IFS} in the pathWeb server or proxy access logs (outside endpoint telemetry)GAP N1 native log hunt only
3 · Injection executesThe launch bash -c runs the substituted command as the web account; the same text also lands in CODE server launch argumentsProcess creation with command line (see step 1 platforms)Q1, Q2
4 · Web shell writtenBase64-decoded one-line PHP file named shx*.php or shrf*.php written under apps/richdocumentscodeFile write or file open (CrowdStrike: FileOpenInfo, CriticalFileModified, policy dependent). Native file integrity checkQ8, N2, N3
5 · Beaconcurl or wget sends uname -a to a webhook.site URLProcess creation with command line; DNS lookup with the requesting process (CrowdStrike: DnsRequest. SentinelOne: DNS Resolved. Defender: DeviceEvents DnsQueryResponse); outbound network connectionQ5, Q6
6 · Web shell useOperator sends commands to the web shell over HTTP (?c= parameter); PHP spawns sh -c childrenHTTP requests (outside endpoint telemetry); child processes (process creation, above)Q3 for children; GAP for the requests
7 · Credential and wallet searchfind or grep for *.kdbx, wallet.dat, keystore.json, bitwarden, 1password, .bitcoin, .electrum, .monero, .ethereumProcess creation with command lineQ4
Post · Privilege escalation attemptFailed sudo attempts by the web account (how two administrators noticed)Process creation; auth log (native)Q5, N5

Hunt hypotheses (fidelity-ordered)

  • H1 (high): A shell owned by the web account carries ${IFS}, base64 -d or encoded traversal text in its command line → Q1.
  • H2 (high): Command-substitution text appears in the CODE server launch arguments (remote_font_config.url); on a patched host this is a failed attempt → Q2.
  • H3 (high): The web account searches for credential stores and wallets → Q4.
  • H4 (medium): The web account runs recon, download or sudo commands → Q5, with Q3 as the shell baseline.
  • H5 (medium): Known IPs or webhook.site appear in host network or DNS telemetry → Q6, and rare web-account egress → Q7.
  • H6 (medium): A PHP file matching the shx or shrf pattern, or any new PHP under the app path, appears → Q8 where file telemetry exists, otherwise N2 and N3.
04

Consolidated IOC Table

⚠ No indicator ships as prevent. Every secondary report re-reports one GitHub issue, so no value has two independent sources. The IPs are perishable opportunistic infrastructure. webhook.site is a public service. proxy.php and the other richdocumentscode files are the vulnerable stock app: a hunt location, never an indicator of malice. Counts: 13 atomic indicators, 0 prevent, 13 detect or hunt.
TypeValueConfActionContext
IPv4139.28.18.122mediumdetectSource of the 2026-09-10 and 2026-09-11 attacks in the reporter logs (snapshots 02, 05). Opportunistic and perishable.
IPv489.38.98.50mediumdetectSource of the 2026-09-14 attempt against a host already on 26.4.303 (snapshots 02, 05). Opportunistic and perishable.
SHA-2564b9fbd0663fbfd6028fc216ed18e0d6ef3ae498a7fdc97083e4b0ce3cec40562mediumdetectOne recovered one-line PHP web shell sample (snapshots 02, 07). Single sample; trivial edits change it.
Domainwebhook.sitelowdetectPublic webhook service used as the callback for the uname beacon. Legitimate users exist. Context signal only.
Callback ID774cf5e1-b5a4-4174-a1ad-0241861029a2mediumhuntwebhook.site callback path ID in the reporter IOC list (snapshots 02, 07). Search it in web logs and command lines.
Callback ID45060ed4-de0b-4119-8f0d-2a2776bbd413lowhuntSecond callback ID, named in one article only (snapshot 07).
Filenameshrf4645.phphighhuntWeb shell, reported under apps/richdocumentscode (snapshots 02, 07).
Filenameshxa35d0.phphighhuntWeb shell, same location (snapshots 02, 07).
Filenameshx5cf91.phphighhuntWeb shell, same location (snapshots 02, 07).
Filenameshxd33ce.phphighhuntWeb shell, same location (snapshots 02, 07).
Filenameshxf4645.phphighhuntWeb shell, same location (snapshots 02, 07).
Filenameshx13a86.phphighhuntWeb shell found by a second administrator via the Nextcloud integrity check (snapshots 02, 07).
Filenameshx56367.phpmediumhuntName in the 2026-09-14 attempt; no file was created (snapshot 07).
Pattern/apps/richdocumentscode/proxy.php?statushighhuntRequest target in the reported attacks. Stock file; a hunt location, not an indicator of malice.
Pattern%2e%2e, $(, ${IFS}, base64 -d piped to shhighhuntMarkers in the request path and in the spawned shell command line.
Versionrichdocumentscode < 26.4.303highdetectVulnerable range per advisory and CVE record. Fixed in 26.4.303.
05

Affected Surface & Telemetry Matrix

SurfaceTelemetryPriorityGap
Nextcloud host running the built-in CODE server app (Linux)Process creation with command line, path, parent and user (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents)criticalPrerequisite: a Falcon Linux sensor must be installed on the Nextcloud host or container host. Without it there is no endpoint coverage at all.
Web server and PHP-FPM processesProcess creation with parent and user (as above); outbound network connection (CrowdStrike: NetworkConnectIP4. SentinelOne: IP Connect. Defender: DeviceNetworkEvents)highContainers: the sensor must run on the node and see container processes.
App directory apps/richdocumentscode and other web rootsFile write or open (CrowdStrike: FileOpenInfo, CriticalFileModified, policy dependent). Native Nextcloud integrity checkhighNo default write event for every PHP file; coverage depends on policy. Outside endpoint telemetry unless file monitoring is enabled.
Initial exploit requestWeb server or reverse proxy access logshighOutside endpoint telemetry. Visible only if HTTP or proxy logs reach the SIEM.
Outbound DNS and connectionsDNS lookup with the requesting process (CrowdStrike: DnsRequest. SentinelOne: DNS Resolved. Defender: DeviceEvents DnsQueryResponse); inbound accept (CrowdStrike: NetworkReceiveAcceptIP4)mediumLinux DNS and inbound-accept coverage varies by sensor version and policy.
Standalone Collabora Online, Nextcloud Office without the built-in appn/anot affectedNot vulnerable per advisory. Do not spend hunt time here.
06

ATT&CK Mapping

TacticTechniqueObserved behaviorQuery or control
Initial AccessT1190 Exploit Public-Facing ApplicationUnauthenticated request to proxy.php?status with command substitution in the pathN1; Q1, Q2 for the consequence; patch
ExecutionT1059.004 Unix ShellInjected bash -c and decoded sh payload run as the web accountQ1, Q2, Q3
Defense EvasionT1027 Obfuscated Files or Information; T1140 Deobfuscate/Decode Files${IFS} whitespace obfuscation; base64 decoded and piped to shQ1
PersistenceT1505.003 Web ShellOne-line PHP shell named shx*.php or shrf*.php in the app folderQ8, N2, N3
DiscoveryT1082 System Information Discovery; T1083 File and Directory Discoveryuname -a; searches for wallet and vault filesQ4, Q5
Credential AccessT1552.001 Credentials In Files; T1555.005 Password ManagersSearch for *.kdbx, bitwarden, 1password, keystore.jsonQ4
CollectionT1005 Data from Local SystemWallet.dat and key material search (no collection or archive step was observed)Q4; containment scope
ExfiltrationT1567 Exfiltration Over Web Serviceuname -a output sent to a webhook.site URL (only the beacon was observed)Q5, Q6, Q7
Privilege EscalationT1548.003 Sudo and Sudo CachingFailed sudo attempts by the web accountQ5, N5
07

Native Audit-Log Hunts

Non-CQL checks, run on the host or against forwarded logs. N1 is the only view of the initial request. Read-only; nothing here changes the host.

N1 · Web access log hunt for the injection request (the only view of the initial exploit)

# N1. Web access log hunt (run on the web server or against forwarded logs)
# Nginx / Apache default locations shown; adjust for your layout.
# Requests to proxy.php carrying traversal, command substitution or IFS markers, since 2026-09-10:
grep -hE 'proxy\.php' /var/log/nginx/access.log* /var/log/apache2/access.log* 2>/dev/null \
  | grep -E '%2e%2e|%24%28|\$\(|\$\{IFS\}|%7bIFS%7d'
# Zipped rotations:
zgrep -hE 'proxy\.php' /var/log/nginx/access.log*.gz /var/log/apache2/access.log*.gz 2>/dev/null \
  | grep -E '%2e%2e|%24%28|\$\(|\$\{IFS\}'
# Reported callback ID and source IPs in any log:
grep -rhE '774cf5e1-b5a4-4174-a1ad-0241861029a2|139\.28\.18\.122|89\.38\.98\.50' /var/log/nginx /var/log/apache2 2>/dev/null

N2 · Web shell filename and content search

# N2. Web shell file search (read-only)
APPS=/var/www/nextcloud/apps        # adjust to your Nextcloud path
find "$APPS/richdocumentscode" -maxdepth 2 -type f -name '*.php' -printf '%TY-%Tm-%Td %TH:%TM  %p\n' | sort
find /var/www -type f \( -name 'shx*.php' -o -name 'shrf*.php' \) -printf '%TY-%Tm-%Td %TH:%TM  %p\n' 2>/dev/null
# Content signature of the recovered one-line shell (a command passed through shell_exec from a GET parameter):
grep -rlE 'shell_exec\(\$c' /var/www --include='*.php' 2>/dev/null
# PHP files newer than the 2026-09-10 first attack in the app directory:
find "$APPS/richdocumentscode" -type f -name '*.php' -newermt 2026-09-10 -print

N3 · Nextcloud integrity check and installed version

# N3. Nextcloud integrity check (how two administrators found the shell)
# Run as the web account from the Nextcloud directory. The check flags files that are not in the signed app manifest.
sudo -u www-data php occ integrity:check-app richdocumentscode
sudo -u www-data php occ integrity:check-core
# Installed app version (vulnerable if lower than 26.4.303):
sudo -u www-data php occ app:list | grep -i richdocumentscode

N4 · CODE server processes and launch arguments

# N4. Running CODE server processes and their arguments (read-only)
# Look for $( or ${IFS} inside remote_font_config.url. On a patched host this is a failed attempt, not a compromise.
ps -eo user,pid,lstart,args | grep -E 'Collabora_Online|coolwsd|coolforkit' | grep -v grep
ls -la /tmp | grep -E 'appimage_extracted|coolwsd'
# The CODE server log, if present:
ls -la /tmp/coolwsd.*/coolwsd.log 2>/dev/null

N5 · Authentication log hunt for sudo attempts by the web account

# N5. Authentication log hunt for sudo attempts by the web account (read-only)
# Debian / Ubuntu:
grep -E 'sudo:.*(www-data|apache|nginx)' /var/log/auth.log* 2>/dev/null
# RHEL family:
grep -E 'sudo:.*(apache|nginx)' /var/log/secure* 2>/dev/null
# systemd journal:
journalctl _COMM=sudo --since 2026-09-10 --no-pager | grep -E 'www-data|apache|nginx'
# Mail spool: failed-sudo notification mails were how one administrator found the intrusion.
08

Hunt Queries: CrowdStrike CQL, SentinelOne PowerQuery and Defender XDR KQL

Show all queries in
Pick your tenant's cloud first: every "Open in Falcon" button below uses this selection.
⚠ Prerequisite: every query reads Linux sensor telemetry (event_platform=Lin), so the Nextcloud host, or the container host running it, must carry a Falcon Linux sensor. The exploit request itself is web-tier and is not visible to these queries. Q6 and Q8 depend on sensor version and file or DNS policy: validate them in your tenant before trusting a quiet result.
Q1 · Web server account spawns a shell carrying injection markers
CONF HIGHFP LOWCOST LOW

Looks for: A shell (sh/bash/dash) running as the web server account whose command line carries the whitespace-obfuscation token ${IFS}, a base64 -d decode stage, or the URL-encoded traversal / command-substitution markers the reporters saw in the request path. This is the endpoint-side shadow of a successful injection. FP: near zero; the stock launch command carries none of these tokens.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: A shell started by a process the agent did not observe starting is not captured.
  • PowerQuery assumes: On a Process Creation event the account that started the shell is read from src.process.user (the creating process); a created-process user field is not attested.
  • PowerQuery assumes: endpoint.os holds the lowercase value linux.
  • KQL – Defender XDR: A shell started by a process the sensor did not observe starting is not captured.
  • KQL – Defender XDR assumes: AccountName holds the Linux account name of the created process.
  • KQL – Defender XDR assumes: The query does not filter on operating system; the web account names are Linux conventions.
// HUNT: Web server account spawns a shell carrying injection markers (CVE-2026-110447 exploitation)
// MITRE: T1190, T1059.004, T1027, T1140 | CONF: high  FP: low  COST: low
// REQUIRES: Falcon Linux sensor on the Nextcloud host; ProcessRollup2 (Linux) with UserName + CommandLine
// FALSE POSITIVES: admin scripts run as the web account that legitimately decode base64 through a shell
// LOOKBACK: 30d (set in the time picker; first reported exploitation 2026-09-10)
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| event_platform=Lin
| UserName=/^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www)$/i
| FileName=/^(sh|bash|dash|zsh|ksh)$/i
| CommandLine=/\$\{IFS\}|base64(\$\{IFS\}|\s)+-d|%2e%2e|%24%28/i
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine], limit=200)
| sort(@timestamp, order=desc)
Q2 · Injection text inside the CODE server launch (AppImage / coolwsd) arguments
CONF HIGHFP LOWCOST LOW

Looks for: The built-in CODE server is started with a remote_font_config.url option built from the request path. When the path carried command substitution, the same text shows up in the launch arguments of bash, AppRun, coolwsd and coolforkit-ns. The project says this output looks alarming but is harmless on a patched host, so a hit on a patched host means an exploitation attempt was made, not that it succeeded. FP: near zero.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: A launch by a process the agent did not observe starting is not captured.
  • PowerQuery assumes: endpoint.os holds the lowercase value linux.
  • PowerQuery assumes: The AppImage name is matched as a literal image name; an AppImage renamed on disk is not matched by name, but the command-line predicate still applies to shell launches.
  • KQL – Defender XDR: A launch by a process the sensor did not observe starting is not captured.
  • KQL – Defender XDR assumes: ProcessCommandLine holds the full argument string of the launch.
// HUNT: Command-substitution text inside the Collabora CODE server launch arguments
// MITRE: T1190, T1059.004, T1027 | CONF: high  FP: low  COST: low
// REQUIRES: Falcon Linux sensor on the Nextcloud host; ProcessRollup2 (Linux) with CommandLine
// FALSE POSITIVES: none known; a hit on a host already at 26.4.303 or later is an attempt, not a compromise
// LOOKBACK: 30d
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| event_platform=Lin
| FileName=/^(bash|sh|dash|AppRun|coolwsd|coolforkit-ns|Collabora_Online\.AppImage)$/i
| CommandLine=/remote_font_config\.url=\S*(\$\(|\$\{IFS\}|%24%28)/i
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine], limit=200)
| sort(@timestamp, order=desc)
Q3 · Web server account shell baseline (rare command lines first)
CONF MEDIUMFP MEDIUMCOST LOW

Looks for: Every shell the web server account starts, grouped by host and parent, with a sample of command lines. On a healthy Nextcloud host the only regular entry is the stock CODE server launch (bash -c wrapping Collabora_Online.AppImage). Anything else, including a sh -c from a PHP parent that ends in 2>&1, is the web shell running a command. FP: cron-style helpers, preview generators and backup hooks that shell out as the web account.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: approximate-analytic, equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: Only one sample command line per group is returned, where the CQL returns up to five; pivot on the group to see the rest.
  • PowerQuery assumes: On a Process Creation event the account is read from src.process.user (the creating process).
  • PowerQuery assumes: endpoint.os holds the lowercase value linux.
  • KQL – Defender XDR: A shell started by a process the sensor did not observe starting is not captured.
  • KQL – Defender XDR assumes: AccountName holds the Linux account name of the created process.
// HUNT: Web server account shell baseline, rare command lines first
// MITRE: T1505.003, T1059.004 | CONF: medium  FP: medium  COST: low
// REQUIRES: Falcon Linux sensor on the Nextcloud host; ProcessRollup2 (Linux)
// FALSE POSITIVES: stock CODE server launch, preview generators, backup or housekeeping hooks run via sh -c
// TUNING: baseline the stock launch line (bash -c wrapping Collabora_Online.AppImage, no $( or ${IFS}) over 30 days, then exclude it per host; Q1 and Q2 still catch the injected variant
// LOOKBACK: 30d
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| event_platform=Lin
| UserName=/^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www)$/i
| FileName=/^(sh|bash|dash)$/i
| groupBy([ComputerName, UserName, ParentBaseFileName, FileName], function=[count(as=execs), collect(CommandLine, limit=5)])
| sort(execs, order=asc, limit=200)
Q4 · Web server account hunting for credential stores and cryptocurrency wallets
CONF HIGHFP LOWCOST LOW

Looks for: After the web shell was in place, the reported operator searched for password-manager databases and wallet files. This matches any process owned by the web server account whose command line names those artifacts: *.kdbx, wallet.dat, keystore.json, bitwarden, 1password, .bitcoin, .electrum, .monero, .ethereum. FP: near zero for the web account; a backup or malware scanner owned by it is the only plausible source.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: Access to these files through a library call, with no process command line naming them, is not captured.
  • PowerQuery assumes: On a Process Creation event the account is read from src.process.user (the creating process).
  • PowerQuery assumes: endpoint.os holds the lowercase value linux.
  • KQL – Defender XDR: Access to these files through a library call, with no process command line naming them, is not captured.
  • KQL – Defender XDR assumes: AccountName holds the Linux account name of the created process.
// HUNT: Web server account hunting for credential stores and cryptocurrency wallets
// MITRE: T1083, T1552.001, T1555.005, T1005 | CONF: high  FP: low  COST: low
// REQUIRES: Falcon Linux sensor on the Nextcloud host; ProcessRollup2 (Linux) with CommandLine
// FALSE POSITIVES: an AV or backup job running as the web account that enumerates these file types
// LOOKBACK: 30d
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| event_platform=Lin
| UserName=/^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www)$/i
| CommandLine=/\.kdbx|wallet\.dat|keystore\.json|bitwarden|1password|\.bitcoin|\.electrum|\.monero|\.ethereum/i
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine], limit=200)
| sort(@timestamp, order=desc)
Q5 · Web server account running recon, download or sudo commands
CONF MEDIUMFP MEDIUMCOST LOW

Looks for: Web shell tradecraft seen in the reports: the payload runs base64 -d, curl or wget to webhook.site, the operator runs uname / id, and administrators learned of the break-in from failed sudo attempts by the web account. Grouped per host so a single noisy cron does not bury a real hit. FP: health checks or update scripts that call curl or wget as the web account.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: approximate-analytic, equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: Only one sample command line per group is returned, where the CQL returns up to five; pivot on the group to see the rest.
  • PowerQuery assumes: On a Process Creation event the account is read from src.process.user (the creating process).
  • PowerQuery assumes: endpoint.os holds the lowercase value linux.
  • KQL – Defender XDR: A command started by a process the sensor did not observe starting is not captured.
  • KQL – Defender XDR assumes: AccountName holds the Linux account name of the created process.
// HUNT: Web server account running recon, download or sudo commands
// MITRE: T1082, T1033, T1105, T1567, T1548.003 | CONF: medium  FP: medium  COST: low
// REQUIRES: Falcon Linux sensor on the Nextcloud host; ProcessRollup2 (Linux)
// FALSE POSITIVES: health checks or update scripts that run curl or wget as the web account
// TUNING: exclude known health-check command lines per host; rank by the webhook.site and base64 -d markers, and always escalate any sudo or su attempt by the web account
// LOOKBACK: 30d
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| event_platform=Lin
| UserName=/^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www)$/i
| FileName=/^(curl|wget|base64|uname|id|whoami|hostname|sudo|su)$/i
| groupBy([ComputerName, UserName, FileName], function=[count(as=execs), collect(CommandLine, limit=5)])
| sort(execs, order=desc, limit=200)
Q6 · Reported attacker IPs and webhook.site callbacks on the endpoint
CONF MEDIUMFP LOWCOST LOW

Looks for: Inbound connections accepted from, or outbound connections to, the two IPs named in the reports, plus DNS lookups for webhook.site. The IPs are opportunistic and perishable, so a miss proves little. The domain is a public service with legitimate users: judge a hit by the process (a web server or PHP parent) and the host role, not by the domain alone. Linux DNS and inbound-accept coverage depends on sensor version and policy. FP: low for the IPs; developers testing webhooks can touch the domain.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: approximate-analyticProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: Linux DNS and inbound-accept telemetry depend on agent version and policy; a clean result is not proof of no contact.
  • PowerQuery assumes: event.dns.request holds the queried name (the vendor field docs also name dns.request; check which is populated).
  • PowerQuery assumes: A remote peer appears as dst.ip.address on outbound connections and as src.ip.address on inbound ones.
  • PowerQuery assumes: endpoint.os holds the lowercase value linux.
  • KQL – Defender XDR: A name lookup with no resulting connection, and a connection with an empty RemoteUrl, are not captured by the webhook.site predicate; a connection to the reported addresses is.
  • KQL – Defender XDR assumes: RemoteUrl is populated for connections to webhook.site; it is not populated for every connection.
  • KQL – Defender XDR assumes: Inbound accepted connections appear with the reported address in RemoteIP.
// HUNT: Reported attacker IPs and webhook.site callbacks on Linux hosts
// MITRE: T1190, T1071.001, T1567 | CONF: medium  FP: low  COST: low
// REQUIRES: NetworkReceiveAcceptIP4 / NetworkConnectIP4 / DnsRequest on Linux (sensor-version and policy dependent; validate in tenant)
// FALSE POSITIVES: developers testing webhooks from the same host; the IPs are opportunistic and may be reassigned
// LOOKBACK: 30d (exploitation first reported 2026-09-10)
#event_simpleName=/^(DnsRequest|NetworkConnectIP4|NetworkReceiveAcceptIP4)$/
| event_platform=Lin
| (DomainName=/(^|\.)webhook\.site$/i or RemoteAddressIP4=/^(139\.28\.18\.122|89\.38\.98\.50)$/)
| table([@timestamp, ComputerName, #event_simpleName, ContextBaseFileName, RemoteAddressIP4, RemotePort, DomainName], limit=max)
| sort(@timestamp, order=desc)
Q7 · Web server account outbound connections to rare external addresses
CONF LOWFP MEDIUMCOST MEDIUM

Looks for: Joins outbound connections to the process record so the owning account is known, keeps only the web server account, drops private ranges, and ranks destinations by how few hosts talk to them. A web shell that fetches tools or sends data out shows up as a rare destination. The reported activity sent only a uname -a beacon, so treat this as a pivot, not a detector. FP: Nextcloud federation, app store, update checks, mail and push-notification services.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: approximate-analyticProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: Only one sample process and command line per destination is returned, where the CQL returns up to five pairs.
  • PowerQuery assumes: On an IP Connect event the connecting process and its account are src.process.* and src.process.user.
  • PowerQuery assumes: endpoint.os holds the lowercase value linux.
  • PowerQuery assumes: The host count is an estimate (estimate_distinct), where the CQL counts exactly.
  • KQL – Defender XDR: A connection the sensor recorded with a different ActionType, and a connection that RemoteIPType does not classify, are not captured.
  • KQL – Defender XDR assumes: RemoteIPType is populated and reads Public for routable destinations.
  • KQL – Defender XDR assumes: InitiatingProcessAccountName holds the Linux account name of the connecting process.
  • KQL – Defender XDR assumes: The host count is an estimate (dcount), where the CQL counts exactly.
// HUNT: Web server account outbound connections to rare external addresses
// MITRE: T1071.001, T1105, T1567 | CONF: low  FP: medium  COST: medium
// REQUIRES: NetworkConnectIP4 (Linux) joined to ProcessRollup2 on aid + process id
// FALSE POSITIVES: Nextcloud federation, app store and update checks, SMTP, push notification relays
// TUNING: exclude your known federation, mail and update destinations; keep only destinations seen from one or two hosts
// LOOKBACK: 7d
#event_simpleName=NetworkConnectIP4
| event_platform=Lin
| RemoteAddressIP4!=/^(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|127\.|169\.254\.)/
| rename(field=ContextProcessId_decimal, as=TargetProcessId_decimal)
| join(query={#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | event_platform=Lin | UserName=/^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www)$/i}, field=[aid, TargetProcessId_decimal], include=[UserName, FileName, CommandLine])
| groupBy([RemoteAddressIP4, RemotePort], function=[count(ComputerName, distinct=true, as=hosts), count(aid, as=conns), collect([FileName, CommandLine], limit=5)])
| sort(hosts, order=asc, limit=200)
Q8 · PHP files created or modified under the Collabora app path or matching the web shell name pattern
CONF MEDIUMFP MEDIUMCOST LOW

Looks for: Any .php file touched under a path containing richdocumentscode, or any .php file anywhere whose name matches the reported pattern (shx or shrf followed by 4 to 5 hex characters). Excludes the stock proxy.php, which is a legitimate app file that the app upgrade rewrites; tampering with it is covered by the Nextcloud integrity check in N3. Coverage caveat: Falcon does not emit a write event for every PHP file by default. These two events only fire where file-open or file-integrity telemetry is enabled for the path, so a silent result here is not proof of absence. FP: app upgrades writing new app files.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: File telemetry on Linux depends on agent version and policy; a clean result is not proof that no file was written.
  • PowerQuery assumes: tgt.file.path holds the full path, so the file name is the final path segment.
  • PowerQuery assumes: endpoint.os holds the lowercase value linux.
  • KQL – Defender XDR: File telemetry on Linux depends on sensor version and policy; a clean result is not proof that no file was written.
  • KQL – Defender XDR assumes: FolderPath holds the directory of the written file on Linux sensors.
  • KQL – Defender XDR assumes: FileCreated and FileModified are the ActionType values for these writes.
// HUNT: PHP files created or modified under the Collabora app path or matching the web shell name pattern
// MITRE: T1505.003, T1105 | CONF: medium  FP: medium  COST: low
// REQUIRES: FileOpenInfo or CriticalFileModified on Linux for the Nextcloud apps path (policy dependent; validate in tenant)
// FALSE POSITIVES: richdocumentscode app upgrades writing new app files; the stock proxy.php is excluded because it is a legitimate app file
// TUNING: suppress writes during a change window for the app upgrade; always escalate names matching the shx or shrf pattern regardless of path
// LOOKBACK: 30d
#event_simpleName=/^(FileOpenInfo|CriticalFileModified)$/
| event_platform=Lin
| FileName=/\.php$/i
| FileName!=/^proxy\.php$/i
| (FileName=/^sh(x|rf)[0-9a-f]{4,5}\.php$/i or TargetFileName=*richdocumentscode*)
| table([@timestamp, ComputerName, #event_simpleName, UserName, ContextBaseFileName, TargetFileName], limit=200)
| sort(@timestamp, order=desc)
09

Detection Rules by Platform

CrowdStrike: Custom IOA

IOA nameField PatternsBenign ExclusionsDeployment Path
Web account shell with injection markers. Promote Q1Platform Linux; User www-data|apache|nginx|php-fpm|nobody; Image */sh | */bash | */dash; Command line regex \$\{IFS\}|base64(\$\{IFS\}|\s)+-d|%2e%2e|%24%28None expected. Allow-list a named admin script by exact command line if one existsEndpoint Security → Custom IOA → Process Creation (Detect first; Block after a clean 30-day baseline)
Injection text in CODE server launch. Promote Q2Platform Linux; Image */bash | */sh | */AppRun | */coolwsd | */coolforkit-ns; Command line regex remote_font_config\.url=\S*(\$\(|\$\{IFS\}|%24%28)None expectedCustom IOA → Process Creation (Detect; a hit on a patched host is an attempt)
Web account credential and wallet search. Promote Q4Platform Linux; User www-data|apache|nginx|php-fpm|nobody; Command line regex \.kdbx|wallet\.dat|keystore\.json|bitwarden|1password|\.bitcoin|\.electrum|\.monero|\.ethereumA named backup or AV job owned by the web account, by exact command lineCustom IOA → Process Creation (Detect, then Block after baseline)
Q1, Q2 and Q4 are strong IOA promotions because the stock app launch carries none of these tokens. Q3, Q5, Q6, Q7 and Q8 stay hunt or investigate only: they are baseline, pivot or telemetry-dependent. No IOA targets proxy.php or any richdocumentscode file, because those are the legitimate vulnerable app, not malware.

SentinelOne: custom detection rules

RuleSource queryRun asNotes
Web account shell with injection markers. Promote Q1Q1, PowerQuery tabScheduled custom detection ruleBind entities so the alert names the host instead of "Unknown Device". This pack's own text mentions a baseline or pilot; follow it as written
Injection text in CODE server launch. Promote Q2Q2, PowerQuery tabScheduled custom detection ruleBind entities so the alert names the host instead of "Unknown Device". This pack's own text mentions a baseline or pilot; follow it as written
Web account credential and wallet search. Promote Q4Q4, PowerQuery tabScheduled custom detection ruleBind entities so the alert names the host instead of "Unknown Device". This pack's own text mentions a baseline or pilot; follow it as written

Create the rule with POST /web/api/v2.1/cloud-detection/rules, queryType: "scheduled" and queryLang: "2.0", with the query in data.scheduledParams.query. Use 2.0 operator spellings: a rule written with the 1.0 token ContainsCIS was accepted and never fired on a tenant. The API does not warn, so lint the body before deploying.

Microsoft Defender: custom detection rules

RuleSource queryRun asNotes
Web account shell with injection markers. Promote Q1Q1, KQL – Defender XDR tabCustom detection rule, alert only, every hourThe query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself. This pack's own text mentions a baseline or pilot; follow it as written
Injection text in CODE server launch. Promote Q2Q2, KQL – Defender XDR tabCustom detection rule, alert only, every hourThe query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself. This pack's own text mentions a baseline or pilot; follow it as written
Web account credential and wallet search. Promote Q4Q4, KQL – Defender XDR tabCustom detection rule, alert only, every hourThe query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself. This pack's own text mentions a baseline or pilot; follow it as written

Q3, Q5, Q6, Q7 and Q8 stay a hunt in this pack, not a rule. A rule can generate only 150 alerts per run. Continuous (NRT) frequency needs a single table and no comment lines, so delete the generated comment header from the query first.

10

Machine-Readable IOC Appendix

Grouped IOC Quick-Copy

Four importable atomics (two IPs, one hash, one domain), all detect, none prevent: no value has two independent sources. Filenames and callback IDs are hunt values and are not importable as Falcon IOCs.

Falcon IOC Management CSVbulk import
type,value,action,severity,expiration,description,tags
ipv4,139.28.18.122,detect,medium,2027-01-16,Reported source of CVE-2026-110447 exploitation 2026-09-10 and 2026-09-11 (opportunistic and perishable),cve:CVE-2026-110447
ipv4,89.38.98.50,detect,medium,2027-01-16,Reported source of CVE-2026-110447 attempt 2026-09-14 (opportunistic and perishable),cve:CVE-2026-110447
sha256,4b9fbd0663fbfd6028fc216ed18e0d6ef3ae498a7fdc97083e4b0ce3cec40562,detect,high,2027-04-16,Recovered one-line PHP web shell sample from CVE-2026-110447 exploitation,cve:CVE-2026-110447
domain,webhook.site,detect,low,2027-01-16,Public webhook service used as the uname beacon callback; legitimate users exist so judge by host role,cve:CVE-2026-110447
Microsoft Defender indicators CSVSettings, Endpoints, Indicators, Import: audit only
indicatorType,indicatorValue,action,title,description,expirationTime,severity,recommendedActions,rbacGroups,category,mitretechniques,GenerateAlert
IpAddress,139.28.18.122,Audit,Reported source of CVE-2026-110447 exploitation 2026-09-10 a,Reported source of CVE-2026-110447 exploitation 2026-09-10 and 2026-09-11 (opportunistic and perishable),2027-01-16T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True
IpAddress,89.38.98.50,Audit,Reported source of CVE-2026-110447 attempt 2026-09-14,Reported source of CVE-2026-110447 attempt 2026-09-14 (opportunistic and perishable),2027-01-16T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True
FileSha256,4b9fbd0663fbfd6028fc216ed18e0d6ef3ae498a7fdc97083e4b0ce3cec40562,Audit,Recovered one-line PHP web shell sample from CVE-2026-110447,Recovered one-line PHP web shell sample from CVE-2026-110447 exploitation,2027-04-16T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True
DomainName,webhook.site,Audit,Public webhook service used as the uname beacon callback; le,Public webhook service used as the uname beacon callback; legitimate users exist so judge by host role,2027-01-16T00:00:00.0Z,Low,Investigate the device and pivot into the pack's queries,,,,True

Column names and allowed values are from Microsoft's documented parameter table. Check each expirationTime before importing: an indicator whose date has passed is already expired. Download the sample CSV from the Import page to confirm the column order before a bulk import. Each file takes at most 500 indicators. Learn describes the import per entity type, so if a mixed file is rejected, split it by indicatorType. An imported indicator does nothing unless the matching Advanced features are on (for example Custom network indicators and Allow or block file).

SentinelOne IOC importnot included

This pack does not ship a SentinelOne import block: the import format could not be verified against SentinelOne's own documentation. Export a sample from your console and the block can be added with confidence.

Behavioral Signaturesdetection logic
# Behavioral signatures (detection logic, not IOCs)
web account (www-data|apache|nginx|php-fpm|nobody) spawns sh/bash/dash with ${IFS}, base64 -d, %2e%2e or %24%28 in the command line
CODE server launch (bash, AppRun, coolwsd, coolforkit-ns) with $( or ${IFS} inside remote_font_config.url
web account runs find/grep/cat naming .kdbx, wallet.dat, keystore.json, bitwarden, 1password, .bitcoin, .electrum, .monero, .ethereum
web account runs curl/wget to webhook.site, or uname/id/whoami/sudo/su
PHP file written under apps/richdocumentscode other than the stock proxy.php
GET /apps/richdocumentscode/proxy.php?status with %2e%2e, $( or ${IFS} in the path (web tier)
repeat GET of a new shx*.php with ?c= (web tier, web shell use)
Hunt Values (names and callback IDs)hunt only
# Reported web shell filenames (hunt values; pattern: sh + x or rf + 4 to 5 hex characters + .php)
shrf4645.php
shxa35d0.php
shx5cf91.php
shxd33ce.php
shxf4645.php
shx13a86.php
shx56367.php
# Reported webhook.site callback IDs (hunt in web logs and command lines)
774cf5e1-b5a4-4174-a1ad-0241861029a2
45060ed4-de0b-4119-8f0d-2a2776bbd413
# Reported location (stock app, hunt location only, never an indicator of malice)
/apps/richdocumentscode/proxy.php?status
Patch / Removal Auditthe fix
# Patch / removal audit (the fix)
# Fixed: richdocumentscode 26.4.303 or later. Everything below 26.4.303 is vulnerable.
# Disabling the app does NOT close the flaw: upgrade or remove.
# Nextcloud 29 and older are end of life and cannot install 26.4.303: upgrade Nextcloud or remove the app.
sudo -u www-data php occ app:list | grep -i richdocumentscode
sudo -u www-data php occ app:update richdocumentscode
# or remove it:
sudo -u www-data php occ app:remove richdocumentscode
# Caveat reported by one administrator: 26.4.402 fails to start the CODE server where open_basedir blocks /dev/null.
# Standalone Collabora Online servers are not affected.
11

Detection Validation Gates

GateCheckPass Criteria
Telemetry readyConfirm a Falcon Linux sensor is on the Nextcloud host. Confirm process creation with command line, parent and user is populated (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents), and test whether file open or integrity events (CrowdStrike: FileOpenInfo, CriticalFileModified) and DNS lookups (CrowdStrike: DnsRequest) arrive for the apps pathUserName, ParentBaseFileName and CommandLine populated for www-data children; file and DNS events either present or recorded as a known gap
Benign baselineRun Q1, Q2 and Q4 over 30 days on a patched host. Run Q3 to baseline the stock launch lineQ1, Q2, Q4 return zero rows; Q3 shows only the stock bash -c launch and known housekeeping
Positive testLab host only: as the web account, run a harmless shell whose command line contains the literal text ${IFS} and one that names wallet.dat. Never use the live exploit or a real payloadQ1 and Q4 fire with user, parent and command line intact
Web-tier visibilityConfirm whether web server or proxy access logs reach the SIEM; if not, N1 is the only check for the initial requestLogs forwarded, or the gap is documented in the ticket
PromotionMove Q1, Q2 and Q4 to Custom IOA Detect, then BlockNo false positive for 30 days before Block mode
12

Hardening: Tiered & Deployable

This is patchable: moving to 26.4.303 or removing the app is the fix. Platforms touched: Linux hosts, Nextcloud app configuration, the web server (nginx or Apache with PHP-FPM), host firewall, and containers. Controls cite MITRE mitigations plus the platform source; where no formal benchmark exists the control is flagged.

Immediate: patch, remove, find and clean
  • Upgrade richdocumentscode to 26.4.303 or later, or remove the app (M1051 Update Software; vendor advisory GHSA-g3vx-3m76-fghh). Disabling is not enough because proxy.php runs without Nextcloud. Check: occ app:list shows the version. On Nextcloud 29 and older, upgrade Nextcloud or remove the app. Where the app is removed, confirm the directory is gone.
  • Find and delete web shells, then confirm integrity (M1049 Antivirus/Antimalware, M1022 Restrict File and Directory Permissions). Search apps/richdocumentscode and web roots for shx*.php and shrf*.php (N2) and run occ integrity:check-app (N3). Patching alone leaves an existing shell in place.
  • Treat web-account-readable secrets as exposed on any confirmed hit (M1027 Password Policies; vendor and reporter guidance). Rotate Nextcloud config.php secrets and database credentials, app passwords, and any password-manager database or wallet material stored on the host. Remove vaults and wallets from application servers.
  • Where an external Collabora server is in use, remove the built-in app instead of keeping it dormant (M1042 Disable or Remove Feature). A standalone Collabora Online server is not affected by this flaw.
Near term: shrink what the web account can do (pilot first)
  • Egress-filter the web account (M1037 Filter Network Traffic). Allow only the destinations Nextcloud needs (database, mail, update and federation endpoints) for the web account's UID; this breaks the webhook.site beacon and tool download. Verify with iptables -S OUTPUT | grep owner. See Playbook D. ⚠ best-practice, no formal benchmark for Nextcloud
  • Confine PHP-FPM with systemd sandboxing (M1048 Application Isolation and Sandboxing; systemd.exec hardening). Set NoNewPrivileges=yes, ProtectSystem=full and a minimal ReadWritePaths in a drop-in. Verify with systemd-analyze security php-fpm. Pilot: Nextcloud needs write access to its data and config directories.
  • Make PHP files in the apps directory non-writable by the web account where the app store is not used (M1022). A writable apps directory is what lets a shell drop a PHP file. Pilot: this blocks in-product app updates, so update apps from the shell during a change window.
  • Forward web server access logs to the SIEM (M1047 Audit). The injection request is visible only there. Without it, N1 on the host is the only check.
Strategic: remove the exposure class
  • Run Collabora as a separate standalone server or container (M1030 Network Segmentation; vendor guidance that the built-in app is for local testing and non-production). Removes the shell-out from the Nextcloud web tier entirely.
  • File integrity monitoring on the apps and web root paths (M1047 Audit). Alert on any new PHP file; this is also what makes Q8 meaningful.
  • Patch cadence for self-hosted apps that fix security bugs silently (M1051). This fix shipped on 2026-09-11 with release notes that did not mention security, and was exploited the day before. Track app releases, not just advisories.
13

Deployable Playbooks

Playbook A · Inventory and web shell sweep (Linux shell, read-only)

# Prerequisites: shell access to the Nextcloud host as a user who can read the web root and logs. Read-only.
# Reboot required: no
# Rollback: none required (read-only)
APPS=/var/www/nextcloud/apps        # adjust to your install
echo "== app version =="; sudo -u www-data php /var/www/nextcloud/occ app:list | grep -i richdocumentscode
echo "== web shells ==";  find /var/www -type f \( -name 'shx*.php' -o -name 'shrf*.php' \) 2>/dev/null
echo "== recent php in app dir =="; find "$APPS/richdocumentscode" -type f -name '*.php' -newermt 2026-09-10
echo "== integrity =="; sudo -u www-data php /var/www/nextcloud/occ integrity:check-app richdocumentscode

Playbook B · Update or remove richdocumentscode (occ)

# Prerequisites: a current backup of the Nextcloud data, config and database; a change window (Collabora editing is briefly unavailable);
#                Nextcloud 30 or later for 26.4.303 and above (Nextcloud 29 and older cannot install it).
# Reboot required: no (restart PHP-FPM so no old handler lingers).
# Rollback: restore the app directory from backup. Note the rollback target is vulnerable: only use it to restore service, then re-remove.
cd /var/www/nextcloud
sudo -u www-data php occ app:update richdocumentscode
sudo -u www-data php occ app:list | grep -i richdocumentscode        # expect 26.4.303 or later
# If the app is not needed (external Collabora in use), remove it instead:
# sudo -u www-data php occ app:remove richdocumentscode
# Stop any CODE server the old code started, then restart PHP-FPM:
sudo pkill -u www-data -f 'coolwsd|Collabora_Online' || true
sudo systemctl restart php-fpm      # unit name varies, e.g. php8.3-fpm

Playbook C · Deny the app path at the web server (only where the app is removed or unused)

# Prerequisites: ONLY where the built-in CODE server app is removed or unused. If the built-in app is in use, this breaks Collabora editing.
# Reboot required: no (reload the web server).
# Rollback: delete the snippet and reload the web server.
# nginx (inside the server block):
#   location ~* ^/apps/richdocumentscode/ { return 404; }
# Apache:
#   <LocationMatch "^/apps/richdocumentscode/">
#       Require all denied
#   </LocationMatch>
sudo nginx -t && sudo systemctl reload nginx
# or: sudo apachectl configtest && sudo systemctl reload apache2
# Verify: curl -s -o /dev/null -w '%{http_code}\n' https://YOUR-HOST/apps/richdocumentscode/proxy.php?status    # expect 404 or 403

Playbook D · Egress filter for the web account (iptables owner match, pilot first)

# Prerequisites: a staging host first. List the destinations the web account really needs (database, SMTP, update and federation
#                endpoints) by observing outbound traffic for a week. Replace the REPLACE_WITH_ values below.
# Reboot required: no. Rules are not persistent across reboot unless you save them (iptables-persistent or nftables.conf).
# Rollback: iptables -D OUTPUT -m owner --uid-owner www-data -j REPLACE_WITH_CHAIN  (or delete the chain), then re-save rules.
WEBUID=$(id -u www-data)
sudo iptables -N NC_WEB_EGRESS
sudo iptables -A NC_WEB_EGRESS -o lo -j ACCEPT
sudo iptables -A NC_WEB_EGRESS -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A NC_WEB_EGRESS -d REPLACE_WITH_DB_AND_SERVICE_CIDR -j ACCEPT
sudo iptables -A NC_WEB_EGRESS -j LOG --log-prefix 'NC-WEB-EGRESS-DROP ' --log-uid
sudo iptables -A NC_WEB_EGRESS -j DROP
sudo iptables -A OUTPUT -m owner --uid-owner "$WEBUID" -j NC_WEB_EGRESS
# Verify: sudo -u www-data curl -sm 5 https://webhook.site/    # should now fail; check the log line

Playbook E · Post-fix verification with the pack queries

# Prerequisites: Falcon Linux sensor on the host; the Q1, Q2 and Q4 queries from section 8.
# Reboot required: no
# Rollback: none required (read-only verification)
# After patching and cleanup, run Q1, Q2 and Q4 over the 7 days since the change. Expect zero rows.
# Re-run N1 against the access logs: requests with %2e%2e, $( or ${IFS} may keep arriving; they should no longer start a shell (Q1 zero).
# Re-run N4: a patched host may still show injection text in CODE server arguments after an attempt; that is harmless per the project.
14

Containment Runbook

PhaseActionsOwnerEvidence
IsolateNetwork-contain the host in Falcon on a confirmed Q1, Q2, Q4 or Q8 hit. Do not power off, to preserve volatile state. Block the host from reaching other internal systems. If the host is a container, cordon the node and stop routing to the podSOC / IRContainment timestamp, host and sensor ID
Isolate: by platformCrowdStrike: network-contain the host. SentinelOne: network quarantine (Disconnect from Network; confirm the label in your console version). Microsoft Defender: Isolate device (full network isolation).SOC / IRContainment timestamp, host and sensor ID
PreserveCopy the web shell file, the Nextcloud access and error logs from 2026-09-10 onward, ps output, /tmp/appimage_extracted_* and /tmp/coolwsd.* contents, and the Nextcloud config.php metadata (not secrets). Hash the filesIREvidence bundle with hashes
ScopeRun Q1 to Q8 fleet-wide and N1 on every host that ran richdocumentscode below 26.4.303. Check whether the web account read secrets: config.php, database credentials, any .kdbx, wallet or key files. No persistence beyond the web shell was reported; still check the web account's crontab, ~/.ssh, and new local usersIR / Threat huntingPer-host verdict table
EradicateUpgrade or remove the app (Playbook B). Delete every shx*.php and shrf*.php after preserving it. Kill CODE server processes started by the old code. Rebuild the host from a known good image if the web account ran anything beyond the reported discoverySysadminIntegrity check clean, N2 empty
RecoverRotate Nextcloud config.php secrets and database credentials, force logout of sessions, reset app passwords, and treat password-manager databases and wallets that were readable by the web account as compromised: move funds and change vault master passwords from a clean deviceSysadmin / Identity / FinanceRotation tickets with dates
ImproveRemove the built-in app in favor of a standalone Collabora server, forward web logs to the SIEM, enable the near-term hardening in section 12, and promote Q1, Q2 and Q4 to Custom IOASecurity engineeringChange records
15

Detection Coverage Map

TechniqueBehaviorCQLIOACrowdStrike coverageSentinelOneDefender XDR
T1190Initial request to proxy.php?status with $( and ${IFS} in the pathN1n/aGAP web-tier. Not visible to endpoint telemetry. Visible only if HTTP or proxy logs reach the SIEM, otherwise N1 on the hostGAPGAP
T1059.004 / T1027 / T1140Web account shell with injection markers; base64 decode to shQ1IOA-1Good needs a Falcon Linux sensor on the hostGood, with assumptionsGood, with assumptions
T1190 / T1059.004Injection text inside CODE server launch argumentsQ2IOA-2Good a hit on a patched host is an attempt, not a compromiseGood, with assumptionsGood, with assumptions
T1505.003Web shell file written under the app pathQ8n/aPartial default Falcon telemetry does not log every PHP write; depends on file policy. N2 and N3 are the reliable checksPartial: same limit as CrowdStrikePartial: same limit as CrowdStrike
T1505.003Web shell used: PHP spawns shell childrenQ3n/aPartial children are visible, the HTTP requests that drive them are not (web tier gap)Partial: Q3 approximatePartial: same limit as CrowdStrike
T1083 / T1552.001 / T1555.005Credential store and wallet searchQ4IOA-3Good only if the search runs as a process; reading files directly from PHP is not visibleGood, with assumptionsGood, with assumptions
T1082 / T1033 / T1105 / T1548.003Recon, download and sudo by the web accountQ5n/aPartial FP from health checksPartial: Q5 approximatePartial: same limit as CrowdStrike
T1567 / T1071.001webhook.site beacon; known IPsQ6, Q7n/aPartial perishable IPs; Linux DNS and inbound coverage vary; a beacon from PHP itself (not curl) shows only as a connectionPartial: Q6 approximate, Q7 approximatePartial: Q6 approximate, Q7 approximate
T1048 / T1041Data staging and exfiltration of filesn/an/aGAP no archive or upload step was observed in the reports, so there is nothing sourced to detectGAPGAP

Validation gates: (1) a Falcon Linux sensor is installed on the Nextcloud host and process telemetry carries user, parent and command line; (2) Q1, Q2, Q4 return zero in a 30-day clean baseline and Q3 shows only the stock launch; (3) file and DNS event availability is tested and recorded; (4) a lab shell carrying harmless marker text fires Q1 and Q4, never the live exploit; (5) promote Q1, Q2 and Q4 to Custom IOA Detect, then Block. The initial request is an accepted endpoint gap, covered only by web or proxy logs and by the patch.

16

Hunt Summary Ticket

TITLE:        Hunt - Nextcloud Collabora Built-in CODE Server CVE-2026-110447 (unauth command injection, web shells)
SEVERITY:     Critical (CVSS v4 9.2; remote, unauthenticated; exploited in the wild since 2026-09-10)
SCOPE:        Nextcloud hosts and containers running richdocumentscode below 26.4.303 (Linux). Standalone Collabora not affected
HYPOTHESIS:   While the built-in CODE server is stopped, a request to proxy.php with command substitution in the path runs a
              command as the web account; the operator writes a PHP web shell (shx*.php / shrf*.php) into apps/richdocumentscode,
              beacons uname output to webhook.site, then searches for password vaults and wallets. No actor is attributed.
QUERIES RUN:  Q1 web-account shell with injection markers (IOA) | Q2 injection text in CODE launch args (IOA) | Q3 web-account shell
              baseline | Q4 credential and wallet search (IOA) | Q5 recon, download, sudo | Q6 known IPs and webhook.site |
              Q7 rare web-account egress | Q8 PHP writes under app path + Native N1-N5
DO FIRST:     Upgrade richdocumentscode to 26.4.303 or later, or remove it (disabling is not enough); find and delete shx*.php and
              shrf*.php (N2, N3); check access logs back to 2026-09-10 (N1); then run Q1, Q2, Q4 and escalate any hit
FINDINGS:     <pending analyst execution>
GAPS:         Initial exploit request is outside endpoint telemetry (needs web or proxy logs in the SIEM). Process telemetry needs a
              Falcon Linux sensor on the Nextcloud host. PHP file writes are not logged by default; file and DNS coverage varies
ACTIONS:      Patch or remove the app; rotate secrets readable by the web account; egress-filter the web account; run Collabora as a
              standalone server; promote Q1, Q2, Q4 to Custom IOA
OWNER:        HuntPack
VERSION:      v0.1 - 2026-10-16
17

Changelog

v0.12026-10-16Initial pack. Covers CVE-2026-110447 (first exploitation 2026-09-10; advisory published 2026-10-14; fixed in richdocumentscode 26.4.303). 8 CQL hunts, 5 native hunts, 3 IOA candidates, 13 atomic indicators (4 importable), none prevent. Behavior-keyed on the web server account; initial request is a documented web-tier gap. No actor attributed and no campaign name used. SentinelOne PowerQuery and Microsoft Defender KQL tabs added to Q1-Q8: SIEM Query Rosetta release 0.1.0 (public-beta; static-reviewed translations).
18

References

TierSourceUsed ForAccess Date
1Collabora security advisory GHSA-g3vx-3m76-fghhAuthoritative scope, affected and fixed versions, CVSS, preconditions2026-10-16
1GitHub issue CollaboraOnline/richdocumentscode #354First-hand logs, web shell names, hash, IPs, callback IDs, credential search terms2026-10-16
1CVE.org record CVE-2026-110447Affected range and exploited-in-the-wild statement2026-10-16
1richdocumentscode fix commit 6cc8daeInjection primitive and the escapeshellarg fix2026-10-16
2The Hacker News: Hackers Backdoor Nextcloud ServersTimeline, mechanism, filenames, both callback IDs, hunt guidance2026-10-16
2IT-Connect: Nextcloud Servers Hacked Through Critical Collabora Built-in CODE FlawMitigation steps, IPs, 26.4.402 caveat2026-10-16
2GBHackers: Critical Collabora Online FlawAdvisory restatement, CVSS vector2026-10-16
2cvefeed.io CVE-2026-110447Description and dates2026-10-16
2Nextcloud help forum: To Collabora Built-in user, update ASAPEarly community warning; affected and not-affected deployments2026-10-16