yamory Blog

URL の取り扱いには要注意! SSRF の攻撃と対策

SSRF (Server Side Request Forgery : サーバーサイド・リクエスト・フォージェリ)は、外部から到達できない領域にあるサーバーなどに対して、バグを悪用することでリクエストを送る攻撃手法・脆弱性です。

この一言だけではリスクの大きさや、この問題がなぜ発生するのかが想像しづらいと思います。そこで本記事では、SSRF が起きる原因を解説したあと、脆弱性の影響について攻撃シナリオをベースに説明していきます。

最後にそれらを踏まえた上で、どのようにして SSRF が発生しないコードを記載するか、またコードレビューの中で問題を見つけるのか等、対策についても触れていきます。

扱うリスクシナリオとしては「内部 API を悪用した侵入」および「クラウドベンダーが用意する管理用 API を悪用したクレデンシャルの奪取」となります。

SSRF の概要

冒頭で SSRF とは「外部から到達できない領域にあるサーバーなどに対して、バグを悪用することでリクエストを送る攻撃手法・脆弱性」であると述べました。

早速ではありますが、この SSRF の説明にある「外部」と「到達できない領域」とはなんでしょうか?

ここでいう「外部」とは、「攻撃対象のネットワーク外」を指しています。
そして「到達できない領域」とはイントラネット・ローカルネットワーク、アクセスコントロールがされたネットワーク領域など、通常の手段ではアクセスできない領域のことを指します。

例として、当サービスである yamory のアプリケーションネットワークが攻撃対象であれば、そこの外側にあるネットワーク領域全てが「外部」となります。
そして、yamory のアプリケーションネットワーク領域内にある、アプリケーションサーバーや DB などが存在する領域が「(外部からは)到達できない領域」に当たります。

例として、yamory のアプリケーションネットワークが攻撃対象であれば、そこの外側にあるネットワーク領域全てが「外部」となります。そして、yamory のアプリケーションネットワーク領域内にある、アプリケーションサーバーや DB などが存在する領域が「(外部からは)到達できない領域」に当たります。

上記のサーバー構成図ですと、「外部」とは攻撃者がいる領域にあたり、「到達できない領域」は、「管理者用 API」や「データベース」のあるネットワーク領域となります。

Web アーカイブサービスの例

では、どのようにして攻撃者は本来「到達できない領域」に対し、リクエストを送るのでしょうか?

ここからは脆弱性のあるコードをベースに解説していきます。
次のコードは、Web ページを保存するサービスのコードだとします(俗にいう魚拓サービスです)。

ユーザは保存したい Web ページの URL を入力し、サービス上のクローラーが対象 URL へ移動&保存するという処理を行います。

※解説記事のため、簡略化した記載となっており、実際には動作しないコードになります。

class WebArchive {
  // ユーザの入力したURLを取得する
  public String getUserInputURL(){
    ...
  }

  public void webArchiving(String uri) {
    String userInputUrl = getUserInputUrl();

    // ユーザの入力したURLにリクエストを送る
    HttpClient client = HttpClient.newHttpClient();

    client.send(
      HttpRequest
        .newBuilder(new URI(userInputUrl)) // ユーザの入力をそのままリクエストに使う
        .headers()
        .GET()
        .build(),
        BodyHandler.asString()
    );
  }
}

このコードには SSRF の脆弱性が存在します。
問題の箇所は、15行目のユーザが指定した URL パラメータを直接利用している部分です。
本来であればここには http://yamory.io/blog/ などの URL が入力され、ページコンテンツがアーカイブされるというのが想定された使われ方です。

しかしながら、ここに適切な制御がないと、攻撃者は以下のようなパラメータを送ることで、SSRF の攻撃が可能となります。

192.168.0.2

この場合、外部公開 API は、自身の所属するローカルネットワークアドレス内の 192.168.0.2 に配置された「管理者用 API」に対しクローリングを行ってしまいます。

このような「外部パラメータを直接使い、対象にリクエストを送る」ようなシステムでは、SSRF の脆弱性を持ってしまう可能性があります。

SSRF のリスクシナリオ

ここまでの内容で内部ネットワークに向けたリクエストなどを送れるケースが存在することがわかりました。

ここからは発展的な内容として、この SSRF を使うことで「どのようにして攻撃者はサービスに侵入するのか」をシナリオベースで見ていきます。

内部の管理者向けシステムの悪用

1つ目のリスクシナリオは、内部サーバに管理者向けコンソールがあり、SSRF を悪用することで、そのコンソールに侵入する例です。

先ほどと同じネットワーク構成の脆弱なサービスがあったとします。

脆弱なサービスの内部サーバでは、SSRF と Jenkins の脆弱性を合わせることで、内部サーバに侵入することができるかもしれません。

