Jeu sécurité

Le bug du mois de juin

Proposé par Eric Vernié

Dans ce code il existe plusieurs trous de sécurité : à vous de les découvrir !

DataSet RecupererDonnees(String^ OrderID)
{

String^ sqlStmt = "Select OrderID from Orders where OrderID='" + OrderID + "'";
SqlClient::SqlConnection^ cnx;
cnx=gcnew SqlClient::SqlConnection(ChaineConnexion()) ;
SqlCommand^ cmd=cnx->CreateCommand ();
cmd->CommandText =sqlStmt;
cmd->CommandType =CommandType::Text ;

cmd->Connection =cnx;

SqlDataAdapter^ da=gcnew SqlDataAdapter (cmd);
DataSet^ ds=gcnew DataSet();
cnx->Open ();
da->Fill (ds);

return ds;
}


La bonne réponse

La bonne réponse est :
Une injection SQL est possible, et un crash mémoire est possible (déni de service)


En effet, même si la requête SQL est en dur dans le code, rien n’indique ici, que ce code ne soit pas exécuté sur un serveur. Le binaire n’étant pas directement visible, il est difficile de pouvoir le désassembler pour voir les chaînes de caractères qu’il comporte. Bien évidement, pour les puristes, une procédure stockée serait la bienvenue.

Aucun test de saisie n’est pris en compte. Un déni de service est toujours possible si l’utilisateur envoi un OrderID de 50 G0. L’injection SQL est également possible.
Dans cette exemple, nous concaténons, via une requête SQL, la saisie de l’utilisateur contenu dans OrderID.

Rien n’empêche l’utilisateur de passer dans OrderID, une chaine du type : Bogue’ ; Select * from sysobjects; --
La concaténation, dans ce cas là, forme une requête SQL tout à fait valide et le résultat sera le retour de tous les objets définis dans la base de données.


Et le code corrigé

Proposé par Eric Vernié

<pseudo Code="">
DataSet RecupererDonnees(String^ OrderID)
{
//1) Testez la longueur OrderID : Evite qu’une trop grande chaine soit envoyé (DOS)
if (OrderID->Length >MAX_LEN)
Throw new Exception(....)

//2) Vérifiez que cette saisie correspond à une pattern bien définie
if (Regex::IsMatch (OrderID,"MaPattern") ==false
Throw new Exception

//3) Utilisez les objets commande qui formaterons correctement la requête SQL

SqlClient::SqlConnection cnx;
cnx.ConnectionString= ChaineConnexion(); SqlClient::SqlParameter param;
param.DbType =DbType::String ;
param.ParameterName ="@OrderID";
param.Value =OrderID;
SqlClient::SqlCommand cmd;
cmd.CommandText =" Select * from employes where login=@OrderID ";
....
cmd.Connection =%cnx;
cmd.Parameters->Add (%param);

SqlDataAdapter^ da=gcnew SqlDataAdapter (cmd);
DataSet^ ds=gcnew DataSet();
cnx->Open ();
da->Fill (ds);
return ds;
}

Note :
Nous pourrions encore améliorer ce code, en appelant une procédure stockée, et en gérant les exceptions correctement.

Pour aller plus loin

Un article à lire consacré à l’injection SQL

Le webcast : Ecrire du code sécurisé : modélisation des menaces ? Exemples concrets - Partie I

Le webcast : Rencontres Sécurité - partie 6 : toute saisie est source de problèmes

Consultez la rubrique « Introduction à la sécurité », comportant des articles indispensables et des webcasts pour bien commencer

Le centre de conseils sur la sécurité pour les développeurs

Writing Secure Code, l’ouvrage de référence 

Le règlement | Comment jouer ? | Liste des gagnants


**
**
**
**
**
**