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-~+_.?#=!&;,/:%@$\|*\'()\\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 &&
substr( $url,0,1 ) != '/' &&substr( $url,0,1 ) != '#' &&!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('/&([^#])(?![a-z]{2,8};)/','&$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;
+}