開発者ドキュメント

Ubuntu14.04でNginxとPhp-fpmを使用して複数のWebサイトを安全にホストする方法

序章

LEMPスタック(Linux、nginx、MySQL、PHP)が、PHPサイトの実行に比類のない速度と信頼性を提供することはよく知られています。 ただし、セキュリティや分離など、この人気のあるスタックの他の利点はあまり人気がありません。

この記事では、さまざまなLinuxユーザーがいるLEMPでサイトを実行することのセキュリティと分離の利点を紹介します。 これは、nginxサーバーブロック(サイトまたは仮想ホスト)ごとに異なるphp-fpmプールを作成することによって行われます。

前提条件

このガイドはUbuntu14.04でテストされています。 説明されているインストールと構成は、他のOSまたはOSバージョンでも同様ですが、コマンドと構成ファイルの場所が異なる場合があります。

また、すでにnginxとphp-fpmが設定されていることを前提としています。 そうでない場合は、記事 Ubuntu 14.04 にLinux、nginx、MySQL、PHP(LEMP)スタックをインストールする方法のステップ1とステップ3に従ってください。

このチュートリアルのすべてのコマンドは、root以外のユーザーとして実行する必要があります。 コマンドにrootアクセスが必要な場合は、その前にsudoが付きます。 まだセットアップしていない場合は、次のチュートリアルに従ってください: Ubuntu14.04を使用したサーバーの初期セットアップ。

デフォルトのlocalhostに加えて、テスト用のドロップレットを指す完全修飾ドメイン名(fqdn)も必要になります。 手元にない場合は、site1.example.orgを使用できます。 /etc/hostsファイルをこのsudo vim /etc/hostsのようなお気に入りのエディターで編集し、次の行を追加します(site1.example.orgを使用している場合はfqdnに置き換えます)。

/ etc / hosts
...
127.0.0.1 site1.example.org
... 

LEMPをさらに保護する理由

一般的なLEMPセットアップでは、同じユーザーの下にあるすべてのサイトのすべてのPHPスクリプトを実行するphp-fpmプールは1つだけです。 これには2つの大きな問題があります。

上記の問題は、php-fpmで、サイトごとに異なるユーザーの下で実行される異なるプールを作成することで解決されます。

ステップ1—php-fpmの設定

前提条件を満たしている場合は、Dropletに1つの機能するWebサイトがすでにあるはずです。 カスタムfqdnを指定していない限り、ローカルでfqdn localhostの下で、またはリモートでドロップレットのIPによってアクセスできるはずです。

次に、独自のphp-fpmプールとLinuxユーザーを使用して2番目のサイト( site1.example.org )を作成します。

必要なユーザーの作成から始めましょう。 最適な分離のために、新しいユーザーは独自のグループを持つ必要があります。 したがって、最初にユーザーグループsite1を作成します。

  1. sudo groupadd site1

次に、このグループに属するユーザーsite1を作成してください。

  1. sudo useradd -g site1 site1

これまでのところ、新しいユーザーsite1にはパスワードがなく、Dropletにログインできません。 このサイトのファイルへの直接アクセスを誰かに提供する必要がある場合は、コマンドsudo passwd site1を使用してこのユーザーのパスワードを作成する必要があります。 新しいユーザーとパスワードの組み合わせを使用すると、ユーザーはsshまたはsftpを使用してリモートでログインできます。 詳細とセキュリティの詳細については、記事ディレクトリアクセスが制限されたセカンダリSSH/SFTPユーザーのセットアップを確認してください。

次に、site1の新しいphp-fpmプールを作成します。 本質的にphp-fpmプールは、特定のユーザー/グループで実行され、Linuxソケットでリッスンする通常のLinuxプロセスです。 IP:ポートの組み合わせでリッスンすることもできますが、これにはより多くのDropletリソースが必要であり、推奨される方法ではありません。

