Wednesday, September 5, 2018

secure PHP web applications - prevent attacks

  1. Cross site scripting (XSS)
  2. Injections
    • SQL injection
    • Directory traversal (path injection)
    • Command injection
    • Code injection
  3. Cross site request forgery (XSRF/CSRF)
  4. Public files
  5. Passwords
  6. Uploading files
  7. Session hijacking
  8. Remote file inclusion
  9. PHP configuration
    • Error reporting
    • Exposing PHP Version
    • Remote files
    • open_basedir
    • Session settings
  10. Use HTTPS

Cross site scripting (XSS)

XSS attacks happen when client-side code (usually JavaScript) gets injected into the output of your PHP script. This can be through the URL, but can also occur via a stored technique such as the database.
// GET data is sent through URL: http://example.com/search.php?search=<script>alert('test')</script>
$search = $_GET['search'] ?? null;
echo 'Search results for '.$search;

// This can be solved with htmlspecialchars
$search = htmlspecialchars($search, ENT_QUOTES, 'UTF-8');
echo 'Search results for '.$search;
  • ENT_QUOTES is used to escape single and double quotes beside HTML entities
  • UTF-8 is used for pre PHP 5.4 environments (now it is default). In some browsers some characters might get pass the htmlspecialchars()

Injections

SQL injection

When accessing databases from your application, SQL injection attack can happen by injecting malicious SQL parts into your existing SQL statement.

Directory traversal (path injection)

Directory traversal attacks, also known as ../ (dot, dot, slash) attacks, happen when users supply filenames as input that can traverse to parent directories. Data can be set as index.php?page=../secret, or /var/www/secret, or something more catastrophic:
$page = $_GET['page'] ?? 'home';

require $page;
// or something like this
echo file_get_contents('../pages/'.$page.'.php');
In such cases you must check if there are attempts to access the parent or some remote folder:
// Checking if the string contains parent directory
if (strstr($_GET['page'], '../') !== false) {
    throw new \Exception("Directory traversal attempt!");
}

// Checking remote file inclusions
if (strstr($_GET['page'], 'file://') !== false) {
    throw new \Exception("Remote file inclusion attempt!");
}

// Using whitelists of pages that are allowed to be included in the first place
$allowed = ['home', 'blog', 'gallery', 'catalog'];
$page = (in_array($page, $allowed)) ? $page : 'home';
echo file_get_contents('../pages/'.$page.'.php');

Command injection

Be careful when dealing with commands executing functions and data you don’t trust.
exec('rm -rf '.$GET['path']);

Code injection

Code injection happens when malicious code can be injected via the eval() function, so remember to always sanitize your data when using it:
eval('include '.$_GET['path']);

Cross site request forgery (XSRF/CSRF)

Cross site request forgery, one click attacks, or session riding is an exploit whereby users execute unwanted actions on web applications.

Public files

Make sure to move all your application files, configuration files and similar parts of your web application to a folder that isn’t publicly accessible when you visit URLs of your web application. Some types of files (e.g., .yml files) might not be processed by your web server and users could view them online.
An example of good folder structure:
app/
  config/
    parameters.yml
  src/
public/
  index.php
  style.css
  javascript.js
  logo.png
Configure your web server to serve files from the public folder instead of from your application root folder. The public folder contains the front controller (index.php). In case of a web server misconfiguration resulting in PHP files failing to be served properly, the source code of index.php will be visible to the public.

Passwords

When working with users’ passwords, hash them properly with the password_hash() function.

Uploading files

Many security breaches occur when users can upload files onto a server. Make sure you go through all the vulnerabilities associated with uploading files and take appropriate precautions against these vulnerabilities, such as by renaming uploaded files, moving them to publicly inaccessible folders, checking the types of files uploaded and so on. 

Session hijacking

Session hijacking is an attack where an attacker steals the session ID of a user. The session ID is sent to the server where the associated $_SESSION array is populated. Session hijacking is possible through an XSS attack or when someone gains access to the folder on a server where the session data is stored.

Remote file inclusion

An RFI (remote file inclusion) attack is when an attacker can include custom scripts:
$page = $_GET['page'] ?? 'home'

