Coder Social home page Coder Social logo

henryvoorburg / php-security-cheatsheet Goto Github PK

View Code? Open in Web Editor NEW

This project forked from tolgadevsec/php-security-cheatsheet

0.0 0.0 0.0 255 KB

This cheatsheet is an overview of some techniques, Proof-of-Concept (PoC) implementations and recommendations of countermeasures against vulnerabilities that can occur within a PHP web application

php-security-cheatsheet's Introduction

PHP Security Cheatsheet

This cheatsheet is an overview of some techniques, Proof-of-Concept (PoC) implementations and recommendations of countermeasures against vulnerabilities that can occur within a PHP web application.

Articles, Tutorials, Guides and Cheatsheets

In case you are keen on learning more about PHP security, you can check out the following resources:

Table of Vulnerabilities

Cross-Site Request Forgery

Anti-CSRF Tokens

You can use the random_bytes function to generate a cryptographically secure pseudo-random token. The following example describes a basic proof of concept in which a Anti-CSRF token is delivered to the client in a custom HTTP response header (X-CSRF-Token). The bin2hex function will be used in order to prevent issues with the character representation of non-character bytes returned by random_bytes.

session_start();

$tokenLength = 64;

$_SESSION["CSRF_TOKEN"] = bin2hex(random_bytes($tokenLength));

header("X-CSRF-Token: " . $_SESSION["CSRF_TOKEN"]);

// ...

Instead of simply comparing two values and their data types with ===, the hash_equals function is used to prevent timing attacks against string comparisons. Have a look at this article on timing attacks for further details.

$serverToken = $_SESSION["CSRF_TOKEN"];
$requestHeaders = apache_request_headers();

if($requestHeaders !== false &&
   array_key_exists("X-CSRF-Token", $requestHeaders)){
   
   $clientToken = $requestHeaders["X-CSRF-Token"];
   
   if(hash_equals($serverToken, $clientToken)){
      // Move on with request processing
   }
}

Enforce CORS Preflight with Custom Headers

If a HTTP request contains a custom header, the Browser will send a CORS preflight request before it continues to send the original request. If no CORS policy has been set on the server, requests coming from another origin will fail.

You can enforce this situation by checking for the existence of a custom HTTP request header in the list of headers returned by apache_request_headers.

$requestHeaders = apache_request_headers();
if($requestHeaders !== false && 
   array_key_exists("X-Custom", $requestHeaders)){
   // Move on with request processing   
}

This technique should not be the main line of defense against CSRF attacks as there have been vulnerabilities in the past that enabled the sending of cross-site requests containing arbitrary HTTP request headers (CVE-2017-0140). There is no guarantee that this cannot happen again in the future.

SameSite Cookie Attribute

The support of the SameSite cookie attribute was introduced in PHP 7.3.

bool setcookie ( string $name [, string $value = "" [, int $expire = 0 [, string $path = "" [, 
string $domain = "" [, bool $secure = false [, bool $httponly = false [, string $samesite = "" 
]]]]]]] )

However, since this cookie attribute is relatively new, some older browser versions do not support or only partially support this cookie attribute.

Be also aware that the SameSite cookie attribute won't prevent request forgery attacks that occur on-site (OSRF).

Cross-Site Scripting

Server-side countermeasures will not be enough to prevent XSS attacks as certain types of XSS, such as DOM-based XSS, are the results of flaws in the client-side code. In case of DOM-based XSS, I recommend to use DOMPurify and to take a look at the DOM-based XSS Prevention Cheatsheet. Furthermore, you should also follow the development of Trusted Types for DOM Manipulation - See Trusted Types help prevent Cross-Site Scripting for a recent article on that topic.

Automatic Context-Aware Escaping

Automatic context-aware escaping should be your main line of defense against XSS attacks. Personally, I recommend using the Latte template engine as it covers various contexts such as HTML element, HTML attribute and the href attribute of an anchor element.

Manual Context-Aware Escaping

Context: Inside a HTML element and HTML element attribute

htmlentities encodes all characters which have a reference in a specified HTML entity set.

$escapedString = htmlentities("<script>alert('xss');</script>", ENT_QUOTES | ENT_HTML5, "UTF-8", true);