デフォルトでは、Ubuntu 14.04では、すべてのphp-fpmプールをディレクトリ/etc/php5/fpm/pool.d内のファイルで構成する必要があります。 このディレクトリ内の拡張子が.confのすべてのファイルは、php-fpmグローバル構成に自動的にロードされます。

それで、私たちの新しいサイトのために、新しいファイル/etc/php5/fpm/pool.d/site1.confを作成しましょう。 これは、次のようなお気に入りのエディターで実行できます。

  1. sudo vim /etc/php5/fpm/pool.d/site1.conf

このファイルには次のものが含まれている必要があります。

/etc/php5/fpm/pool.d/site1.conf
[site1]
user = site1
group = site1
listen = /var/run/php5-fpm-site1.sock
listen.owner = www-data
listen.group = www-data
php_admin_value[disable_functions] = exec,passthru,shell_exec,system
php_admin_flag[allow_url_fopen] = off
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir = /

上記の構成では、次の特定のオプションに注意してください。

注:上記のphp_admin_valueおよびphp_admin_flagの値は、グローバルに適用することもできます。 ただし、サイトでそれらが必要になる場合があるため、デフォルトでは構成されていません。 php-fpmプールの利点は、各サイトのセキュリティ設定を微調整できることです。 さらに、これらのオプションは、セキュリティ範囲外の他のphp設定に使用して、サイトの環境をさらにカスタマイズできます。

pmオプションは現在のセキュリティトピックの範囲外ですが、プールのパフォーマンスを構成できることを知っておく必要があります。

chdirオプションは、ファイルシステムのルートである/である必要があります。 別の重要なオプションchrootを使用しない限り、これを変更しないでください。

オプションchrootは、意図的に上記の構成に含まれていません。 それはあなたが投獄された環境でプールを実行することを可能にするでしょう、すなわち ディレクトリ内にロックされています。 これは、サイトのWebルート内のプールをロックできるため、セキュリティに最適です。 ただし、この究極のセキュリティは、システムバイナリに依存する適切なPHPアプリケーションや、利用できないImagemagickなどのアプリケーションに深刻な問題を引き起こします。 このトピックにさらに興味がある場合は、記事Firejailを使用して投獄された環境でWordPressのインストールをセットアップする方法をお読みください。

上記の構成が完了したら、php-fpmを再起動して、次のコマンドで新しい設定を有効にします。

  1. sudo service php5-fpm restart

次のようなプロセスを検索して、新しいプールが正しく実行されていることを確認します。

  1. ps aux |grep site1

ここまでの正確な手順に従った場合は、次のような出力が表示されます。

site1   14042  0.0  0.8 133620  4208 ?        S    14:45   0:00 php-fpm: pool site1
site1   14043  0.0  1.1 133760  5892 ?        S    14:45   0:00 php-fpm: pool site1

赤字は、プロセスまたはphp-fpmプールを実行するユーザー(site1)です。

さらに、opcacheによって提供されるデフォルトのphpキャッシングを無効にします。 この特定のキャッシュ拡張機能はパフォーマンスには優れている可能性がありますが、後で説明するようにセキュリティには適していません。 これを無効にするには、スーパーユーザー権限でファイル/etc/php5/fpm/conf.d/05-opcache.iniを編集し、次の行を追加します。

/etc/php5/fpm/conf.d/05-opcache.ini
opcache.enable=0

次に、php-fpm(sudo service php5-fpm restart)を再起動して、設定を有効にします。

ステップ2—nginxを構成する

サイトのphp-fpmプールを構成したら、nginxでサーバーブロックを構成します。 この目的のために、次のようなお気に入りのエディターで新しいファイル/etc/nginx/sites-available/site1を作成してください。

  1. sudo vim /etc/nginx/sites-available/site1

このファイルには次のものが含まれている必要があります。

