Thứ tư, 01/07/2015 | 00:00 GMT+7

Cách lưu trữ nhiều trang web một cách an toàn với Nginx và Php-fpm trên Ubuntu 14.04

Ai cũng biết rằng LEMP (Linux, nginx, MySQL, PHP) cung cấp tốc độ và độ tin cậy chưa từng có để chạy các trang web PHP. Tuy nhiên, các lợi ích khác của ngăn xếp phổ biến này như bảo mật và cách ly ít phổ biến hơn.

Trong bài viết này, ta sẽ chỉ cho bạn những lợi ích về bảo mật và cách ly của việc chạy các trang web trên LEMP với những user Linux khác nhau. Điều này sẽ được thực hiện bằng cách tạo các group php-fpm khác nhau cho mỗi khối server nginx (trang web hoặc server ảo).

Yêu cầu

Hướng dẫn này đã được thử nghiệm trên Ubuntu 14.04. Cài đặt và cấu hình được mô tả sẽ tương tự trên các version HĐH hoặc HĐH khác, nhưng các lệnh và vị trí của file cấu hình có thể khác nhau.

Nó cũng giả định bạn đã cài đặt nginx và php-fpm. Nếu không, hãy làm theo bước một và bước ba từ bài viết Cách cài đặt ngăn xếp Linux, nginx, MySQL, PHP (LEMP) trên Ubuntu 14.04 .

Tất cả các lệnh trong hướng dẫn này phải được chạy với quyền user không phải root. Nếu cần có quyền truy cập root cho lệnh, nó sẽ được đặt trước sudo . Nếu bạn chưa cài đặt , hãy làm theo hướng dẫn này: Cài đặt server ban đầu với Ubuntu 14.04 .

Bạn cũng cần một domain đủ điều kiện (fqdn) trỏ đến Server để thử nghiệm ngoài localhost mặc định. Nếu bạn không có trong tay, bạn có thể sử dụng site1.example.org . Chỉnh sửa file /etc/hosts bằng editor yêu thích của bạn như sudo vim /etc/hosts và thêm dòng này (thay thế site1.example.org bằng fqdn của bạn nếu bạn đang sử dụng nó):

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

Ngoài ra, lý do đảm bảo an toàn cho LEMP

Theo cài đặt LEMP thông thường, chỉ có một group php-fpm chạy tất cả các tập lệnh PHP cho tất cả các trang web trong cùng một user . Điều này đặt ra hai vấn đề lớn:

  • Nếu một ứng dụng web trên một khối server nginx, tức là domain phụ hoặc trang web riêng biệt, bị xâm phạm, thì tất cả các trang web trên Server này cũng sẽ bị ảnh hưởng. Kẻ tấn công có thể đọc các file cấu hình, bao gồm chi tiết database , của các trang web khác hoặc thậm chí thay đổi file của chúng.
  • Nếu bạn muốn cấp cho user quyền truy cập vào một trang web trên Server của bạn , trên thực tế, bạn sẽ cấp cho họ quyền truy cập vào tất cả các trang web. Ví dụ: nhà phát triển của bạn cần làm việc trên môi trường dàn dựng. Tuy nhiên, ngay cả với các quyền đối với file rất nghiêm ngặt, bạn vẫn sẽ cấp cho anh ta quyền truy cập vào tất cả các trang web, bao gồm cả trang web chính của bạn, trên cùng một Server.

Các vấn đề trên được giải quyết trong php-fpm bằng cách tạo một group khác chạy dưới một user khác nhau cho mỗi trang web.

Bước 1 - Cấu hình php-fpm

Nếu bạn đã đề cập đến các yêu cầu , thì bạn đã có một trang web chức năng trên Server. Trừ khi bạn đã chỉ định một fqdn tùy chỉnh cho nó, bạn có thể truy cập nó dưới localhost fqdn localhost hoặc bằng IP của server từ xa.

