なぜこんなことをしようと思ったのか
EC-CUBE 4 系から index.php
がルートディレクトリに配置されるようになり、3 系と比べるとファイルやディレクトリのセキュリティレベルが低下しているように感じます。
たとえば Git を使ってソースを管理している場合、ルートディレクトリに README.md を置いたりするけどアクセス拒否されていない…などですね。(README.md は公式の GitHub でも配置されていて .htaccess で除外設定がされていないのですがどういう扱いなんでしょうか)
実際、これまで関わったパートナーのエンジニアさんでもプロジェクト情報を README.md に書いていてめちゃくちゃ外部に漏れてるっていうことがありました。
4 系ではレンタルサーバ向けに URL に html
をつけないで動くようにする方に寄せたことで現在の構造になっているのかもしれませんが、個人的には 3 系のディレクトリ構造の方が安全(心配事が少ない)と思っています。そこで、4 系を 3 系のディレクトリ構造で動かせるか試してみました。
やってみようと思ったきっかけは Symfony の公式ガイドでデフォルトディレクトリ設定を上書きできるというものを見かけたからです。(これを見ると index.php はそもそも Symfony では public 以下、つまり公開領域下に置いているようです)
How to Override Symfony's default Directory Structure
https://symfony.com/doc/4.4/configuration/override_dir_structure.html
今回は下記のように index.php
を html
以下に移動して EC-CUBE が動くようにしてきたいと思います。
ec-cube/ | +-- .env | +-- app/ | +-- html/ | | | +-- index.php | | | +-- template/ | | | +-- upload/ | | | +-- user_data/ | +-- vendor/ | +-- autoload.php
index.php
まずはルートディレクトリにある index.php
を html/index.php
に移動します。(個人的には /var/www/html
と被るので html
じゃなくて public
にしてもらいたいなぁ、といつも思います)
移動したら index.php
内の autoload.php
を呼び出す部分と .env
を参照する部分を書き換えていきます。メンテナンス用の .maintenance
と maintenance.php
の階層も変わるので同様に書き換えます。
diff --git a/html/index.php b/html/index.php index 103b4e6..843c3a0 100644 --- a/html/index.php +++ b/html/index.php @@ -10,7 +10,7 @@ if (version_compare(PHP_VERSION, '7.1.3') < 0) { die('Your PHP installation is too old. EC-CUBE requires at least PHP 7.1.3. See the <a href="http://www.ec-cube.net/product/system.php" target="_blank">system requirements</a> page for more information.'); } -$autoload = __DIR__.'/vendor/autoload.php'; +$autoload = __DIR__.'/../vendor/autoload.php'; if (!file_exists($autoload) && !is_readable($autoload)) { die('Composer is not installed.'); @@ -23,14 +23,14 @@ if (!isset($_SERVER['APP_ENV'])) { throw new \RuntimeException('APP_ENV environment variable is not defined. You need to define environment variables for configuration or add "symfony/dotenv" as a Composer dependency to load variables from a .env file.'); } - if (file_exists(__DIR__.'/.env')) { - (new Dotenv(__DIR__))->overload(); + if (file_exists(__DIR__.'/../.env')) { + (new Dotenv(__DIR__.'/..', '.env'))->overload(); if (strpos(getenv('DATABASE_URL'), 'sqlite') !== false && !extension_loaded('pdo_sqlite')) { - (new Dotenv(__DIR__, '.env.install'))->overload(); + (new Dotenv(__DIR__.'/..', '.env.install'))->overload(); } } else { - (new Dotenv(__DIR__, '.env.install'))->overload(); + (new Dotenv(__DIR__.'/..', '.env.install'))->overload(); } } @@ -55,7 +55,7 @@ if ($trustedHosts) { $request = Request::createFromGlobals(); -$maintenanceFile = env('ECCUBE_MAINTENANCE_FILE_PATH', __DIR__.'/.maintenance'); +$maintenanceFile = env('ECCUBE_MAINTENANCE_FILE_PATH', __DIR__.'/../.maintenance'); if (file_exists($maintenanceFile)) { $pathInfo = \rawurldecode($request->getPathInfo()); @@ -67,7 +67,7 @@ if (file_exists($maintenanceFile)) { $baseUrl = \htmlspecialchars(\rawurldecode($request->getBaseUrl()), ENT_QUOTES); header('HTTP/1.1 503 Service Temporarily Unavailable'); - require __DIR__.'/maintenance.php'; + require __DIR__.'/../maintenance.php'; return; } }
.htaccess と html/.htaccess
ルートディレクトリはドキュメントルートではなくなりますので 3 系の .htaccess と同じように全体的にアクセスを拒否するようにするだけです。
Require all denied
mod_access_compat で書きたい場合は下記のようにします。(非推奨)
Order Allow,Deny
html
以下は 3 系と同じになり .env
などの重要なファイルは一つ上の階層にあるのでごちゃごちゃしたアクセス制限を書く必要はなくなります。
https://github.com/EC-CUBE/ec-cube3/blob/3.0/html/.htaccess にある 3 系の html/.htaccess
と同じものでいいかと。4.1 から PHP ファイルの実行抑制が入りましたがそのままでは index.php
まで動かなくなるので Files index.php
で index.php
のみ実行できるようにします。(サブディレクトリの index.php
どうするねんってなるかもしれませんが)
※Apache モジュール版はテストしてないです
/html/.htaccess
Require all granted <IfModule mod_headers.c> # クリックジャッキング対策 Header always set X-Frame-Options SAMEORIGIN # XSS対策 Header set X-XSS-Protection "1; mode=block" Header set X-Content-Type-Options nosniff </IfModule> <IfModule mod_rewrite.c> #403 Forbidden対応方法 #ページアクセスできない時シンボリックリンクが有効になっていない可能性あります、 #オプションを追加してください #Options +FollowSymLinks +SymLinksIfOwnerMatch RewriteEngine On # Authorization ヘッダが取得できない環境への対応 RewriteCond %{HTTP:Authorization} ^(.*) RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1] # さくらのレンタルサーバでサイトへのアクセスをSSL経由に制限する場合の対応 # RewriteCond %{HTTP:x-sakura-forwarded-for} !^$ # RewriteRule ^(.*) - [E=HTTPS:on] RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !^(.*)\.(gif|png|jpe?g|css|ico|js|svg)$ [NC] RewriteRule ^(.*)$ index.php [QSA,L] </IfModule> RemoveType .php AddType text/plain .php <FilesMatch "\.(php|phar)$"> Require all denied SetHandler None </FilesMatch> <Files "index.php"> Require all granted <IfModule !mod_php5.c> <IfModule !mod_php7.c> SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost" </IfModule> </IfModule> <IfModule mod_php7.c> SetHandler application/x-httpd-php </IfModule> </Files>
EC-CUBE 公式では .php
のみ対象にしているのですが PHP パッケージの conf では下記のように .phar
にもハンドラーをセットしているので両方無効にしないと .phar
で PHP スクリプトを実行できてしまいます。
<FilesMatch \.(php|phar)$> SetHandler application/x-httpd-php </FilesMatch>
composer.json
Symfony のガイドにしたがって公開領域の場所を変更します。extra.public-dir
の値を .
から html
に変更します。
/composer.json
diff --git a/composer.json b/composer.json index cc932e0..dcbe63a 100644 --- a/composer.json +++ b/composer.json @@ -178,7 +178,7 @@ "bin-dir": "bin", "src-dir": "src/Eccube", "config-dir": "app/config/eccube", - "public-dir": "." + "public-dir": "html" }, "config": { "platform": {
ここまでの変更で EC-CUBE 自体は動くはずですが画像などが呼び出せていないので次の項目で修正していきます。
framework.yaml
Twig 内で使われている asset()
関数が使用するパスが /html
からになっているので html
を削除していきます。
/app/config/eccube/packages/framework.yaml
diff --git a/app/config/eccube/packages/framework.yaml b/app/config/eccube/packages/framework.yaml index 6145711..91ab734 100644 --- a/app/config/eccube/packages/framework.yaml +++ b/app/config/eccube/packages/framework.yaml @@ -23,20 +23,20 @@ framework: php_errors: log: true assets: - base_path: '/html/template/%eccube.theme%' + base_path: '/template/%eccube.theme%' packages: admin: - base_path: '/html/template/admin' + base_path: '/template/admin' save_image: - base_path: '/html/upload/save_image' + base_path: '/upload/save_image' plugin: - base_path: '/html/plugin' + base_path: '/plugin' install: - base_path: '/html/template/install' + base_path: '/template/install' temp_image: - base_path: '/html/upload/temp_image' + base_path: '/upload/temp_image' user_data: - base_path: '/html/user_data' + base_path: '/user_data' # json_manifest_path: '%kernel.project_dir%/public/build/manifest.json' cache: # this value is used as part of the "namespace" generated for the cache item keys
これで .env
などの重要なファイルが外部に公開される危険性はかなり下がります。ルートディレクトリが外部に公開されていないので Git 関連のファイルを配置したりプロジェクトで共有したい README.md を置いても外部には公開されません。(.git
などの Git 関連のファイルは現状 .htaccess でアクセス拒否されるようになっていますが万全とは言えません)
Symfony のドキュメントでは下記のようなヒントも書かれています。これを読むとやはり index.php
は公開領域ディレクトリに置かれているのが望ましいのではないかと感じます。
Some shared hosts have a public_html/ web directory root. Renaming your web directory from public/ to public_html/ is one way to make your Symfony project work on your shared host. Another way is to deploy your application to a directory outside of your web root, delete your public_html/ directory, and then replace it with a symbolic link to the public/ dir in your project.
https://doc4.ec-cube.net/spec_directory-structure には 3 系のディレクトリ構成で運用する方法の記載が無いのですができれば公式でカスタマイズする方法を公開してくれればありがたいですね…。