株式会社I-SEED(アイシード)|大阪・心斎橋のWEB制作集団

【PHP】画像の保存ディレクトリを隠蔽する

【PHP】画像の保存ディレクトリを隠蔽する

全力疾走熱中症全力疾走熱中症
この記事は、2020年3月9日に編集し、約 7 分で読めます。

今回はPHPで画像を表示させる時に保存ディレクトリを隠蔽する方法を紹介したいと思います。

今回のコードで画像URLが下記のように変更されます。

// 例:変更前(通常のディレクトリ表示)
<img src="https://example.com/upload/img/1/001.jpg" alt="img001">

// 例:変更後(保存ディレクトリを隠蔽)
<img src="https://example.com/image.php?p=1&f=001&e=jpg" alt="img001">
DemoセットおよびソースコードはGitHubにて公開しています。

Invisible image url


画像の保存しているディレクトリを隠蔽することで以下の利点が得られます。

  • 微量のセキュリティ向上
  • 画像自体に認証機能を設けることができる
  • ドキュメントルートより上の階層に画像を配置することが可能になる

特定のユーザーのみに画像を表示させたい場合に認証機能を付与できるのでとても便利だと思います。

また、ドキュメントルート外に画像を置くことができるようになります。

ソースコード

コードを下記に記します。

<?php 
// =========================================================== 
// If you require access restrictions, you can write here. 
// =========================================================== 

// option param 
const FILE_REGEX = '@\A[a-z0-9_-]+\z@ui'; 
const EXT_REGEX = '@\A[a-z]+\z@u'; 
const ALLOW_MIME_TYPE = [ 
  'gif' => 'image/gif',
  'jpg' => 'image/jpeg',
  'png' => 'image/png',
];

// get query param
$path = (int)filter_input(INPUT_GET, 'p');
$input_name = (string)filter_input(INPUT_GET, 'f');
$input_ext = strtolower((string)filter_input(INPUT_GET, 'e'));

// switching img path
if ($path === 1) {
  $img_path = __DIR__ . 'path_to_img1';
} elseif ($path === 2) {
  $img_path = __DIR__ . 'path_to_img2';
} else {
  header('Content-Type: text/plain; charset=UTF-8', true, 400);
  exit('No such image.');
}


// ===========================================================
// If you require access restrictions for each image,
// you can write here.
// ===========================================================


// validation
if (preg_match(FILE_REGEX, $input_name) && preg_match(EXT_REGEX, $input_ext)) {
  $input_file = sprintf('%s.%s', $input_name, $input_ext);

  $finfo = new finfo(FILEINFO_MIME_TYPE);
  $mime_type = $finfo->file($img_path . $input_file);

  // response
  if ($ext = array_search($mime_type, ALLOW_MIME_TYPE, true)) {
    $filename = sprintf('%s.%s', $input_name, $ext);
    header('Content-Disposition: inline; filename="' . $filename . '"', true);
    header('Content-type:' . $mime_type, true);
    readfile($img_path . $filename);
    exit();
  }
}

// error handler
header('Content-Type: text/plain; charset=UTF-8', true, 400);
exit('No such image.');
執筆時点のコードのため現在は異なっている可能性があります。GitHubにもコードをあげているのでそちらで最新版をご確認下さい。

解説

// get query param
$path = (int)filter_input(INPUT_GET, 'p');
$input_name = (string)filter_input(INPUT_GET, 'f');
$input_ext = strtolower((string)filter_input(INPUT_GET, 'e'));

まずGETクエリを3つ受け取ります。

pは画像ファイルまでのパスを切り替えるための数値、fはファイル名、eは拡張子になっています。

画像ファイルへのパスが1つの場合はpパラメータを削除して問題ありません。
また、eパラメータもアップロード時に拡張子を統一させているのであれば設定する必要はないかもしれません。

// validation
if (preg_match(FILE_REGEX, $input_name) && preg_match(EXT_REGEX, $input_ext)) {

次に画像ファイル名や拡張子が任意の正規表現パターンを満たしているかチェックします。

basename()関数が存在しますが、今回は許可した文字列のみを通すという正規表現を使用しました。いずれにしてもディレクトリトラバーサル問題等が存在するため検証をしっかり行っておく必要があります。

$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime_type = $finfo->file($img_path . $input_file);

// response
if ($ext = array_search($mime_type, ALLOW_MIME_TYPE, true)) {
  $filename = sprintf('%s.%s', $input_name, $ext);
  header('Content-Disposition: inline; filename="' . $filename . '"', true);
  header('Content-type:' . $mime_type, true);
  readfile($img_path . $filename);
  exit();
}

ファイル名・拡張子に問題がなければ該当するファイルが存在するかを確認し、mimetypeを取得します。
header()関数にてContent-typeを正確に出力するためです。

minetypeが許可した物であればreadfile()関数で出力するのですが、このテクニックの最大のポイントがheader('Content-Disposition: inline; filename="' . $filename . '"', true);になります。

このfilename属性を任意のものに設定することで画像の保存ディレクトリを隠蔽することが可能になっています。

Content-Disposition
通常の HTTP レスポンスにおける Content-Disposition レスポンスヘッダーは、コンテンツがブラウザでインラインで表示されることを求められているか、つまり、Webページとして表示するか、Webページの一部として表示するか、ダウンロードしてローカルに保存する添付ファイルとするかを示します。

まとめ

画像の保存ディレクトリ隠蔽や画像毎に認証機能を設けることができるため、非常に便利なものになっていると思います。

日々、進化していく技術を私達と一緒に共有していきませんか?
職場で待っています!

最後まで読んでいただきありがとうございました。

この記事を書いた人
全力疾走熱中症

好きな言葉「休憩」, 全然できないこと「PHP」

全力疾走熱中症をフォローする

WEBの事でお悩みなら、まずは一度 I-SEED にご相談ください!

I-SEED は 「デザイン」「SEOマーケティング」「システム開発」を併せ持つWEB制作会社です。

コーポレート、オウンドメディア、ECなど様々な分野のサイト制作を得意としています。システム開発も自社で行っているので、様々なプロジェクトに柔軟かつ迅速に対応できるのが強みです。

大阪に良いWEB制作会社がいないとお困りの方は、是非一度 I-SEED までご相談ください。

I-SEED では随時採用も行っています。各種エンジニア、ディレクター、デザイナー、ライター、マーケターの全職種を募集しています。ご興味がある方は採用ページもご覧ください。

ブログページに戻る