Bây giờ ta sẽ tạo một trang thứ hai (site1.example.org) với group php-fpm của riêng nó và user Linux.

Hãy bắt đầu với việc tạo user cần thiết. Để cách ly tốt nhất, user mới nên có group riêng. Vì vậy, trước tiên hãy tạo site1 group user :

  • sudo groupadd site1

Sau đó, vui lòng tạo một trang web user 1 thuộc group này:

  • sudo useradd -g site1 site1

Cho đến nay user mới site1 không có password và không thể đăng nhập vào Server. Nếu bạn cần cung cấp cho ai đó quyền truy cập trực tiếp vào các file của trang web này, thì bạn nên tạo password cho user này bằng lệnh sudo passwd site1 . Với sự kết hợp user / password mới, user có thể đăng nhập từ xa bằng ssh hoặc sftp. Để biết thêm thông tin và chi tiết bảo mật, hãy xem bài viết Cài đặt user SSH / SFTP phụ với quyền truy cập folder hạn chế .

Tiếp theo, tạo một group php-fpm mới cho site1. Bản chất của một php-fpm pool chỉ là một quy trình Linux thông thường chạy dưới một số user / group nhất định và lắng nghe trên một socket Linux. Nó cũng có thể nghe trên kết hợp IP: cổng nhưng điều này sẽ yêu cầu nhiều tài nguyên Server hơn và nó không phải là phương pháp ưa thích.

Theo mặc định, trong Ubuntu 14.04 mọi group php-fpm phải được cấu hình trong một file bên trong folder /etc/php5/fpm/pool.d . Mọi file có phần mở rộng .conf trong folder này sẽ được tự động tải trong cấu hình chung của php-fpm.

Vì vậy, đối với trang web mới của ta , hãy tạo một file mới /etc/php5/fpm/pool.d/site1.conf . Bạn có thể làm điều này với trình soạn thảo yêu thích của bạn như sau:

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

Tệp này phải chứa:

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

Trong cấu hình trên, lưu ý các tùy chọn cụ thể sau:

  • [site1] là tên của group . Đối với mỗi group , bạn phải chỉ định một tên duy nhất.
  • usergroup cho user Linux và group mà group mới sẽ chạy.
  • listen nên chỉ đến một vị trí duy nhất cho mỗi hồ bơi.
  • listen.ownerlisten.group xác định quyền sở hữu của người nghe, tức là socket của hồ bơi php-fpm mới. Nginx phải có thể đọc socket này. Đó là lý do tại sao socket được tạo với user và group mà nginx chạy - www-data .
  • php_admin_value cho phép bạn đặt các giá trị cấu hình php tùy chỉnh. Ta đã sử dụng nó để vô hiệu hóa các chức năng có thể chạy các lệnh Linux - exec,passthru,shell_exec,system .
  • php_admin_flag tương tự như php_admin_value , nhưng nó chỉ là một lựa chọn cho các giá trị boolean, tức là bật và tắt. Ta sẽ vô hiệu hóa hàm allow_url_fopen của PHP để cho phép tập lệnh PHP mở các file từ xa và kẻ tấn công có thể sử dụng.

Lưu ý: Các giá trị php_admin_flagphp_admin_value ở trên cũng có thể được áp dụng trên phạm vi global . Tuy nhiên, một trang web có thể cần chúng và đó là lý do tại sao chúng không được cấu hình theo mặc định. Vẻ đẹp của php-fpm pool là nó cho phép bạn tinh chỉnh cài đặt bảo mật của từng trang web. Hơn nữa, các tùy chọn này được dùng cho cài đặt php nào khác, ngoài phạm vi bảo mật, để tùy chỉnh thêm môi trường của một trang web.

Các tùy chọn pm nằm ngoài chủ đề bảo mật hiện tại, nhưng bạn nên biết rằng chúng cho phép bạn cấu hình hiệu suất của pool.

