ファイルアップロード機能の脆弱性を考える
Webサイトのセキュリティ対策でまず知っておきたいのがファイルアップロードに対する対策です。
ホワイトハッカーへの道 一歩目で見てきたローカルプロキシーツールを使えば、運用中のサイトについて検証が可能です。
ローカルプロキシーツールburpsuiteを使った攻撃手順は下の動画を参考にしてください。
https://www.youtube.com/watch?time_continue=134&v=LLF-DNsFCfU
攻撃手法
ファイルアップロードからどんな攻撃があるかを見ていきましょう。
BKDR_ZZPEG (JPGファイルのExifにスクリプトを埋め込む攻撃)
Exif とは「Exchangeable image file format」の略称です。
jpgにはデータサイズや画素数、圧縮の種類などの情報を保管できる領域が設けられています。
この領域にスクリプトを埋め込み他のファイルからこのjpgファイルのExif情報を実行する攻撃です。
手法はこちらを参考にしてください。
FileUploadAttack (ヘッダーのcontent-typeを改竄してスクリプトをアップロードする攻撃)
アップロード処理の穴をついてスクリプトファイルをサーバにアップロードし、アップロードしたスクリプトファイルを通して意図しない動作を引き起こす攻撃です。
手法はこちらを参考にしてください。
ファイル名にパス情報を追加してアップロードする攻撃
ファイル名に「../../hoge.jpg」などのパス情報を含めアップロードします。
アプリケーションで適切な対応を取らないとサーバに書き込み、実行権限のあるフォルダにWebshellをアップロードされ実行されてしまいます。
- WebShellとはphpなどのスクリプトからshellコマンドが実行できてしまうスクリプトファイルのことです。
対策
アプリケーション、サーバに以下の対策がなされているか確認します。
- ファイル名にパスが含めれないかチェックする。
- ファイルにスクリプトタグが含まれていないかのチェックする。
- MIMEタイプに対応する拡張子をチェックする。
- アップロードサイズをチェックする。
- アップロード時にEXIFは削除する。
- アップロードされたファイル名をランダムなファイル名にする。
- アップロードディレクトリ、ファイルは実行権限を付与しない。
- アップロード先をドキュメントルート外フォルダかクラウドにする。
サンプルコード
PHPではグローバル変数$_FILES
を使って、$_FILES
[inputのname属性][アップロードファイルの項目]でファイル情報を取得できます。取得できる項目は以下になります。
項目 | 内容 |
---|---|
name | 元ファイル名 |
type | ファイルタイプ |
tmp_name | サーバーに一時保管されたファイル名 |
error | エラーコード |
size | ファイルサイズ |
またerrorの値を使ってエラーチェックが可能です。
コード | エラー定数 | 説明 |
---|---|---|
0 | UPLOAD_ERR_OK | エラーなし |
1 | UPLOAD_ERR_INI_SIZE | php.iniのupload_max_filesizeのサイズを超えている |
2 | UPLOAD_ERR_FORM_SIZE | HTMLフォームで指定された MAX_FILE_SIZE を超えている |
3 | UPLOAD_ERR_PARTIAL | 一部のみしかアップロードされていない |
4 | UPLOAD_ERR_NO_FILE | アップロードされなかった |
6 | UPLOAD_ERR_NO_TMP_DIR | サーバー一時保管フォルダがない |
7 | UPLOAD_ERR_CANT_WRITE | ディスクへの書き込みに失敗 |
8 | UPLOAD_ERR_EXTENSION | PHPの拡張モジュールがアップロードを中止した |
こちらを使ってファイルアップロード時のエラーチェックを作成します。
権限は実行権限のない644に変更してます。
d | r | w | x |
---|---|---|---|
ディレクトリ | 読み | 書き | 実行 |
記号 | rw- | r-- | r-- |
---|---|---|---|
属性値 | 6 | 4 | 4 |
(4+2+0) | (4+0+0) | (4+0+0) | |
自分 | グループ | 他人 |
<?php
$fileName = $_FILES['upload']['name'];
$tmpName = $_FILES['upload']['tmp_name'];
$fileSize = $_FILES['upload']['size'];
$extentison = pathinfo($fileName, PATHINFO_EXTENSION);
try {
// ファイル名にパスが含めれないかチェック
if (basename(realpath($fileName)) !== $fileName) {
throw new RuntimeException('ファイル名が不正です');
}
// ファイルにスクリプトタグが含まれていないかのチェック
if (count(token_get_all($fileName)) >= 2) {
throw new RuntimeException('ファイル形式が不正です');
}
// MIMEタイプに対応する拡張子チェック
if ($extentison != array_search(
mime_content_type($tmpName),
array('gif' => 'image/gif', 'jpg' => 'image/jpeg', 'png' => 'image/png',)
, true)
) {
throw new RuntimeException('ファイル形式が不正です');
}
// $_FILES['upload']['error']の値チェック
// アップロードサイズをチェック
if (!isset($_FILES['upload']['error'])
|| !is_int($_FILES['upload']['error'])
) {
throw new RuntimeException('ファイル形式が不正です');
} else {
switch ($_FILES['upload']['error']) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
throw new RuntimeException('ファイルが選択されていません');
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
throw new RuntimeException('ファイルサイズが大きすぎます');
default:
throw new RuntimeException('その他のエラーが発生しました');
}
}
// アップロード時にJPEGの場合はEXIFをGDで削除
list(, , $type) = getimagesize($tmpName);
if($type === IMG_JPG) {
// 新規サイズを取得
$gd = imagecreatefromjpeg($tmpName);
$w = imagesx($gd);
$h = imagesy($gd);
// 再サンプル
$out = imagecreatetruecolor($w, $h);
imagecopyresampled($out, $gd, 0, 0, 0, 0, $w, $h, $w, $h);
// 元画像を削除した上で同一名でファイルを再生成
unlink($tmpName);
imagejpeg($out, $tmpName, 100);
imagedestroy($gd);
}
// アップロードされたファイル名をランダムなファイル名にする。
// ドキュメントルート外のフォルダを指定する クラウドストレージが安全。
if (!move_uploaded_file(
$tmpName,
$path = sprintf('./uploads/%s.%s', sha1_file($tmpName),
$extentison
)
)) {
throw new RuntimeException('ファイル保存時にエラーが発生しました');
}
// アップロードファイルは実行権限のない状態へ
chmod($path, 0644);
echo '正常アップロード';
} catch (RuntimeException $e) {
echo $e->getMessage();
}
こちらのコードは検証中です。
そのまま使うのは自己責任でお願いいたします。
まとめ
ファイルアップロードには危険がいっぱいです。フレームワークを入れても上記の攻撃は基礎として対応することが必要です。攻撃手法を理解し、対策を実施しましょう。
また他の攻撃手法やそれに対する対策がある方はアドバイス頂けますでしょうか。
三歩目は「今、改めてXSSを考える」です。
2019/02/19 更新
ファイルアップロードでアップロードされたWebShellについて
理解を深めるには下記の記事が大変参考になります。
https://qiita.com/WhatRune/items/f89581fb6b4041d7c68a