Wednesday, October 16, 2013

more php security tips

Restrict PHP Information Leakage

To restrict PHP information leakage disable expose_php. Edit /etc/php.d/secutity.ini and set the following directive:
expose_php=Off
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;string-contains=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.

#4: Minimize Loadable PHP Modules (Dynamic Extensions)

PHP supports "Dynamic Extensions". By default, RHEL loads all the extension modules found in /etc/php.d/ directory. To enable or disable a particular module, just find the configuration file in /etc/php.d/ directory and comment the module name. You can also rename or delete module configuration file. For best PHP performance and security, you should only enable the extensions your webapps requires. For example, to disable gd extension, type the following commands:
# cd /etc/php.d/
# mv gd.{ini,disable}
# /sbin/service httpd restart

To enable php module called gd, enter:
# mv gd.{disable,ini}
# /sbin/service httpd restart

#5: Log All PHP Errors

Do not expose PHP error messages to all site visitors. Edit /etc/php.d/security.ini and set the following directive:
display_errors=Off
Make sure you log all php errors to a log file:
log_errors=On
error_log=/var/log/httpd/php_scripts_error.log

#6: Disallow Uploading Files

Edit /etc/php.d/security.ini and set the following directive to disable file uploads for security reasons:
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
 

#7: Turn Off Remote Code Execution

If enabled, allow_url_fopen allows PHP's file functions -- such as file_get_contents() and the include and require statements -- can retrieve data from remote locations, like an FTP or web site.
The allow_url_fopen option allows PHP's file functions - such as file_get_contents() and the include and require statements - can retrieve data from remote locations using ftp or http protocols. Programmers frequently forget this and don't do proper input filtering when passing user-provided data to these functions, opening them up to code injection vulnerabilities. A large number of code injection vulnerabilities reported in PHP-based web applications are caused by the combination of enabling allow_url_fopen and bad input filtering. Edit /etc/php.d/security.ini and set the following directive:
allow_url_fopen=Off
I also recommend to disable allow_url_include for security reasons:
allow_url_include=Off

#8: Enable SQL Safe Mode

Edit /etc/php.d/security.ini and set the following directive:
sql.safe_mode=On
 
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

#9: Control POST Size

The HTTP POST request method is used when the client (browser or user) needs to send data to the Apache web server as part of the request, such as when uploading a file or submitting a completed form. Attackers may attempt to send oversized POST requests to eat your system resources. You can limit the maximum size POST request that PHP will process. Edit /etc/php.d/security.ini and set the following directive:
; Set a realistic value here 
post_max_size=1K
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>
 

#10: Resource Control (DoS Control)

You can set maximum execution time of each php script, in seconds. Another recommend option is to set maximum amount of time each script may spend parsing request data, and maximum amount of memory a script may consume. Edit /etc/php.d/security.ini and set the following directives:
# set in seconds
max_execution_time =  30
max_input_time = 30
memory_limit = 40M
 

#11: Install Suhosin Advanced Protection System for PHP

From the project page:
Suhosin is an advanced protection system for PHP installations. It was designed to protect servers and users from known and unknown flaws in PHP applications and the PHP core. Suhosin comes in two independent parts, that can be used separately or in combination. The first part is a small patch against the PHP core, that implements a few low-level protections against bufferoverflows or format string vulnerabilities and the second part is a powerful PHP extension that implements all the other protections.
See how to install and configure suhosin under Linux operating systems.

#12 Disabling Dangerous PHP Functions

PHP has a lot of functions which can be used to crack your server if not used properly. You can set list of functions in /etc/php.d/security.ini using disable_functions directive:
 
disable_functions =exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec
,parse_ini_file,show_source
 

#13 PHP Fastcgi / CGI - cgi.force_redirect Directive