Tùy chọn chdir phải là / là root của hệ thống file . Điều này sẽ không được thay đổi trừ khi bạn sử dụng chroot tùy chọn quan trọng khác.

Tùy chọn chroot không có chủ đích trong cấu hình trên. Nó sẽ cho phép bạn chạy một pool trong môi trường bị giam giữ, tức là bị khóa bên trong một folder . Điều này rất tốt cho bảo mật vì bạn có thể khóa group bên trong folder root của trang web. Tuy nhiên, tính bảo mật tối cao này sẽ gây ra các vấn đề nghiêm trọng đối với bất kỳ ứng dụng PHP tốt nào dựa trên hệ thống binary và ứng dụng như Imagemagick, sẽ không khả dụng. Nếu bạn quan tâm hơn đến chủ đề này, vui lòng đọc bài viết Cách sử dụng Firejail để cài đặt cài đặt WordPress trong môi trường bị giam giữ .

Khi bạn đã hoàn tất cấu hình ở trên, hãy khởi động lại php-fpm để các cài đặt mới có hiệu lực bằng lệnh:

  • sudo service php5-fpm restart

Xác minh group mới đang chạy đúng cách bằng cách tìm kiếm các quy trình của nó như sau:

  • ps aux |grep site1

Nếu bạn đã làm theo các hướng dẫn chính xác đến đây, bạn sẽ thấy kết quả tương tự như:

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 

Màu đỏ là user mà tiến trình hoặc group php-fpm chạy - site1.

Ngoài ra, ta sẽ vô hiệu hóa cache php mặc định do opcache cung cấp. Tiện ích mở rộng bộ nhớ đệm cụ thể này có thể rất tốt cho hiệu suất, nhưng nó không phải để bảo mật như ta sẽ thấy sau. Để vô hiệu hóa nó, hãy chỉnh sửa file /etc/php5/fpm/conf.d/05-opcache.ini với quyền user cấp cao và thêm dòng:

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

Sau đó khởi động lại php-fpm ( sudo service php5-fpm restart ) để cài đặt có hiệu lực.

Bước 2 - Cấu hình nginx

Khi ta đã cấu hình group php-fpm cho trang web của bạn , ta sẽ cấu hình khối server trong nginx. Vì mục đích này, vui lòng tạo một file mới /etc/nginx/sites-available/site1 với editor yêu thích của bạn như sau:

  • sudo vim /etc/nginx/sites-available/site1

Tệp này phải chứa:

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

Đoạn mã trên hiển thị cấu hình chung cho khối server trong nginx. Lưu ý các phần được đánh dấu thú vị:

  • Gốc web là /usr/share/nginx/sites/site1 .
  • Tên server sử dụng fqdn site1.example.org là tên được đề cập trong yêu cầu của bài viết này.
  • fastcgi_pass chỉ định trình xử lý cho các file php. Đối với mỗi trang web, bạn nên sử dụng một socket unix khác nhau như /var/run/php5-fpm-site1.sock .

Tạo folder root của web:

  • sudo mkdir /usr/share/nginx/sites
  • sudo mkdir /usr/share/nginx/sites/site1

Để kích hoạt trang web trên, bạn phải tạo một softlink cho nó trong folder /etc/nginx/sites-enabled/ . Điều này có thể được thực hiện bằng lệnh:

  • sudo ln -s /etc/nginx/sites-available/site1 /etc/nginx/sites-enabled/site1

Cuối cùng, khởi động lại nginx để thay đổi có hiệu lực như sau:

  • sudo service nginx restart

Bước 3 - Kiểm tra

Để chạy các bài kiểm tra, ta sẽ sử dụng hàm phpinfo nổi tiếng cung cấp thông tin chi tiết về môi trường php. Tạo một file mới với tên info.php chỉ chứa dòng <?php phpinfo(); ?> . Trước tiên, bạn cần file này trong trang web nginx mặc định và web root /usr/share/nginx/html/ . Vì mục đích này, bạn có thể sử dụng một trình soạn thảo như sau:

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

