Difference between revisions of "Secure Programming"
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. | |
− | + | ===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>}} | ||
− | + | ===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==== | |
− | + | {{code|text=<source lang="php"> | |
− | + | <?php | |
− | + | $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=== | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
+ | ==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==== | ||
+ | {{code|text=<source lang="php"> | ||
+ | <?php | ||
+ | $id = abs((int)$_GET['id']); | ||
+ | @mysql_query("SELECT * FROM user WHERE user_id = " . $id . " LIMIT 1"); | ||
+ | ?> | ||
+ | </source>}} | ||
+ | ====Auditing==== | ||
+ | |||
+ | ==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: | Pitfalls resulting from improper combination of whitelisting and modification: | ||
Delete after length-check (Empty Where Clauses) | Delete after length-check (Empty Where Clauses) |
Revision as of 09:51, 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
- 1 Concepts
- 2 Common pitfalls of traditional whitelisting:
- 3 Common pitfalls encountered during input modification
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 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 $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()