2018年12月5日水曜日

SSRF(Server Side Request Forgery)徹底入門

SSRF(Server Side Request Forgery)という脆弱性ないし攻撃手法が最近注目されています。以下は、ここ3ヶ月にSSRFについて言及された記事です。
この「空前のSSRFブーム」に便乗して、SSRFという攻撃手法および脆弱性について説明します。

SSRF攻撃とは

SSRF攻撃とは、攻撃者から直接到達できないサーバーに対する攻撃手法の一種です。下図にSSRF攻撃の様子を示します。

攻撃者からは、公開サーバー(203.0.113.2)にはアクセスできますが、内部のサーバー(192.168.0.5)はファイアウォールで隔離されているため外部から直接アクセスできません。しかし、公開サーバーから内部のサーバーにはアクセスできる想定です。
攻撃者は *何らかの方法で* 公開サーバーから内部のサーバーにリクエストを送信することにより、内部のサーバーを攻撃できる場合があります。これがSSRF攻撃です。

SSRF攻撃の例1

SSRF攻撃に脆弱なサンプルを以下に示します。これは、はてなブックマークのようなソーシャルブックマークの機能のうち、URLを指定してプレビューを表示するというものです。PHPのcurl関数によりHTTPリクエストを送信し、XSS対策のためHTMLPurifierによりHTMLからscriptタグ等を取り除いています。

<?php
  require_once('./htmlpurifier/library/HTMLPurifier.includes.php');
  $purifier = new HTMLPurifier();

  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $_GET['url']);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  $html = curl_exec($ch);
  echo $purifier->purify($html);
下図はAWS EC2上に上記を設置して、徳丸のブログ記事を表示した例です、。


HTMLPurifierの設定を厳しく(安全に)しているのでCSS等が無効になっていますが、記事の内容は取り込めていますね。

次に攻撃例です。EC2のインスタンスからhttp://169.254.169.254/ にアクセスすると、そのインスタンスの設定情報が読み込めるという機能がEC2にあります(ドキュメント)。この機能を悪用して、EC2のクレデンシャルを読み込んでみましょう。
まずは、以下のURLにアクセスしてみます。

/ssrf.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

表示は下記となります。test-roleというロールがあることがわかります。

次に、先程のURLの末尾に test-role を追加した以下のURLでアクセスします。

/ssrf.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/test-role

上記(見やすいようにHTMLソースで表示)のように、外部からEC2インスタンスのクレデンシャルが読み出せました。

SSRF攻撃の例2

次に、XXE脆弱性(CWE-611)を用いてSSRF攻撃をやってみましょう。以下は拙著「体系的に学ぶ安全なWebアプリケーションの作り方」に掲載されているサンプルです。XXEについてはこの記事も参考になります。
<?php
$doc = new DOMDocument();
$doc->load($_FILES['user']['tmp_name']);
$name = $doc->getElementsByTagName('name')->item(0)->textContent;
$addr = $doc->getElementsByTagName('address')->item(0)->textContent;
?><body>
以下の内容で登録しました<br>
氏名: <?php echo htmlspecialchars($name); ?><br>
住所: <?php echo htmlspecialchars($addr); ?><br>
</body>
上記をEC2上のPHPで動かしてみます。Amazon Linuxで標準に用意されている環境では脆弱性が再現しないので、わざと libxml2-2.7.8という脆弱な libxml2 をインストールしました。
攻撃用のXMLは以下となります。3行目が外部実体の定義で、169.254.169.254へのリクエストを含んでいます。
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE foo [
<!ENTITY credential SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/test-role">
]>
<user>
<name>徳丸浩</name>
<address>&credential;</address>
</user>
これを読み込ませた結果は下図となります。クレデンシャルが読み込まれていることがわかります(時間経過のため先の例とは中身が変わっています)。


SSRF脆弱性(CWE-918)とは