The ENT_QUOTES flag makes sure that both single and double quotes will be encoded since the default flag does not encode single quotes. The ENT_HTML5 flag encodes characters to their referenced entities in the HTML5 entity set. Using the HTML5 entity set has the advantage that most of the special characters will be encoded as well in comparsion to the entity set defined by the default flag (ENT_HTML401).

Special Characters:

+-#~_.,;:@€<§%&/()=?*'"°^[]{}\`´=<,|²³

Encoded with ENT_HTML401 Flag:

+-#~_.,;:@&euro;&lt;&sect;%&amp;/()=?*&#039;&quot;&deg;^[]{}\`&acute;=&lt;,|&sup2;&sup3;

Encoded with ENT_HTML5 Flag:

&plus;-&num;~&lowbar;&period;&comma;&semi;&colon;&commat;&euro;&lt;&sect;&percnt;&amp;&sol;
&lpar;&rpar;&equals;&quest;&ast;&apos;&quot;&deg;&Hat;&lbrack;&rsqb;&lbrace;&rcub;&bsol;&grave;
&DiacriticalAcute;&equals;&lt;&comma;&vert;&sup2;&sup3;

The default flag won't protect you sufficiently if you forget to enclose your HTML attributes in single quotes or double quotes. For example, the htmlentities function won't encode the characters of the following XSS payload:

1 onmouseover=alert(1)

This payload can be used in a situation like the following:

<div data-custom-attribute-value=1 onmouseover=alert(1)></div>

However, with the ENT_HTML5 flag, the payload would not be usable in the previously described situation:

<div data-custom-attribute-value=1 onmouseover&equals;alert&lpar;1&rpar;></div>

Regardless of the flag you set, always enclose HTML attributes in single quotes or double quotes.

With the third parameter of the htmlentities function, the target character set is specified. The value of this parameter should be equal to the character set defined in the target HTML document (e.g. UTF-8).

Finally, the fourth parameter prevents double escaping if set to true.

Context: User-provided URLs

User-provided URLs should not beginn with the JavaScript (javascript:) or a data (data:) URI scheme. This can be prevented by accepting only URLs that beginn with the HTTPS (https) protocol.

if(substr($url, 0, strlen("https")) === "https"){
   // Accept and process URL
}
Context: Inside a script element or inline event handler

PHP does not provide a native function to escape user input in a JavaScript context. The following code snippet is from the Escaper Component of the Zend Framework which implements JavaScript context escaping.

 /**
  * Escape a string for the Javascript context. This does not use json_encode(). An extended
  * set of characters are escaped beyond ECMAScript's rules for Javascript literal string
  * escaping in order to prevent misinterpretation of Javascript as HTML leading to the
  * injection of special characters and entities. The escaping used should be tolerant
  * of cases where HTML escaping was not applied on top of Javascript escaping correctly.
  * Backslash escaping is not used as it still leaves the escaped character as-is and so
  * is not useful in a HTML context.
  *
  * @param string $string
  * @return string
  */
 public function escapeJs($string)
 {
     $string = $this->toUtf8($string);
     if ($string === '' || ctype_digit($string)) {
         return $string;
     }
     $result = preg_replace_callback('/[^a-z0-9,\._]/iSu', $this->jsMatcher, $string);
     return $this->fromUtf8($result);
 }
 /**
  * Callback function for preg_replace_callback that applies Javascript
  * escaping to all matches.
  *
  * @param array $matches
  * @return string
  */
 protected function jsMatcher($matches)
 {
     $chr = $matches[0];
     if (strlen($chr) == 1) {
         return sprintf('\\x%02X', ord($chr));
     }
     $chr = $this->convertEncoding($chr, 'UTF-16BE', 'UTF-8');
     $hex = strtoupper(bin2hex($chr));
     if (strlen($hex) <= 4) {
         return sprintf('\\u%04s', $hex);
     }
     $highSurrogate = substr($hex, 0, 4);
     $lowSurrogate = substr($hex, 4, 4);
     return sprintf('\\u%04s\\u%04s', $highSurrogate, $lowSurrogate);
 }

The jsMatcher function escapes each character of the target string that matches the regular expression used in the escapeJs function ([^a-z0-9,\._]/iSu). The current character will be encoded in hexadecimal if it is not greater than one byte. Note that strlen returns the number of bytes and not the number of characters (this is a documented behavior).

