OVH Cloud OVH Cloud

[Securite] magic_quotes et SQL

9 réponses
Avatar
r
Bonjour à tous,

Pensez-vous qu'on peut balancer des arguments utilisateur directement dans
une requête SQL si le magic_quotes_gpc est activé ?

J'ai vu des bouts de code où les types faisaient un truc du genre :
mysql_real_escape_string(stripslashes($_REQUEST['arg']));

Je pense qu'ils ont raison, car :
mysql_real_escape_string() calls MySQL's library function
mysql_real_escape_string, which prepends backslashes to the following
characters: \x00, \n, \r, \, ', " and \x1a.

Merci.
Raphael

9 réponses

Avatar
Patrick Mevzek
Pensez-vous qu'on peut balancer des arguments utilisateur directement dans
une requête SQL si le magic_quotes_gpc est activé ?


Oui.
Ceux qui croient que les injections SQL viennent du guillement/apostrophe
n'ont rien compris ni au SQL, ni aux injections SQL, ni à la sécurité.

J'ai vu des bouts de code où les types faisaient un truc du genre :
mysql_real_escape_string(stripslashes($_REQUEST['arg']));


Cela reste vulnérable à une injection SQL.
Ce n'est pas la bonne façon de faire, sans compter que bonjour la
portabilité ....

Je pense qu'ils ont raison, car :
mysql_real_escape_string() calls MySQL's library function
mysql_real_escape_string, which prepends backslashes to the following
characters: x00, n, r, , ', " and x1a.


On peut faire des injections SQL sans ces caractères...

--
Patrick Mevzek . . . . . . Dot and Co (Paris, France)
<http://www.dotandco.net/> <http://www.dotandco.com/>

Avatar
r
Salut Patrick,
Est-ce que tu pourrais développer un peu stp ?
Quelle est la "bonne methodologie" selon toi ? Controler type et format pour
chaque argument ?
Merci.
Raph

Pensez-vous qu'on peut balancer des arguments utilisateur directement
dans
une requête SQL si le magic_quotes_gpc est activé ?


Oui.
Ceux qui croient que les injections SQL viennent du guillement/apostrophe
n'ont rien compris ni au SQL, ni aux injections SQL, ni à la sécurité.

J'ai vu des bouts de code où les types faisaient un truc du genre :
mysql_real_escape_string(stripslashes($_REQUEST['arg']));


Cela reste vulnérable à une injection SQL.
Ce n'est pas la bonne façon de faire, sans compter que bonjour la
portabilité ....

Je pense qu'ils ont raison, car :
mysql_real_escape_string() calls MySQL's library function
mysql_real_escape_string, which prepends backslashes to the following
characters: x00, n, r, , ', " and x1a.


On peut faire des injections SQL sans ces caractères...



Avatar
Patrick Mevzek
Est-ce que tu pourrais développer un peu stp ?


Je viens de répondre au post plus récent «Sécurité et faille» et il
y a la bonne méthodologie dedans (qui se résume à : vérifier le format
de ce qu'on reçoit)

Controler type et format pour chaque argument ?


Oui.
Plus généralement, en termes de sécurité, maintenir des listes noires
de choses interdites et laisser passer tout le reste, c'est connu, ca
aboutit Murphy aidant à un oubli et donc une faille. Donc il faut
inverser, maintenir une liste de ce qu'on autorise, et refuser tout le
reste.

Ajouter dans sa boîte à outils :
1) utiliser une bibliothèque intelligente qui gère la protection des
caractères sensibles selon le SGBDR, de manière portable.
2) utiliser un SGBDR qui supporte les «prepared statements» afin de
pouvoir envoyer d'une part la requête sans les variables, d'autre part
les variables, ce qui est tout bonus pour la sécurité et a priori les
performances.

Car, protéger le guillemet n'atteint pas l'objectif escompté, sans
parler de l'erreur sémantique sous-jacente.

Pour les incrédules :
SELECT * FROM user WHERE id=$_POST[id]
est exploitable sans utiliser de guillemets,
sans compter envoyer plusieurs requêtes (merci le ; - mais pareil ca ne
sert à rien de le filtrer), car oui c'est possible malgré ce que
certains puissent croire, y compris en MySQL (dépend de la version, et de
la configuration, il ne vaut donc mieux pas espérer utiliser cela comme
verrouillage de sécurité).

Et je ne vous parle même pas des problèmes d'échappement des
caractères multi-octets (cf failles récentes chez PostgreSQL), l'usage
des commentaires SQL, les obfuscations diverses et variées, ou l'appel à
des commandes externes du SGBDR depuis la requête SQL.

