PHP Security 3: XSS and Password Storage

The first two parts of this series focused on avoiding injection-related security vulnerabilities in PHP (SQL Injection and Code Injection) as well as on how to prevent directory traversal. This part introduces ways to avoid Cross-site Scripting (XSS) vulnerabilities and methods that you should use to handle passwords in a secure fashion.

Cross-site Scripting

In a Cross-site Scripting attack, client-side code is injected into the output of a web page, for example as an HTML attribute, and executed in the user’s browser. The impact of successful exploitation varies. An XSS attack may redirect the user to a malicious website or it may be used to steal credentials, cookies, or anti-CSRF tokens. Cross-site Scripting vulnerabilities are one of the most common vulnerability types found in web applications (featured in the OWASP Top 10 list).

Insecure Code Sample

In this code snippet, user input from the GET parameter is used directly as output. This leads to the simplest form of reflected XSS vulnerability.

$string = $_GET['string'];
echo $string;

If the attacker passes JavaScript code instead of pure text (for example, <script>alert(“xss”);</script>), it is executed as soon as the page is rendered:

PHP Security

Some developers use a blend of inline HTML tags, PHP, and JavaScript code, which can also be easily exploited:

<script type="text/javascript">
var term = <?php echo $_GET['string']; ?>
</script>

In this example, the attacker does not even need to use the script tag because the malicious input argument will be injected directly into JavaScript.

Secure Code Sample

Cross-site Scripting can be exploited in many ways, which means that blacklisting is not a good idea. The recommended solution is input validation by escaping data before using it as output. When escaping (encoding), HTML characters are converted into HTML entities, which means that the browser will not interpret them as code but as data.

There are two functions that can be used in PHP applications to escape data: htmlspecialchars() and htmlentities(). The htmlspecialchars() function converts the following special characters ',",&,<,> to ', ", &, <, and >. The htmlentities() function is very similar to htmlspecialchars() with one difference – it encodes all characters that have HTML entity equivalents. As of PHP 5.6, UTF-8 is the default character set for both functions.

$string = $_GET['string'];
echo htmlspecialchars($string, ENT_QUOTES , 'UTF-8');

// Alternatively you can use the filter_var() function
echo 'You have searched for: '.filter_var($search, FILTER_SANITIZE_FULL_SPECIAL_CHARS);

Using either function in your PHP script results in the malicious code being parsed by the browser as text:

PHP security

In the source code, we can see the HTML-encoded characters that prevent the script from executing:

PHP Security

If you prefer using ready-made solutions, there are several open source libraries available for PHP that you can use instead of built-in functions. These libraries are constantly updated with new protection techniques. Some examples include: Google’s php-antixss and xssprotect, HTML Purifier, and htmLawed. For more details about preventing XSS vulnerabilities in general, see the following article: Preventing XSS Attacks.

Password Storage

Secure password storage is a very important aspect of web application security. It is an extra layer of protection that helps to avoid a breach of confidentiality in case of an attack. For example, an attacker may exploit an overlooked SQL Injection vulnerability and dump the database. In such a case, a strong password hash will make cracking the data very hard, thus protecting it from further exploitation.

Two most important password storage security factors (other than having a strong password) are the hashing algorithm and the use of a salt. A salt is a string that is added to a password before it is hashed. If the salt is long and random, it makes some common cracking methods ineffective, for example, dictionary attacks and lookup tables.

Insecure Code Sample

One of the most widely used and fastest hashing algorithms is MD5. It is also one of the fastest to crack. Even though MD5 is weak, people still use it for password hashing.

$password = 'j0hnny140';
$hash = md5($password);

Result: 3451f3e47aaa22beee5af1ebf5ae6828

Secure Code Sample

Instead of md5(), you should use the password_hash() function. By default, this function produces a 60-character string based on the CRYPT_BLOWFISH algorithm (BCRYPT) and provides strong hashes. This function accepts 3 parameters: the password to hash, the hashing algorithm, and additional options such as cost. In the following example, we will use the default hashing algorithm.

$password = 'j0hNny$140!';
$hash = password_hash($password, PASSWORD_DEFAULT);

Result: $2y$10$VNA4syJiHTI6XXQVQCpZX.amjcHOjOZxIUImLafsSxedGZkumFVqC

A hash can be verified using the password_verify() function.

$password = 'j0hNny$140!';
$hash = '$2y$10$VNA4syJiHTI6XXQVQCpZX.amjcHOjOZxIUImLafsSxedGZkumFVqC';
if(password_verify($password, $hash)) {
  // Action to perform if the password is successfully verified
}

Tips

  • Do not use weak hashing algorithms such as MD5 and SHA1
  • Do not use (or reuse) weak salts (let password_hash() set the salts)
  • Do not create your own cryptographic or hashing functions
  • Do not store encrypted passwords because an attacker might get access to the private key
  • By increasing the cost value (work factor) in the password_hash() function, you can get much stronger hashes but setting the value too high can affect server performance