Otherwise, the current character will be encoded in Unicode. Some characters can only be encoded in UTF-16, using two 16-bit code units (referred as $highSurrogate and $lowSurrogate at the end of the jsMatcher function). This article on JavaScript's internal character encoding will help you understand the details why certain characters need to be encoded in UTF-16.

HTTPOnly Cookie Attribute

The HTTPOnly cookie attribute signals the Browser to prevent any client-side scripts from accessing data stored in a cookie. The intention behind this cookie attribute is to protect session identifiers within cookies from XSS attacks with a session hijacking payload. Please note that this cookie attribute does not prevent XSS attacks in general.

bool setcookie ( string $name [, string $value = "" [, int $expire = 0 [, string $path = "" [, 
string $domain = "" [, bool $secure = false [, bool $httponly = false [, string $samesite = "" 
]]]]]]] )

You can also set the HTTPOnly cookie attribute in your PHP configuration file using the session.cookie_httponly parameter.

session.cookie_httponly = true

Content Security Policy

Another effective defense against XSS attacks is to utilize a so called Content Security Policy (CSP). Essentially, a CSP is a whitelist of trusted sources from which a web application (the frontend part) is allowed to download and render/execute content. A CSP could therefore prevent the exfiltration of data (e.g. session ID) to a source that is not whitelisted.

In cases where an attacker cannot exfiltrate data but execute code, a CSP can still be beneficial as it provides mechanisms to prevent the execution of inline JavaScript code (unless unsafe-inline is explicitly specified as a trusted source). This, however, presumes an application architecture in which aspects such as the application's behavior and its appearance are separated (e.g. all JavaScript code are contained in .js files, all style instructions are cointained in .css files). If that should not be the case, you might be able to use nonces to whitelist inlined resources.

A CSP is delivered to a Browser as a HTTP response header as shown below:

// Starter Policy from https://content-security-policy.com/
header("Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';");

While the CSP in the example above is short and simple, it is not unusual to have a large CSP or a different CSP for specific pages. In such scenarios, it makes sense to make use of libraries such as the CSP Builder to ease the integration and maintenance of CSPs.

A CSP is a mitigation technique against XSS attacks, it does not fix the vulnerability through which an XSS attack has been executed. For that reason, a CSP should be rather seen as a defense-in-depth strategy on top of context-aware escaping of user-controlled input, which is far more important. Furthermore, as with all mitigation techniques, CSPs can be bypassed with Script Gadgets or by exploiting common CSP mistakes to name but a few examples.

File Inclusion

The user should not have the possibility to control parameters that include files from the local filesystem or from a remote host. If this behavior cannot be changed, apply parameter whitelisting such that only valid parameters are accepted. This will also prevent attackers from traversing through the local file system.

$parameterWhitelist = ["preview", "gallery"];
// Activate type checking of the needle-parameter by setting 
// the third parameter of the in_array function to true
if(in_array($parameter, $parameterWhitelist, true)){
    include($parameter . ".php");
}

HTTP Header Injection

The header function prevents the injection of multiple headers since PHP 5.1.2 (see Changelog at the bottom).

HTTP Header Parameter Injection

User-provided header parameters should be avoided if possible. If it can't be avoided, consider a whitelist approach to accept only specific values. The following sample shows how to prevent unvalidated redirection attacks with a whitelist of valid locations.

$parameterWhitelist = ["ManagementPanel", "Dashboard"];
// Activate type checking of the needle-parameter by setting 
// the third parameter of the in_array function to true
if(in_array($parameter, $parameterWhitelist, true)){
    header("Location: /" . $parameter, true, 302);
    exit;
}

Information Disclosure

Error Messages

Many attacks against web applications exploit error messages to infer information on how the attack payload needs to be adjusted for a successful attack. Example attack techniques that utilize error messages are SQL Injection or a Padding Oracle. For that reason, production systems should never display error messages. Instead, error messages should be logged using a library like Monolog.

PHP provides the display_errors configuration parameter to determine if error messages should be part of the output. Use the value off to disable displaying any error messages.

display_errors = off

The same value should be applied for the display_startup_errors configuration parameter which determines whether to display error messages that occur during PHP's startup sequence.

display_startup_errors = off

It is also possible to set these configuration parameters at runtime with, e.g., ini_set("display_errors", "off");. But it is not recommended as any fatal error would stop the execution of a PHP script and thus ignore the line with the ini_set function call.

Disabling the displaying of error messages should not be the primary defense against attacks like SQL Injection as there are other techniques such as Blind SQL Injection that do not necessarily rely on error messages.