<shameless plug>
Le cours de la mairie de paris sur Web et bases de données parle en
grandes largeurs de ces problèmes de sécurité, en général, et en
particulier avec PHP.
</shameless>

--
Patrick Mevzek . . . . . . Dot and Co (Paris, France)
<http://www.dotandco.net/> <http://www.dotandco.com/>

Avatar
MaXX
Patrick Mevzek wrote:
Pensez-vous qu'on peut balancer des arguments utilisateur directement dans
une requête SQL si le magic_quotes_gpc est activé ?
[...]


Je pense qu'ils ont raison, car :
mysql_real_escape_string() calls MySQL's library function
mysql_real_escape_string, which prepends backslashes to the following
characters: x00, n, r, , ', " and x1a.


On peut faire des injections SQL sans ces caractères...
Ca a éveillé ma curiosité et je me suis mis à faire des petits tests

pour me convaincre qu'une injection sql n'a pas besoin de caractères
spéciaux... Bah c'est ridiculement facile, j'espère juste ne pas donner
de mauvaise idées à certains wanabes h4x0rZ...

D'accord, j'ai fabriqué le truc de toutes pièces mais ça ressemble
tellement à certains exemples que l'on trouve sur le web que ca en est
presque crédible...

NB: j'utilise postgresql dans l'exemple, mais ça doit fonctionner sur
tous les SGDB qui supportent UNION.

Imaginons une script voir_utilisateur.php contenant ceci:
<pre>
<?php
//$id­dslashes($_GET['id']);
$id=$_GET['id'];
$q="SELECT nom,prenom FROM utilisateurs WHERE id=$id LIMIT 1";
//print $q;
$r=pg_query($q);
while ($data = pg_fetch_row($r)){
print "l'utilisateur avec l'indentifiant $id s'appelle
".$data[0]." ".$data[1];
}
?>
</pre>
donnons lui a manger cette url
/voir_utilsateur.php?id=1%20UNION%20SELECT%20login%2Cmotdepasse%20FROM%20utilisateurs%20%3B--

ce qui donnera:
l'utilisateur avec l'indentifiant 1 UNION SELECT login,motdepasse FROM
utilisateurs ;-- s'appelle denis 098f6bcd4621d373cade4e832627b4f6
l'utilisateur avec l'indentifiant 1 UNION SELECT login,motdepasse FROM
utilisateurs ;-- s'appelle jean_22 8b3a7c1e2d97de605878066ec7b6be70
...
... tous les autres enregistrements de la base...
...
l'utilisateur avec l'indentifiant 1 UNION SELECT login,motdepasse FROM
utilisateurs ;-- s'appelle Dupont Jean

Que s'est-il passé? Simple la requète a été interprété comme:
'SELECT nom,prenom FROM utilisateurs WHERE id=1 UNION SELECT
login,motdepasse FROM utilisateurs'. LIMIT 1 a été mis en commentaire
donc n'a pas pu fonctionner... Le serveur a gentillement fait ce qu'on
lui demandait et servi le résultat.

UNION dit au serveur d'accoler les résultats des deux requètes à la
condition que le nombre de colonnes soit identiques. Si par ex il y a
plus de colonnes dans la requete initiale, on ajoute quelques ',1', si
il y en a moins on peu concaténer plusieures colonnes entre elles.

Et addslashes? pas de guillemets, pas de slashes...
magic_quotes? idem
mysql_real_escape? pas pu tester, mais il semblerait que l'injection ne
serait pas passée à cause de la virgule. Pas sur pour autant qu'il n'y
ait pas moyen de contourner ça, par exemple en abandonnant la requète
initiale et en lui accolant un `;update utilisateurs set
motdepasse=chr(65)` rageur par exemple.

Découvrir la structure d'une table n'est pas très difficile, les
messages d'erreurs sont souvant explicites...

Les solutions:

