Application security

Defending Against Web Attacks: X-XSS Protection

Srinivas
May 6, 2015 by
Srinivas

In the previous article, we have seen how we can defend against click jacking attacks using the X-Frame-Options header. In this article, we will discuss another header: X-XSS-Protection. Similar to the previous article, we will first see the vulnerable code and then attempt to defend against the attack using this header.

11 courses, 8+ hours of training

11 courses, 8+ hours of training

Learn cybersecurity from Ted Harrington, the #1 best-selling author of "Hackable: How to Do Application Security Right."

Setup is the same as the previous article. Once the user logs in, there will be a little dashboard where the user can search for some values. Below is the code used to implement the functionality.

Vulnerable code:

[php]

<?php session_start(); session_regenerate_id(); if(!isset($_SESSION['admin_loggedin'])) { header('Location: index.php'); } if(isset($_GET['search'])) { if(!empty($_GET['search'])) { $text = $_GET['search']; } else { $text = "No text Entered"; } } ?>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>Admin Home</title>

<link rel="stylesheet" href="styles.css">

</head>

<body>

<div id="home">

<center>

</br>

<legend><text id=text><text id="text2">Welcome to Dashboard...</text></br></br> You are logged in as: <?php echo $_SESSION['admin_loggedin']; ?> <a href="logout.php">[logout]</a></text></legend>

</br>

<form action="" method="GET">

<div id="search">
<text id="text">Search Values</text><input type="text" name="search" id="textbox"></br></br>

<input type="submit" value="Search" name="Search" id="but"/>

<div id="error"><text id="text2">You Entered:</text><?php echo $text; ?></div>

</div>

</form>

</center>
</div>

</body>

</html>

[/php]

If you clearly notice in the above code, the application is not sanitizing the user input before it echoes back and thus leaves it vulnerable.

Currently, there is no additional protection mechanism implemented to prevent this. We can even have a quick look at HTTP headers.

HTTP HEADERS

[plain]
HTTP/1.1 200 OK

Date: Sun, 12 Apr 2015 14:53:37 GMT

Server: Apache/2.2.29 (Unix) mod_fastcgi/2.4.6 mod_wsgi/3.4 Python/2.7.8 PHP/5.6.2 mod_ssl/2.2.29 OpenSSL/0.9.8y DAV/2 mod_perl/2.0.8 Perl/v5.20.0

X-Powered-By: PHP/5.6.2

Expires: Thu, 19 Nov 1981 08:52:00 GMT

Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0

Pragma: no-cache

Set-Cookie: PHPSESSID=f94dc2ac2aa5763c636f9e75365102b5; path=/

Content-Length: 820

Keep-Alive: timeout=5, max=100

Connection: Keep-Alive

Content-Type: text/html; charset=UTF-8
[/plain]

So, let us execute some simple JavaScript in the search box and see if it gets executed.

Well, it seems the script is not getting executed. Let us inspect the error console and see what's happening.

It is clear from the console that XSS Auditor in Google Chrome is preventing execution of the script. Additionally, it says that it is enabled because there is no X-XSS-Protection or Content-Security-Policy header sent by the server.

We can customize this filtering by enabling X-XSS-Protection or Content-Security-Policy headers.

Let us first try to disable the protection using the following line.

[plain]

header("X-XSS-Protection: 0");

[/plain]

After adding the above line of code to our page, the page should look as shown below.

[php]

<?php session_start(); session_regenerate_id(); header("X-XSS-Protection: 0"); if(!isset($_SESSION['admin_loggedin'])) { header('Location: index.php'); } if(isset($_GET['search'])) { if(!empty($_GET['search'])) { $text = $_GET['search']; } else { $text = "No text Entered"; } } ?>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>Admin Home</title>

<link rel="stylesheet" href="styles.css">

</head>

<body>

<div id="home">

<center>

</br>

