Questions about this topic? Sign up to ask in the talk tab.

Difference between revisions of "Secure Programming"

From NetSec
Jump to: navigation, search
 
(2 intermediate revisions by the same user not shown)
Line 309: Line 309:
 
   
 
   
 
==Integer handled as string==
 
==Integer handled as string==
  Improper type handling is as common as it is dangerous.  If a programmer makes the mistake of handling an integer as a string (or other type confusions), sanitizing inputs can become useless.  There are a variety of approaches for mitigation of this type of issue.
+
Improper type handling is as common as it is dangerous.  If a programmer makes the mistake of handling an integer as a string (or other type confusions), sanitizing inputs can become useless.  There are a variety of approaches for mitigation of this type of issue.
 
   
 
   
  Examples of integral handling vulnerabilities:
+
===Examples of integral handling vulnerabilities===
 +
====PHP====
 +
{{code|text=<source lang="php">
 +
<?php
 +
  $id = mysql_real_escape_string($_GET['id']);
 +
  @mysql_query("SELECT * FROM user WHERE user_id = " . $id . " LIMIT 1");
 +
?>
 +
</source>}}
 
   
 
   
    PHP:
+
===Mitigation===
        <?php
+
If user_id is an integer, this statement is still vulnerable, if a user  inputs "id=1%20OR1=1", the statement will not be escaped (since there  are no quotes) and still a valid SQL statement, rendering the database open to injection. While this example uses SQL, other situations may still be vulnerable. In this case, for forward compatibility and "universal solution" purposes, the programmer should use the appropriate native (or generic) method to cast to an integer instead of mysql_real_escape_string():
          $id = mysql_real_escape_string($_GET['id']);
+
          @mysql_query("SELECT * FROM user WHERE user_id = " . $id . " LIMIT 1");
+
        ?>
+
 
   
 
   
    Ruby:
+
====PHP====
     
+
{{code|text=<source lang="php">
    Perl:
+
<?php
    Python:
+
  $id = (int)$_GET['id'];
 +
  @mysql_query("SELECT * FROM user WHERE user_id = " . $id . " LIMIT 1");
 +
?>
 +
</source>}}
 +
 
 +
====Ruby====
 +
Warning: (link to ruby paramaterized section)Activerecord supports paramaterized  statements(/link).  It is likely that if you need to use this in  conjunction with active record, you may be (link to unparamaterized  queries) doing something wrong (/link).
 +
 
 +
===Auditing===
 
   
 
   
  Mitigation:
 
  If user_id is an integer, this statement is still vulnerable, if a user  inputs "id=1%20OR1=1", the statement will not be escaped (since there  are no quotes) and still a valid SQL statement, rendering the database open to injection. While this example uses SQL, other situations may still be vulnerable. In this case, for forward compatibility and "universal solution" purposes, the programmer should use the appropriate native (or generic) method to cast to an integer instead of mysql_real_escape_string():
 
 
   
 
   
    PHP:
+
==Improper signedness==
      <?php
+
Improper signedness is caused by allowing signed data when expecting  unsigned data. This can cause information disclosure or even code  execution in extreme circumstances. Our previous "fixed" code in the  "Integer Handled as String" section does still have this problem. It is  handled as an integer but the sign is not considered, as "-10" is still a  valid integer, this will slip through the (int) cast. Therefore, we  need to pass this to the abs() function which takes the absolute value  of the data.
        $id = (int)$_GET['id'];
+
 
        @mysql_query("SELECT * FROM user WHERE user_id = " . $id . " LIMIT 1");
+
===Mitigation===
      ?>
+
====PHP====
 +
{{code|text=<source lang="php">
 +
<?php
 +
  $id = abs((int)$_GET['id']);
 +
  @mysql_query("SELECT * FROM user WHERE user_id = " . $id . " LIMIT 1");
 +
?>
 +
</source>}}
 
   
 
   
    Ruby:
+
====Auditing====
        Warning:  (link to ruby paramaterized section)Activerecord supports paramaterized  statements(/link).  It is likely that if you need to use this in  conjunction with active record, you may be (link to unparamaterized queries) doing something wrong (/link).
+
 
     
+
==Unparamaterized Statements==
     
+
===Examples===
     
+
===Mitigation===
 +