Sau đó, sao chép file vào folder root của trang web khác (site1.example.org) như sau:

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

Đến đây bạn đã sẵn sàng chạy thử nghiệm cơ bản nhất để xác minh user server . Bạn có thể thực hiện kiểm tra bằng trình duyệt hoặc từ terminal Server và lynx, trình duyệt dòng lệnh. Nếu bạn chưa có lynx trên Server của bạn , hãy cài đặt nó bằng lệnh sudo apt-get install lynx .

Trước tiên, hãy kiểm tra file info.php từ trang web mặc định của bạn. Nó sẽ có thể truy cập được dưới localhost như thế này:

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

Trong lệnh trên, ta lọc kết quả bằng grep chỉ cho biến SERVER["USER"] là viết tắt của user server . Đối với trang web mặc định, kết quả sẽ hiển thị user www-data mặc định như sau:

_SERVER["USER"]                 www-data 

Tương tự, tiếp theo hãy kiểm tra user server cho site1.example.org:

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

Bạn sẽ thấy thời gian này trong kết quả user site1 :

_SERVER["USER"]                 site1 

Nếu bạn đã thực hiện cài đặt php tùy chỉnh nào trên cơ sở group php-fpm, thì bạn cũng có thể kiểm tra các giá trị tương ứng của chúng theo cách trên bằng cách lọc kết quả mà bạn quan tâm.

Lúc này, ta biết rằng hai trang web của ta chạy dưới những user khác nhau, nhưng bây giờ ta hãy xem cách bảo mật kết nối. Để chứng minh vấn đề bảo mật mà ta đang giải quyết trong bài viết này, ta sẽ tạo một file có thông tin nhạy cảm. Thông thường một file như vậy chứa chuỗi kết nối với database và bao gồm chi tiết user và password của user database . Nếu bất kỳ ai phát hiện ra thông tin đó, người đó có thể làm bất cứ điều gì với trang web liên quan.

Với trình soạn thảo yêu thích của bạn, hãy tạo một file mới trong trang web chính của bạn /usr/share/nginx/html/config.php . Tệp đó phải chứa:

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

Trong file ở trên, ta xác định một biến có tên là pass giữ secret giá trị. Đương nhiên, ta muốn hạn chế quyền truy cập vào file này, vì vậy ta sẽ đặt quyền của nó thành 400, cấp quyền truy cập chỉ đọc cho chủ sở hữu file .

Để thay đổi quyền thành 400, hãy chạy lệnh:

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

Ngoài ra, trang web chính của ta chạy dưới www-data user có thể đọc file này. Do đó, hãy thay đổi quyền sở hữu file cho user đó như sau:

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

Trong ví dụ của ta , ta sẽ sử dụng một file khác có tên /usr/share/nginx/html/readfile.php để đọc thông tin bí mật và in nó. Tệp này phải chứa mã sau:

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

Thay đổi cả quyền sở hữu của file này thành www-data :

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

Để xác nhận tất cả các quyền và quyền sở hữu là chính xác trong folder root của web, hãy chạy lệnh ls -l /usr/share/nginx/html/ . Bạn sẽ thấy kết quả tương tự như:

-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 

Bây giờ hãy truy cập file sau trên trang web mặc định của bạn bằng lệnh lynx --dump http://localhost/readfile.php . Bạn có thể thấy được in trong secret kết quả cho thấy rằng file có thông tin nhạy cảm có thể truy cập được trong cùng một trang web, đó là hành vi đúng dự kiến.

Bây giờ sao chép file /usr/share/nginx/html/readfile.php vào trang web thứ hai của bạn, site1.example.org như sau:

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