PHP work with FastCGI. Fascgi reduces the memory footprint of your web server, but still gives you the speed and power of the entire PHP language. You can configure Apache2+PHP+FastCGI or cgi as described here. The configuration directive cgi.force_redirect prevents anyone from calling PHP directly with a URL like http://www.cyberciti.biz/cgi-bin/php/hackerdir/backdoor.php. Turn on cgi.force_redirect for security reasons. Edit /etc/php.d/security.ini and set the following directive:
; Enable cgi.force_redirect for security reasons in a typical *Apache+PHP-CGI/FastCGI* setup
cgi.force_redirect=On
 

#14 PHP User and Group ID

mod_fastcgi is a cgi-module for Apache web server. It can connect to an external FASTCGI server. You need to make sure php run as non-root user. If PHP executes as a root or UID under 100, it may access and/or manipulate system files. You must execute PHP CGIs as a non-privileged user using Apache's 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      6012  0.0  0.4 225036 60140 ?        S    Nov22   0:12 /usr/bin/php-cgi
phpcgi      6054  0.0  0.5 229928 62820 ?        S    Nov22   0:11 /usr/bin/php-cgi
phpcgi      6055  0.1  0.4 224944 53260 ?        S    Nov22   0:18 /usr/bin/php-cgi
phpcgi      6085  0.0  0.4 224680 56948 ?        S    Nov22   0:11 /usr/bin/php-cgi
phpcgi      6103  0.0  0.4 224564 57956 ?        S    Nov22   0:11 /usr/bin/php-cgi
phpcgi      6815  0.4  0.5 228556 61220 ?        S    00:52   0:19 /usr/bin/php-cgi
phpcgi      6821  0.3  0.5 228008 61252 ?        S    00:55   0:12 /usr/bin/php-cgi
phpcgi      6823  0.3  0.4 225536 58536 ?        S    00:57   0:13 /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.

#15 Limit PHP Access To File System

The open_basedir directive set the directories from which PHP is allowed to access files using functions like fopen(), and others. If a file is outside of the paths defined by open_basdir, PHP will refuse to open it. You cannot use a symbolic link as a workaround. For example only allow access to /var/www/html directory and not to /var/www, or /tmp or /etc directories:
; Limits the PHP process from accessing files outside 
; of specifically designated directories such as /var/www/html/
open_basedir="/var/www/html/"
; ------------------------------------
; Multiple dirs example 
; open_basedir="/home/httpd/vhost/cyberciti.biz/html/:/home/httpd/vhost/nixcraft.com/html/:
/home/httpd/vhost/theos.in/html/"
; ------------------------------------
 

#16 Session Path

Session support in PHP consists of a way to preserve certain data across subsequent accesses. This enables you to build more customized applications and increase the appeal of your web site. This path is defined in /etc/php.ini file and all data related to a particular session will be stored in a file in the directory specified by the session.save_path option. The default is as follows under RHEL/CentOS/Fedora Linux:
session.save_path="/var/lib/php/session"
; Set the temporary directory used for storing files when doing file upload
upload_tmp_dir="/var/lib/php/session"
 
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.

#17 Keep PHP, Software, And OS Up to Date