SSRF攻撃の例を2種類紹介しました。これらのうち、例2はXXE脆弱性を悪用したものでした。
それでは、例1の方で悪用されている脆弱性はなんでしょうか? 実は、例1は、SSRF脆弱性(CWE-918)と言われるものです。以下は、SSRF脆弱性の分類を定義しているCWE-918の冒頭の引用です。
The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
参考訳
Webサーバーは、上流のコンポーネントからURLまたは類似のリクエストを受け取り、このURLの内容を取得しますが、リクエストが想定される送信先に送られることを十分確実にしていません。
要はURLのチェックが不十分なために読まれてはいけないコンテンツを読まれてしまった、ということで、例1はまさにそのような例になっています。
ということで、例1および例2の「脆弱性と攻撃」の関係は以下の通りです。ややこしいですね。

例1: SSRF脆弱性(CWE-918)を悪用してSSRF攻撃を行う
例2: XXE脆弱性(CWE-611)を悪用してSSRF攻撃を行う

細かいことを気にすると言われそうですね。しかし、CWE-918とCWE-611では、脆弱性の混入メカニズムがまったく違います。なので、攻撃の影響が一部重なっているからと言って、両者をごっちゃにしてはいけないと思います。

SSRF攻撃が可能な脆弱性

SSRF攻撃が可能となる脆弱性には、CWE-918CWE-611の他に以下があります。