Để giữ cho trang web / quan hệ user có trật tự, hãy đảm bảo trong mỗi trang web, các file được sở hữu bởi user trang web tương ứng. Thực hiện việc này bằng cách thay đổi quyền sở hữu file mới được sao chép thành site1 bằng lệnh:

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

Để xác nhận bạn đã đặt đúng quyền và quyền sở hữu file , vui lòng liệt kê nội dung của root web site1 bằng lệnh ls -l /usr/share/nginx/sites/site1/ . Bạn nên thấy:

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

Sau đó, cố gắng truy cập cùng một file từ site1.example.com bằng lệnh lynx --dump http://site1.example.org/readfile.php . Bạn sẽ chỉ thấy không gian trống được trả lại. Hơn nữa, nếu bạn tìm kiếm lỗi trong log lỗi của nginx bằng lệnh grep sudo grep error /var/log/nginx/error.log bạn sẽ thấy:

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  

Lưu ý: Bạn cũng sẽ gặp lỗi tương tự trong kết quả lynx nếu bạn đặt display_errors thành On trong file cấu hình php-fpm /etc/php5/fpm/php.ini .

Cảnh báo cho thấy rằng một tập lệnh từ trang web site1.example.org không thể đọc file nhạy cảm config.php từ trang web chính. Do đó, các trang web chạy dưới quyền của những user khác nhau không thể xâm phạm tính bảo mật của nhau.

Nếu bạn quay lại phần cuối cấu hình của bài viết này, bạn sẽ thấy rằng ta đã tắt bộ nhớ đệm mặc định do opcache cung cấp. Nếu bạn tò mò tại sao, hãy thử chạy lại opcache bằng cách cài đặt với quyền user cấp cao opcache.enable=1 trong file /etc/php5/fpm/conf.d/05-opcache.ini và khởi động lại php5-fpm bằng lệnh sudo service php5-fpm restart .

Thật ngạc nhiên, nếu bạn chạy lại các bước kiểm tra theo đúng thứ tự, bạn có thể đọc file nhạy cảm dù quyền sở hữu và quyền của nó. Sự cố này trong opcache đã được báo cáo từ lâu, nhưng đến thời điểm bài viết này, nó vẫn chưa được khắc phục.

Kết luận

Từ quan điểm bảo mật, điều cần thiết là sử dụng group php-fpm với một user khác nhau cho mọi trang web trên cùng một web server Nginx. Ngay cả khi nó đi kèm với một hình phạt hiệu suất nhỏ, lợi ích của việc cô lập như vậy có thể ngăn chặn các vi phạm bảo mật nghiêm trọng.

Ý tưởng được mô tả trong bài viết này không phải là duy nhất và nó hiện diện trong các công nghệ cách ly PHP tương tự khác như SuPHP. Tuy nhiên, hiệu suất của tất cả các lựa chọn thay thế khác kém hơn nhiều so với php-fpm.


Tags:

Các tin liên quan

Cách tạo blog bằng Ghost và Nginx trên Ubuntu 14.04
2015-06-28
Cách tạo chứng chỉ ECC trên Nginx cho Debian 8
2015-06-23
Cách nâng cấp Nginx tại chỗ mà không làm rớt kết nối client
2015-06-15
Cách cấu hình Nginx để sử dụng các trang lỗi tùy chỉnh trên Ubuntu 14.04
2015-06-05
Cách cấu hình Nginx để sử dụng các trang lỗi tùy chỉnh trên CentOS 7
2015-06-05
Cách chuyển hướng www sang không có www với Nginx trên CentOS 7
2015-05-04
Cách chuyển hướng www thành không có www với Nginx trên Ubuntu 14.04
2015-05-04
Cách triển khai ứng dụng Rails với Puma và Nginx trên Ubuntu 14.04
2015-04-01
Cách triển khai ứng dụng Rails với Unicorn và Nginx trên Ubuntu 14.04
2015-03-26
Cách cung cấp ứng dụng flask với Gunicorn và Nginx trên CentOS 7
2015-03-23