「外部公開 API」には、SSRF の脆弱性があり、任意の URL に対してリクエストができるものとします。
また、管理者用 API には(内部 API だからと手を抜き)認証がないものとします。

仮にこの「外部からアクセスできないサーバ」に、Jenkins が動いていたとしましょう。

このような内部サーバは、外部から攻撃に晒される可能性が比較的低いため、多くの企業でアップデートの優先度も低い傾向にあります。

そのため、Jenkins に出ていた過去の Remote Code Execution の脆弱性(CVE-2019-1003000)などがまだ修正されていない可能性があります。

すると、SSRF と Jenkins の脆弱性を合わせることで、内部サーバに侵入することができるかもしれません。

他にも、フレームワーク側でサポートされた API などが内部向けに公開されているケースも考えられます。

例えば Javaの Tomcat では、 /manager という管理用コンソール画面が用意されています。

他にも Spring Boot というフレームワークでは、アプリを管理するための APIがあります。
この API には、ログファイルの中身をレスポンスで吐き出したり、アプリケーション自体をシャットダウンしたり、 flyway を用いた DB マイグレーション用の API が提供されています。

このように「内部向けだから大丈夫」と管理を怠っていると、SSRF や様々な手法により、攻撃者が侵入してしまう可能性があります。

クラウドに用意された管理者向け API の悪用

2つ目のリスクシナリオは、AWS などのクラウドで管理されたサーバ上に、SSRF の脆弱性がある場合の話です。

今回も先ほどと同様に、「SSRF の脆弱性が存在するアプリケーションサーバ」があるとします。
先ほどと違う点は、このリスクシナリオではこのサーバひとつで攻撃が完結する点です。

この脆弱なアプリケーションは、仮に AWS の EC2 というサーバでホストされていたとします。

この時、SSRF を悪用することで、攻撃者はその脆弱なサーバに「自身(サーバ)のクレデンシャルを返却する API にリクエストを送れ」と指示することができます。

すると、脆弱なサーバは当該 API にリクエストを送り、レスポンスとして自身のクレデンシャルを攻撃者に返却してしまいます。

EC2 の機能として、IP アドレス 169.254.169.254 へアクセスすると、実行中のインスタンスのメタデータを取得できます。
AWS EC2 上に構築された API サーバにリクエストを送る例

上の図のように、EC2 の機能として、IP アドレス 169.254.169.254 へアクセスすると、実行中のインスタンスのメタデータを取得することができます。

こういったクレデンシャルを返却する API は、各クラウドやコンテナ環境・アプリケーション環境によって存在している場合があります。

例えば先ほど記載した AWS の場合だと http://169.254.169.254 という API、Docker の API だと、デフォルトで TCP Port 2375 にバインドされる API などが存在します。

これらの管理 API は、SSRF において格好の標的となります。

SSRF が発生する脆弱なコードと脆弱性をどのように見つけるか

SSRF が発生する脆弱なコードは、概要にて記載した Web アーカイブサービスのコードが典型的な例となります。

これらの脆弱なコードには、以下のような特徴があります。

  • 任意の URL に対してサーバがリクエストを送る仕様になっている
  • 任意の URL は、攻撃者が指定できる

基本的にはこの条件が当てはまった場合に、SSRF の脆弱性が発現します。
上記の特徴は、次のような機能においてよく見られます。

  • クローリング・スクレイピング機能
  • Webhook 機能

例えばクローリングに関しては、ブログなどで見られる「URL を貼り付けた際に、キャプチャ画像を自動で取得・添付する機能」がこれに当たるでしょう。
他にも、Webhook 機能は、チャットサービスや CI / CD サービスなどによく見られます。

これらの要素を元に、脆弱性を事前に見つけることができれば、サービスのデプロイ前に問題に対処することが可能です。

開発者として SSRF に対してどう注意すればよいか

では、デベロッパー・コードレビュワーは SSRF に対してどう注意すればよいでしょうか。
デベロッパー(開発者)およびコードレビュワー、二者の視点で見ていきます。

デベロッパーが SSRF で注意すること

デベロッパーの場合は、HTTP Request などが発生する際の処理を書いた場合に、「この処理の送信先は、外部から操作可能ではないか?」という観点を持つことが重要です。
この観点を持つことができれば、ほとんどの SSRF は、事前に察知することが可能です。

コードレビュワーが SSRF で注意すること

コードレビュワーもほとんどデベロッパーと同じ観点を持つことが重要になります。
そうすることで、レビュー段階でこの脆弱性をブロックすることができるでしょう。