require $page . '.php';
In the above code, $_GET can be set to a remote file http://yourdomain.tld/index.php?page=http://example.com/evilscript
Make sure you disable this in your php.ini unless you know what you’re doing:
; Disable including remote files
allow_url_fopen = off
; Disable opening remote files for include(), require() and include_once() functions.
; If above allow_url_fopen is disabled, allow_url_include is also disabled.
allow_url_include = off

PHP configuration

Always keep the installed PHP version updated. You can use versionscan to check for possible vulnerabilities of your PHP version. Update open source libraries and applications, and keep your web server well maintained.
Here are some of the important settings from php.ini that you should check out. You can also use iniscan to scan your php.ini files for best security practices.

Error reporting

In your production environment, you must always turn off displaying errors to the screen. If errors occur in your application and they are visible to the outside world, an attacker could get valuable data for attacking your application. display_errors and log_errors directives in the php.ini file:
; Disable displaying errors to screen
display_errors = off
; Enable writing errors to server logs
log_errors = on

Exposing PHP version

PHP version is visible in HTML headers. You might want to consider hiding your PHP version by turning off the expose_php directive, preventing the web server from sending back the X-Powered-By header:
expose_php = off

Remote files

In most cases, it’s important to disable access to remote files:
; disabled opening remote files for fopen, fsockopen, file_get_contents and similar functions
allow_url_fopen =  0
; disabled including remote files for require, include ans similar functions
allow_url_include = 0

open_basedir

This settings defines one or more directories (subdirectories included) where PHP has access to read and write files. This includes file handling (fopenfile_get_contents) and also including files (includerequire):
open_basedir = "/var/www/test/uploads"

Session settings

  • session.use_cookies and session.use_only_cookies
    PHP is by default configured to store session data on the server and a tracking cookie on client-side (usually called PHPSESSID) with unique ID for the session.
; in most cases you'll want to enable cookies for storing session
session.use_cookies = 1
; disabled changing session id through PHPSESSID parameter (e.g foo.php?PHPSESSID=<session id>)
session.use_only_cookies = 1
session.use_trans_sid = 0
; rejects any session ID from user that doesn't match current one and creates new one
session.use_strict_mode = 1
  • session.cookie_httponly
    If the attacker somehow manages to inject JavaScript code for stealing a user’s current cookies (the document.cookie string), the HttpOnly cookie you’ve set won’t show up in the list.
session.cookie_httponly = 1
  • session.cookie_domain
    This sets the domain for which cookies apply. For wildcard domains you can use .example.com, or set this to the domain where it should be applied. By default, it isn’t enabled, so it’s highly recommended for you to enable it:
session.cookie_domain = example.com
  • session.cookie_secure
    For HTTPS sites, this accepts only cookies sent over HTTPS. If you’re still not using HTTPS, you should consider it.
session.cookie_secure = 1

Use HTTPS

HTTPS is a protocol for securely communication over networks. It’s highly recommended that you enable it on all sites

Optimize MYSQL

Optimize SQL Queries:  

  1. Indexes 
  2. Symbol Operator 
  3. Wildcard 
  4. NOT Operator 
  5. COUNT VS EXIST 
  6. Wildcard VS Substr 
  7. Index Unique Column
  8. Max and Min Operators 
  9. Data Types 
  10. Primary Index
  11. String indexing 
  12. Limit The Result 
  13. In Subquery
  14. union vs OR

Indexes 


To create Index for your column is a common way to optimize your search result. 

Symbol Operator



Symbol operator such as >,<,=,!=, etc. are very helpful in our query. We can optimize some of our query with symbol operator provided the column is indexed.


example:
SELECT * FROM TABLE WHERE COLUMN > 16
SELECT * FROM TABLE WHERE COLUMN >= 15


Wildcard


In SQL, wildcard is provided for us with ‘%’ symbol. Using wildcard will definitely slow down.

We can optimize our query with wildcard by doing a postfix wildcard instead of pre (or) full wildcard. 


Note: That column must be indexed for such optimize to be applied

#Full wildcard  SELECT * FROM TABLE WHERE COLUMN LIKE '%hello%'; 


#Postfix wildcard
 SELECT * FROM TABLE WHERE COLUMN LIKE 'hello%';