<legend><text id=text><text id="text2">Welcome to Dashboard...</text></br></br> You are logged in as: <?php echo $_SESSION['admin_loggedin']; ?> <a href="logout.php">[logout]</a></text></legend>

</br>

<form action="" method="GET">

<div id="search">
<text id="text">Search Values</text><input type="text" name="search" id="textbox"></br></br>

<input type="submit" value="Search" name="Search" id="but"/>

<div id="error"><text id="text2">You Entered:</text><?php echo $text; ?></div>

</div>

</form>

</center>
</div>

</body>

</html>

[/php]

Well! If we now load the page, it pops up an alert box as shown below.

Let us also check the same page in Firefox, which pops up an alert box as expected.

Now, let us change the value of this header to 1 and try again in the browser.

[plain]

header("X-XSS-Protection: 1");

[/plain]

If you observe the HTTP headers, you can notice that the header has been enabled.

HTTP HEADERS:

[plain]
HTTP/1.1 200 OK

Date: Sun, 12 Apr 2015 14:54:42 GMT

Server: Apache/2.2.29 (Unix) mod_fastcgi/2.4.6 mod_wsgi/3.4 Python/2.7.8 PHP/5.6.2 mod_ssl/2.2.29 OpenSSL/0.9.8y DAV/2 mod_perl/2.0.8 Perl/v5.20.0

X-Powered-By: PHP/5.6.2

Expires: Thu, 19 Nov 1981 08:52:00 GMT

Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0

Pragma: no-cache

Set-Cookie: PHPSESSID=8dfb86b13ec9750d1f1afdfc004f5042; path=/

X-XSS-Protection: 1

Content-Length: 820

Keep-Alive: timeout=5, max=100

Connection: Keep-Alive

Content-Type: text/html; charset=UTF-8
[/plain]

Well, if we now execute the same vulnerable URL, the script won't be executed.

Let us look at the Chrome's console and see what happened.

As we can see in the above console, the script is not executed because of the header we sent.

[plain]

header("X-XSS-Protection: 1");

[/plain]

The above header, when sent with no additional arguments, just stops the script from its execution.

We can also add an additional value to this header as shown below.

[plain]

header("X-XSS-Protection: 1; mode=block");

[/plain]

When this header is sent, the browser doesn't execute the script and shows a blank document to the user as shown below.

Below are the headers sent:

[plain]
HTTP/1.1 200 OK

Date: Mon, 13 Apr 2015 09:59:22 GMT

Server: Apache/2.2.29 (Unix) mod_fastcgi/2.4.6 mod_wsgi/3.4 Python/2.7.8 PHP/5.6.2 mod_ssl/2.2.29 OpenSSL/0.9.8y DAV/2 mod_perl/2.0.8 Perl/v5.20.0

X-Powered-By: PHP/5.6.2

Expires: Thu, 19 Nov 1981 08:52:00 GMT

Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0

Pragma: no-cache

Set-Cookie: PHPSESSID=729f2f716310ccfe353c81ced1602cf0; path=/

X-XSS-Protection: 1; mode=block

Content-Length: 846

Keep-Alive: timeout=5, max=100

Connection: Keep-Alive

Content-Type: text/html; charset=UTF-8
[/plain]

Though it works fine with popular browsers like Internet Explorer, Chrome and Safari, Firefox doesn't support this header and still we can see the alert box popping up as shown below.

11 courses, 8+ hours of training

11 courses, 8+ hours of training

Learn cybersecurity from Ted Harrington, the #1 best-selling author of "Hackable: How to Do Application Security Right."

So, this header should be used to have defense in depth in place, but it can't protect the site completely and thus developers have to make sure they have additional mitigation controls implemented.

Srinivas
Srinivas

Srinivas is an Information Security professional with 4 years of industry experience in Web, Mobile and Infrastructure Penetration Testing. He is currently a security researcher at Infosec Institute Inc. He holds Offensive Security Certified Professional(OSCP) Certification. He blogs atwww.androidpentesting.com. Email: srini0x00@gmail.com