Applying security patches is an important part of maintaining Linux, Apache, PHP, and MySQL server. All php security update should be reviewed and applied as soon as possible using any one of the following tool (if you're installing PHP via a package manager):
# yum update
OR
# apt-get update && apt-get upgrade
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.

#18: Restrict File and Directory Access

Make sure you run Apache as a non-root user such as Apache or www. All files and directory should be owned by non-root user (or apache user) under /var/www/html:
# chown -R apache:apache /var/www/html/
/var/www/html/ is a subdirectory and DocumentRoot which is modifiable by other users since root never executes any files out of there, and shouldn't 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 {}

A Note About Setting Up Correct File Permissions

The chown and chmod command make sures that under no circumstances DocumentRoot or files contained in DocumentRoot are writable by the Web server user apache. Please note that you need to set permissions that makes the most sense for the development model of your website, so feel free to adjust the chown and chmod command as per your requirements. In this example, the Apache server run as apache user. This is configured with the User and Group directives in your httpd.conf file. The apache user needs to have read access to everything under DocumentRoot but should not have write access to anything.
Make sure httpd.conf has the following directives for restrictive configuration:
 
<Directory / >
    Options None
    AllowOverride None
    Order allow,deny
</Directory>
 
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

#19: Write Protect Apache, PHP, and, MySQL Configuration Files

Use the chattr command to write protect configuration files:
# chattr +i /etc/php.ini
# chattr +i /etc/php.d/*
# 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/

#20: Use Linux Security Extensions (such as SELinux)

Linux comes with various security patches which can be used to guard against misconfigured or compromised server programs. If possible use SELinux and other Linux security extensions to enforce limitations on network and other programs. For example, SELinux provides a variety of security policies for Linux kernel and Apache web server. To list all Apache SELinux protection variables, enter:
# getsebool -a | grep httpd
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
To disable Apache cgi support, enter:
# setsebool -P httpd_enable_cgi off
See Red Hat SELinux guide for more information.

#21 Install Mod_security

ModSecurity is an open source intrusion detection and prevention engine for web applications. You can easily install mod_security under Linux and protect apache and php based apps from xss and various other attacks:
 
## A few Examples ##
# Do not allow to open files in /etc/
SecFilter /etc/
 
# Stop SQL injection
SecFilter "delete[[:space:]]+from"
SecFilter "select.+from"
 

#22 Run Apache / PHP In a Chroot Jail If Possible

Putting PHP and/or Apache in a chroot jail minimizes the damage done by a potential break-in by isolating the web server to a small section of the filesystem. You can use traditional chroot kind of setup with Apache. However, I recommend FreeBSD jails, XEN virtulization, KVM virtulization, or OpenVZ virtualization which uses the concept of containers.

#23 Use Firewall To Restrict Outgoing Connections

The attacker will download file locally on your web-server using tools such as wget. Use iptables to block outgoing connections from apache user. The ipt_owner module attempts to match various characteristics of the packet creator, for locally generated packets. It is only valid in the OUTPUT chain. In this example, allow vivek user to connect outside using port 80 (useful for RHN or centos repo access):
 
/sbin/iptables -A OUTPUT -o eth0 -m owner --uid-owner vivek -p tcp --dport 80 -m state --state
 NEW,ESTABLISHED  -j ACCEPT
 
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 RETURN
# 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 RETURN
/sbin/iptables --append apache_user -p tcp --syn -d  66.135.58.61 --dport 80 -j RETURN
/sbin/iptables --append apache_user -p tcp --syn -d  72.233.69.89 --dport 80 -j RETURN
/sbin/iptables --append apache_user -p tcp --syn -d  72.233.69.88 --dport 80 -j RETURN
#########################
## Add more rules here ##
#########################
# No editing below
# Drop everything for apache outgoing connection
/sbin/iptables --append apache_user -j REJECT
 

#24 Watch Your Logs & Auditing

Check the apache log file:
# 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

Check the php log file:
# tail -f /var/log/httpd/php_scripts_error.log
# grep "...etc/passwd" /var/log/httpd/php_scripts_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.

#25 Run Service Per System or VM Instance

For large installations it is recommended that you run, database, static, and dynamic content from different servers.
///////////////
/ 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.

#26 Additional Tools

From the project page:
PHPIDS (PHP-Intrusion Detection System) is a simple to use, well structured, fast and state-of-the-art security layer for your PHP based web application. The IDS neither strips, sanitizes nor filters any malicious input, it simply recognizes when an attacker tries to break your site and reacts in exactly the way you want it to.
You can use PHPIDS to detect malicious users, and log any attacks detected for later review. Please note that I've personally not used this tool.
From the project page:
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.
Fig.02: Security Information About PHP Application
See Linux security hardening tips which can reduce available vectors of attack on the system.

A Note About PHP Backdoors

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
  • Set a spam mail servers / relay server
  • Set a proxy server to hide tracks
  • Take control of server
  • Take control of database server
  • Steal all information
  • Delete all information and database
  • Open TCP / UDP ports and much more

Tip: How Do I Search PHP Backdoors?

Use Unix / Linux grep command to search c99 or r57 shell:
# grep -iR 'c99' /var/www/html/
# grep -iR 'r57' /var/www/html/
# find /var/www/html/ -name \*.php -type f -print0 | xargs -0 grep c99
# grep -RPn "(passthru|shell_exec|system|base64_decode|fopen|fclose|eval)" /var/www/html/

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. It's 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.

how to php best security practices tips set

The Apache web server provides access to files and content via the HTTP OR HTTPS protocol. A misconfigured server-side scripting language can create all sorts of problems. So, PHP should be used with caution. Here are  php security best practices for system admins for configuring PHP securely.

Tip 1: Use Proper Error Reporting

During the development process, application error reporting is your
best friend. Error reports can help you find spelling mistakes in your
variables, detect incorrect function usage and much more. However, once
the site goes live the same reporting that was an ally during
development can turn traitor and tell your users much more about your
site than you may want them to know (the software you run, your folder
structure, etc).
Once your site goes live, you should make sure to hide all error
reporting. This can be done by invoking the following simple function
at the top of your application file(s).
  1. error_reporting(0);  
Get rid of those public errors!
If something does go wrong, you still want and need to know about
it. Therefore, you should always make sure to log your errors to a
protected file. This can be done with the PHP function set_error_handler.
Sample Error Log

Tip 2: Disable PHP’s “Bad Features”

From its earliest days, PHP’s designers have always included some
features to make development easier. Or so they thought! Some of these
helpful features can have unintended consequences. I call these “bad
features” because they have allowed data validation nightmares and
created a pathway for bugs to finding their way into scripts. One of
the first things you should do when the development process begins is
disable certain of these features.
Note: Depending on your host, these may or may not be turned off for
you. If you are developing on your own computer or other similar local
environment, they probably won’t be turned off. Some of these features
have also been removed in the upcoming PHP6, but are ubiquitous in PHP4
applications and are only deprecated in PHP5 applications.
Register Globals (register_globals)
In short, register_globals was meant to help rapid application
development. Take for example this URL,
http://yoursite.tld/index.php?var=1, which includes a query string. The
register_globals statement allows us to access the value with $var
instead of $_GET['var'] automatically. This might sound useful to you,
but unfortunately all variables in the code now have this property, and
we can now easily get into PHP applications that do not protect against
this unintended consequence. The following code snippet is just one
common example you will see in PHP scripts:
 
if( !empty$_POST['username'] ) && $_POST['username'] == 'test' && !empty$_POST['password' && $_POST['password'] == "test123" ) 
 {    
  $access = true;  
 }  
 
If the application is running with register_globals ON, a user could
just place access=1 into a query string, and would then have access to
whatever the script is running.
Unfortunately, we cannot disable register_globals from the script
side (using ini_set, like we normally might), but we can use an
.htaccess files to do this. Some hosts also allow you to have a php.ini
file on the server.
Disabling with .htaccess
php_flag register_globals 0
Disabling with php.ini
register_globals = Off
Note: If you use a custom php.ini file that is not applicable to the
entire server, you must include these declarations in every sub folder
that has PHP.
Flow of register global
Magic Quotes (magic_quotes_gpc, magic_quotes_runtime, magic_quotes_sybase)
Magic Quotes was a feature meant to save programmers the trouble of
using addslashes() and other similar security features in their code.
There are at least three problems associated with magic quotes. One
problem with this helpful feature is if both magic quotes and
addslashes() are used. If this is the case, then you end up with
multiple slashes being added, causing errors. The second problem is if
you make the assumption magic quotes is turned on and it actually is
not. Then all the input goes unchecked. The third problem is that magic
quotes only escapes single and double quotes, but if you are using a
database engine, there are also many database-specific characters that
also need to be escaped. It is recommended use that you disable this
feature and use proper variable validation instead (see below).
Unfortunately, we also cannot disable magic quotes from the script
side using ini_set. As with register_globals, we can use .htaccess or
php.ini files to do this.
Disabling with .htaccess
php_flag magic_quotes_gpc 0 php_flag magic_quotes_runtime 0
Disabling with php.ini
magic_quotes_gpc = Off
magic_quotes_runtime = Off
magic_quotes_sybase = Off
Note: If you use a custom php.ini file that is not applicable to the
entire server, you must include these declarations in every sub folder
that has PHP.
Example htaccess file

Tip 3: Validate Input

In addition to escaping characters, another great to way to protect
input is to validate it. With many applications, you actually already
know what kind of data you are expecting on input. So the simplest way
to protect yourself against attacks is to make sure your users can only
enter the appropriate data.
For example, say we are creating an application that lists users
birthdays and allows users to add their own. We will be wanting to
accept a month as a digit between 1-12, a day between 1-31 and a year
in the format of YYYY.
Having this kind of logic in your application is simple and regular
expressions (regex) are the perfect way to handle input validation.
Take the following example:
  1. if ( ! preg_match( "/^[0-9]{1,2}$/"$_GET['month'] ) )  
  2. {  
  3.     // handle error  
  4. }  
  5. if ( ! preg_match( "/^[0-9]{1,2}$/"$_GET['day'] ) )  
  6. {  
  7.     // handle error  
  8. }  
  9. if ( ! preg_match( "/^[0-9]{4}$/"$_GET['year'] ) )  
  10. {  
  11.     // handle error  
  12. }  
In this example, we simply checked (in the first two if statements)
for integers [0-9] with a length of one or two {1,2} and we did the
same in the third if statement, but checked for a strict length of 4
characters {4}.
In all instances, if the data doesn’t match the format we want, we
return some kind of error. This type of validation leaves very little
room for any type of SQL attack.
Regex expressions like those above can be a little difficult to
grasp at first, but explaining them is out of the scope of this
article. The php manual has some additional resources to help you with validation. The PEAR database also has a few packages such as the Validate package to help with emails, dates, and URLS.
Below is an example of the above script in action using 200 as an input for a month, abc for the day and just 09 for the year.
Example of a validation script running

Tip 4: Watch for Cross Site Scripting (XSS) Attacks in User Input

A web application usually accepts input from users and displays it
in some way. This can, of course, be in a wide variety of forms
including comments, threads or blog posts that are in the form of HTML
code. When accepting input, allowing HTML can be a dangerous thing,
because that allows for JavaScript to be executed in unintended ways.
If even one hole is left open, JavasScript can be executed and cookies
could be hijacked. This cookie data could then be used to fake a real
account and give an illegal user access to the website’s data.
There are a few ways you can protect yourself from such attacks. One
way is to disallow HTML altogether, because then there is no possible
way to allow any JavaScript to execute. However, if you do this then
formatting is also disallowed, which is not always an option for forum
and blog software.
If you want HTML mostly disabled, but still want to allow simple
formatting, you can allow just a few selected HTML tags (without
attributes) such as <strong> or <em>. Or, alternatively,
you can allow a popular set of tags called “BBCode” or “BB Tags,”
commonly seen on forums in the format of [b]test[/b]. This can be a
perfect way to allow some formatting customization while disallowing
anything dangerous. You can implement BBCode using pre-existing
packages such as HTML_BBCodeParser or write your own BBCode implementation with regular expressions and a series of preg_replace statements.
Example of BBCode in action

Tip 5: Protecting against SQL Injection

Last, but not least, is one of the most well-known security attacks
on the web: SQL injection. SQL injection attacks occur when data goes
unchecked, and the application doesn’t escape characters used in SQL
strings such as single quotes (‘) or double quotes (“).
If these characters are not filtered out users can exploit the system by making queries always true and thus allowing them to trick login systems.
Pesky login box being hacked
Luckily, PHP does offer a few tools to help protect your database
input. When you are connected to an sql server you can use these
functions with a simple call, and your variables should be safe to use
in queries. Most of the major database systems offered with PHP include
these protection functions.
MySQLi allows you to do this in one of two ways. Either with the mysqli_real_escape_string function when connected to a server:
  1. $username = mysqli_real_escape_string( $GET['username'] );  
  2. mysql_query( "SELECT * FROM tbl_members WHERE username = '".$username."'");  
Or with prepared statements.
Prepared statements are a method of separating SQL logic from the data being passed to it. The functions used within the MySQLi library filter our input for us when we bind variables to the prepared statement. This can be used like so (when connected to a server):
  1. $id = $_GET['id'];  
  2. $statement = $connection->prepare( "SELECT * FROM tbl_members WHERE id = ?" );  
  3. $statement->bind_param( "i"$id );  
  4. $statement->execute();  
One thing to note when using prepared statements is the “i” in bind_param. i stands for for integer but you can use s for string, d for double, and b for blob depending on what data we are passing.
Although this will protect you in most circumstances, you should
still keep in mind proper data validation as mentioned previously.

 

Javascript: don't echo/print user input in javascript

A very common exploit like XSS is usably the fault of javascript code beeing run on the domain. Javascript can steal and forward user cookies, or make any (unintended) user actions like "delete account". For example this javascript php combination:

<script>
<!--
document.getElementByID('welcome').innerHTML = '<? echo $_GET['username'] ?>';
-->
</script>

PHP: uploading files

Never let people upload stuff to your server, unless you know what you are doing. Best way is to store the uploaded file in a folder unavailable via a url (e.g. next to your public_html folder) and have a file act as a proxy that grabs the uploaded file and forces a innocent extension like .jpg (only when it's an jpeg of course).

header("Content-type:image/jpeg");
echo file_get_contents("/uploads/id/randomfilename.jpg");

If you still want to host the uploaded file on your server available through a direct url make sure it doesn't make flash cross-domain available or other content like text, html, or executable files that could compromise the whole server. Always wonder if you are ready to host anything that isn't yours?

PHP: Clean up all the user input

One of the most common exploits are the result of unintended user input. User input by URL, forms and cookies has to get cleaned up from any exploitable input before doing anything with it. Most importantly you want html characters (like <,>) to be encoded to their harmless html representative and ', " escaped by a slash to exclude external code to be forced into your site. This script code below runs through the array $_GET, $_POST and $_COOKIE, and cleans up the values passed from the user. Please force integers on user input e.g. ID's by stripping out any other character. I'ts best to always also have Mod security installed.
<?php
function cleanArray($array){
    if(is_array($array)){
        foreach($array as $key=>$value){

            $value = eregi_replace("script","scrip t",$value); //no easy javascript injection
            $value = eregi_replace("union","uni on",$value); //no easy common mysql temper

            $value = htmlentities($value, ENT_QUOTES); //encodes the string nicely
            $value = addslashes($value); //mysql_real_escape_string() //htmlentities

            if($key == "UserID" || $key == "PageID"){ //List variables that MUST be integers. Look at your mysql scheme and find every int(*) field.
                $value = filter_var($value, FILTER_SANITIZE_NUMBER_INT); //Forces an integer
            }elseif($key == "CountryCode" || $key == "StateCode"){
                $value = substr(trim($value),0,2); //Forces a max two character string
            }elseif($key == "arrivalDate" || $key == "departureDate"){
                $value = substr(trim($value),0,10); //Forces a max 10 character string. Could be also be tested by regular expression for a date value.
            }else{
                $value = substr($value,0,100);
                $value = trim(filter_var($value, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW)); //All weird chars will be stripped. I usually also limit the characters to (alpha)nummeric, spaces, and punctuation.
            }

            $array[$key] = $value;
    }else{
        return false;
    }

    return $array;
}
cleanArray($_GET);
cleanArray($_POST);
?>
These filters (filter_var) only work in PHP5, but with a good regular expression it can be also run in other versions. It's also good to truncate a string to a maximum number of characters or else you could exposed to this. In this script the string allowed is limited to 100 characters this could break alot of systems (like long user comments) so be carefull with that


 

Closing

This short tutorial can only scratch the surface of web security.
Ultimately, it is up to developers to ensure that the applications they
build are safe by educating themselves about the dangers of the web and
the most common kinds of vulnerabilities and attacks. If you wish to
read more about security issues in PHP, there is a section on security in the php manual devoted to them.