さらに注意するのであれば、以下の要素がレビュー内容に含まれてきた場合はコードを精査すると良いでしょう。

  • パラメータで URL が指定できるような単語が入ってきた場合
    • パラメータ名が url, to, sendTo などの、URL が入りそうな名前になっている
    • パラメータ自体が URL になっている
  • コードに HttpClient などのクラス名が登場する場合
  • 仕様に(Headless)Browser などが登場する場合
  • OS コマンドが直接記載され、Curl などのコマンドを利用している場合

どのように SSRF を含んだコードを修正すればよいのか

では仮に SSRF を見つけた場合、どのように修正すればいいでしょうか?

実は SSRF の対策は、非常に難しいという特徴があります。

一番最初に思いつく修正方法の「バリデーション」ですが、この方法は何個もの落とし穴が隠されています。

では、入力チェックの例を元に対策の難しさを見ていきましょう。

以下は、過剰なブロックをしてしまいますがわかりやすさを重視した「IP のような文字列」をブロックをするための正規表現だとします。

※ この書き方だと 300.300.300.300 といった文字列にもマッチしてしまいますが、そこは無視してください。

/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/

では、これをバイパスし、ローカル IP へのリクエストが発生する文字列をみてみましょう。

# Port 指定
192.168.0.2:80

# scheme 省略
//192.168.0.2/

# HTTP Scheme 表記
http://192.168.0.2/
http://localhost:80/

# IP v6 表記
http://[::]:80/

# 特殊エンコーディング(curl などで送れます)
http://①②⑦.⓪.⓪.①/

# Decimal 表記
http://0177.0.0.1/
http://2130706433/

これらの表記は全てローカル IP やループバックアドレスを指し示しています。
見ていただくとわかると思いますが、入力チェックは至難の業です。

その上、簡易的なバリデーションを行なっていても、リダイレクトを操ることで SSRF ができてしまうケースもあります。

例えばヘッドレスブラウザ(Headless Browser)などを利用した機能が存在し、 http://yamory.io/?redirectTo=127.0.0.1 といった URL を処理するとします。
この場合、以下のようなフローで処理が進む可能性があります。

  1. 攻撃者が http://yamory.io/?redirectTo=127.0.0.1 と入力する
  2. 入力チェックで yamory.io がドメインなので問題がないと判定される
  3. Headless Browser が http://yamory.io/?redirectTo=127.0.0.1 へ移動する
  4. サーバが 127.0.0.1 にリダイレクトするように指定する
  5. Headless Browser がリダイレクトを行い 127.0.0.1 へリクエストを送る

このように、リダイレクトを操ることで、サーバの入力チェックをバイパスする手法なども存在します。

果たして開発者はこれらのブラウザの挙動や悪性な入力を防ぎきれるでしょうか?
上記の問題が存在するため、SSRF の対策は困難を極めます。

SSRF 対策の難しさと問題点

対策について、専門家でも意見が分かれるほどです。

基本的なベストプラクティスは「そもそも外部からの入力でリクエストを送る機能を作らない」となっており、次に「推移先の URL を固定にし、ドメインなどの許可リストを利用する」という対策が推奨されます。

しかし、これらの対策方法は機能そのものをユーザへ提供できなくなる場合があり、実装できないケースが存在します。

この問題点について、OWASP ASVS v4 では対策について議論がなされたことがありますが、多くの対策方法については、その対策自体をすり抜ける攻撃シナリオが存在することが言及されています。

このように、SSRF の完璧な対策は「SSRF が発生しそうな機能を作らない」や「許可リストを作成し、それ以外をブロックする」といった強い制約を行う方法以外に存在しない状態にあります。

そのため他の脆弱性と違って SSRF は非常に厄介であり、危険な脆弱性として認識されています。

さいごに

ここまで SSRF の問題について記載しましたが、SSRF は自身で書いたロジック以外にも、ライブラリで発生するケースがあります。

それらの脆弱性を把握するには、定期的なライブラリなどの棚卸しや脆弱性の発行状況をウォッチしていく必要があります。

yamory では、これらの OSS ライブラリの脆弱性利用状況を可視化し、脆弱性をスキャンすることができます。

もし興味がございましたらぜひ無料トライアルをお試しください。

オープンソース脆弱性管理ツール yamory

  • 利用中のOSSを抽出し
    脆弱性を自動スキャン
  • 脆弱性への対応優先度を自動で分類
  • 組織規模に合わせたプランを選択可能

フリー

¥0

個人向け

1チームまでの開発チーム作成

yamory を使いはじめる

プロ

有料料金はお問い合わせください

中〜大規模組織向け

無制限の開発チーム作成

開発チームを俯瞰できるセキュリティチーム機能

危険な脆弱性の Slack 連携通知機能

Jira Cloud と連携

シングル・サインオン(SSO)連携*

* SSO 連携は無料トライアルではご利用できません。プロプラン契約後からご利用できます。

30日間の無料トライアルを開始