Application security

Web Vulnerabilities Explained

Dejan Lukan
November 15, 2012 by
Dejan Lukan

Introduction

We all know that vulnerabilities in web pages are quite common these days. They range from SQL injections, XSS vulnerabilities, CSRF, etc. In this article we'll provide basic examples of the most common vulnerabilities you'll find in web pages—including and especially WordPress. We'll describe them in detail below.

1. DoS (Denial of Service)

The denial of service happens for various reasons, but we won't describe anything like attackers trying to DoS a specific web site with a botnet of compromised computers, but we'll rather describe how a DoS vulnerability can happen when writing a code.

Usually we have to make a logical mistake to create a DoS scenario in our web application. Let's present such a scenario with a little PHP code. The code below is a PHP sample code that contains a logical error that can be exploited to cause a denial of service.

[plain]

[php]

<?php

if(empty($_GET['file']))

die('You didn't enter the file parameter.');

$file = $_GET['file'];

if(!file_exists($file))

die('The chosen file does not exist.');

include($file);

?>

[php]

[/plain]

In the above code, we're first checking whether the file parameter exists. If yes, we're reading the value stored in the file parameter, otherwise we're closing the application with an error message that says that we didn't enter the file parameter. Thus, we have to provide the file parameter in order to continue execution of the application. After that, we're reading the value stored in the file parameter and checking whether the file exists. If it doesn't, we're again closing the application with an error message about a non-existent file. But if a file does exist, we're including it into the current application execution. From the above code it's not instantly evident that the code contains a vulnerability that can result in a denial of service.

Let's say that we saved the above code as index.php and we're supplying a value of "testing.php" in a file parameter. In such a scenario everything works fine as long as the testing.php file exists and does some work. But what happens if we provide index.php as a value for the file parameter. In such a case, the index.php file is including itself into the current execution, and this happens infinitely, resulting in a denial of service. By default, the operating system allocates just so much memory to each application, and if that application wants more memory, it is usually forcibly closed by the operating system. This is exactly what happens in this case. When the index.php allocates as much memory as permitted, the operating system forcibly closes it.

Let's also present a request that we would have to send to the above application to force a DoS. Such a request is presented below:

[plain]

GET /index.php?file=index.php HTTP/1.0

[/plain]

2. SQL Injection

SQL injection vulnerability is still quite common these days even though it's been present for over ten years. SQL injection vulnerability happens when the application isn't checking the input values for special characters and encapsulating them, and uses the inputted value in the SQL query. An example of such an application can be seen below:

[plain]

[php]

<html>

<body>

<?php

$show = 1;

if(!empty($_GET['username']) and !empty($_GET['password'])) {

$user = $_GET['username'];

$pass = $_GET['password'];

mysql_connect("localhost", "admin", "admin");

mysql_select_db("db");

$result = mysql_query("SELECT * FROM users WHERE username='".$user."'

AND password='".$pass."';");

$row = mysql_fetch_row($result);

if($row) {

echo "Welcome user: ".$user;

$show = 0;

}

}

?>

<?php if($show) { ?>

<form action="#">

Username: <input type="text" name="username" /><br />

Password : <input type="password" name="password" /><br />

</form>

</body>

</html>

<?php } ?>

[php]

[/plain]

We can see that we're checking whether the parameters username and password exist and have a correspondent value. If they have, we're reading the values into the $user and $pass variables. Afterwards we're connecting to the SQL database on localhost:3306 with a username admin and password admin and selecting the database db. Then we're constructing an SQL query sentence in which we're including the exact values from the username and password inputted values without checking it for special characters.

But we should do that, because the above code is vulnerable to SQL injections, because we're not encapsulating the username and password inputted values. Imagine that we enter a value ' OR 1=1--' into both the username and password field. After that the constructed SQL query will be as follows:

[plain]

SELECT * FROM users WHERE username='' OR 1=1--'' AND password='' OR 1=1--''

[/plain]

This effectively selects all users from the table users because of the OR directive we've passed to the vulnerable application. The above SQL query is always evaluated to true and we don't need to enter the right username and password, which would log us into the application. Instead we can enter special input values to break the logic behind the application and login nevertheless.

3. CSRF (Cross-Site Request Forgery)

The cross-site request forgery vulnerability is present when we can plant a request to the user, which is then sent to the targeted web site in his/her name. The request then executes certain action on the target web site in the user's name. There are two questions we need to ask ourselves:

  1. How can we plant a request to the user?
  2. What kind of action can we execute on the target web site?

The answer to the first question is simple. We can plant a request to the user in various ways through which the end goal is the same: the user's browser has to send the request to the target web site on one way or another. We can plant the request to the user in one of the following ways:

  • In case the target web site is vulnerable and we can temporarily inject some code into it, we need to construct the right URI that we sent to the user, who must click on it. Upon clicking on the URI, the initial request will be sent, but because of the vulnerability a second request will be made to request the action that we specified.
  • In case the target web site is vulnerable and we can permanently inject some code into it, we can simply insert another request into the source code of the web page. When the user visits that specific web page sometime in the future, our custom request will be executed in the user's name. This approach doesn't even need social engineering to work, because all the user has to do is visit the vulnerable web page.
  • We can also construct our own web page, which we have total control over. This is why we can include the code that will do the malicious request in the source code of the web page alone. But in the end the user must still visit that web page, which is why we must send him/her a link to our own web page. When the user visits the web page, the requests embedded into the web page alone will be executed in the user's name.

We can see that there are various ways to plant a request to the user's browser, which must execute the request. But this is only half of the story; we still need to talk about what kind of request we can embed into the web page. The requested action can really be anything we like, but the targeted web page must support that action and execute accordingly.

Let's say that we programmed the web page presented below:

[html]

<html>

<body>

<img src="http://www.anything.com/index.php?id=1000&action=up"/>

</body>

</html>

[/html]

When the user visits this web page, a new request will be made requesting the index.php resource on the web page "http://www.anything.com" with parameters id=1000 and action=up. Now, if that web page doesn't have the resource index.php the request will fail. But if the index.php is present but doesn't use the parameters id and action, the request will again fail. This means that we need to know about the files present on the targeted web page as well as the parameters that the web page uses to do some action. This way we could alter the results of the survey, where we would make a request that would vote for the first candidate of the survey instead of the other (which might be more popular). After that we would need to attract users to our page, so the voting requests will be sent to the survey as well.

But this is just a tip of the ice berg; imagine what we could do if we could send requests that would add another administrator user to some database, delete all the usernames, or even send arbitrary emails to contacts in users' mailbox signed by that user, etc.

Now we're going to explore other web vulnerabilities, which are also both prominent and common.

XSS (Cross-Site Scripting)

This attack is arguably as common as the original three. The cross-site scripting attack allows us to inject arbitrary code into the vulnerable web page, which we can use to obtain sensitive information like usernames, passwords, cookies, etc. With the XSS attack we can circumvent the same-origin policy, which is present in all scripting languages executing at the client-side in a web browser. An example of such a language is Javascript. The same-origin policy allows the web browser to execute the client-side code only on a web page from which the code originated.

There are three types of XSS attacks:

a. Reflected XSS

The webpage is vulnerable if it accepts the user input and displays its contents on a web page without proper validation of special characters like slash ('/'), apostrophe ('"'), etc. In such cases we can include a Javascript in the URI that we send to the user, which clicks on a link. Upon that, the included Javascript is executed in the users' browser. The Javascript can grab the users' cookie and sends it to the attackers' network. An example of an application that contains the reflected XSS vulnerability is seen below:

[html]

<html>

<body>

<?php

if(isset($_GET['p'])) {

print "V parametru p se nahaja vrednost: " . $_GET['p'];

}

else {

print "Niste nastavili parametra p.";

}

?>

</body>

</html>

[/html]

First we're checking to see if the parameter p is set and displaying its value without filtering any of the special characters. We can store a Javascript code in a value of parameter p that will be executed when the user clicks on the URI (which also includes that malicious Javascript code). An example of the URI that contains the Javascript code, which will display the user's cookie, is as follows:

[plain]

GET /index.php?p=<script>alert(document.cookie)</script> HTTP/1.1

[/plain]

b. Stored XSS

A stored XSS attack is present when we can store the malicious code inside the vulnerable web page permanently. Thus, our code will be executed every time the user visits the vulnerable web page. Because the code is stored right in the web page, we don't have to send emails to users convincing them to click on the link or something. Such a web page must use some kind of a backend database where the user inputted values are stored. When a user visits a web page, those values are taken from the database and displayed on the web page, thus executing the malicious code.

c. DOM-based XSS

DOM-based XSS attack happens when we send a malicious URI to the user, who clicks on it. But this isn't the same as with reflected XSS attack, because the web site returns a valid non-malicious response (thus the web site is not vulnerable to reflected XSS attack). The attack happens because the web site uses a Javascript code that in turn uses the values from the URI address. An example of a DOM-based XSS is presented in the code below:

[html]

<html>

<body>

<script type="text/javascript">

p = document.location.href.substring(document.location.href.indexOf("p=")+2);

document.write("V parametru p se nahaja vrednost: " + p);

</script>

</body>

</html>

[/html]

From the source code we can see that we're getting that Javascript back as a response on a request. That Javascript first reads the value of parameter p from the used URI address into a variable p. Afterwards it displays the value of the parameter p. Because of this, the Javascript is actually referring to the value stored in parameter p, which can be a malicious Javascript. Let's say that we're executing the request below:

[plain]

/index.php?p=<script>alert(document.cookie)</script>

[/plain]

When the Javascript from the response is executed, it will read the value of parameter p, which is <script>alert(document.cookie)</script> and include it into processing. Therefore the malicious code in parameter p is executed nevertheless, even if the web site itself is not vulnerable to reflected or stored XSS attack.

Buffer Overflow

Sometimes we can see executable programs being used as part of the application providing unique features. But even though the executables are being used as part of web applications, buffer overflow vulnerabilities still exist. Imagine that a web application is calling a system function to call an executable with the user inputted parameter. This doesn't prevent the buffer overflows that could be present in executables from overflowing the program stack or heap structures.

An example of a C program that contains a buffer overflow vulnerability is as follows:

[cpp]

void copy(char **argv) {

char array[20];

strcpy(array, argv[1]);

printf("%sn", array);

}

main(int argc, char **argv) {

copy(argv);

}

[/cpp]

The program accepts an input argument, but doesn't check for the length of the argument. When it accepts the input argument, that argument is sent to the copy function, which copies the argument into a local buffer with the strcpy function call. But there are only 20 reserved bytes in the local array, so if we copy an argument that is longer than 20 characters, a buffer overflow will occur. This will crash the program at least, but a specifically crafted input argument could execute arbitrary code on the target system.

Format String

Format string is a special vulnerability where the user can control what gets inputted into a function call like printf, fprintf, sprintf, snprintf, vprintf, vprintf, vsprintf and vsnprintf. To understand this attack it's best to look at the example. An example code written in programming language C that contains a format string vulnerability is presented below:

[cpp]
#include <stdio.h>

int main(int argc, char **argv) {

printf(argv[1]);

return 0;

}

[/cpp]

We can see that the program accepts one input argument, which is directly passed to the printf function call. By default, the program echoes the input argument on the screen, but what if we enter some special character that the printf function will treat differently, like %n, %x or %s. If we enter the special character %x, the printf function will output four bytes from the stack, if we enter two %x characters, the printf function will output eight bytes from the stack, etc. This happens because we've called the function printf without the necessary parameters, which should be present. But the function can't know if those parameters are present or not and takes the next value from the stack (where the missing parameter should be).

With the %x we can read arbitrary values from the stack, whereas with %n we can write an arbitrary value on an arbitrary address in the memory. With both the %x and the %n, we can gain control over the execution of the program and execute arbitrary code.

Directory Traversal

The working directory traversal vulnerability can be present in an application that allows the users to read the files from the filesystem, but fails to properly identify which file the user is allowed to read. The vulnerability is usually present because the application doesn't check what file the user is trying to read. The basic problem is that application is not checking whether the user is trying to move up the directory chain into the parent directory with the use of special characters "../" or "..". Therefore the user can not only read files from the allowed directory, but also from all other directories the application has access to.

An example of a vulnerable application written in PHP is as follows:

[php]

<?php

if(empty($_GET['file']))

die('You didn't enter the name of the file.');

$file = getcwd().'/'.$_GET['file'];

if(!file_exists($file))

die('The filename doesn't exist.');

readfile($file);

?>

[/php]

In the above code we're first checking whether the parameter file exists and contains a value. After that we're constructing a whole path to the file we're trying to read with the use of getcwd function that gets the current directory and appends it the value of the parameter p. At the end we're reading the file from the constructed path and displaying it to the user.

The problem occurs because we're not checking for any malicious characters in the value of parameter p. This allows the attacker to traverse up the directory tree by using the special sequence of characters "../" or "..". Let's present the request that would read the file /etc/password from the system even though the application doesn't have access to that file. The request would have to look something like as follows:

[plain]

GET /index.php?file=../../../../etc/passwd HTTP/1.0

[/plain]

This effectively reads and displays the system file /etc/password that contains all the usernames on the system.

File Inclusion

The file inclusion attack is very similar to directory traversal attack. The only difference is that with directory traversal attack, we can only read the file we're not allowed to read, but with file inclusion we're including the file into the current web page execution, thus executing the file we're not allowed to execute.

We must know that there are two types of file inclusion attacks:

  • LFI (Local File Inclusion)

    Here we're including the local file into current execution. By local file we mean the file that is already present on the server's system. The LFI attack is possible because the application doesn't encapsulate the user inputted data.

  • RFI (Remote File Inclusion)

    Here we're including the remote file into current execution. This can happen if the application has an option to upload the file to the server's filesystem. In such cases, we can upload the malicious file from our client to the server and execute it. With this attack, we can upload a malicious web shell onto the vulnerable application and obtain total control of the web server (under the context of the application's user of course).

An example of a vulnerable code can be seen in the source code output below:

[php]

<?php

if(empty($_GET['file']))

die('You didn't enter the name of the file.');

$file = getcwd().'/'.$_GET['file'];

if(!file_exists($file))

die('The filename doesn't exist.');

include($file);

?>

[/php]

In the above code we're first checking whether the parameter file exists and contains a value. After that we're constructing a whole path to the file we're trying to read with the use of getcwd function that gets the current directory and appends it the value of the parameter p. At the end we're including the file from the constructed path into the current execution of the web page.

Command Injection

The command injection vulnerability can be present in an application where the user inputted value can affect the command that gets executed on the server. An example of a vulnerable application written in PHP is as follows:

[html]

<html>

<body>

<?php

if(empty($_GET['user']))

die('You didn't enter the name of the user.');

$user = $_GET['user'];

system("id $user");

?>

</body>

</html>

[/html]

In the above code we're first checking if the parameter user exists and if it does, we're reading the value of that parameter into a local variable. After that we're executing the command "id user" on the system. The vulnerability is present because the application doesn't check the inputted value for special characters. Because of that we can execute a second command on the system if we pass a value like "root;ls -l /" in parameter user. The application will then execute the following command: "id root;ls -l /". But because the ';' is a separator between multiple commands, the application will actually execute two commands one after another, returning the result of both of them.

Privilege Escalation

Privilege escalation attack is the attack where we're using the logical error in the application to obtain privileges to do something we're not supposed to do. This vulnerability often happens in applications that use multiple user roles like unauthorized user, authorized user, administrator, etc. It's redundant to say that some user roles have more permissions than others. For example, the administrator should have the right to add other user accounts, while other user roles shouldn't have that privilege. The vulnerability would then happen if the normal user could create new user accounts nevertheless.

Conclusion

We've seen that there are many vulnerabilities that we need to watch out for when programming a web based application. We looked at basic examples of most common vulnerabilities and explained them in detail to better present the vulnerabilities out there. This can be a reference of most common mistakes in web applications that web developers can study and use.


Dejan Lukan
Dejan Lukan

Dejan Lukan is a security researcher for InfoSec Institute and penetration tester from Slovenia. He is very interested in finding new bugs in real world software products with source code analysis, fuzzing and reverse engineering. He also has a great passion for developing his own simple scripts for security related problems and learning about new hacking techniques. He knows a great deal about programming languages, as he can write in couple of dozen of them. His passion is also Antivirus bypassing techniques, malware research and operating systems, mainly Linux, Windows and BSD. He also has his own blog available here: http://www.proteansec.com/.