Public Website Defacement via Compromised CMS Admin Session or Mass Static Asset Tampering
Adversaries who obtain stolen CMS administrator credentials or exploit an unpatched plugin/theme vulnerability on a public-facing content management system (WordPress, Joomla, Drupal, Umbraco, Sitecore, etc.) frequently pivot straight to defacing the site — replacing the homepage, posting propaganda content, or mass-editing pages — to deliver a political message, claim credit, or intimidate the target organization. This pattern shows up in two complementary ways depending on the compromise vector: (1) a burst of authenticated content-edit HTTP requests (POST/PUT to admin post/page/theme-editor endpoints) from a single source IP or session hitting many distinct URLs in a short window, captured in WAF or reverse-proxy access logs, which is characteristic of a scripted mass-edit rather than a human editor working through the admin UI one page at a time; and (2) direct filesystem tampering where an attacker who has RCE (via a vulnerable plugin, exposed file manager, or stolen SFTP/SSH credentials) bypasses the CMS application layer entirely and mass-modifies static HTML, PHP, CSS, JS, and image assets directly in the web root, captured via endpoint file-monitoring telemetry. Both paths differ sharply from routine content operations: legitimate CMS editors and CI/CD deployment pipelines touch a handful of pages per session or deploy through a recognized service account/process, not dozens of distinct admin endpoints or web-root files from a single caller within minutes. Detection correlates a distinct-URL or distinct-file count threshold within a short burst window against the initiating source IP, session, or account to separate scripted mass defacement from normal editorial and deployment activity.
What is THREAT-Impact-PublicWebsiteDefacement Public Website Defacement via Compromised CMS Admin Session or Mass Static Asset Tampering?
Public Website Defacement via Compromised CMS Admin Session or Mass Static Asset Tampering (THREAT-Impact-PublicWebsiteDefacement) maps to the Impact tactic — the adversary is trying to manipulate, interrupt, or destroy your systems and data in MITRE ATT&CK.
This page provides production-ready detection logic for Public Website Defacement via Compromised CMS Admin Session or Mass Static Asset Tampering, covering the data sources and telemetry it touches: WAF / reverse proxy access logs (CEF), Microsoft Defender for Endpoint file events, File: File Modification. The queries below are rated critical severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.
MITRE ATT&CK
- Tactic
- Impact
let EditWindow = 15m;
let MinEditCount = 8;
let MinFilesModified = 5;
let AdminPaths = dynamic(["/wp-admin/post.php", "/wp-admin/post-new.php", "/wp-admin/edit.php", "/wp-admin/theme-editor.php", "/wp-admin/customize.php", "/wp-json/wp/v2/pages", "/wp-json/wp/v2/posts", "/administrator/index.php", "/administrator/components/com_content", "/user/login", "/node/add", "/umbraco/backoffice", "/sitecore/shell"]);
let WebRootPaths = dynamic(["\\inetpub\\wwwroot\\", "\\wwwroot\\", "\\htdocs\\", "\\public_html\\", "/var/www/", "/usr/share/nginx/html/"]);
let WebAssetExtensions = dynamic([".html", ".htm", ".php", ".css", ".js", ".jpg", ".png", ".svg"]);
// Branch 1: burst of CMS admin content-edit requests from a single source IP, seen in WAF/reverse-proxy logs forwarded as CEF
let CmsAdminEditBurst = CommonSecurityLog
| where TimeGenerated > ago(2h)
| where RequestMethod in ("POST", "PUT")
| where RequestURL has_any (AdminPaths)
| summarize EditCount = count(), DistinctURLs = dcount(RequestURL), URLSample = make_set(RequestURL, 10) by SourceIP, DestinationHostName, bin(TimeGenerated, EditWindow)
| where EditCount >= MinEditCount
| extend DetectionType = "CmsAdminEditBurst", Severity = "High"
| project TimeGenerated, Actor = SourceIP, Target = DestinationHostName, Count = EditCount, DistinctItems = DistinctURLs, Sample = URLSample, DetectionType, Severity;
// Branch 2: mass direct modification of static web assets on the host filesystem, bypassing the CMS application layer entirely
let MassAssetTamper = DeviceFileEvents
| where TimeGenerated > ago(2h)
| where ActionType == "FileModified"
| where FolderPath has_any (WebRootPaths)
| where FileName has_any (WebAssetExtensions)
| where InitiatingProcessFileName !in~ ("svchost.exe", "TrustedInstaller.exe", "msiexec.exe", "WUDFHost.exe", "MicrosoftEdgeUpdate.exe")
| summarize FilesModified = dcount(FileName), FileSample = make_set(FileName, 10), Processes = make_set(InitiatingProcessFileName) by DeviceName, InitiatingProcessAccountName, bin(TimeGenerated, EditWindow)
| where FilesModified >= MinFilesModified
| extend DetectionType = "MassStaticAssetTamper", Severity = "Critical"
| project TimeGenerated, Actor = InitiatingProcessAccountName, Target = DeviceName, Count = FilesModified, DistinctItems = FilesModified, Sample = FileSample, DetectionType, Severity;
CmsAdminEditBurst
| union MassAssetTamper
| sort by TimeGenerated desc Detects public website defacement via two complementary branches. Branch 1 mines WAF/reverse-proxy access logs (ingested into CommonSecurityLog as CEF) for a single source IP issuing 8 or more POST/PUT requests to CMS admin content-edit endpoints (WordPress post/page editor and REST API, Joomla administrator, Drupal node/add, Umbraco/Sitecore backoffice) across 8 or more distinct URLs within a 15-minute window — the signature of a scripted mass-edit rather than manual editorial work. Branch 2 mines Microsoft Defender for Endpoint file telemetry for 5 or more distinct static web assets (HTML, PHP, CSS, JS, images) modified in a web root directory within the same window by a process not associated with routine OS servicing, catching the RCE/file-manager path where the attacker bypasses the CMS UI entirely. Either branch firing independently is a strong defacement indicator; both firing in the same window against the same host is near-certain.
Data Sources
Required Tables
False Positives
- Legitimate content editors performing bulk edits during a scheduled content migration, redesign, or seasonal campaign refresh from a known editorial account
- CI/CD deployment agents (Jenkins, GitHub Actions runners, Azure DevOps build agents) pushing an updated site build to the web root as part of a normal release
- CMS auto-update processes (WordPress core/plugin/theme auto-updater, Composer-driven Drupal deploys) that rewrite many PHP/HTML/CSS files during a scheduled update window
- Marketing or SEO automation tools that bulk-update metadata or content across many pages via the CMS REST API on a schedule
- Site migration or staging-to-production sync jobs that copy a large static asset set into the web root in one operation
Sigma rule & cross-platform mapping
The detection logic for Public Website Defacement via Compromised CMS Admin Session or Mass Static Asset Tampering (THREAT-Impact-PublicWebsiteDefacement) above is provided in a vendor-neutral
form so you can deploy it on any SIEM. The same logic is shipped here as native
KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the
following logsource:
logsource:
category: network_connection
product: windows Browse the community-maintained Sigma rules for this technique:
Platform-specific guides for THREAT-Impact-PublicWebsiteDefacement
References (6)
- https://attack.mitre.org/techniques/T1491/002/
- https://attack.mitre.org/techniques/T1491/
- https://attack.mitre.org/tactics/TA0040/
- https://owasp.org/www-community/attacks/Content_Spoofing
- https://www.cisa.gov/news-events/cybersecurity-advisories
- https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1491.002/T1491.002.md
Testing Methodology
Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Simulate Burst of CMS Admin Content-Edit Requests
Expected signal: WAF/reverse-proxy access logs (CommonSecurityLog) showing 10 POST requests to /wp-admin/post.php from the test source IP within a few minutes, targeting 10 distinct post IDs.
- Test 2Simulate Mass Static Asset Tampering on Web Root (Linux)
Expected signal: File-modification events (Sysmon Event ID 11 / auditd) for 6 distinct files under /tmp/atomictest-webroot within seconds of each other, written by a shell process.
- Test 3Simulate Mass Static Asset Tampering on IIS Web Root (Windows)
Expected signal: MDE DeviceFileEvents / Sysmon Event ID 11 entries for 5 distinct files under C:\atomictest\wwwroot within seconds, with InitiatingProcessFileName = powershell.exe.
Response Playbook
Triage
- Confirm the defacement is real, not a false positive: browse the affected page(s) directly (or check a cached/archived copy) to verify content was actually altered, rather than relying on the alert alone
- Identify the source: for Branch 1, pull the source IP and user session/account tied to the admin-edit burst from the WAF logs; for Branch 2, identify the initiating process and account that wrote the modified files on the host
- Determine the entry vector — check CMS admin login history for the account used (was it a known editor, or a first-time/anomalous login?) and check for recent plugin/theme installs or known CVEs in the CMS version if Branch 2 fired without a matching Branch 1 admin session
- Enumerate the full scope of modified content: list every distinct URL (Branch 1) or file (Branch 2) touched in the burst window, not just the homepage — attackers often modify multiple pages to maximize visibility or plant additional backdoors
- Check for webshells or backdoor files dropped alongside the defacement content (unexpected .php/.aspx/.jsp files with recent creation timestamps in or near the web root)
- Review DNS and CDN configuration for tampering — some defacement campaigns modify DNS records or CDN origin settings instead of (or in addition to) site content directly
Containment
- Take the affected page(s) or the entire site offline (maintenance mode, CDN-level block, or full server isolation) if the defaced content is actively serving to the public, to limit reputational damage and prevent secondary payload delivery
- Disable or reset credentials for the compromised CMS admin account(s) and any account showing anomalous login activity, and force session/token invalidation
- Isolate the host from the network if Branch 2 (direct filesystem tampering) fired, to stop an active RCE session or backdoor from re-deploying defacement content after cleanup
- Block the source IP(s) identified in the admin-edit burst at the WAF/firewall while investigation continues
- Restore the web root and CMS database from a known-good backup taken before the compromise window, rather than manually reverting individual files, since manual reversion can miss planted backdoors
Evidence Collection
- Full WAF/reverse-proxy access logs for the source IP and session covering the incident window and the 48-72 hours preceding it
- CMS admin audit log / revision history for every page or post touched, including before/after content diffs where the CMS retains revisions
- Host-level file modification records (Sysmon Event ID 11, MDE DeviceFileEvents) for the full web root directory tree, plus any newly created files not part of the original site build
- CMS plugin/theme inventory and version list at time of incident, cross-referenced against known CVEs, to establish the exploited vulnerability if the vector was unpatched software rather than stolen credentials
- A forensic copy (disk image or targeted file capture) of the affected web server before remediation begins, to preserve evidence of the exploitation path and any dropped webshells
Escalation Criteria
- ! 8 or more distinct admin-edit requests from a single source IP within 15 minutes with no matching change ticket or known editorial session
- ! 5 or more distinct static web assets modified directly on the filesystem within 15 minutes by a process outside the routine servicing exclusion list
- ! Both branches fire against the same host in the same window (application-layer edit burst plus filesystem-layer tampering)
- ! A webshell or other backdoor artifact is discovered alongside the defacement content
- ! The affected site handles payment processing, authentication, or customer PII, escalating this from a reputational incident to a potential data-breach investigation
Investigation Guide
Forensic Artifacts
- >
WAF/reverse-proxy access log entries (CommonSecurityLog CEF) showing the admin-edit request burst, including full RequestURL, SourceIP, and response codes - >
CMS application audit log / content revision history for every modified page or post - >
Endpoint file-modification telemetry (Sysmon Event ID 11, MDE DeviceFileEvents) for the web root directory tree, including InitiatingProcessFileName and InitiatingProcessAccountName - >
CMS admin authentication logs (login success/failure, source IP, session tokens) for the account used in the edit burst - >
Web server process list and recently created files in or near the web root, to identify webshells or other persistence artifacts dropped during the same incident window
Tuning Guidance
Build and maintain an allowlist of known CI/CD deployment agent IPs and CMS auto-updater service accounts that legitimately touch many pages/files in a short window, and exclude them by identity rather than by request pattern, since the request/file signatures are otherwise identical between legitimate deployment and malicious defacement. Tune MinEditCount and MinFilesModified down for smaller sites with infrequent legitimate bulk edits, and up for large multi-editor sites or sites with frequent scheduled content pushes — validate against your own 7-day baseline using the first hunting query before deploying to production. Treat the login-then-file-modification sequence (second hunting query) as materially higher confidence than either base detection branch alone, since it isolates the pattern with the fewest legitimate explanations. Where the site sits behind a CDN or WAF that does not forward CEF logs to your SIEM, prioritize deploying Branch 2 (endpoint file telemetry) alone as a compensating control.
Hunting Queries
7-day baseline hunt to distinguish routine editorial/automation traffic (steady, low daily volume, usually a recognized office IP range or CI/CD egress IP) from bursty or first-time high-volume callers hitting admin endpoints, which are the higher-suspicion population worth reviewing individually.
// Hunt: source IPs issuing any volume of POST/PUT requests to CMS admin endpoints over the last 7 days, to baseline normal editorial traffic volume vs anomalous callers
CommonSecurityLog
| where TimeGenerated > ago(7d)
| where RequestMethod in ("POST", "PUT")
| where RequestURL has_any (dynamic(["/wp-admin/", "/administrator/", "/umbraco/", "/sitecore/", "/node/add"]))
| summarize RequestCount = count(), DistinctURLs = dcount(RequestURL), ActiveDays = dcount(bin(TimeGenerated, 1d)) by SourceIP
| where DistinctURLs >= 3
| order by DistinctURLs desc index=waf sourcetype=cef (method=POST OR method=PUT)
| eval AdminPath=if(like(request_url,"%/wp-admin/%") OR like(request_url,"%/administrator/%") OR like(request_url,"%/umbraco/%") OR like(request_url,"%/sitecore/%") OR like(request_url,"%/node/add%"), 1, 0)
| where AdminPath=1
| stats count as RequestCount, dc(request_url) as DistinctURLs by src_ip
| where DistinctURLs >= 3
| sort - DistinctURLs Hunts for the specific sequence of a CMS admin login followed quickly by web root file modification from the same source IP — this ordering is a stronger compromise signal than either event alone, since it isolates fresh/anomalous logins that lead directly into content tampering rather than long-lived legitimate editorial sessions.
// Hunt: web root file modifications immediately preceded by a new/anomalous CMS admin login (within 30 minutes), correlating the credential-compromise and defacement stages
let AdminLogins = CommonSecurityLog
| where TimeGenerated > ago(7d)
| where RequestURL has_any (dynamic(["/wp-login.php", "/administrator/index.php", "/user/login"]))
| where RequestMethod == "POST"
| project LoginTime = TimeGenerated, SourceIP;
DeviceFileEvents
| where TimeGenerated > ago(7d)
| where ActionType == "FileModified"
| where FolderPath has_any (dynamic(["\\wwwroot\\", "/var/www/"]))
| join kind=inner (AdminLogins) on $left.RemoteIP == $right.SourceIP
| where TimeGenerated - LoginTime between (0min .. 30min)
| project TimeGenerated, DeviceName, FileName, SourceIP, LoginTime index=waf sourcetype=cef method=POST (like(request_url,"%/wp-login.php%") OR like(request_url,"%/administrator/index.php%") OR like(request_url,"%/user/login%"))
| rename src_ip as SourceIP
| join type=inner SourceIP
[ search index=wineventlog sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=11 (like(TargetFilename,"%wwwroot%") OR like(TargetFilename,"%/var/www/%")) | rename SourceIp as SourceIP ]
| where (_time - _time) <= 1800 Novelty-based companion to the Branch 1 admin-edit burst: rather than thresholding request volume, this flags a CMS content-publish or content-change action from a source IP that has never previously been associated with that admin account across a 90-day baseline — the credential-theft / session-hijack path folded in from the T1491.001 internal-defacement detection. Catches the low-volume defacement case (a single homepage edit from a new location) that the count-based burst branch would miss. Seed the 90-day baseline before enabling alerting, and consider excluding known corporate egress / VPN exit ranges to reduce travel-related noise.
// Hunt: CMS admin content-publish/content-change action from a source IP never previously seen for that admin account
// Novelty-based companion to the volume-based Branch 1 admin-edit burst — catches the low-and-slow defacement case (a single homepage/page edit after credential theft or session hijack) that a request-count threshold would miss.
// Requires a CMS/web-application audit log ingested as a custom table (modeled here as CMSAuditLogs_CL) exposing the admin account, action, and source IP.
let Lookback = 24h;
let KnownIPsPerAccount = CMSAuditLogs_CL
| where TimeGenerated between (ago(90d) .. ago(Lookback))
| where Action_s in ("login_success", "admin_login", "publish_post", "update_page")
| summarize KnownSourceIPs = make_set(SourceIP_s) by AdminAccount_s;
CMSAuditLogs_CL
| where TimeGenerated > ago(Lookback)
| where Action_s in ("publish_post", "update_page", "edit_theme_file", "plugin_install")
| lookup KnownIPsPerAccount on AdminAccount_s
| where isnotempty(SourceIP_s) and KnownSourceIPs !has SourceIP_s
| project TimeGenerated, AdminAccount_s, SourceIP_s, Action_s, KnownSourceIPs
| extend Indicator = "CMSAdminPublishFromNeverSeenSourceIP", RiskScore = 75
| sort by TimeGenerated desc index=cms sourcetype="cms:audit" Action IN ("publish_post","update_page","edit_theme_file","plugin_install")
| rename AdminAccount as AdminAccount_publish, SourceIP as SourceIP_publish
| join type=inner AdminAccount_publish
[ search index=cms sourcetype="cms:audit" Action IN ("login_success","admin_login","publish_post","update_page") earliest=-90d
| stats values(SourceIP) AS KnownSourceIPs by AdminAccount
| rename AdminAccount as AdminAccount_publish ]
| where NOT mvfind(KnownSourceIPs, SourceIP_publish) >= 0
| eval Indicator="CMSAdminPublishFromNeverSeenSourceIP", RiskScore=75
| table _time, AdminAccount_publish, SourceIP_publish, Action, KnownSourceIPs, Indicator, RiskScore
| sort - _time Atomic Red Team Tests
Issues a rapid burst of authenticated POST requests to a test CMS instance's admin content-edit endpoints, simulating the application-layer mass-edit pattern. Run only against a disposable lab/test CMS instance, never a production site.
Command
for i in $(seq 1 10); do curl -s -b "$COOKIE_JAR" -X POST "http://lab-cms.internal/wp-admin/post.php" -d "post_ID=$i&content=defaced-test-content&action=editpost" -o /dev/null; done Cleanup
Restore the original content of each test post/page from the CMS revision history, or delete the disposable test CMS instance entirely if it was created solely for this exercise. Expected Telemetry
WAF/reverse-proxy access logs (CommonSecurityLog) showing 10 POST requests to /wp-admin/post.php from the test source IP within a few minutes, targeting 10 distinct post IDs.
Expected Detection
Fires once EditCount >= 8 and DistinctURLs reflects the distinct post IDs for the same SourceIP within the 15-minute EditWindow.
Directly overwrites several static asset files in a disposable test web root directory in rapid succession, simulating the filesystem-layer defacement path used when an attacker has RCE or file-manager access bypassing the CMS UI.
Command
mkdir -p /tmp/atomictest-webroot && for f in index.html about.html contact.html style.css app.js logo.png; do echo '<html>DEFACED-ATOMIC-TEST</html>' > /tmp/atomictest-webroot/$f; done Cleanup
rm -rf /tmp/atomictest-webroot Expected Telemetry
File-modification events (Sysmon Event ID 11 / auditd) for 6 distinct files under /tmp/atomictest-webroot within seconds of each other, written by a shell process.
Expected Detection
Fires once FilesModified >= 5 for the same DeviceName/InitiatingProcessAccountName within the 15-minute EditWindow. Point the WebRootPaths list at the test directory when validating in a lab environment.
Overwrites several static asset files in a disposable test IIS web root in rapid succession from a non-service account context, simulating a Windows-hosted defacement scenario. Run only against a lab IIS instance.
Command
New-Item -ItemType Directory -Force -Path C:\atomictest\wwwroot | Out-Null; 'index.html','about.html','contact.html','style.css','app.js' | ForEach-Object { Set-Content -Path "C:\atomictest\wwwroot\$_" -Value '<html>DEFACED-ATOMIC-TEST</html>' } Cleanup
Remove-Item -Recurse -Force C:\atomictest\wwwroot Expected Telemetry
MDE DeviceFileEvents / Sysmon Event ID 11 entries for 5 distinct files under C:\atomictest\wwwroot within seconds, with InitiatingProcessFileName = powershell.exe.
Expected Detection
Fires once FilesModified >= 5 for the same DeviceName/InitiatingProcessAccountName within the 15-minute EditWindow. Point the WebRootPaths list at the test directory when validating in a lab environment.