====Rails====
 +
{{code|text=<source lang="ruby">
 +
user = User.Find(:conditions => ['id = ?', params[id]]  
 +
</source>}}
 +
 
 +
===Auditing===
 +
 
 +
=Pitfalls resulting from improper combination of whitelisting and modification=
 +
 
 +
==Delete after length-check (Empty Where Clauses)==
 
   
 
   
 
+
===Proof of concept===
    Perl:
+
While the example here is based on SQL injection, it can apply to other scenarios as well.  Suppose the following php code:
     Python:
+
 
 +
{{code|text=<source lang="php">
 +
<?php
 +
  # pwreset.php
 +
  if (strlen($_GET['reset']) == 16) {
 +
     $pwreset_code = preg_replace_all('/([^\w]+)/','',$_GET['reset']); #alphanumeric only.
 +
    $user_query  = "SELECT * FROM user WHERE pwreset_code= '" . $pwresetcode ."' LIMIT 1";
 +
    $user_data    = @mysql_query($user_query);
 +
    ...
 +
  }
 +
 
 +
 
 +
  // pwreset.php?reset=''''''''''''''''
 +
  //  The generated sql statement:
 +
  //  SELECT * FROM user WHERE pwreset_code='' LIMIT 1;
 +
</source>}}
 
   
 
   
  Auditing:
+
As reset codes are generated on reset request (forgot password form),  this will leave the first user (usually the admin) of the web app  exposed to arbitrary password overwrite.
 
   
 
   
 +
===Mitigation===
 +
Instead of checking length before replacement, verify after deletion.
 +
 +
====PHP====
 +
 +
{{code|text=<source lang="php">
 +
<?php
 +
  # pwreset.php
 +
  $pwreset_code = preg_replace_all('/([^\w]+)/','',$_GET['reset']); #alphanumeric only.
 +
  if (strlen($pwreset) == 16) {
 +
    $user_query  = "SELECT * FROM user WHERE pwreset_code= '" . $pwresetcode ."' LIMIT 1";
 +
    $user_data    = @mysql_query($user_query);
 +
    ...
 +
  }
 +
</source>}}
 
   
 
   
Improper signedness
+
===In the wild===
    Improper signedness is caused by allowing signed data when expecting  unsigned data. This can cause information disclosure or even code  execution in extreme circumstances. Our previous "fixed" code in the  "Integer Handled as String" section does still have this problem. It is  handled as an integer but the sign is not considered, as "-10" is still a  valid integer, this will slip through the (int) cast. Therefore, we  need to pass this to the abs() function which takes the absolute value  of the data.
+
A Joomla! framework exploit leveraged this weakness for an administrative password reset (2008). [http://www.exploit-db.com/exploits/6234/ CVE 2008-3861]
   
+
  Mitigation:
+
 
   
 
   
    PHP:
+
===Auditing===
      <?php
+
Follow the same auditing steps as listed for unsafe deletion, with an  eye towards where clauses or other empty type comparisons that could  lead to problematic outcomes.
        $id = abs((int)$_GET['id']);
+
        @mysql_query("SELECT * FROM user WHERE user_id = " . $id . " LIMIT 1");
+
      ?>
+
 
   
 
   
  Auditing:
 
 
   
 
   
 +
==Deletion or truncation after reformat==
 +
This vulnerability is caused by reformatting a string and then  truncating it to a specific length, this allows an attacker to trigger  an error and possibly execute code. For example, if we have a filter  that runs mysql_real_escape_string() and then truncates the string to 16  characters, we could run into this problem. The attacker inputs  "123456789012345'", when run through mysql_real_escape_string() it  becomes "123456789012345\'", when truncated to 16 characters, this  string finally becomes "123456789012345\", which would escape the quotes  surrounding it. This would at the very least cause an error for  information disclosure, but could also lead to sql injection and xss notwithstanding.
 
   
 
   
Unparamaterized Statements:
+
===Examples===
+
Examples:
+
+
Mitigation:
+
        Rails:
+
            user = User.Find(:conditions => ['id = ?', params[id]] 
+
Auditing:
+
+
+
+
Pitfalls resulting from improper combination of whitelisting and modification:
+
Delete after length-check (Empty Where Clauses)
+
+
  Proof of concept:
+
+
  ------------------------
+
  While the example here is based on SQL injection, it can apply to other scenarios as well.  Suppose the following php code:
+
+
  <?php
+
  # pwreset.php
+
  if (strlen($_GET['reset']) == 16) {
+
    $pwreset_code = preg_replace_all('/([^\w]+)/','',$_GET['reset']); #alphanumeric only.
+
    $user_query  = "SELECT * FROM user WHERE pwreset_code= '" . $pwresetcode ."' LIMIT 1";
+
    $user_data    = @mysql_query($user_query);
+
    ...
+
  }
+
  -----------------------
+
+
  pwreset.php?reset=''''''''''''''''
+
+
  The generated sql statement:
+
    SELECT * FROM user WHERE pwreset_code='' LIMIT 1;
+
+
    As reset codes are generated on reset request (forgot password form),  this will leave the first user (usually the admin) of the web app  exposed to arbitrary password overwrite.
+
+
  Mitigation:
+
+
  Instead of checking length before replacement, verify after deletion.
+
+
  * PHP:
+
    <?php
+
    # pwreset.php
+
    $pwreset_code = preg_replace_all('/([^\w]+)/','',$_GET['reset']); #alphanumeric only.
+
    if (strlen($pwreset) == 16) {
+
      $user_query  = "SELECT * FROM user WHERE pwreset_code= '" . $pwresetcode ."' LIMIT 1";
+
      $user_data    = @mysql_query($user_query);
+
      ...
+
    }
+
+
  In the wild:
+
    A Joomla! framework exploit leveraged this weakness for an administrative password reset (2008).
+
    CVE 2008-3861 http://www.exploit-db.com/exploits/6234/
+
+
  Auditing:
+
    Follow the same auditing steps as listed for unsafe deletion, with an  eye towards where clauses or other empty type comparisons that could  lead to problematic outcomes.
+
+
+
Deletion or truncation after reformat
+
    This vulnerability is caused by reformatting a string and then  truncating it to a specific length, this allows an attacker to trigger  an error and possibly execute code. For example, if we have a filter  that runs mysql_real_escape_string() and then truncates the string to 16  characters, we could run into this problem. The attacker inputs  "123456789012345'", when run through mysql_real_escape_string() it  becomes "123456789012345\'", when truncated to 16 characters, this  string finally becomes "123456789012345\", which would escape the quotes  surrounding it. This would at the very least cause an error for  information disclosure, but could also lead to sql injection and xss notwithstanding.
+
+
  Examples:
+
 
   
 
   
 
   * PHP:
 
   * PHP:

Latest revision as of 10:03, 20 April 2013

Throughout the history of application development, programmers have repeatedly made the same mistakes, their educators have repeatedly inadvertantly missed important nuances, and the industry has globally repeated these mistakes in the corporate world. This has yielded many developers who write vulnerable applications simply without knowing better; in some cases, developers even think that they are securing the application when in fact they are creating the vulnerabilities themselves. Only once developers are properly educated from the ground up does it become possible to foster the proper development methodology and good practices for security-centric and quality-assurance driven development.

Many of the common programming pitfalls responsible for modern vulnerabilities are less than obvious and completely language agnostic, or they are entirely small mistakes that are easy to miss or glance over. These pitfalls are mostly based around concepts, so proper and improper implementations exist in nearly each language. This article focuses on interpreted languages, but this does not mean that the vulnerabilities exist only in interpreted languages.


Contents

Concepts

Its as simple as realizing that certain things may cause a bug. Bugs are what hamper stability, performance/efficiency, and security. By solving any of these problems in entirety at least some of the others will be solved in the process. So, talking about bugs - bugs include everything from the program displaying funny characters it shouldn't on the screen to reproducable application crashes. The more accidental impact (complete crash vs. small hiccup) a bug has, typically its impacts in all these areas are to scale with the impact on a user experience - while risk applies to and defines the broader scale (the purpose for the existence of the application or system).

Most bugs are the result of a program receiving unexpected data and using it in a way that results in the problem. Because the ways that an application is traditionally programmed to receive input are finite, solutions to several types of problems with user input can be found. Data may contain illegal characters, exceed an allowed length or maximum value, require a type or sign translation, ad naseum. When an application is able to compensate for this, it is because the developer makes one of two choices: to convert the data somehow into acceptable data, or to reject the data entirely with an error message and demand re-submission of the input in proper form. Sometimes the input's "illegal characters" may even be a part of the application's desired output - making the challenge more difficult.


Sanitizing

There are two primary techniques used for input sanitization in interpreted languages: whitelisting and modification. Modification is a process which modifies data submitted or inputted by a user, whereas input whitelisting simply checks to see if the input matches a particular criterium. In the case of input modification, the functionality exposed is usually executed regardless of the resulting data; whereas in the case of input whitelisting, the functionality is only exposed if the input meets the whitelist's criterium. If the functionality provided by a whitelisted input is still exposed, usually the whitelisted input is reset to a particular default value of some sort. Some more specific examples of each technique are below:

Input Whitelisting

  • Cancel process if malformatted - Only input matching a particular format will trigger the processing of the inputted data
  • Cancel process if incorrect type - Only input of a particular type (integer, string, negative number, etc) will trigger the processing of the inputted data
  • Cancel process if out of bounds - Only input of a particular length, between two particular lengths, or between two particular values will trigger the processing of the inputted data
  • Cancel process if not whitelisted - If the inputted data is not in a list of allowed choices, it is not processed.

Input Modification

  • Sanitize-by-reformat - Characters or strings which may be malicious in user input are escaped or encoded to make them safe for evaluation
  • Sanitize-by-typecast - User input is forced into the correct data type. (Casted from string to integer, or vice versa).
  • Sanitize-by-boundary - When a user input reaches a particular size, all additional input is ignored. In some cases (e.g. decimals) this could be a string truncation, an integer whole number cast, or rounding.
  • Sanitize-by-delete - Characters or strings which may be malicious are completely removed from a user input.

You may notice that whitelisting technique "A" corresponds to modification technique "A", so on and so forth. In many cases, a combination of both modification and whitelisting is used for sanitizing. Performing these steps in the wrong order, or even inadvertantly casting a value to the wrong data type may prove costly. We will outline some of the more common mistakes involved with implementations of each technique throughout this paper. Additionally, some more generic accidental programming pitfalls (even simple typos) will be identified - along with solutions to them - so that they may be avoided by future programmers. Each language-agnostic pitfall contains examples in each of the traditional four interpreted languages, mitigation techniques in each of them, and code audit suggestions.

Common pitfalls of traditional whitelisting:

Accidental Assignment

This is a problem common to all programming languages in general. Due to the general nature of this pitfall, examples will not be extensively provided. Effectively, during a traditional "if" statement comparison, at least two '=' characters must be used to perform a comparison as the single '=' character is reserved for value assignment. An accidental placement of a single '=' operator without any additional comparison operators will change the value in the variable, rather than comparing it.

Unsafe substring indexing

String positioning functions are often inadvertantly misused in a wide variety of languages when determining where a pattern exists within a string.

Examples of unsafe indexing

The problem is that ruby and perl's index(), php's strpos(), or python's find() may return 0 or something else that equates to false if the search character or string is at position 0 in the string (starting with the first letter at 0). Because 0 evaluates to null or false, the conditions will not be met even if the needle exists in the haystack. Often times these functions are used to determine whether or not a string exists within a string (rather than regular expressions or other string comparison operators) due to the performance benefits. The downside is that careless implementations result in vulnerabilities.

PHP

 
if (strpos('/', $var)) ...
 

Python

 
if (var.find('/')): ...
 

Ruby

 
if (var.index('/')) ...
 

Perl

 
if (index($var,'/')) ...
 

Mitigation

Because of the simplicity of the problem, it is trivial to add additional boundary checks are added. While this can be a bit language specific, they are all easily remembered. In PHP, multiple '=' operators are used to ensure that strpos() is returning an integer zero rather than a null value (adding an implicit type-check).

PHP

 
if (strpos("/",$var) >== 0) ...
 

Python

 
if (var.find("/") >= 0): ...
 

Ruby

 
if (var.index('/') >= 0) ...
 

Perl

 
if (index($var,'/') >= 0) ...
 

Note: Some perl, ruby, or python implementations may require 'gte' rather than '>=' as the "greater-than or equals" comparison operator. Tip: Some lanuages will require you to properly enforce type (scalar vs. array) and may have unpredictable behaviors if an array is passed (e.g. var[1]=foo&var[2]=bar in the url.)

Auditing

Using a simple bash command, one can search for all of the occurances (with context) of these functions:

 
find -name \*.py -exec grep -HnC2 \\.find\( '{}' \; \
  -o -name \*.php -exec  grep -HnC2 str.\?pos\( '{}' \; \
  -o -regextype posix-awk -regex ".*\.(rb|pl|pm)" -exec grep -HnC2 \\bindex\( '{}' \; &> string_locating.txt
 

It is not hard to isolate vulnerable implementations from there:

 
grep \\.php:[0-9]\\+: string_locating.txt|grep -v "\!==\|==="
 

Unsafe string comparison

Comparing strings, if not done properly, can be dangerous, too. Checking only that a string contains a certain value rather than checking for equality can leave inputs open to exploitation. Normally this is not something worth mentioning, but these are often easily and accidentally confused. This can happen any time strings are compared, whether with regular expressions or traditional comparisons.

Examples of unsafe string comparison (traditional)

The below examples are unsafe because 'admin123' is still still valid, as 'admin' still occurs in the string. This would allow "admin123" to access features that should only be available to "admin":

 
>>> username = "admin123"
>>> if "admin" in username:
...   print("User is an admin")
'User is an admin'
 

These functions check to see if the search string occurs at all within a string, but do not properly check equality between the two. When on a programming binge (hours and hours of coding), developers often overlook simple flaws like this.

PHP

 
if (strstr("admin", $username)) ...
 

Python

 
if "admin" in username: ...
 

Ruby

 
if username.include? "admin" ...
# OR
# if username["admin"] ...
 

Perl

 
if (index($username, "admin") != -1) ...
 

Regular Expressions

 
>>> username = "admin"
>>> if re.match("admin", username): print("admin")
...
'admin'
 

Mitigation

Mitigation is rather simple, make sure that comparison operators are used instead of find functions. Be extra careful to avoid accidental assignment during input checks.

PHP

 
if ($username == "admin") ...
 

Python

 
if username == "admin": ...
 

Ruby

 
if  username == 'admin': ...
 

Perl

 
if ($username eq 'admin') ...
 

All regular expressions used for absolute comparison should be all inclusive. This means that the first character of the pattern should be '^', indicating the beginning of the string, while the last character should be '$', indicating the end of the string.

 
>>> pattern = '^admin$'
>>> username = "admin123"
>>> if re.match(pattern, username): print("is an admin")
...
 

Unless the developer is explicity searching for the position or index of a substring or character within the target or trying to determine its presence in the target string, find functions should not be used and all regular expressions must be exact (with the exception of case insensitivity).

In the wild

Wordpress timthumb.php domain name handling exploit (2011) leveraged this type of vulnerability. Secunia Adivsory 45416

Auditing

In order to audit this, a developer needs to identify all uses of find functions and verify that the usage is safe. A search can be done using:

 
find -name \*.py -exec grep -HnC2 \\bin\\b '{}' \; \
  -o -name \*.php\* -exec grep -HnC2 str.\?str\( '{}' \; \
  -o -name \*.rb -exec grep -HnC2 \\.include? '{}' \; \
  -o -regextype posix-awk -regex ".*\.(pl|pm)"  -exec grep -HnC2 \\bindex\( '{}' \; &>  string_comparison.txt
 

Note: Regular expressions auditing information provided later in this article.

Common pitfalls encountered during input modification

Non-recursive sanitize by delete

In this scenario the attacker supplies a string, that only after being parsed by the sanitization routines becomes capable of causing harm. Without the sanitizing routines in place, the input would be unsanitized and vulnerable. With the sanitizing routines in place, they have only increased the difficulty level for exploitation by 1. This scenario exists because all of the existing string replace functions only replace all occurrences of a given string search within the string target with no check to see if the newly parsed string still contains string search. As a result, it is possible to embed the string within itself to prevent removal.

For example, suppose a developer wanted to remove all instances of the string "delete" from the user-supplied string "delete me". Because delete occurs only once, the string replace functionality of any given language will return the value " me"; however, if the user supplied value was "deldeleteete me", the string replace function for the given language returns "delete me".

Warning: This pitfall applies to every string replace function, including Perl-Compliant-Regular-Expressions (PCRE).

Examples of unsafe string replacement

=Python

 
>>> string = "....//....//....//"
>>> string.replace("../","")
'../../../'
 

PHP

 
str_replace("../", "", $string);
 

Note: Natively, Perl and Ruby use exclusively regular expressions for string replacement.

Examples of unsafe pattern replacement

PHP

 
$string = preg_replace('@[.]{2}\/@','',$string);
 

Ruby

 
string =~ s/[.]{2}\///g
# OR
# string = string.gsub("../", "")
</sourc>}}
 
====Perl====
{{code|text=<source lang="perl">
$string =~ s/[.]{2}\///g;
 

Python

 
string = re.sub('[.]{2}\/','',string)
 

Mitigation

These cases are somewhat trivial in that they only attempt to remove the unix parent directory ("../") string non-recursively. Because this does not apply exclusively to path strings, but also to xss and other dynamic-content related vulnerabilities, we have documented the proper way to recursively remove unwanted substrings. There are multiple ways to mitigate this particular pitfall. The two to that immediately come to mind are loops and recursion.

We'll test loops out in the python shell:

 
>>> # traditional
>>> string = "....//....//....//....//"    
>>> while "../" in string: string = string.replace('../','')
...
>>> string
''
>>> # regular expressions
>>> string = "....//....//....//....//"
>>> while re.search('[.]{2}\/',string): string = re.sub('[.]{2}\/','',string)
...
>>> string
''
 

Python

 
def safereplace(text, replacement, string):   # Traditional Replacement
  if text in string: return safereplace(text, replacement, string.replace(text, replacement))
  return string
 
def safesub(pattern, replace, string):           # Pattern/regex
  if re.search(pattern,string): return safesub(pattern, replace, re.sub(pattern, replace, string))
  return string
 
>>> string = "....//....//....//....//"
>>> safereplace("../","",string)
''
>>> safesub('[.]{2}\/','',string)
''
 

PHP

Note: Additional recursion functions should be added for preg_replace and ereg_replace if you plan on using these for sanitizing.

 
function safe_str_replace($search, $replace, $string) {
  if (strpos($string, $search) >== 0) return  safe_str_replace($search,$replace,str_replace($search, $replace,  $string));
  return($string);
}
 

Perl

 
sub safe_str_replace {
    my $pattern         = shift;
    my $replacement = shift;
    my $string           = shift;
    $string =~ s/$pattern/$replace/g until ($string !~ /$pattern/);
    return($string);
}
 

Ruby

 
def safe_sub(pattern, replacement, string)
  if (string ~ '/#{pattern}/') return safe_sub(pattern, replacement, string.gsub(pattern,replacement))
  return string
end
 

Auditing

 
find -regextype posix-awk -regex ".*\.e?(rb|py|php)"  -exec grep -HnC2 \\b\\(.*sub\|replace\\)\( '{}' \; &>  string_replacement.txt
 

Integer handled as string

Improper type handling is as common as it is dangerous. If a programmer makes the mistake of handling an integer as a string (or other type confusions), sanitizing inputs can become useless. There are a variety of approaches for mitigation of this type of issue.

Examples of integral handling vulnerabilities

PHP

 
<?php
   $id = mysql_real_escape_string($_GET['id']);
   @mysql_query("SELECT * FROM user WHERE user_id = " . $id . " LIMIT 1");
?>
 

Mitigation

If user_id is an integer, this statement is still vulnerable, if a user inputs "id=1%20OR1=1", the statement will not be escaped (since there are no quotes) and still a valid SQL statement, rendering the database open to injection. While this example uses SQL, other situations may still be vulnerable. In this case, for forward compatibility and "universal solution" purposes, the programmer should use the appropriate native (or generic) method to cast to an integer instead of mysql_real_escape_string():

PHP

 
<?php
  $id = (int)$_GET['id'];
  @mysql_query("SELECT * FROM user WHERE user_id = " . $id . " LIMIT 1");
?>
 

Ruby

Warning: (link to ruby paramaterized section)Activerecord supports paramaterized statements(/link). It is likely that if you need to use this in conjunction with active record, you may be (link to unparamaterized queries) doing something wrong (/link).

Auditing

Improper signedness

Improper signedness is caused by allowing signed data when expecting unsigned data. This can cause information disclosure or even code execution in extreme circumstances. Our previous "fixed" code in the "Integer Handled as String" section does still have this problem. It is handled as an integer but the sign is not considered, as "-10" is still a valid integer, this will slip through the (int) cast. Therefore, we need to pass this to the abs() function which takes the absolute value of the data.

Mitigation

PHP

 
<?php
  $id = abs((int)$_GET['id']);
  @mysql_query("SELECT * FROM user WHERE user_id = " . $id . " LIMIT 1");
?>
 

Auditing

Unparamaterized Statements

Examples

Mitigation

Rails

 
user = User.Find(:conditions => ['id = ?', params[id]]  
 

Auditing

Pitfalls resulting from improper combination of whitelisting and modification

Delete after length-check (Empty Where Clauses)

Proof of concept

While the example here is based on SQL injection, it can apply to other scenarios as well. Suppose the following php code:

 
<?php
  # pwreset.php
  if (strlen($_GET['reset']) == 16) {
    $pwreset_code = preg_replace_all('/([^\w]+)/','',$_GET['reset']); #alphanumeric only.
    $user_query   = "SELECT * FROM user WHERE pwreset_code= '" . $pwresetcode ."' LIMIT 1";
    $user_data    = @mysql_query($user_query);
    ...
  }
 
 
  // pwreset.php?reset=''''''''''''''''
  //  The generated sql statement:
  //  SELECT * FROM user WHERE pwreset_code='' LIMIT 1;
 

As reset codes are generated on reset request (forgot password form), this will leave the first user (usually the admin) of the web app exposed to arbitrary password overwrite.

Mitigation

Instead of checking length before replacement, verify after deletion.

PHP

 
<?php
  # pwreset.php
  $pwreset_code = preg_replace_all('/([^\w]+)/','',$_GET['reset']); #alphanumeric only.
  if (strlen($pwreset) == 16) {
    $user_query   = "SELECT * FROM user WHERE pwreset_code= '" . $pwresetcode ."' LIMIT 1";
    $user_data    = @mysql_query($user_query);
    ...
  }
 

In the wild

A Joomla! framework exploit leveraged this weakness for an administrative password reset (2008). CVE 2008-3861

Auditing

Follow the same auditing steps as listed for unsafe deletion, with an eye towards where clauses or other empty type comparisons that could lead to problematic outcomes.


Deletion or truncation after reformat

This vulnerability is caused by reformatting a string and then truncating it to a specific length, this allows an attacker to trigger an error and possibly execute code. For example, if we have a filter that runs mysql_real_escape_string() and then truncates the string to 16 characters, we could run into this problem. The attacker inputs "123456789012345'", when run through mysql_real_escape_string() it becomes "123456789012345\'", when truncated to 16 characters, this string finally becomes "123456789012345\", which would escape the quotes surrounding it. This would at the very least cause an error for information disclosure, but could also lead to sql injection and xss notwithstanding.

Examples

  * PHP:
    <?php
      $username = substring(mysql_real_escape_string($_GET['username']), 0, 16);
      $query   = "SELECT * FROM user WHERE username= '" . $username ."'";
      $user_data    = @mysql_query($query);
    ?>
  
   * Python:
     >>> username = "123456789012345'"
     >>> username = username.replace("'", "\\'")
     >>> print("SELECT * FROM users WHERE username = '%s'" % username[0:16])
     SELECT * FROM users WHERE username = '123456789012345\'
    
   * Perl:
     my $username = "123456789012345'";
     $username =~ s/\'/\\'/g;
     $username = substr($username, 0, 16);
     print "$username\n";

   Ruby:


 Mitigation:
   This attack can be mitigated by truncating the input before reformatting and checking the length (failing if not correct).
  
  * PHP:
    <?php
      $username = mysql_real_escape_string(substring($_GET['username'], 0, 16));
      if(strlen($username) == 16){
         $query   = "SELECT * FROM user WHERE username= '" . $username ."'";
         $user_data    = @mysql_query($query);
         ...
      }
    ?>
  
   * Python:
     >>> username = "123456789012345'"
     >>> username = username[0:16].replace("'", "\\'")
     >>> if len(username) == 16:
     ...        print("SELECT * FROM users WHERE username = '%s'" % username)

   * Perl:    
     my $username = "123456789012345'";
     $username = substr($username, 0, 16);
     $username =~ s/\'/\\'/g;
     if (length $username == 16){
       print "$username\n";
     }
  
 Auditing:

Common language-agnostic non-sanitizing related pitfalls

   Lack of paramaterization for prepared statements
      Examples:
    
      Mitigation:
    
      Auditing:
    
   Unsafe programmatic construct referencing
    Dynamic referencing of functions and classes through user input is  dangerous and should never be done, some examples are listed below.

     Unsafe PHP examples:
        Anonymous function implementation example:
          <?php
            $func = $_GET['func'];
            $func($args);
          ?>          

        Anonymous object implementation example:
          <?php
            $class = $_GET['object_type'];
            $object = new $class();
          ?>

        Anonymous object method implementation example:
          <?php
            $class    = $_GET['object_type'];
            $method = $_GET['method'];
            $object   = new $class();
            $object->$method();
          ?>
      
     Unsafe Perl examples:
        Anonymous function implementation example:
          #!/usr/bin/perl
          $func = <>;
          $func->();

        Anonymous object implementation example:
          #!/usr/bin/perl
          $class = <>;
          $object = $class->new();

        Anonymous object method implementation example:
          #!/usr/bin/perl
          $class = <>;
          $method = <>;
          $object = $class->new()->method();
        
     Unsafe Python examples:
        Anonymous function implementation example:
          var = "function_name"
          locals()[var]() # or globals()

        Anonymous object implementation example:
          class_name = input()
          instance = globals()[class_name]()
          # OR
          # instance = locals()[class_name]()
           # instance = dict(target  for classlist in  [inspect.getmembers(sys.modules[module],inspect.isclass) for module in  sys.modules]  for target in classlist)[class_name]()
           any_scoped_class = lambda x:  dict(target  for classlist in   [inspect.getmembers(sys.modules[module],inspect.isclass) for module in   sys.modules]  for target in classlist)[x]()
          
        Anonymous object method implementation example:
          ClassName = "ObjectName"
          func_name = "object_method"
          instance = globals()[ClassName]()
          func = getattr(instance, func_name)
          instance.func();

     Unsafe ruby examples:
       Anonymous function implementation example:
         send("function_name")
        
       Anonymous object implementation example:
         Object.const_get('ClassName').new()
        
       Anonymous object method implementation example:
         Object.const_get("ClassName").new().send("function_name")

    Mitigation:
       These functions are best left unused unless the data is whitelisted and  not directly based on user input. There is no safe way to allow users  to call arbitrary (non-whitelisted) classes and functions. Do not do this!  Whitelists should always be in place when referencing objects from user input.

      Whitelisting examples:
      PHP:
         <?php
             $class_whitelist['class_name'] = 'real_class_name';
            
             if (isset($_GET['module']) &&                                                # Check that input is defined
                  isset($class_whitelist[$_GET['module']]   &&                       # Be sure the class is in the whitelist
                  class_exists($class_whitelist[$_GET['module']])) {              # even  if its whitelisted, lets make sure its a class... typos exist.

                    $class = new $class_whitelist[$_GET['module']]();            # new  real_class_name() - this way user input isn't trusted directly for  instantiation.
             } else {
                 # Handle Error...
             }
         ?>


    Auditing:
      To audit:
        find -name \*.py -exec grep -EHnC2 "[a-zA-Z0-9\)]\^\'\"\\\+\]\(" '{}' \; \
               -o -name \*.rb -exec grep -EHnC2 "const_get\(\|send\(\|eval\(" '{}' \; &>  string_comparison.txt
              
   Unsafe file i/o:
       Accepting path names as input over the network should be avoided if at all possible, and instead should use a whitelist containing possible file-names.  Accepting file names as input will cause  file disclosure, source disclosure, and file inclusion  vulnerabilities.  This can apply to more than file inclusion.  Some  languages even output entire directory listings when "." is read as a  file.  Filenames provided by user input simply should not be used.  An  SQL database (for constantly changing filenames and updated listings) or  other similar indexing method (static array whitelisting) can provide a  safe way (like an ID) for file access without allowing the user to  directly specify the path to the file on the server.

 Auditing:

   Unsanitized input split
        If input is being split and parsed based on an expected number of  delimeters, it is trivial for an attacker to input an extra delimeter  and depending on the severity of the issue, execute code or other  mayhem.
      
   Proof of concept:

 * PHP:
   <?php
     $username = $argv[1];
     if ($username == "admin") {
       $username = $username . ":1";
     } else {
       $username = $username . ":0";
    }
    $username = split(":", $username);
    if($username[1] == "1") {
      echo "Is an admin\n";
    } else {
      echo "Not an admin\n";
    }
   ?>

   Mitigation:
  
    In order to mitigate this attack, sanitize input before splitting. Be  certain that there are no malicious delimeters. For example:
  
 * PHP:
   <?php
     $username = str_replace(":", "", $argv[1]);
     if ($username == "admin") {
       $username = $username . ":1";
     } else {
       $username = $username . ":0";
    }
    $username = split(":", $username);
    if($username[1] == "1") {
      echo "Is an admin\n";
    } else {
      echo "Not an admin\n";
    }
   ?>
  
   Auditing:
  
   Unsafe evaluation
     Proof of concept:
       It is never safe to eval() user input, plain and simple.  There is always another way to skin the cat; try and consider that!
    
     Mitigation:
       Design around combining the function "eval" with user input.
    
     Auditing:
       IGNORECASE=1 find -regextype posix-awk -regex ".*\.(rb|php|pl|py|pm)" -exec grep -HnC2 \\beval\( '{}' \;

   Unsafe command processing
       Using user input as arguments for a system call should never be done.  The system() and exec() functions are mostly universal between languages along with the backtick (`) characters for command substitution.  In some languages, the $() operator for command substitution is also valid.

     Proof of concept:  
       >>> os.system("echo %s" % input("# "))
      # unsafe; ping -c1 google.com
      unsafe
      PING google.com (74.125.224.168) 56(84) bytes of data.
      64 bytes from lax02s01-in-f8.1e100.net (74.125.224.168): icmp_req=1 ttl=51 time=13.1 ms
    
    Mitigation:
      Instead of using system, backticks, popen, exec or any other command executing function, use the language's native built in library.   If it does not have one, then write one - but do not simply wrap  system() and call this a library; this is bad form.  All interpreted  languages have a way to load shared libraries (*.so files) and interface  with the functions provided by their export tables.  These are what  should be utilized when authoring libraries that seemingly need you to  run a command.  Instead, the C interface (CTypes) can be used.  
    
      Ctypes Examples:
      * Python:
          >>> from ctypes import *;  cdll.LoadLibrary("libc.so.6").printf(c_char_p("abcdefgh\n")); # import  printf() from libc.so.6
         'abcdefgh'
        
      * Perl:
    
      * PHP:
    
      * Ruby:
    

    Auditing:  
       Auditing command processing is simple: check for all uses of system(),  exec(), backticks, popen, and any language specific function.
    
        find -type f -regextype posix-awk -regex ".*\.(rb|php|py|pl|pm)"  -exec grep -EHnC2 "system\(\|[pP]open\(\|\`\|exec.*\(\|passthru\(" '{}' \; &>  command_processing.txt

   Value overwrite by mass assignment
      These are rare scenarios caused by mass object property assignment from  HTTP parameters.  Many web applications do this on one level or another  to simplify form generation.  Advanced ORM's are usually (but not  always) involved in this sort of problem, so be on the lookout for PHP's  "Doctrine", Ruby's "ActiveRecord", or Python's "sqlAlchemy".  
    
     Suppose when a signup form post occurs, an ajax request is sent to:
       /users/signup/?username=newguy&password1=crackme&password2=crackme&[email protected]
    
     And our SQL table creation statement looked something like:
    
      create table user (id int auto_increment primary key, group_id int  foreign key not null default 1, username varchar(24), password  varchar(512), activated timestamp, email varchar(256)) foreign key  (group_id) references group(id);
  
     The target software will automatically put new users in group 1  (non-activated users list) on registration.  Perhaps on update it would  update us to group 2, and additional groups became available for further  permissions.  
  
     Suppose the "administrators group" for the web application is group id  5.  When unchecked mass assignment is in place, the following signup URI  would make user `superhacker' an admin:
       /users/signup/?username=superhacker&password=1337&[email protected]&group_id=5
    
   Examples:
  
     PHP:
         <?php
            foreach ($_GET as $property => $value) {
              $user->$property = $value;
            }
            $user->save();
         ?>

     Python:
        for param in request.GET:
          user.__dict__[param] = request.GET[param]
    
     Ruby on Rails (ActiveRecord):
         user = User.new(params[:user])
        
   Mitigation:
       PHP:
      


    
   Auditing:

Common language-specific pitfalls:
 PHP specific pitfalls:
   File inclusion by remote and local

   Situationally bad sanitizing:
    addslashes()
    htmlspecialchars()
    mysql_real_escape_string()

 Perl specific pitfalls:
   Command injection with open()      

 Python specific pitfalls:
   Urllib opens/follows file:// resource location response headers   (Python)

 Ruby (eruby and rails) specific pitfalls:
   attr_protected
   CGI.EscapeHTML()