Popular Vulnerable Code

Creative –HTTP Header Injection

Details

Affected Software:WordPress

Fixed in Version:2.1

Issue Type:HTTP Header Injection

Original Code:Found Here

Description

This week’s bug was a Set-Cookie/HTTP Header injection bug in WordPress Core.  Looking at the code,we see that the WordPress Developers assign several variables directly from the $_POST body.  These assignments are listed below:

$comment_author       = trim($_POST['author']);

$comment_author_email = trim($_POST['email']);

$comment_author_url   = trim($_POST['url']);

$comment_content      = trim($_POST['comment']);

Each of these variables should now be considered tainted and should be sanitized/encoded before using their values in any operations.  It seems that the WordPress developers were aware that the values assigned to the variables listed above could not be trusted and proceeded to escape the values before using them in database related operations.  Some examples of the sanitization can are provided below:

$comment_author       = $wpdb->escape($user_identity);

$comment_author_email = $wpdb->escape($user_email);

$comment_author_url   = $wpdb->escape($user_url);

Unfortunately,the developers utilized the incorrect sanitizing routines for the tainted values in the setcookie API.  The developers used stripslashes() to sanitize the user controlled value before passing it to the setcookie() API.  Although stripslashes can help prevent other types of vulnerabilities,it was not intended to defend against HTTP HEADER injection or cookie poisoning.  By injecting a series of CRLF (%0d%0a) characters,an attacker could use this bug to inject arbitrary headers into the HTTP response and in some cases use this header injection bug for XSS.  The WordPress developers addressed this issue by passing the attacker controlled value to the clean_url() function before allowing it in the setcookie.  The full source for clean_url() can be found here,however the more interesting sanitizing routines are provided below:

$url = preg_replace(‘|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i’,”,$url);

$strip = array(‘%0d’,‘%0a’,‘%0D’,‘%0A’);

$url = _deep_replace($strip,$url);

$url = str_replace(‘;//’,‘://’,$url);

Developers Solution

<?phprequire( dirname(__FILE__) . '/wp-config.php' );nocache_headers();$comment_post_ID = (int) $_POST['comment_post_ID'];$status = $wpdb->get_row("SELECT post_status,comment_status FROM $wpdb->posts WHERE ID = '$comment_post_ID'");if ( empty($status->comment_status) ){do_action('comment_id_not_found',$comment_post_ID);exit} elseif ( 'closed' == $status->comment_status ){do_action('comment_closed',$comment_post_ID);die( __('Sorry,comments are closed for this item.') )} elseif ( 'draft' == $status->post_status ){do_action('comment_on_draft',$comment_post_ID);exit}$comment_author  = trim($_POST['author']);$comment_author_email = trim($_POST['email']);$comment_author_url  = trim($_POST['url']);$comment_content = trim($_POST['comment']);// If the user is logged inget_currentuserinfo();if ( $user_ID ):$comment_author  = $wpdb->escape($user_identity);$comment_author_email = $wpdb->escape($user_email);$comment_author_url  = $wpdb->escape($user_url);else:if ( get_option('comment_registration') )die( __('Sorry,you must be logged in to post a comment.') );endif;$comment_type = '';if ( get_settings('require_name_email') &&!$user_ID ){if ( 6 >strlen($comment_author_email) || '' == $comment_author )die( __('Error:please fill the required fields (name,email).') );elseif ( !is_email($comment_author_email))die( __('Error:please enter a valid email address.') )}if ( '' == $comment_content )die( __('Error:please type a comment.') );$commentdata = compact('comment_post_ID','comment_author','comment_author_email','comment_author_url','comment_content','comment_type','user_ID');wp_new_comment( $commentdata );if ( !$user_ID ):setcookie('comment_author_' . COOKIEHASH,stripslashes($comment_author),time() + 30000000,COOKIEPATH,COOKIE_DOMAIN);setcookie('comment_author_email_' . COOKIEHASH,stripslashes($comment_author_email),time() + 30000000,COOKIEPATH,COOKIE_DOMAIN);-setcookie('comment_author_url_' . COOKIEHASH,stripslashes($comment_author_url),time() + 30000000,COOKIEPATH,COOKIE_DOMAIN);+setcookie('comment_author_url_' . COOKIEHASH,stripslashes(clean_url($comment_author_url)),time() + 30000000,COOKIEPATH,COOKIE_DOMAIN);endif;$location = ( empty( $_POST['redirect_to'] ) ) ? get_permalink( $comment_post_ID ):$_POST['redirect_to'];wp_redirect( $location );?>

These Pipes are Clean –CRLF

Details

Affected Software:WordPress

Fixed in Version:2.8.1

Issue Type: URL Redirection / CRLF Injection / HTTP Header Injection / XSS

Original Code:Found Here

Description

When appended together the %0d (Carriage Return) and %0a (Line Feed) characters represent a Carriage Return Line Feed (CRLF).

The vulnerable WordPress code snippet actually contained logic to detect carriage returns and line feed characters,attempting to strip CRLF from data being assigned to the $url variable.

The CRLF detection logic used by the vulnerable WordPress version was not very robust.  The CRLF detection logic simply checked for the presence of “%0d”and “%0a” in the $url variable and failed to consider UPPERCASE “%0D”or “%0A”.  The $url variable is assigned the CRLF tainted string and eventually passed to a HTTP Location header,giving the attacker an opportunity for URL Redirection,CRLF injection,HTTP header injection,and even Cross Site Scripting (XSS).

In addition to adding UPPERCASE variants of “%0D”and “%0A”to the detection logic,a function to recursively detect the presence of CRLF was also added.  Before this function was added,it was possible to defeat the detection logic by simply passing a string such as %0%0d%0ad%0%0d%0aa which would have “%0d%0a” character sequences stripped out,resulting in %0d%0a being passed to the $url variable.  The WordPress developers addressed this issue by adding a recursive verifier (_deep_request()),whose source is included in the Developers Solution.

Developers Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
function clean_url( $url,$protocols = null,$context = 'display' ) {
$original_url = $url;

if ('' == $url) return $url;
$url = preg_replace('|[^a-z0-9-~+_.?#=!&amp;;,/:%@$\|*\'()\\x80-\\xff]|i','',$url);
-       $strip = array('%0d','%0a');
-       $url = str_replace($strip,'',$url);
+       $strip = array('%0d','%0a','%0D','%0A');
+       $url = _deep_replace($strip,$url);
$url = str_replace(';//','://',$url);

if ( strpos($url,':') === false &amp;&amp;
substr( $url,0,1 ) != '/' &amp;&amp;substr( $url,0,1 ) != '#' &amp;&amp;!preg_match('/^[a-z0-9-]+?\.php/i',$url) )
$url = 'http://' . $url;

// Replace ampersands and single quotes only when displaying.
if ( 'display' == $context ) {
$url = preg_replace('/&amp;([^#])(?![a-z]{2,8};)/','&amp;$1',$url);
$url = str_replace( "'",''',$url );
}

if ( !is_array($protocols) )
$protocols = array('http','https','ftp','ftps','mailto','news','irc','gopher','nntp','feed','telnet');
if ( wp_kses_bad_protocol( $url,$protocols ) != $url )
return '';

return apply_filters('clean_url',$url,$original_url,$context);
}

With _DEEP_REPLACE for recursive checks!
+
+function _deep_replace($search,$subject){
+        $found = true;
+        while($found) {
+                $found = false;
+                foreach( (array) $search as $val ) {
+                        while(strpos($subject,$val) !== false) {
+                                $found = true;
+                                $subject = str_replace($val,'',$subject);
+                        }
+                }
+        }
+
+        return $subject;
+}