웹사이트 검색

Ubuntu 14.04에서 Nginx 및 Php-fpm을 사용하여 여러 웹사이트를 안전하게 호스팅하는 방법


소개

LEMP 스택(Linux, nginx, MySQL, PHP)이 PHP 사이트 실행을 위한 탁월한 속도와 안정성을 제공한다는 것은 잘 알려져 있습니다. 그러나 보안 및 격리와 같은 이 인기 있는 스택의 다른 이점은 덜 인기가 있습니다.

이 기사에서는 다른 Linux 사용자와 함께 LEMP에서 사이트를 실행하는 보안 및 격리 이점을 보여줍니다. 이는 각 nginx 서버 블록(사이트 또는 가상 호스트)에 대해 서로 다른 php-fpm 풀을 생성하여 수행됩니다.

전제 조건

이 가이드는 Ubuntu 14.04에서 테스트되었습니다. 설명된 설치 및 구성은 다른 OS 또는 OS 버전과 유사하지만 구성 파일의 명령 및 위치는 다를 수 있습니다.

또한 nginx 및 php-fpm이 이미 설정되어 있다고 가정합니다. 그렇지 않은 경우 Ubuntu 14.04에 Linux, nginx, MySQL, PHP(LEMP) 스택을 설치하는 방법 문서의 1단계와 3단계를 따르십시오.

이 자습서의 모든 명령은 루트가 아닌 사용자로 실행해야 합니다. 명령에 루트 액세스가 필요한 경우 앞에 sudo가 옵니다. 해당 설정이 아직 없는 경우 Ubuntu 14.04를 사용한 초기 서버 설정 자습서를 따르십시오.

기본 localhost 외에 테스트를 위해 Droplet을 가리키는 정규화된 도메인 이름(fqdn)도 필요합니다. 가지고 있지 않은 경우 site1.example.org를 사용할 수 있습니다. 이 sudo vim /etc/hosts와 같이 선호하는 편집기로 /etc/hosts 파일을 편집하고 다음 행을 추가합니다(site1.example.org 를 대체). fqdn을 사용하는 경우):

...
127.0.0.1 site1.example.org
... 

추가로 LEMP를 확보해야 하는 이유

일반적인 LEMP 설정에는 동일한 사용자의 모든 사이트에 대해 모든 PHP 스크립트를 실행하는 php-fpm 풀이 하나만 있습니다. 이는 두 가지 주요 문제를 제기합니다.

  • 하나의 nginx 서버 블록, 즉 하위 도메인 또는 별도의 사이트에 있는 웹 애플리케이션이 손상되면 이 Droplet의 모든 사이트도 영향을 받습니다. 공격자는 데이터베이스 세부 정보를 비롯한 다른 사이트의 구성 파일을 읽거나 해당 파일을 변경할 수도 있습니다.
  • 사용자에게 Droplet의 사이트에 대한 액세스 권한을 부여하려는 경우 사실상 모든 사이트에 대한 액세스 권한을 부여하는 것입니다. 예를 들어 개발자는 스테이징 환경에서 작업해야 합니다. 그러나 파일 권한이 매우 엄격하더라도 동일한 Droplet에 있는 기본 사이트를 포함한 모든 사이트에 대한 액세스 권한을 그에게 부여할 수 있습니다.

위의 문제는 각 사이트마다 다른 사용자로 실행되는 다른 풀을 생성하여 php-fpm에서 해결됩니다.

1단계 — php-fpm 구성

전제 조건을 다뤘다면 이미 Droplet에 하나의 기능적인 웹 사이트가 있어야 합니다. 사용자 지정 fqdn을 지정하지 않은 경우 fqdn localhost에서 로컬로 또는 드롭릿의 IP로 원격으로 액세스할 수 있어야 합니다.