#Prefix wildcard 
 SELECT * FROM TABLE WHERE COLUMN LIKE '%hello'; 


NOT Operator

To Avoid NOT operator in SQL. 


  • Positive Operator much faster than negative operator. 
  • Using a positive operator just stop immediately once the result has been found. But negative operator check the all the records



Positive operators: 
LIKE, IN, EXIST (or) = symbol operator

Negative operator:

NOT LIKE, NOT IN, NOT EXIST or != symbol. 


COUNT VS EXIST


"To use Exist operator instead of Count operator" to determine whether a particular data exist.


SELECT COLUMN FROM TABLE WHERE COUNT(COLUMN) > 0


  • The above query is very bad query since count will search for all record exist on the table to determine the numeric value of field ‘COLUMN’. 
  • The better alternative will be to use the EXIST operator where it will stop once it found the first record. Hence, it exist.

Wildcard VS Substr



"To use wildcard instead of substr". The column should be indexed for using wildcard.

SEARCH FOR ALL ROWS WITH THE FIRST CHARACTER AS 'E'

#BAD
 SELECT * FROM TABLE WHERE substr ( COLUMN, 1, 1 ) = 'E'.

#BETTER
 SELECT * FROM TABLE WHERE COLUMN = 'E%'.


Index Unique Column



  • MySQL search better with column that are unique and indexed. 
  • Hence, it is best to remember to index those columns that are unique.


Max and Min Operators



Using Max and Min operators should help speed up the searching. If you are going to use min and max, The column must be indexed

Data Types


  • Use the most efficient (smallest) data types possible. 
  • VARCHAR will be better than long text to store an email or small details.


Primary Index


The primary column that is used for indexing should be made as short as possible. This makes identification of each row easy and efficient by the DBMS.

String indexing


  • It is unnecessary to index the whole string when a prefix or postfix of the string can be indexed instead. 
  • Especially if the prefix or postfix of the string provides a unique identifier for the string, it is advisable to perform such indexing. 
  • Shorter indexes are faster, not only because they require less disk space, but because they also give you more hits in the index cache, and thus fewer disk seeks.


Limit The Result

Another common way of optimizing your query is to minimize the number of row return. 


SELECT * FROM TABLE

SELECT * FROM TABLE WHERE 1 LIMIT 10



In Subquery



Using dummy table is better than using an IN operator. Alternative, an exist
operator is also better.

SELECT * FROM TABLE WHERE COLUMN IN (SELECT COLUMN FROM TABLE)

Doing this is very expensive because SQL query will evaluate the outer query first before proceed with the inner query. Instead we can use this instead.

SELECT * FROM TABLE, (SELECT COLUMN FROM TABLE) as dummytable WHERE dummytable.COLUMN = TABLE.COLUMN;

Utilize Union instead of OR


Indexes lose their speed advantage when using them in OR ­situations in MySQL at least. Hence, this will not be useful although indexes is being applied

SELECT * FROM TABLE WHERE COLUMN_A = 'value' OR COLUMN_B = 'value'

Union: run faster

SELECT * FROM TABLE WHERE COLUMN_A = 'value'
UNION
SELECT * FROM TABLE WHERE COLUMN_B = 'value'



SQL Performance Tips

  1. use index in the column.
  2. use primary index.
  3. use index unique
  4. use symbol operators like, <,>,=
  5. use positive operator like IN, EXIST instead of NOT IN, NOT EXIST
  6. use full/pre wildcard operator
  7. use prefix/postfix string index.
  8. use LIMIT operator.
  9. use EXIST instead of COUNT
  10. use small data types VARCHAR
  11. Use Slow Query Log (always have it on!)
  12. Don't use DISTINCT when you have or could use GROUP BY
  13. Use LOAD DATA instead of INSERT
  14. Don't use ORDER BY RAND() if you have > ~2K records
  15. Use SQL_NO_CACHE when you are Selecting frequently updated data or large sets of data.
  16. use UNION instead of OR
  17. Avoid using IN(...) when selecting on indexed fields, It will kill the performance of SELECT query.
  18. Use stored procedures to avoid bandwidth wastage
  19. enable key_buffer, query cache, table cache.