・ディレクトリトラバーサル(CWE-22
ディレクトリトラバーサルとCWE-918は、脆弱性混入のメカニズムが非常に似ています。パラメータの中身がURLかパス名かという違いだけです。PHP等ではfopen等ファイル名を扱う機能でURLを指定できるため、ディレクトリトラバーサル脆弱性の悪用でSSRF攻撃が可能になります。

・OSコマンドインジェクション
OSコマンドインジェクション(CWE-78)や、ファイルインクルード(LFI/RFI)(CWE-98)、安全でないデシリアライゼーション(CWE-502)などリモートコード実行(RCE)可能な脆弱性があれば、wgetやcurl等を利用してSSRF攻撃ができます。

・SQLインジェクション
SQLインジェクション(CWE-89)でも任意コマンド実行が可能な場合がありますし、データベースから他のデータベースに接続する機能などがSSRF攻撃の踏み台として使える場合があります。以下の記事は、PostgreSQLを悪用したSSRF攻撃の例が紹介されています。

SIOS "OSSよろず" ブログ出張所: PostgreSQL と SSRF

SSRF脆弱性(CWE-918)の対策

SSRF攻撃を防ぐには、まず、SSRF攻撃の原因となる脆弱性の対策が必須です。RCE可能な脆弱性や、SQLインジェクション、ディレクトリトラバーサル等は単体で非常に危険な脆弱性なので、SSRF攻撃の可否に関わらず対策すべきですし、対策方法も確立しています。問題はSSRF脆弱性(CWE-918)です。これは、要件によってはなかなかに厄介です。
まずスキーム(プロトコル)のチェックは必須です。これがないと、file:///etc/passwdのようなファイルスキームを指定してディレクトリトラバーサル攻撃ができます(下図)。


次にURL中のホスト名に紐付くIPアドレスのチェックですが、これがなかなか厄介です。通常、以下の手順に従うと思いますが、
  • URLをパースしてホスト名を取り出す
  • ホスト名からIPアドレスを求める
いずれも問題が入りやすい処理です。

URLをパースする際の問題

以下のような紛らわしいURLを解析する際に、PHPのparse_url関数がホスト名を誤認するバグがmalaさんから指摘されています。

http://169.254.169.254#@example.jp/

上記URLの正しいホスト名は 169.254.169.254 ですが、古いPHPは example.jp をホスト名として認識します(169.254.169.254#はユーザ名と認識)。このようなバグは他の言語処理系にもあり、IPアドレスのチェックをすり抜ける攻撃に悪用可能です。
この種の問題に対する緩和策として、parse_urlによって得られたホスト名やパス名からURLを組み立て直すという方法が考えられます。万一parse_urlがホスト名を誤認しても、IPアドレスチェックとHTTPリクエストでホスト名が一貫していれば、問題は入りにくいと言えます。ただし、この際にホスト名をバリデーションすることと、ユーザ名とパスワードは捨てられることが条件です。ユーザ名等を含めてしまうと元の木阿弥です。

ホスト名からIPアドレスを求める際の問題

ホスト名からIPアドレスを求める際にも以下の問題が発生します。
  • DNSサーバーが複数のIPアドレスを返す場合の処理の漏れ
  • IPアドレスの表記の多様性(参考記事
  • IPアドレスチェックとHTTPリクエストのタイミングの差を悪用した攻撃(TOCTOU脆弱性)
  • リクエスト先のWebサーバーが、攻撃対象サーバーにリダイレクトする
上記のTOCTOU(Time of check to time of use)問題は、DNSの名前解決の文脈ではDNS Rebindingとも呼ばれます。
これらに注意しつつIPアドレスチェックを実装する必要があります。
PHPのcurl関数の場合は、「最終的にアクセスしたホストのIPアドレス」を受け取る機能があるので、保険的にこれを確認する方法もあります。ただし、「リクエストを送るだけで攻撃が成立する」場合には効果がありません。
$prime_ip = curl_getinfo($ch, CURLINFO_PRIMARY_IP);  // 最終的にアクセスしたIPアドレスを求める
このように、「どこまでチェックで頑張れるか」は、利用するライブラリの機能にも依存します。
仕様的に可能であれば、外部からURLを受け取らないようにするか、許可されたURLの一覧表(ホワイトリスト)によるチェックが安全です。

ネットワーク的な保護

URLの完全な検証は難しいので、ネットワーク的な保護も有効です。以下は、AWSのドキュメントで推奨されている iptables の設定例です。これにより、「docker0 ブリッジのコンテナがコンテナインスタンスのロールに指定されている権限にアクセスするのを防止できます」としています。iptablesによる設定は環境依存なのでご注意ください。
sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP
Amazon ECS コンテナインスタンスの IAM ロール - Amazon Elastic Container Service より引用

まとめ

SSRF攻撃とSSRF脆弱性について紹介しました。ここまで説明したように、任意のURLを対象とする処理はSSRF攻撃を受けやすく、また完全な対策は難しいのが現状です。
言い換えれば、「完全な対策が難しい」からこそ、今SSRFが注目されているとも言えます。
そもそも任意URLを受け取る処理が必要かどうかという仕様面の検討をした上で、実装の際にはできるだけ安全側に倒した処理と、アプリケーションとネットワークの両面からの対策を推奨します。

2018年12月3日月曜日

WordPressのプラグインWP GDPR Complianceの脆弱性CVE-2018-19207について分析した

11月中旬から、レンタルサーバー事業者等から、WordPressのプラグインWP GDPR Complianceの脆弱性について注意喚起が目立つようになりました。
記事本文には以下のように書かれています。
本脆弱性の影響 
WordPressにおいて、権限を持たないユーザーが脆弱性を利用してウェブサイト全体の設定を変更したり、第三者が管理者権限のあるユーザーを追加してウェブサイトの改ざんなどの不正利用を行う危険性がございます。

https://www.sakura.ad.jp/information/announcements/2018/11/22/1968198825/ より引用
以下はSecurity Nextからの引用です。
「同1.4.2」および以前のバージョンに権限昇格の脆弱性が存在し、11月6日にWordPress.orgのPlugin Directory Teamが開発者へ報告。同社では同月7日にアップデートとなる「同1.4.3」をリリースした。

脆弱性の悪用が始まった詳しい時期はわかっていないが、10月中旬ごろに同プラグインの利用環境下において、被害が発生したとの報告もあり、ゼロデイ攻撃が行われていた可能性が高い。またアップデート公開後には、積極的に攻撃が展開されているという。

http://www.security-next.com/100144 より引用
当該脆弱性CVE-2018-19207の原因と影響、対策などについて以下にまとめました。

WP GDPR Complianceプラグインとは

WP GDPR Complianceプラグインは、WordPressにGDPR対応機能を追加するためのプラグインです。下図はWordPressサイトに、クッキーと外部スクリプトの使用について同意を得ている様子です。最近は日本のサイトでも、この種の画面を目にするようになりました。


脆弱性の詳細

この脆弱性はWP GDPR Complianceのバージョン1.4.2以前に存在します。PoC(概念実証コード)は海外のサイトでは公開されていますが、悪用防止および私が逮捕されると嫌なので一部伏せ字(XXの箇所)にて示します。

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: example.jp
Content-Length: XXX
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0
Content-Type: application/x-www-form-urlencoded; charset=UTF-8

action=wpgdprc_process_XXXXX&data={"type":"XXXXXXXXXXXX","append":XXXXX,
"option":"XXXXXXXXXXXXXXXXXX","value":"XXXXXX"}&security=XXXXXXXXX
ご覧のように、data=のパラメータはJSON形式となります。また、security=はnonce(ナンス)ですが、WP GDPR Complianceをインストールすると、以下のようにJavaScriptのコードが挿入されます。以下のJSON中のajaxSecurityがnonceですので、容易に取得できます。nonceの役割はCSRF対策などですので、HTMLソースに貼ってあること自体は問題ではありません。
<script type='text/javascript'>
/* <![CDATA[ */
var wpgdprcData = {"ajaxURL":"http:\/\/example.jp\/wp-admin\/admin-ajax.php",
"ajaxSecurity":"0083ef6e45"};
/* ]]> */
</script>
当該脆弱性により、WordPressの設定を外部からログインなしに変更できることが脆弱性の中身です。具体的には、JSON中のoptionで示した項目が、valueで示した値に変更されます。

原因

当該バージョンのプラグインとPoCが入手できれば、当該脆弱性の原因分析は比較的容易です。以前弊社で実施した勉強会で脆弱性解析の手法を説明した模様を@tigerszkさんが素晴らしいブログ記事にまとめてくださっていますので、おおまかな手順はそちらを参照ください。

デバッガを利用してWebアプリの脆弱性を分析してみた

プラグインの脆弱なバージョンと修正バージョンで差分をとり、「脆弱な箇所」にあたりをつけてブレークポイントを設定したうえで、デバッガ上でPoCを打ち込んで見るのが簡便かと思います。ソースコードの該当箇所を以下に引用します。


./wp-content/plugins/wp-gdpr-compliance/Includes/Ajax.php より引用

ブレークポイントを設定する場合は、14行目のcheck_ajax_referer()関数呼び出しの行に設定するとよいと思います。この行は先程のnonceのチェックをしています。
PoCを打ち込むと、70行目のupdate_option()関数まで到達します。この関数は、WordPressの設定を変更する関数(リファレンス)ですが、引数$optionと$valueは、外部から送られたJSONで自由に設定できます。
すなわち、当該脆弱性を用いると、認証なしで、WordPressの設定を自由に変更できることになります。
修正後のソースを見ると、update_option関数の呼び出しの前に、WordPressの権限確認用の関数current_user_can('manage_options') の呼び出しが追加されています。

※ 22行目のstripslashesや37行目のesc_htmlはおそらく不要というかないのが正しそうです。また、71行目にdo_action関数がupdate_option関数と同じ引数で呼ばれていますが、両関数の引数は意味・内容が違うので、おそらくバグかと思います。

影響

認証なしでWordPressの設定を外部から変更できます。
もっともクリティカル攻撃の例として、海外のサイトでは以下が紹介されています。
  1. WordPressの設定変更で「利用者が自らユーザー登録できる」ようにする
  2. WordPressの設定変更でユーザー登録時の初期権限を『管理者』にする
  3. 攻撃者がユーザー登録すると管理者権限が与えられたユーザが作られる
  4. 攻撃者はそのユーザーでログインするとWordPressの管理者になれる
その他、update_option関数やdo_action関数の引数を自由に設定できることから、さまざまな攻撃が考えられます。

対策

WP GDPR complicaneプラグインの1.4.3で改修されています。本稿執筆時点の最新版は1.4.5です。最新版の導入を推奨します。
当脆弱性の攻撃のうち、管理者ユーザーを勝手に作成される経路については、WordPressのログイン機能 wp-login.php のURLを変更し、隠すことで緩和策になります。

まとめ

WP GDPR complicaneプラグインの脆弱性CVE-2018-19207について説明しました。Security Nextが報道しているように、この脆弱性はゼロデイ攻撃が行われていたという推測もあります。ログインURLを隠す以外の有力な緩和策も見当たらないこと、脆弱性の入り方のレベルが極めて低いことから、「プラグインの導入をできるだけ避ける」ことをお勧めします。

フォロワー

ブログ アーカイブ