/ etc / nginx / sites-available / site1
server {
    listen 80;

    root /usr/share/nginx/sites/site1;
    index index.php index.html index.htm;

    server_name site1.example.org;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm-site1.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

上記のコードは、nginxのサーバーブロックの一般的な構成を示しています。 興味深い強調表示された部分に注意してください。

Webルートディレクトリを作成します。

  1. sudo mkdir /usr/share/nginx/sites
  2. sudo mkdir /usr/share/nginx/sites/site1

上記のサイトを有効にするには、ディレクトリ/etc/nginx/sites-enabled/にそのサイトへのシンボリックリンクを作成する必要があります。 これは、次のコマンドで実行できます。

  1. sudo ln -s /etc/nginx/sites-available/site1 /etc/nginx/sites-enabled/site1

最後に、nginxを再起動して、次のように変更を有効にします。

  1. sudo service nginx restart

ステップ3—テスト

テストを実行するために、php環境に関する詳細情報を提供する有名なphpinfo関数を使用します。 <?php phpinfo(); ?>という行のみを含むinfo.phpという名前の新しいファイルを作成します。 このファイルは、デフォルトのnginxサイトとそのWebルート/usr/share/nginx/html/で最初に必要になります。 この目的のために、次のようなエディターを使用できます。

  1. sudo vim /usr/share/nginx/html/info.php

その後、次のようにファイルを他のサイトのWebルート( site1.example.org )にコピーします。

  1. sudo cp /usr/share/nginx/html/info.php /usr/share/nginx/sites/site1/

これで、サーバーユーザーを確認するための最も基本的なテストを実行する準備が整いました。 テストは、ブラウザーを使用するか、Dropletターミナルおよびコマンドラインブラウザーであるlynxから実行できます。 ドロップレットにまだlynxがない場合は、コマンドsudo apt-get install lynxを使用してインストールします。

まず、デフォルトサイトのinfo.phpファイルを確認します。 次のようにローカルホストでアクセスできる必要があります。

  1. lynx --dump http://localhost/info.php |grep 'SERVER\["USER"\]'

上記のコマンドでは、サーバーユーザーを表す変数SERVER["USER"]に対してのみgrepを使用して出力をフィルタリングします。 デフォルトサイトの場合、出力には次のようなデフォルトのwww-dataユーザーが表示されます。

_SERVER["USER"]                 www-data

同様に、次にサーバーユーザーでsite1.example.orgを確認します。

  1. lynx --dump http://site1.example.org/info.php |grep 'SERVER\["USER"\]'

今回は、site1ユーザーの出力に表示されます。

_SERVER["USER"]                 site1

php-fpmプールごとにカスタムphp設定を行った場合は、関心のある出力をフィルタリングすることにより、上記の方法で対応する値を確認することもできます。

これまでのところ、2つのサイトが異なるユーザーの下で実行されていることはわかっていますが、接続を保護する方法を見てみましょう。 この記事で解決しているセキュリティの問題を示すために、機密情報を含むファイルを作成します。 通常、このようなファイルにはデータベースへの接続文字列が含まれ、データベースユーザーのユーザーとパスワードの詳細が含まれます。 誰かがその情報を見つけた場合、その人は関連サイトで何でもすることができます。

お気に入りのエディタを使用して、メインサイト/usr/share/nginx/html/config.phpに新しいファイルを作成します。 そのファイルには次のものが含まれている必要があります。

/usr/share/nginx/html/config.php
<?php
$pass = 'secret';
?>

上記のファイルでは、値secretを保持するpassという変数を定義しています。 当然、このファイルへのアクセスを制限したいので、そのアクセス許可を400に設定します。これにより、ファイルの所有者に読み取り専用アクセスが許可されます。

権限を400に変更するには、次のコマンドを実行します。

  1. sudo chmod 400 /usr/share/nginx/html/config.php

また、メインサイトは、このファイルを読み取ることができるはずのユーザーwww-dataの下で実行されます。 したがって、ファイルの所有権を次のようにそのユーザーに変更します。

  1. sudo chown www-data:www-data /usr/share/nginx/html/config.php

この例では、/usr/share/nginx/html/readfile.phpという別のファイルを使用して、秘密情報を読み取り、印刷します。 このファイルには、次のコードが含まれている必要があります。

/usr/share/nginx/html/readfile.php
<?php
include('/usr/share/nginx/html/config.php');
print($pass);
?>

このファイルの所有権もwww-dataに変更します。

  1. sudo chown www-data:www-data /usr/share/nginx/html/readfile.php

Webルートですべての権限と所有権が正しいことを確認するには、コマンドls -l /usr/share/nginx/html/を実行します。 次のような出力が表示されます。

-r-------- 1 www-data www-data  27 Jun 19 05:35 config.php
-rw-r--r-- 1 www-data www-data  68 Jun 21 16:31 readfile.php

次に、コマンドlynx --dump http://localhost/readfile.phpを使用して、デフォルトサイトの後者のファイルにアクセスします。 出力secretに印刷されたものが表示されるはずです。これは、機密情報を含むファイルが同じサイト内でアクセス可能であることを示しています。これは、予想される正しい動作です。

次に、ファイル/usr/share/nginx/html/readfile.phpを2番目のサイトsite1.example.orgに次のようにコピーします。

  1. sudo cp /usr/share/nginx/html/readfile.php /usr/share/nginx/sites/site1/

サイトとユーザーの関係を維持するために、各サイト内でファイルがそれぞれのサイトユーザーによって所有されていることを確認してください。 これを行うには、次のコマンドを使用して、新しくコピーしたファイルの所有権をsite1に変更します。

  1. sudo chown site1:site1 /usr/share/nginx/sites/site1/readfile.php

ファイルの正しい権限と所有権を設定したことを確認するには、コマンドls -l /usr/share/nginx/sites/site1/を使用してsite1Webルートのコンテンツを一覧表示してください。 君は見るべきだ:

-rw-r--r-- 1 site1 site1  80 Jun 21 16:44 readfile.php

次に、コマンドlynx --dump http://site1.example.org/readfile.phpを使用して、site1.example.comから同じファイルにアクセスしてみます。 空のスペースのみが返されます。 さらに、grepコマンドsudo grep error /var/log/nginx/error.logを使用してnginxのエラーログでエラーを検索すると、次のように表示されます。

2015/06/30 15:15:13 [error] 894#0: *242 FastCGI sent in stderr: "PHP message: PHP Warning:  include(/usr/share/nginx/html/config.php): failed to open stream: Permission denied in /usr/share/nginx/sites/site1/readfile.php on line 2

注:php-fpm構成ファイル/etc/php5/fpm/php.inidisplay_errorsOnに設定している場合も、lynx出力で同様のエラーが表示されます。

警告は、site1.example.orgサイトのスクリプトがメインサイトから機密ファイルconfig.phpを読み取れないことを示しています。 したがって、異なるユーザーの下で実行されるサイトは、互いのセキュリティを危険にさらすことはできません。

この記事の構成部分の最後に戻ると、opcacheによって提供されるデフォルトのキャッシュが無効になっていることがわかります。 理由がわからない場合は、ファイル/etc/php5/fpm/conf.d/05-opcache.iniでスーパーユーザー権限opcache.enable=1を設定してopcacheを再度有効にし、コマンドsudo service php5-fpm restartでphp5-fpmを再起動してください。

驚くべきことに、まったく同じ順序でテスト手順を再度実行すると、所有権や権限に関係なく、機密ファイルを読み取ることができます。 opcacheのこの問題は長い間報告されていますが、この記事の時点ではまだ修正されていません。

結論

セキュリティの観点から、同じNginxWebサーバー上のサイトごとに異なるユーザーでphp-fpmプールを使用することが不可欠です。 わずかなパフォーマンスの低下が伴う場合でも、このような分離の利点により、重大なセキュリティ侵害を防ぐことができます。

この記事で説明されている考え方はユニークではなく、SuPHPなどの他の同様のPHP分離テクノロジーにも存在します。 ただし、他のすべての選択肢のパフォーマンスは、php-fpmのパフォーマンスよりもはるかに劣ります。

モバイルバージョンを終了