web-dev-qa-db-fra.com

Exploiter la vulnérabilité MD5 sous cette forme PHP?

J'ai pratiqué dans des sujets liés à la sécurité et je suis tombé sur ce problème que je ne comprends pas du tout. Vous recevez un formulaire avec une entrée nommée pass, et voici le code que vous devez contourner:

<?php
error_reporting(0);
session_save_path('/home/mawekl/sessions/');
session_start();
echo '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>';
echo 'This task is as hard to beat as the castle which you can see on the bottom.<br>';
echo '<pre>';
include('./ascii.txt');
echo '</pre><br>';

$_SESSION['admin_level']=1;

if (!isset($_POST['pass']) || md5($_POST['pass'])!='castle')
{
    echo '<b>Wrong or empty password.</b><br>';
    $_SESSION['admin_level']=0;
}

S'il entre dans la dernière instruction if, vous perdez (besoin de le rendre $_SESSION['admin_level'] reste à 1).

Toute aide est la bienvenue, merci!

Précision:

Je ne peux pas modifier le code que j'ai publié. C'est un défi. Tout ce que je peux faire, c'est envoyer un mot de passe via une entrée dont le nom est "pass". Oui, je sais que md5 est censé renvoyer une chaîne longue de 32 caractères. Voilà le défi.

42
Tom

Essayez d'envoyer une demande HEAD.

Je suppose qu'avec ascii.txt inclus, la sortie du script est juste au-dessus d'un nombre agréable comme 4096 octets, un output_buffering valeur. Une fois que le script a écrit output_buffering octets, il doit vider le tampon de sortie avant de continuer.

Normalement, cela fonctionne bien et le script continue, mais si le type de demande est HEAD, il n'y a nulle part où aller et l'exécution s'arrête, espérons-le au milieu du message "mauvais mot de passe" qui signifie admin_level n'est jamais remis à 0.

Si ascii.txt est un fichier que vous pouvez contrôler, vous devrez ajuster la taille pour que les nombres dépassent output_buffering lors de l'écriture du message "mauvais mot de passe".

Voici un article sur cette technique . Il n'est peut-être pas toujours applicable en fonction de la version PHP version/config, mais le dernier exemple de l'article est tellement similaire à ce problème que je m'attendrais à ce que ce soit la solution prévue.

59
gabedwrds

La faille de sécurité n'est pas dans MD5 dans ce cas. Et l'idée n'est pas de faire évaluer if (!isset($_POST['pass']) || md5($_POST['pass'])!='castle') en faveur du pirate. La vulnérabilité apparaît lorsque vous plantez le script. Les privilèges d'administrateur ne sont pas conditionnels. Peu importe ce que l'utilisateur obtient son autorisation élevée en définissant 1 sur $_SESSION['admin_level']. PHP est procédural. Donc, si vous pouvez commettre une erreur dans l'instruction IF suivante, l'autorisation est non seulement définie mais elle reste persistante. Elle est maintenant disponible pour le reste du programme car elle est stockée dans le même si le script se bloque et fait une erreur. Donc, si l'instruction IF n'existe que lors d'une connexion, l'attaquant peut visiter tous les autres scripts restreints en tant que niveau admin_level défini sur 1.

Donc chronologie des événements:

  • L'attaquant met des données incorrectes ou un débordement de données dans la demande POST
  • Le script s'exécute
  • Le script donne admin_level de 1 qui est stocké dans la session
  • Bombes scriptées à l'évaluation
  • L'attaquant va sur une autre page/script qui cherche à voir si $_SESSION['admin_level'] est 1

Cela pourrait être évité si le script faisait de la valeur de session une partie de l'instruction IF:

if (!isset($_POST['pass']) || md5($_POST['pass'])!='castle')
{
    echo '<b>Wrong or empty password.</b><br>';
    $_SESSION['admin_level']=0;
} else {
    $_SESSION['admin_level']=1;
}

Cependant, il reste encore beaucoup à faire pour sécuriser ce code.

27
Bacon Brad

J'ai vu un défi similaire quelque part. Essayez d'envoyer quelque chose comme 'QNKCDZO' En entrée. md5('QNKCDZO') est '0e830400451993494058024219903391' (notez le préfixe 0e dans le hachage indiquant la notation scientifique d'un nombre) et puisque le code utilise l'opérateur != (au lieu de le type sensible !==) PHP (anciennes versions uniquement?) le convertira en un nombre avant de le comparer. De toute évidence, les deux seront ensuite évalués à 0 et Vous gagnez.

Edit: le ruakh a raison. Cela ne fonctionne pas tout à fait, car le hachage et la chaîne 'castle' Devraient ressembler à un nombre à PHP pour que la conversion se produise.

17
Younis Bensalah

Je n'ai pas pu tester en PHP 5.5.9, et ce que je vais proposer ne fonctionne pas sur mon PHP 5.6.22 (mais j'aurais peut-être fait une erreur stupide).