PHP Exposure

The following countermeasures are meant to hide the fact that your web application is built in PHP. Be aware that hiding this fact won't make existing vulnerabilities in your web application go away. It is rather meant as a countermeasure against the reconnaissance process of an attacker, where an attacker attempts to learn as much about a target system as possible.

Obviously, the techniques in this section won't be of much use if functionalities of a web application are accessed by requests such as /showImage.php?id=23 where the file extension exposes the technology in use. However, you can hide the file extension on the fly with mod_rewrite if you are serving your web application with the Apache web server.

Rename PHP Session Name

The default session name is PHPSESSID, you can change this name by setting the session.name parameter in your PHP configuration file.

session.name = "SESSION_IDENTITY"
Disable X-Powered-By Header

Setting the expose_php parameter to off in your PHP configuration file will removed the X-Powered-By Header from any HTTP Response.

expose_php = off

Insecure Password Storage and Hashing

It should be needless to say that passwords should never be stored in clear text. The best practice is to store the hash value of the password instead. PHP provides a built-in function for this purpose which is called password_hash.

$clearTextPassword = $_POST["Password"];
$passwordHash = password_hash($clearTextPassword, PASSWORD_DEFAULT);

You can use the built-in password_verify function to verify a user-provided password. The password_verify function will also require the hash value that you stored and generated with the password_hashfunction.

$clearTextPassword = $_POST["Password"];
if(password_verify($clearTextPassword, $passwordHash)){
   // Password is correct
}

Insecure Random Values

Pseudo-Random Bytes

The random_bytes functions generates an arbitrary length string of pseudo-random bytes which are secure for cryptographic use.

string random_bytes ( int $length )

Pseudo-Random Integers

The random_int functions generates a pseudo-random integer which is secure for cryptographic use.

int random_int ( int $min , int $max )

SQL Injection

This type of vulnerability affects applications that interact with a SQL database for data storage and processing. The vulnerability occurs when a SQL query is dynamically constructed with user-controlled input and the user-controlled input is neither sanitized nor escaped. The best practice to prevent SQL injection vulnerabilities is to process user-controlled input and the SQL query separately and this can be done by using prepared statements. The PDO database abstraction layer in PHP enables prepared statements through the prepare method of the PDO class.

// Init and connect to database / Instantiate a PDO object
// ...

// Read user credentials
$eMail = $_POST["Email"];
$passwordHash = password_hash($_POST["Password"], PASSWORD_DEFAULT);

// Read user record from database based on the provided user credentials
$statement = $pdo->prepare("SELECT * FROM Users WHERE Email = :eMail AND PasswordHash=:passwordHash");
$statement->execute(["eMail" => $eMail, "passwordHash" => $passwordHash]);
$user = $statement->fetch();

Template Injection

This type of vulnerability occurs when the target template is built at runtime and parts of the template are controlled by the user. Template engines provide functions to safely embed user-controlled input into a template, make use of them. The following code snippet shows an example where user input is safely embed in a Smarty template.

// Replacing {$searchTerm} with $_GET["searchTerm"] in the next line
// would introduce a template injection vulnerability
$templateString = "You searched for: {$searchTerm}";

$smarty = new Smarty();
$smarty->assign("searchTerm", $_GET["searchTerm"]);
$smarty->display("string:" . $templateString);

If you want to learn more on template injection vulnerabilities and how they can lead to remote code execution, watch this talk on server-side template injection.

Template injection is not limited to server-side web technologies and can also occur on the client-side. Have a look at this talk on client-side template injection.

UI Redressing

To prevent UI redressing attacks such as Clickjacking, prohibit a malicious website from embedding your website in a frame by using the X-Frame-Options header.

header("X-Frame-Options: deny");

Using Packages With Known Vulnerabilities

When you integrate third party packages in your application, typically via a package manager like Composer, you might not be aware of packages containing exploitable vulnerabilities. Apart from staying up to date on vulnerabilities affecting the packages you use, you can also make use of security packages like the one from Roave which prevents you from installing known vulnerable packages in the first place. Roaves source for vulnerable PHP packages is the PHP Security Advisories Database.

Have a look at A9 - Using Components with Known Vulnerabilities from the OWASP Top 10 project for further guidance.

php-security-cheatsheet's People

Contributors

tolgadevsec avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.