When one of the most prolific extortion crews on the planet gets its own leak site defaced, defenders should pay attention — not for the schadenfreude, but for the technique. The Clop ransomware gang confirmed that its previous data leak site was compromised and defaced, forcing the group to migrate to a new Tor address. According to reporting by BleepingComputer, the intrusion — attributed to the ShinyHunters extortion collective — was executed through an unauthenticated path traversal vulnerability in an unpatched Grav CMS instance.
Let that sink in operationally: a financially motivated threat actor, with presumably mature operational security, was breached by a rival crew because of a known, unpatched CMS vulnerability on an internet-facing server. If Clop can't afford to run unpatched Grav, neither can you. The difference is that your organization isn't hidden behind Tor — your CMS is sitting on a routable IP, indexed by Shodan, and reachable by every automated scanner on the internet.
This post breaks down the vulnerability class, how the exploitation chain works, and — most importantly — how to detect exploitation attempts and harden Grav CMS deployments in your environment today.
Technical Analysis
Affected Product and Exposure
Grav CMS is an open-source, flat-file content management system written in PHP. Unlike database-backed platforms such as WordPress, Grav stores content and configuration in the filesystem — a design decision that makes a path traversal flaw especially dangerous, because file read/write primitives map directly to application content, configuration, and in some cases executable templates.
The flaw leveraged in this campaign is an unauthenticated path traversal — meaning no credentials, session, or prior access are required. Any remote client capable of reaching the web server can attempt exploitation. In the Clop incident, exploitation resulted in full site compromise and defacement, consistent with an attacker gaining the ability to read arbitrary files and/or write content outside the intended web root.
How the Attack Works (Defender's View)
Path traversal in PHP applications like Grav typically follows this chain:
- User-controlled input reaches a filesystem function. Grav routes and plugin endpoints accept parameters (page paths, file download handlers, media/asset resolvers, plugin endpoints) that are concatenated into filesystem paths.
- Insufficient canonicalization. The application fails to reject or normalize traversal sequences (
../, URL-encoded variants like..%2f,%2e%2e%2f, double-encoded%252e%252e, and Unicode overlong encodings) before passing the path tofile_get_contents(),include(), or a write equivalent. - Arbitrary file read (reconnaissance). Attackers first pull sensitive files:
/etc/passwd, Grav's own configuration files (user/config/system.yaml,user/config/security.yaml, environment files, API keys), and web server configs. On a CMS like Grav, config files can expose admin hashes and secret keys. - Arbitrary file write or template injection (impact). Where the vulnerable code path permits writes — or where a read primitive is chained with session/log poisoning — the attacker writes a defacement page, a webshell in the web root, or malicious Twig/PHP templates.
- Post-exploitation. Webshell deployment, credential harvesting, and lateral movement. In this case, ShinyHunters defaced the leak site and Clop was forced to burn the server and stand up new infrastructure.
The irony here is instructive: extortion actors hunt the same attack surface you defend. ShinyHunters has a long track record of exploiting misconfigurations and unpatched internet-facing services to steal data and extort victims. They applied exactly that playbook against a peer criminal operation. Your enterprise CMS deployments are softer targets than Clop's hidden service.
Exploitation Status
- Confirmed active exploitation in the wild — this is not theoretical. A real adversary used this flaw to fully compromise a production server operated by a security-conscious (if criminal) organization.
- The vulnerability requires no authentication and no user interaction — it is a scanner-friendly, automatable attack class.
- No CVE identifier was published in the source reporting at time of writing; defenders should track the official Grav security advisories and GitHub security feed for assignment and patched version numbers.
Detection & Response
Web Server Log Detection — Sigma
Path traversal attempts are among the most detectable attack classes in existence — provided you are actually logging and forwarding web server access logs to your SIEM. These rules target the request itself and the post-exploitation behavior of a PHP application spawning system commands.
---
title: Grav CMS Path Traversal Attempt in Web Request
id: 4f8c2a91-6b3d-4e7a-9c1f-2a5d8e0b3f71
status: experimental
description: Detects path traversal sequences (raw and encoded) in web request URIs targeting PHP/Grav CMS endpoints. Correlates with exploitation of the unauthenticated Grav CMS path traversal used to compromise the Clop leak site.
references:
- https://www.bleepingcomputer.com/news/security/shinyhunters-hacked-clop-leak-site-using-grav-cms-path-traversal-flaw/
- https://attack.mitre.org/techniques/T1190/
- https://owasp.org/www-community/attacks/Path_Traversal
author: Security Arsenal
date: 2026/02/15
tags:
- attack.initial_access
- attack.t1190
logsource:
category: webserver
detection:
selection_uri:
cs-uri|contains:
- '../'
- '..\\'
- '..%2f'
- '..%5c'
- '%2e%2e%2f'
- '%2e%2e/'
- '..%252f'
- '%252e%252e'
- '%c0%ae'
- '..;/'
filter_static:
cs-uri|contains:
- '/health'
- '/status'
condition: selection_uri and not filter_static
falsepositives:
- Legitimate applications with poorly designed URL routing (rare)
- Some scanner/vulnerability-assessment traffic (investigate source)
level: high
---
title: Grav CMS Sensitive File Access via Traversal
id: 9d1e5b42-7f6a-4c8d-b2e3-8a4f1d9c6e20
status: experimental
description: Detects web requests attempting to retrieve sensitive system or Grav configuration files, consistent with post-traversal reconnaissance following exploitation of the Grav CMS flaw used against the Clop leak site.
references:
- https://www.bleepingcomputer.com/news/security/shinyhunters-hacked-clop-leak-site-using-grav-cms-path-traversal-flaw/
- https://attack.mitre.org/techniques/T1190/
author: Security Arsenal
date: 2026/02/15
tags:
- attack.initial_access
- attack.t1190
- attack.t1005
logsource:
category: webserver
detection:
selection:
cs-uri|contains:
- 'etc/passwd'
- 'etc/shadow'
- '/proc/self/environ'
- 'user/config/system.yaml'
- 'user/config/security.yaml'
- 'user/accounts/'
- 'win.ini'
- 'boot.ini'
condition: selection
falsepositives:
- Authorized vulnerability scanning (verify against scanner IP ranges)
level: critical
---
title: PHP Process Spawning Shell or Command Interpreter
id: 2b7a4f18-3d9e-4f1c-a6b5-5c8e2d7a0f43
status: experimental
description: Detects PHP-FPM, PHP-CGI, or Apache/Nginx worker processes spawning command shells or system utilities — a strong indicator of webshell or post-exploitation activity following web application compromise such as the Grav CMS path traversal exploited by ShinyHunters.
references:
- https://www.bleepingcomputer.com/news/security/shinyhunters-hacked-clop-leak-site-using-grav-cms-path-traversal-flaw/
- https://attack.mitre.org/techniques/T1505/003/
- https://attack.mitre.org/techniques/T1059/004/
author: Security Arsenal
date: 2026/02/15
tags:
- attack.persistence
- attack.t1505.003
- attack.execution
- attack.t1059.004
logsource:
category: process_creation
product: linux
detection:
selection_parent:
ParentImage|contains:
- '/php-fpm'
- '/php-cgi'
- '/php'
- '/apache2'
- '/httpd'
- '/nginx'
selection_child:
Image|endswith:
- '/sh'
- '/bash'
- '/dash'
- '/zsh'
- '/curl'
- '/wget'
- '/nc'
- '/ncat'
- '/python'
- '/python3'
- '/perl'
condition: selection_parent and selection_child
falsepositives:
- Rare legitimate plugin functionality executing system commands (Grav backup/maintenance tasks)
- Health-check scripts invoked via cron wrappers (validate parent chain)
level: critical
Hunting Traversal Attempts — KQL (Microsoft Sentinel)
If your Grav hosts front through a WAF, load balancer, or reverse proxy that ships logs to Sentinel via CEF/Syslog, hunt for traversal patterns and sensitive-file targets directly. This query covers both the raw and encoded variants and surfaces requesting sources for pivoting.
// Hunt for path traversal attempts against web applications (Grav CMS and others)
// Ingests via CEF (CommonSecurityLog) and Syslog web access logs
let EncodedPatterns = dynamic(["../", "..\\", "..%2f", "..%5c", "%2e%2e%2f", "%2e%2e/", "..%252f", "%252e%252e", "%c0%ae", "..;/"]);
let SensitiveTargets = dynamic(["etc/passwd", "etc/shadow", "proc/self/environ", "user/config/system.yaml", "user/config/security.yaml", "user/accounts/", "win.ini", "boot.ini"]);
union isfuzzy=true
(CommonSecurityLog
| where TimeGenerated > ago(7d)
| where RequestURL has_any (EncodedPatterns) or RequestURL has_any (SensitiveTargets)
| project TimeGenerated, SourceIP, DestinationIP, DestinationHostName, RequestURL, RequestMethod, ApplicationProtocol, ReceivedBytes, SentBytes, DeviceVendor, DeviceProduct),
(Syslog
| where TimeGenerated > ago(7d)
| where SyslogMessage has_any (EncodedPatterns) or SyslogMessage has_any (SensitiveTargets)
| where SyslogMessage has "GET" or SyslogMessage has "POST"
| project TimeGenerated, HostName, ProcessName, SyslogMessage)
| extend IndicatorType = iff(RequestURL has_any (SensitiveTargets) or SyslogMessage has_any (SensitiveTargets), "Sensitive File Access", "Traversal Sequence")
| summarize AttemptCount = count(), DistinctURIs = dcount(RequestURL), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
by SourceIP, IndicatorType
| where AttemptCount >= 3 or IndicatorType == "Sensitive File Access"
| order by LastSeen desc
Also hunt for post-exploitation on Linux hosts enrolled in Defender for Endpoint — PHP worker processes executing shells is a high-fidelity signal of webshell activity:
// Post-exploitation: PHP/web server processes spawning shells or downloaders
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| where InitiatingProcessFileName has_any ("php-fpm", "php-cgi", "php", "apache2", "httpd", "nginx")
| where FileName in~ ("sh", "bash", "dash", "curl", "wget", "nc", "ncat", "python3", "perl")
| project TimeGenerated, DeviceName, InitiatingProcessFileName, FileName, ProcessCommandLine, InitiatingProcessCommandLine, AccountName
| order by TimeGenerated desc
Endpoint Forensics — Velociraptor VQL
If you suspect a Grav host was hit, check for recently modified or newly created files in the web root (defacement artifacts and webshells) and enumerate outbound connections from web server processes.
-- Hunt for recently created/modified files in Grav web roots (defacement / webshell artifacts)
-- Adjust WebRoot glob to match your deployment path
LET WebRoots = ["/var/www/**", "/srv/grav/**", "/usr/share/nginx/html/**", "/var/www/html/**"]
SELECT FullPath, Size, Mtime, Ctime,
Mtime.String AS ModifiedTime
FROM glob(globs=WebRoots)
WHERE NOT IsDir
AND Mtime > now() - 86400 * 7
AND (FullPath =~ '\\.php$' OR FullPath =~ '\\.twig$' OR FullPath =~ 'index\\.')
ORDER BY Mtime DESC
-- Enumerate outbound network connections from PHP/web server processes (potential webshell C2 or data exfil)
SELECT Pid, Name, Status, Family, Type, LocalIP, LocalPort, RemoteIP, RemotePort
FROM netstat()
WHERE Name =~ 'php|apache|nginx|httpd'
AND Status =~ 'ESTAB'
AND NOT RemoteIP =~ '^(127\\.|10\\.|192\\.168\\.|172\\.(1[6-9]|2[0-9]|3[01])\\.)'
Remediation
The Clop incident demonstrates the blast radius of an unpatched CMS: full compromise, defacement, and forced infrastructure migration. Take the following steps immediately for every Grav deployment in your estate — including staging and forgotten microsites, which are precisely what threat actors like ShinyHunters enumerate.
Immediate Actions
- Inventory every Grav CMS instance. Scan your external attack surface and internal subnets for Grav fingerprints (
/admin, Grav generator meta tags,X-Gravheaders). Flat-file CMS instances are frequently deployed for marketing microsites and abandoned. - Upgrade Grav core and all plugins to the latest release. Monitor the official channels for the patched version covering this unauthenticated path traversal:
- Grav security advisories: https://github.com/getgrav/grav/security/advisories
- Grav releases: https://github.com/getgrav/grav/releases
- Grav official site / changelog: https://getgrav.org/
- Block traversal sequences at the edge as a compensating control while patching is validated — at your WAF, reverse proxy, or web server configuration.
- Rotate credentials and secrets stored in Grav configuration files on any internet-facing instance: admin passwords, API keys, and any secrets referenced in
user/config/— assume read access has occurred. - Hunt before you patch. Run the detections above against 30+ days of retained web logs. Patching first destroys forensic evidence of prior exploitation.
Verification and Hardening Script
The following Bash script audits a Linux-hosted Grav instance: checks core version, scans for existing traversal hits in access logs, verifies no unexpected PHP files were recently written to the web root, and applies a defense-in-depth traversal block for Nginx.
#!/bin/bash
# Grav CMS Path Traversal — Audit & Harden Script
# Run as root or with sudo on the Grav host
GRAV_ROOT="/var/www/grav" # Adjust to your deployment
LOG_DIRS="/var/log/nginx /var/log/apache2 /var/log/httpd"
echo "=== [1] Grav Core Version ==="
if [ -f "$GRAV_ROOT/bin/grav" ]; then
php "$GRAV_ROOT/bin/grav" version 2>/dev/null || php "$GRAV_ROOT/bin/grav" --version
else
echo "Grav CLI not found at $GRAV_ROOT/bin/grav — check GRAV_ROOT path"
fi
echo ""
echo "=== [2] Traversal attempts in access logs (last 30 days) ==="
for d in $LOG_DIRS; do
if [ -d "$d" ]; then
echo "--- Scanning $d ---"
find "$d" -name '*access*' -mtime -30 -type f 2>/dev/null | while read -r log; do
zgrep -aEi '(\.\./|\.\.%2f|%2e%2e%2f|\.\.%5c|%252e%252e|etc/passwd|user/config/(system|security)\.yaml)' "$log" 2>/dev/null \
| awk '{print $1, $7}' | sort | uniq -c | sort -rn | head -20
done
fi
done
echo ""
echo "=== [3] Suspicious recently-written PHP files in web root (last 14 days) ==="
find "$GRAV_ROOT" -name '*.php' -mtime -14 -type f 2>/dev/null \
-not -path '*/vendor/*' -not -path '*/cache/*' -printf '%T+ %p\n' | sort -r | head -30
echo ""
echo "=== [4] World-writable files in user/config (should be none) ==="
find "$GRAV_ROOT/user/config" -perm -o+w -type f 2>/dev/null
echo ""
echo "=== [5] Apply Nginx traversal deny rule ==="
NGINX_SNIPPET="/etc/nginx/snippets/grav-traversal-block.conf"
cat > "$NGINX_SNIPPET" <<'EOF'
# Block path traversal sequences targeting Grav CMS
location ~* (\.\.|%2e%2e|%252e|etc/passwd|proc/self) {
return 403;
}
# Deny direct access to Grav config and system paths
location ~* /(user/config|user/accounts|system|vendor|\.git)/ {
deny all;
return 403;
}
EOF
echo "Wrote $NGINX_SNIPPET"
echo "Include it in your Grav server block: include snippets/grav-traversal-block.conf;"
echo "Then: nginx -t && systemctl reload nginx"
echo ""
echo "=== [6] Upgrade Grav core and plugins (REVIEW BEFORE RUNNING) ==="
echo "cd $GRAV_ROOT && php bin/gpm selfupgrade -y && php bin/gpm update -y"
echo ""
echo "=== Audit complete. Review findings above before patching (preserve evidence). ==="
Longer-Term Hardening
- Treat CMS platforms as Tier-0 attack surface. Flat-file CMSs like Grav turn file read/write primitives directly into code execution paths. They belong in your vulnerability management program with aggressive SLA timelines for unauthenticated flaws.
- WAF with managed rulesets. Ensure your WAF (Cloudflare, AWS WAF, ModSecurity with OWASP CRS) has path traversal rules enabled in blocking mode, not detection-only.
- Egress filtering on web servers. A webshell that cannot reach the internet is a significantly blunted weapon. Deny outbound traffic from web server service accounts except to explicit allowlists.
- Runtime application self-protection and EDR on Linux web tiers. The PHP-spawning-shell detection above only works if you're collecting process telemetry from your web servers.
- Decommission abandoned instances. The most dangerous Grav install is the one nobody owns. Attack surface management tooling should flag CMS fingerprints on assets not mapped to an owner.
The strategic lesson from this incident transcends Grav: ShinyHunters breached a ransomware cartel using an unpatched CMS bug. Your adversaries run the same playbooks against you — the only difference is that your leak site is your production network.
Related Resources
Security Arsenal Healthcare Cybersecurity AlertMonitor Platform Book a SOC Assessment healthcare Intel Hub
Is your security operations ready?
Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.