Le seul truc que j'ai trouvé et qui fonctionne pour empécher ça sans
toucher le code, c'est mod_security avec la config par defaut. ca donne
une superbe erreur "406 Not Acceptable". (Faire gaffe, la config par
defaut ne laissera même pas passer un accent; faut l'ajuster un rien.)
Plus généralement, mod_security est un bon garde fou pour apache, et
apparament la version 2 fonctionnera aussi avec IIS.
http://www.modsecurity.org/

En touchant le code, dans cet exemple on voit clairement que le script
aurait du *refuser* autre chose qu'un nombre entier, un petit `if
(!is_numeric($id)){print "erreur";die;}` fera bien l'affaire, preg_match
tout autant.
Perso je préfère preg_match car elle permet de récupérer certaines
erreurs de saisies dans les formulaires: is_numeric(' 4 ') -> FALSE
tandis que preg_match('/(d+)/',' 4 ') me permet de récupérer une valeur
acceptable sans forcer l'utilisateur à réintroduire les données).
Pour les champs texte, j'utilise soit des requètes préparée soit le
"dollar quoting" (spécifique à PostgreSQL).

Conclusion validez toutes vos entrés et cachez le plus possible les
messages d'erreur dans la version exposée au web sans oublier de faire
backups régulierement.

Bon je retourne à la recherche de ce genre de bourdes dans mon appli,
--
MaXX


Avatar
Pascal
Le Fri, 08 Sep 2006 11:25:38 +0000, MaXX a écrit :


Imaginons une script voir_utilisateur.php contenant ceci:
<pre>
<?php
$q="SELECT nom,prenom FROM utilisateurs WHERE id=".(int)$id." LIMIT
1;
?>
</pre>


Et voila, plus de soucis... :)

Pascal

Avatar
r
Imaginons une script voir_utilisateur.php contenant ceci:
<pre>
<?php
$q="SELECT nom,prenom FROM utilisateurs WHERE id=".(int)$id." LIMIT
1;
?>
</pre>



moi je fais ça aussi --> intval($id)


Avatar
MaXX
Pascal wrote:
Imaginons une script voir_utilisateur.php contenant ceci:
<pre>
<?php
$q="SELECT nom,prenom FROM utilisateurs WHERE id=".(int)$id." LIMIT
1;
?>
</pre>


Et voila, plus de soucis... :)
Yep, j'y pensait plus a celui là...


J'arrive pas à comprendre comment des sites genre php.net présentent de
scripts avec des imbécilité pareilles alors que c'est si simple de
rendre l'exemple plus sur...

Merci de m'avoir rafraichi la mémoire...
--
MaXX


Avatar
Pascal
Le Fri, 08 Sep 2006 16:01:52 +0000, MaXX a écrit :

Pascal wrote:
Imaginons une script voir_utilisateur.php contenant ceci:
<pre>
<?php
$q="SELECT nom,prenom FROM utilisateurs WHERE id=".(int)$id." LIMIT
1;
?>
</pre>


Et voila, plus de soucis... :)
Yep, j'y pensait plus a celui là...



Simple, rapide et efficace :)

Intval(), trop long à taper...


J'arrive pas à comprendre comment des sites genre php.net présentent de
scripts avec des imbécilité pareilles alors que c'est si simple de
rendre l'exemple plus sur...


C'est pas le top, "l'erreur" n'est pas géré. Mais comme la requête ne
retourne pas de résultat (si l'id est de type autoincrement), ça me va.


Merci de m'avoir rafraichi la mémoire...


De rien,
Pascal

--
Suffit de regarder l'exemple de la documentation de hhline, pour
foncer illico à la cuvette des WC. Dégoût et des couleurs, quoi.
-+- Joss in fr.comp.text.tex -+-



Avatar
John GALLET
Simple, rapide et efficace :)


Ca dépend. Tout seul, ça peut devenir "simpliste, facile à oublier et
mal conçu.".

Une application c'est du code :
1) qui fonctionne

2) qui gère les erreurs de bonne foi des utilisateurs avec un moyen de
reprise sur erreur

3) sécurisée

Inversez le 2 et le 3 si vous voulez, cette liste n'est pas un ordre de
priorité.

S'il y a côté client du JS qui valide que ce champ est bien un INT pour
assurer le confort du client, c'est une solution acceptable (le code
proposéd de caster en int, pas le JS, mais c'est un autre débat). Sinon
il faut aussi gérer pour le client les erreurs "normales" comme la
saisie par erreur d'un espace : "1 0" par exemple. Et là caster en int
c'est n'importe quoi sauf à considérer que systématiquement la valeur 0
est interdite.

J'arrive pas à comprendre comment des sites genre php.net présentent de
scripts avec des imbécilité pareilles alors que c'est si simple de
rendre l'exemple plus sur...
Les avis sont encore extrêment partagés sur register_globals, alors les


injections SQL...

C'est pas le top, "l'erreur" n'est pas géré. Mais comme la requête ne
retourne pas de résultat (si l'id est de type autoincrement), ça me va.
Moi ça ne me va pas, parce que ZERO est une valeur possible en autoincr

(dépend du sgbr, et peut parfaitement être insérée manuellement, surtout
en cas de migration de données) et surtout parce qu'il n'y a pas que des
autoincrément dans la vie (quantités aussi au hasard !) ni que des SELECT.

a++;
JG