25 PHP Security Best Practices For SysAdmins
25 PHP Security Best Practices For SysAdmins
25 PHP Security Best Practices For SysAdmins
Admins
by Vivek Gite on November 23, 2011 last updated March 28, 2016
in php, RedHat/Fedora Linux, Security, Sys admin, Tuning
PHP is an open-source server-side scripting language, and it is a widely used. The Apache/Nginx/Lighttpd
web server provides access to files and content via the HTTP OR HTTPS protocol. A misconfigured serverside scripting language can create all sorts of problems. So, PHP should be used with caution. Here are
twenty-five php security best practices for sysadmins for configuring PHP securely.
DocumentRoot: /var/www/html
Default Web server: Apache ( you can use Lighttpd or Nginx instead of Apache)
Our sample php security config file: /etc/php.d/security.ini (you need to create this file using a text
editor)
Operating systems: RHEL / CentOS / Fedora Linux (the instructions should work with any other
Linux distributions such as Debian / Ubuntu or other Unix like operating systems such as
OpenBSD/FreeBSD/HP-UX).
Most of the actions listed in this post are written with the assumption that they will be executed by the root
user running the bash or any other modern shell:
$ php -v
Sample outputs:
PHP 5.3.3 (cli) (built: Oct 24 2011 08:35:41)
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
Sample outputs:
Red Hat Enterprise Linux Server release 6.1 (Santiago)
Sample outputs:
[PHP Modules]
apc
bcmath
bz2
calendar
Core
ctype
curl
date
dom
ereg
exif
fileinfo
filter
ftp
gd
gettext
gmp
hash
iconv
imap
json
libxml
mbstring
memcache
mysql
mysqli
openssl
pcntl
pcre
PDO
pdo_mysql
pdo_sqlite
Phar
readline
Reflection
session
shmop
SimpleXML
sockets
SPL
sqlite3
standard
suhosin
tokenizer
wddx
xml
xmlreader
xmlrpc
xmlwriter
xsl
zip
zlib
[Zend Modules]
Suhosin
I recommends that you use PHP with a reduced modules for performance and security. For example, you can
disable sqlite3 module by deleting (removing) configuration file , OR renaming (moving) a file called
/etc/php.d/sqlite3.ini as follows:
# rm /etc/php.d/sqlite3.ini
OR
# mv /etc/php.d/sqlite3.ini /etc/php.d/sqlite3.disable
Other compiled-in modules can only be removed by reinstallating PHP with a reduced configuration. You
can download php source code from php.net and compile it as follows with GD, fastcgi, and MySQL
support:
./configure --with-libdir=lib64 --with-gd --with-mysql --prefix=/usr --exec-prefix=/usr
\
--bindir=/usr/bin --sbindir=/usr/sbin --sysconfdir=/etc --datadir=/usr/share \
--includedir=/usr/include --libexecdir=/usr/libexec --localstatedir=/var \
--sharedstatedir=/usr/com --mandir=/usr/share/man --infodir=/usr/share/info \
--cache-file=../config.cache --with-config-file-path=/etc \
--with-config-file-scan-dir=/etc/php.d --enable-fastcgi \
--enable-force-cgi-redirect
See how to compile and reinstall php on Unix like operating system for more information.
When enabled, expose_php reports to the world that PHP is installed on the server, which includes the PHP
version within the HTTP header (e.g., X-Powered-By: PHP/5.3.3). The PHP logo guids (see example) are
also exposed, thus appending them to the URL of a PHP enabled site will display the appropriate logo.
When expose_php enabled you can see php version using the following command:
$ curl -I http://www.cyberciti.biz/index.php
Sample outputs:
HTTP/1.1 200 OK
X-Powered-By: PHP/5.3.3
Content-type: text/html; charset=UTF-8
Vary: Accept-Encoding, Cookie
X-Vary-Options: Accept-Encoding;list-contains=gzip,Cookie;stringcontains=wikiToken;string-contains=wikiLoggedOut;string-contains=wiki_session
Last-Modified: Thu, 03 Nov 2011 22:32:55 GMT
...
I also recommend that you setup the ServerTokens and ServerSignature directives in httpd.conf to hide
Apache version and other information.
file_uploads=Off
If users of your application need to upload files, turn this feature on by setting upload_max_filesize limits
the maximum size of files that PHP will accept through uploads:
file_uploads=On
# user can only upload upto 1MB via php
upload_max_filesize=1M
If turned On, mysql_connect() and mysql_pconnect() ignore any arguments passed to them. Please note that
you may have to make some changes to your code. Third party and open source application such as
WordPress, and others may not work at all when sql.safe_mode enabled. I also recommend that you turn off
magic_quotes_gpc for all php 5.3.x installations as the filtering by it is ineffective and not very robust.
mysql_escape_string() and custom filtering functions serve a better purpose (hat tip to Eric Hansen):
magic_quotes_gpc=Off
The 1K sets max size of post data allowed by php apps. This setting also affects file upload. To upload large
files, this value must be larger than upload_max_filesize. I also suggest that you limit available methods
using Apache web server. Edit, httpd.conf and set the following directive for DocumentRoot /var/www/html:
<Directory /var/www/html>
<LimitExcept GET POST>
Order allow,deny
</LimitExcept>
## Add rest of the config goes here... ##
</Directory>
30
manipulate system files. You must execute PHP CGIs as a non-privileged user using Apaches suEXEC or
mod_suPHP. The suEXEC feature provides Apache users the ability to run CGI programs under user IDs
different from the user ID of the calling web server. In this example, my php-cgi is running as phpcgi user
and apache is running as apache user:
# ps aux | grep php-cgi
Sample outputs:
phpcgi
phpcgi
phpcgi
phpcgi
phpcgi
phpcgi
phpcgi
phpcgi
6012
6054
6055
6085
6103
6815
6821
6823
0.0
0.0
0.1
0.0
0.0
0.4
0.3
0.3
0.4
0.5
0.4
0.4
0.4
0.5
0.5
0.4
225036
229928
224944
224680
224564
228556
228008
225536
60140
62820
53260
56948
57956
61220
61252
58536
?
?
?
?
?
?
?
?
S
S
S
S
S
S
S
S
Nov22
Nov22
Nov22
Nov22
Nov22
00:52
00:55
00:57
0:12
0:11
0:18
0:11
0:11
0:19
0:12
0:13
/usr/bin/php-cgi
/usr/bin/php-cgi
/usr/bin/php-cgi
/usr/bin/php-cgi
/usr/bin/php-cgi
/usr/bin/php-cgi
/usr/bin/php-cgi
/usr/bin/php-cgi
You can use tool such as spawn-fcgi to spawn remote and local FastCGI processes as phpcgi user (first, add
phpcgi user to the system):
# spawn-fcgi -a 127.0.0.1 -p 9000 -u phpcgi -g phpcgi -f /usr/bin/php-cgi
Now, you can configure Apache, Lighttpd, and Nginx web server to use external php FastCGI running on
port 9000 at 127.0.0.1 IP address.
Make sure path is outside /var/www/html and not readable or writeable by any other system users:
# ls -Z /var/lib/php/
Sample outputs:
drwxrwx---. root apache system_u:object_r:httpd_var_run_t:s0 session
Note: The -Z option to the ls command display SELinux security context such as file mode, user, group,
security context and file name.
OR
You can configure Red hat / CentOS / Fedora Linux to send yum package update notification via email.
Another option is to apply all security updates via a cron job. Under Debian / Ubuntu Linux you can use
apticron to send security notifications.
Note: Check php.net for the most recent release for source code installations.
/var/www/html/ is a subdirectory and DocumentRoot which is modifiable by other users since root never
executes any files out of there, and shouldnt be creating files in there.
Make sure file permissions are set to 0444 (read-only) under /var/www/html/:
# chmod -R 0444 /var/www/html/
Make sure all directories permissions are set to 0445 under /var/www/html/:
# find /var/www/html/ -type d -print0 | xargs -0 -I {} chmod 0445 {}
You should only grant write access when required. Some web applications such as wordpress and others
may need a caching directory. You can grant a write access to caching directory using the following
commands:
# chmod a+w /var/www/html/blog/wp-content/cache
### block access to all ###
# echo 'deny from all' > /var/www/html/blog/wp-content/cache/.htaccess
# chattr +i /etc/my.ini
# chattr +i /etc/httpd/conf/httpd.conf
# chattr +i /etc/
The chattr command can write protect your php file or files in /var/www/html directory too:
# chattr +i /var/www/html/file1.php
# chattr +i /var/www/html/
Sample outputs:
allow_httpd_anon_write --> off
allow_httpd_mod_auth_ntlm_winbind --> off
allow_httpd_mod_auth_pam --> off
allow_httpd_sys_script_anon_write --> off
httpd_builtin_scripting --> on
httpd_can_check_spam --> off
httpd_can_network_connect --> off
httpd_can_network_connect_cobbler --> off
httpd_can_network_connect_db --> off
httpd_can_network_memcache --> off
httpd_can_network_relay --> off
httpd_can_sendmail --> off
httpd_dbus_avahi --> on
httpd_enable_cgi --> on
httpd_enable_ftp_server --> off
httpd_enable_homedirs --> off
httpd_execmem --> off
httpd_read_user_content --> off
httpd_setrlimit --> off
httpd_ssi_exec --> off
httpd_tmp_exec --> off
httpd_tty_comm --> on
httpd_unified --> on
httpd_use_cifs --> off
httpd_use_gpg --> off
httpd_use_nfs --> off
Here is another example that blocks all outgoing connections from apache user except to our own smtp
server, and spam validation API service:
# ....
/sbin/iptables --new-chain apache_user
/sbin/iptables --append OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
/sbin/iptables --append OUTPUT -m owner --uid-owner apache -j apache_user
# allow apache user to connec to our smtp server
/sbin/iptables --append apache_user -p tcp --syn -d 192.168.1.100 --dport 25 -j
# Allow apache user to connec to api server for spam validation
/sbin/iptables --append apache_user -p tcp --syn -d 66.135.58.62 --dport 80 -j
/sbin/iptables --append apache_user -p tcp --syn -d 66.135.58.61 --dport 80 -j
/sbin/iptables --append apache_user -p tcp --syn -d 72.233.69.89 --dport 80 -j
/sbin/iptables --append apache_user -p tcp --syn -d 72.233.69.88 --dport 80 -j
#########################
## Add more rules here ##
#########################
# No editing below
# Drop everything for apache outgoing connection
/sbin/iptables --append apache_user -j REJECT
RETURN
RETURN
RETURN
RETURN
RETURN
# tail -f /var/log/httpd/error_log
# grep 'login.php' /var/log/httpd/error_log
# egrep -i "denied|error|warn" /var/log/httpd/error_log
Log files will give you some understanding of what attacks is thrown against the server and allow you to
check if the necessary level of security is present or not. The auditd service is provided for system auditing.
Turn it on to audit SELinux events, authetication events, file modifications, account modification and so on.
I also recommend using standard Linux System Monitoring Tools for monitoring your web-server.
///////////////
/ ISP/Router /
//////////////
\
|
Firewall
\
|
+------------+
| LB01
|
+------------+
+--------------------------+
|
| static.lan.cyberciti.biz |
+-----------------+--------------------------+
| phpcgi1.lan.cyberciti.biz|
+--------------------------+
| phpcgi2.lan.cyberciti.biz|
+--------------------------+
| mysql1.lan.cyberciti.biz |
+--------------------------+
| mcache1.lan.cyberciti.biz|
+--------------------------+
(Fig.01: Running Services On Separate Servers)
Run different network services on separate servers or VM instances. This limits the number of other services
that can be compromised. For example, if an attacker able to successfully exploit a software such as Apache
flow, he / she will get an access to entire server including other services running on the same server (such as
MySQL, e-mail server and so on). But, in the above example content are served as follows:
1. static.lan.cyberciti.biz Use lighttpd or nginx server for static assets such as js/css/images.
2. phpcgi1.lan.cyberciti.biz and phpcgi2.lan.cyberciti.biz Apache web-server with php used for
generating dynamic content.
3. mysql1.lan.cyberciti.biz MySQL database server.
4. mcache1.lan.cyberciti.biz Memcached server is very fast caching system for MySQL. It uses
libevent or epoll (Linux runtime) to scale to any number of open connections and uses non-blocking
network I/O.
5. LB01 A nginx web and reverse proxy server in front of Apache Web servers. All connections
coming from the Internet addressed to one of the Web servers are routed through the nginx proxy
server, which may either deal with the request itself or pass the request wholly or partially to the
main web servers. LB01 provides simple load-balancing.
PhpSecInfo provides an equivalent to the phpinfo() function that reports security information about the PHP
environment, and offers suggestions for improvement. It is not a replacement for secure development
techniques, and does not do any kind of code or app auditing, but can be a useful tool in a multilayered
security approach.
You may come across php scripts or so called common backdoors such as c99, c99madshell, r57 and so on.
A backdoor php script is nothing but a hidden script for bypassing all authentication and access your server
on demand. It is installed by an attackers to access your server while attempting to remain undetected.
Typically a PHP (or any other CGI script) script by mistake allows inclusion of code exploiting
vulnerabilities in the web browser. An attacker can use such exploiting vulnerabilities to upload backdoor
shells which can give him or her a number of capabilities such as:
Download files
Upload files
Install rootkits
Conclusion
Your PHP based server is now properly harden and ready to show dynamic webpages. However,
vulnerabilities are caused mostly by not following best practice programming rules. You should be
consulted further resources for your web applications security needs especially php programming which is
beyond the scope of sys admin work.
References:
1. PHP security from the official php project.
2. PHP security guide from the PHP security consortium project.
3. Apache suseexec documentation from the Apache project.
4. Apache 2.2 security tips from the Apache project.
5. The Open Web Application Security Project Common types of application security attacks.
Recommended readings:
1. PHP Security Guide: This guide aims to familiarise you with some of the basic concepts of online
security and teach you how to write more secure PHP scripts. Its aimed squarely at beginners, but
I hope that it still has something to offer more advanced users.
2. Essential PHP Security (kindle edition): A book about web application security written specifically
for PHP developers. It covers 30 of the most common and dangerous exploits as well as simple and
effective safeguards that protect your PHP applications.
3. SQL Injection Attacks and Defense This book covers sql injection and web-related attacks. It
explains SQL injection. How to find, confirm, and automate SQL injection discovery. It has tips and
tricks for finding SQL injection within the code. You can create exploits using SQL injection and
design to avoid the dangers of these attacks.
Please add your favorite php security tool or tip in the comments.
Updated for accuracy!
Share this on:
TwitterFacebookGoogle+Download PDF version Found an error/typo on this page?
About the author: Vivek Gite is a seasoned sysadmin and a trainer for the Linux/Unix & shell scripting.
Follow him on Twitter. OR read more like this:
HowTo: Configure Apache Web Server To Use NFS Shared HTML+PHP5 Files
Apache2 mod_fastcgi: Connect to External PHP via UNIX Socket or TCP/IP Port
Linux: Creating a Network File System (NFS) Share For Apache / Lighttpd /
Apache Security Tip: Serve php / cgi file using different file type /
Reply Link
o Yunus November 26, 2011, 6:24 am
I use http://www.rfxn.com/projects/linux-malware-detect/ which is very useful for detecting
PHP backdoors
Reply Link
Ya. suPHP is also very useful and helpful for securing PHP websites.
Reply Link
In php.ini or security.ini:
sql.safe_mode=On
You app is not aware of the database settings, it consequently cannot disclose them through a bug or
a backdoor, unless code injection is possible. In fact, you can enforce that only an ini-based
authentication procedure is used by enabling SQL safe mode in PHP via the sql.safe_mode directive.
PHP then rejects any database connection attempts that use anything other than ini values for
specifying authentication data.
Source
Reply Link
With proper bind variables, SQL injection becomes far less of a problem.
Reply Link
As allways, I surely would trust outbound/indbound monotor or intrustion system of any kind no
matter what.
Reply Link
I have not seen a clear explanation of how to set it to avoid issues down the road.
(I am using FastCGI so have to set in each users php.ini file)
Reply Link
o Chris Cornutt February 17, 2014, 12:33 am
Personally, Id suggest two things when it comes to open_basedir:
1. Keep it inside the base of the application. This isnt the same thing as the document root as
you could have files outside of that that relate to the application. Id *never* allow access to a
users home directory though.
2. Keep it as limited as possible. Dont specify something like /var/www when
/var/www/site-name/public will work.
Reply Link
4) The permissions depend on who owns the directory, really. If you set the owner to the web
server user, then 600 should be fine (you might need 700, not sure).
Reply Link
o Cody March 13, 2014, 5:57 pm
And to elaborate on point 4 (not that I think Chris did any thing incorrect or wrong here I
just feel theres no such thing as too much information on the subject of permissions). Also,
apologies if I went overboard with some of this but I got carried away with explaining modes
and didnt realise how far I went until after the fact. Anyway:
As Chris wrote it depends on who owns the directory and more so what permission is needed.
But and heres the important bit (pardon the pun permission bits and all) use the most
restrictive permission as possible, always (okay, obviously you shouldnt mess around with
most of your directories, binaries, libraries, etc., and you should never blindly change
ownership of these without knowing what youre doing chmod and chown can be very
dangerous especially when recursively operating but for /var/www or wherever you have
your web directory you should be fine, especially if you dont recursively chmod [in general
the -R option is something you need to be careful of, for chmod and chown]). So if you can
get away with 400 then by all means do so (I doubt youd be OK with that since then youd
have to be root to write to the files, but the point remains the same: restrict where you can
as much as you can, as long as it is safe). On the subject of whether read is sufficient, theres
also the possibility that for example you have a user (regular) for editing files in the web
directory (as a virtual host, say) and the apache user is the group of the directory (for example
it has a CMS that might update a file) or alternatively, if you dont have a CMS, you have the
group also be the same as the owner and only allow read/execute for the rest of the world (so
that apache can open and serve files to viewers of the website(s)). Then, you can just grant
rwx to the user and apache (or just others) can get away with rx (so can read files and can
open directory). Others (world) should never include write on a website. If there were any
files that the site itself (lets say a CMS) needs to edit (say on update of the CMS) then you
can grant those files write access to the apache user (or however you have apache set up). In
short: youre restricting it as much as possible but not breaking anything either.
An important elaboration on permissions is it also goes for database permissions. Example:
wordpress documentation insists you need to GRANT ALL to the user on the database that
wordpress uses. However, that is complete and utter nonsense. I know because I use much
more restricted on my wordpress installs and there has never been a problem related to this
(mod_security2 and other things can cause issues but thats nothing to do with the database
itself and mod_security2 is actually well worth any hassle getting it to function correctly
[false positives, certain rules needing modifications for your site, whatever it is]).
Furthermore, and I seem to remember wordpress documentation is guilty here too, when
people suggest that to fix (or make sure it isnt a permissions issue) read/write permissions
you should (even if temporarily) set 777, dont do it. For your own sake and for your
systems sake (why on earth would anyone think a directory should be world
read/write/execute to make sure there is no permission issues, is to this day something that
bewilders and bemuses me). The only directories that should be 777 (which are actually 1777
restricted delete; 1777 on a file would be sticky bit with read/write/execute) are directories
like /var/tmp and /tmp
If you ever do need write and read permission, then 6 (or rw) should suffice. If you only need
read then 4 suffices. Likewise, if you only need read and execute, then 5 suffices. Also, as for
execute bit (1=x, 2=w, 4=r, and its bits so binary which is why 7 = rwx, 6 = rw, 5 = rx, etc.
Note: the modes itself, that is 0-7, is in fact not binary but octal [hence 0-7] but they add up
like binary [observe that 8 is divisible by 2 just like 16 is, which is why octal and
hexadecimal are often used in programming much more convenient than binary), it depends
on if the file is a directory or a regular file as to what it is used for. For regular files it allows
execution. For directories it allows: changing to the directory (youll need +r for viewing files
in it and +w for writing to the directory).
Any way, hopefully that is of value to _someone_ (and hopefully the fact I am still trying to
wake up did not allow me to make any stupid mistakes/errors but if I did I am sure someone
will correct me I hope so anyway)!
Reply Link