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

今回は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">
目次

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.');

解説

// 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();
}

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

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

あわせて読みたい
Content-Disposition - HTTP | MDN 通常の HTTP レスポンスにおける Content-Disposition レスポンスヘッダーは、コンテンツがブラウザーでインラインで表示されることを求められているか、つまり、ウェブペ...

まとめ

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

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

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

この記事の著者

I-SEEDブログ編集部のアバター
I-SEEDブログ編集部

システム開発やWeb制作・デザイン、Webマーケティングを強みに持つI-SEEDスタッフが、さまざまなノウハウや最新情報をお届けします。

CONTACT/ お問い合わせ

システム開発やWeb制作・デザイン、Webマーケティングに関するご質問やご相談はこちらから。

お問い合わせする

ESTIMATE/ 料金シミュレーター

Web上で簡単なお見積もりが可能です。シミュレーション結果をもとにお問い合わせいただくことも可能ですので、お気軽にご相談ください。

Web見積もりをする
目次