La seule solution de contournement que je peux comprendre est que MD5 renvoie quelque chose qui est traité comme un nombre . J'ai googlé et trouvé quelque chose qui semble pertinent .

9
LSerni

Je vois que vous avez accepté la solution gabedwrds (qui est excellente, soit dit en passant), mais je voulais publier une solution potentielle que j'ai utilisée dans le passé lors d'une pratique intéressante de pentesting. (Je ne suis en aucun cas un expert dans ce domaine, mais je suis parfois un hobbiest.)

Si vous le remarquez, le script ne définit jamais de stratégie pour gérer un abandon d'utilisateur. Par défaut (sauf configuration contraire dans le PHP ini), si un utilisateur abandonne sa connexion pendant le chargement d'une page, le script PHP se ferme immédiatement et arrête tout De ce fait, si vous synchronisez correctement votre connexion, vous pouvez interrompre votre connexion entre le $_SESSION['admin_level']=1; et l'évaluation du conditionnel, forçant ainsi le serveur à quitter le script immédiatement, sans terminer l'évaluation conditionnelle qui entraînerait la remise à zéro de admin_level.

Bien sûr, cela dépend entièrement de la configuration PHP n'ayant pas ignore_user_abort=1. Donc, si cela est configuré pour continuer même si un utilisateur abandonne, les privilèges seront toujours réinitialisés.

Pour augmenter votre fenêtre d'opportunité d'utiliser cet exploit, vous pouvez augmenter la taille de votre pass charge utile/données variables. md5 doit hacher en morceaux, donc en augmentant la taille des données pass, vous provoquerez md5 fonction pour passer plus de temps à calculer le hachage, ce qui donne un grand intervalle de temps pour interrompre la connexion.

4
Spencer D

En supposant que vous puissiez modifier le ./ascii.txt, je mettrais ce texte à l'intérieur:

<?php

override_function('md5', '$a', 'return "castle";');

Et ça devrait marcher.

2
peterpeterson

Semblable à ce que d'autres ont dit, PHP a une taille de mémoire maximale. Si vous insérez POST données qui étaient JUSTE assez grandes pour utiliser PRESQUE toute la mémoire de PHP) vers le haut, alors il pourrait en théorie tomber en panne quand il arrive exactement à cette ligne s'il manquait de mémoire:

echo 'Mot de passe incorrect ou vide.'

Cela repose sur trois principes:

1) Lorsque les sessions sont activées, PHP ne vide pas la sortie, au lieu de tout stocker dans RAM jusqu'à ce que le rendu de la page soit terminé).

2) PHP a une taille maximale RAM taille allouée pour chaque requête et, une fois atteint, plante votre script).

3) Lorsque de nouvelles lignes sont écrites, il utilise plus de RAM, en utilisant votre RAM restante.

Compte tenu des faits ci-dessus, si vous mettez plus de données HTTP POST dans votre requête HTTP (ex: $ _POST ["data"] = "plus grandring")), vous pourriez éventuellement déclencher une erreur de mémoire insuffisante avant l'exécution de $ _SESSION ["admin_level"] = 0.

Dans un scénario réel, cela est hautement improbable car PHP a également une taille maximale POST taille du corps, taille maximale de l'en-tête et quel que soit le conteneur de serveur qui l'héberge) (nginx, Apache, etc.) a également généralement des limites maximales. Vous atteindriez probablement toutes ces limites maximales, mais si elles sont mal configurées, il est possible que vous obteniez $ _SESSION ["admin_level"] = 1 devenant effectivement un administrateur lorsque vous accédez à n'importe quel d'autres pages du site Web utilisant cette méthode.

pdate: Une autre méthode similaire consisterait à mettre un gros mot de passe pour que la fonction md5 () échoue lors du traitement ou quelque chose, provoquant à nouveau une panne car elle manque de mémoire. Dépend du fonctionnement interne de md5 (). Il s'agit probablement d'une COPIE du champ $ _POST ["pass"], donc si $ _POST ["pass"] était gigantesque, il pourrait être très facile de faire en sorte que cette copie soit ce qui utilise tous les RAM et le fait planter.

2
Jacob Beasley