이제 자체 php-fpm 풀과 Linux 사용자로 두 번째 사이트(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:port 조합에서도 수신 대기할 수 있지만 이렇게 하려면 더 많은 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

이 파일에는 다음이 포함되어야 합니다.

[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 = /

위의 구성에서 다음 특정 옵션에 유의하십시오.

  • [site1]는 풀의 이름입니다. 각 풀에 대해 고유한 이름을 지정해야 합니다.
  • usergroup은 Linux 사용자와 새 풀이 실행될 그룹을 나타냅니다.
  • listen은 각 풀의 고유한 위치를 가리켜야 합니다.
  • listen.ownerlisten.group은 수신기의 소유권, 즉 새로운 php-fpm 풀의 소켓을 정의합니다. Nginx는 이 소켓을 읽을 수 있어야 합니다. 이것이 바로 nginx가 실행되는 사용자 및 그룹(www-data)으로 소켓이 생성되는 이유입니다.
  • php_admin_value를 사용하면 사용자 정의 php 구성 값을 설정할 수 있습니다. 이를 사용하여 Linux 명령( exec,passthru,shell_exec,system)을 실행할 수 있는 기능을 비활성화했습니다.
  • php_admin_flagphp_admin_value와 유사하지만 부울 값(예: 켜기 및 끄기)에 대한 스위치일 뿐입니다. PHP 스크립트가 원격 파일을 열고 공격자가 사용할 수 있도록 허용하는 PHP 함수 allow_url_fopen을 비활성화합니다.

참고: 위의 php_admin_valuephp_admin_flag 값은 전역적으로 적용될 수도 있습니다. 그러나 사이트에 필요할 수 있으므로 기본적으로 구성되지 않습니다. php-fpm 풀의 장점은 각 사이트의 보안 설정을 미세 조정할 수 있다는 것입니다. 또한 이러한 옵션은 사이트의 환경을 추가로 사용자 지정하기 위해 보안 범위 밖의 다른 모든 php 설정에 사용할 수 있습니다.

pm 옵션은 현재 보안 주제 밖에 있지만 풀의 성능을 구성할 수 있다는 점을 알아야 합니다.

chdir 옵션은 파일 시스템의 루트인 /여야 합니다. 다른 중요한 옵션 chroot를 사용하지 않는 한 변경해서는 안 됩니다.

chroot 옵션은 위의 구성에 의도적으로 포함되지 않습니다. 이를 통해 감옥에 갇힌 환경, 즉 디렉터리 내부에 잠긴 환경에서 풀을 실행할 수 있습니다. 사이트의 웹 루트 내에서 풀을 잠글 수 있기 때문에 보안에 좋습니다. 그러나 이 궁극적인 보안은 사용할 수 없는 Imagemagick과 같은 애플리케이션과 시스템 바이너리에 의존하는 괜찮은 PHP 애플리케이션에 심각한 문제를 일으킬 것입니다. 이 주제에 더 관심이 있으시면 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 파일을 편집하고 다음 행을 추가하십시오.

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

이 파일에는 다음이 포함되어야 합니다.

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의 서버 블록에 대한 일반적인 구성을 보여줍니다. 흥미롭게 강조 표시된 부분에 유의하십시오.

  • 웹 루트는 /usr/share/nginx/sites/site1입니다.
  • 서버 이름은 이 문서의 전제 조건에서 언급한 fqdn site1.example.org를 사용합니다.
  • fastcgi_pass는 php 파일에 대한 핸들러를 지정합니다. 모든 사이트에 대해 /var/run/php5-fpm-site1.sock과 같은 다른 유닉스 소켓을 사용해야 합니다.

웹 루트 디렉터리를 만듭니다.

  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 사이트와 해당 웹 루트 /usr/share/nginx/html/에서 먼저 이 파일이 필요합니다. 이를 위해 다음과 같은 편집기를 사용할 수 있습니다.

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

그런 다음 파일을 다음과 같이 다른 사이트(site1.example.org)의 웹 루트에 복사합니다.

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

이제 가장 기본적인 테스트를 실행하여 서버 사용자를 확인할 준비가 되었습니다. 브라우저 또는 Droplet 터미널과 명령줄 브라우저인 lynx에서 테스트를 수행할 수 있습니다. 아직 Droplet에 lynx가 없으면 sudo apt-get install lynx 명령으로 설치하십시오.

먼저 기본 사이트에서 info.php 파일을 확인하십시오. 다음과 같이 localhost에서 액세스할 수 있어야 합니다.

  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 설정을 만든 경우 관심 있는 출력을 필터링하여 위의 방식으로 해당 값을 확인할 수도 있습니다.

지금까지 두 사이트가 서로 다른 사용자로 실행된다는 것을 알고 있지만 이제 연결을 보호하는 방법을 살펴보겠습니다. 이 문서에서 해결하려는 보안 문제를 시연하기 위해 중요한 정보가 포함된 파일을 생성합니다. 일반적으로 이러한 파일에는 데이터베이스에 대한 연결 문자열이 포함되어 있으며 데이터베이스 사용자의 사용자 및 암호 세부 정보가 포함되어 있습니다. 누군가 그 정보를 알아내면 그 사람은 관련 사이트로 무엇이든 할 수 있다.

즐겨 사용하는 편집기를 사용하여 기본 사이트 /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라는 다른 파일을 사용하여 비밀 정보를 읽고 인쇄합니다. 이 파일에는 다음 코드가 포함되어야 합니다.

<?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

웹 루트에서 모든 권한과 소유권이 올바른지 확인하려면 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 파일을 다음과 같이 두 번째 사이트인 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/ 명령을 사용하여 site1 웹 루트의 콘텐츠를 나열하십시오. 넌 봐야 해:

-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/에서 display_errorsOn으로 설정한 경우 lynx 출력에도 유사한 오류가 표시됩니다. php.ini.

경고는 site1.example.org 사이트의 스크립트가 기본 사이트에서 중요한 파일 config.php를 읽을 수 없음을 보여줍니다. 따라서 서로 다른 사용자가 운영하는 사이트는 서로의 보안을 손상시킬 수 없습니다.

이 기사의 구성 부분으로 돌아가면 opcache에서 제공하는 기본 캐싱이 비활성화되었음을 알 수 있습니다. 이유가 궁금하다면 /etc/php5/fpm/conf.d/05-opcache 파일에서 슈퍼 사용자 권한 opcache.enable=1을 설정하여 opcache를 다시 활성화하십시오. ini를 열고 sudo service php5-fpm restart 명령으로 php5-fpm을 다시 시작합니다.

놀랍게도 정확히 동일한 순서로 테스트 단계를 다시 실행하면 소유권과 권한에 관계없이 중요한 파일을 읽을 수 있습니다. opcache의 이 문제는 오랫동안 보고되었지만 이 기사가 작성되는 시점까지 아직 수정되지 않았습니다.

결론

보안 관점에서 동일한 Nginx 웹 서버의 모든 사이트에 대해 다른 사용자와 함께 php-fpm 풀을 사용하는 것이 필수적입니다. 약간의 성능 저하가 있더라도 이러한 격리의 이점은 심각한 보안 위반을 방지할 수 있습니다.

이 기사에서 설명하는 아이디어는 고유하지 않으며 SuPHP와 같은 다른 유사한 PHP 격리 기술에 존재합니다. 그러나 다른 모든 대안의 성능은 php-fpm보다 훨씬 나쁩니다.