Intro
L’émergence de l’industrie 4.0 se caractérise par la numérisation de l’industrie et une plus grande interconnexion entre les différentes machines qui composent un SI (Système d’Information) industriel. Cependant, cette croissance des communications au sein des SI industriels engendre également une augmentation de leur surface d’attaque. De plus, les protocoles historiquement utilisés au sein de ces SI (comme Modbus), n’offrent que très peu, voire aucun mécanisme de sécurité. Certains de ces protocoles étaient également propriétaires ce qui pouvait poser des problèmes d’interopérabilités entre les différentes machines du SI.
Le standard OPC UA a été créé en 2008 par la fondation OPC pour répondre à ces problématiques, en proposant une uniformisation des communications entre les machines des SI industriels, et en intégrant de nombreux mécanismes pour garantir la sécurité de ces communications.
Le standard OPC UA
Le standard OPC UA est un standard de communication open-source, qui a été conçu pour être multiplateforme. Il peut être implémenté sur tout type de composant, quel que ce soit son système d’exploitation.
Communications permises par OPC UA (Source : site web de la OPC Foundation)
Le standard propose deux types d’architectures, pouvant être utilisées de manière complémentaire :
- L’architecture client-serveur : c’est l’architecture la plus utilisée. Elle est composée d’éléments matériels et/ou logiciels qui contiennent des données, de serveurs OPC UA qui mettent à disposition ces données ou des services, ainsi que de clients OPC UA qui peuvent interagir avec les serveurs pour utiliser leurs services ou accéder à leurs données.
- L’architecture PubSub : elle peut être est utile lorsque le volume de données échangées commence à devenir important. Elle est composée de Publishers qui émettent des messages, et de Subscribers, qui reçoivent ces messages par le biais d’un Message Oriented Middleware (MOM).
Sécurité de l’architecture client-serveur
L’architecture client-serveur étant de loin la plus utilisée, nous allons à présent nous pencher plus en détail sur les mécanismes de sécurité proposés par le standard OPC UA dans ce type d’architecture.
Tout d’abord, trois niveaux de sécurité dont disponibles concernant le chiffrement des communications entre un client et un serveur OPC UA :
- None : les messages sont envoyés en clair, sans aucune protection
- Sign : les messages sont signés. Cela protège l’intégrité des données transmises, mais pas leur confidentialité
- SignAndEncrypt : les messages sont signés et chiffrés. Dans ce cas, la confidentialité des messages est également protégée
Pour mettre en place ce chiffrement, le client et le serveur disposent chacun d’un certificat X.509 et d’une clé privée associée, qu’ils utilisent pour s’échanger une clé de session de manière chiffrée. Ils peuvent ensuite utiliser cette clé de session pour chiffrer la suite des échanges avec des algorithmes de chiffrement symétriques.
Plusieurs niveaux de sécurité concernant l’authentification des utilisateurs sont également disponibles. Pour s’authentifier, les clients envoient aux serveurs des jetons appelés UserIdentityTokens, qui contiennent les informations nécessaires à l’authentification. Il existe plusieurs types de UserIdentityToken, et c’est le serveur qui choisit quels types il accepte :
- AnonymousIdentityToken : ce jeton ne contient aucune information particulière. Si le serveur l’accepte, il authentifie l’utilisateur en tant qu’utilisateur anonyme
- UserNameIdentityToken : ce jeton contient un nom d’utilisateur et un mot de passe. Si ces derniers sont valides, l’utilisateur est authentifié et dispose ensuite du profil et des droits associés à son nom d’utilisateur
- X509IdentityToken : ce jeton contient un certificat X.509. Si le serveur a enregistré ce certificat, l’utilisateur est authentifié et dispose ensuite d’un profil et des droits associés au certificat
- IssuedIdentityToken : ce jeton encapsule un jeton d’accès fourni par un service tiers de gestion des accès, comme un serveur OAuth2 par exemple
Enfin, une fois authentifié, l’utilisateur a accès aux nœuds du serveur. Ci-dessous, un exemple de nœuds qui pourraient être rencontrés sur un serveur OPC UA :
Nœuds d’un serveur OPC UA
Un contrôle d’accès peut être mis en place pour restreindre l’accès à certains nœuds aux utilisateurs à haut privilège (administrateurs, …), ou bien pour exiger que le canal de communication soit chiffré pour accéder à certains nœuds sensibles. La figure ci-dessous récapitule comme la gestion de l’accès à un nœud se déroule :
Représentation de la gestion des permissions extraite du chapitre 2 des spécifications OPC UA
Outillage d’audit OPC UA
Les outils publics existants pour faciliter l’audit d’applications OPC UA sont très peu nombreux. Un des plus connus est le module Metasploit appelé « msf-opcua ».
Ce module est composé de trois scripts :
- opcua_hello : permet d’envoyer un « Hello Message » à une liste d’adresses IPs, pour un port donné, afin de détecter la présence de serveurs OPC UA parmi cette liste
- opcua_server_config : ce script s’utilise avec pour prérequis un accès authentifié à un serveur OPC UA. Si cette condition est remplie, ce script permet de récupérer des informations sur la configuration des points d’accès du serveur (chiffrement, authentification…)
- opcua_login : permet d’effectuer une attaque par dictionnaire sur un serveur utilisant une authentification par nom d’utilisateur et mot de passe
Bien que fournissant des premiers éléments utiles pour effectuer un audit, cet outil comporte tout de même quelques limites. Par exemple, il n’est pas possible de scanner plusieurs ports à la fois avec le script opcua_hello. Autre exemple, le script opcua_server_config requiert une authentification pour récupérer certaines informations de configuration alors que ces dernières sont en réalité disponibles sans authentification.
C’est pourquoi Wavestone a décidé de proposer une amélioration de cet outil. Il a été décidé de ne plus utiliser le framework Metasploit, qui imposait trop de contraintes et l’outil prend à présent la forme d’un script Python indépendant, renommé « opcua_scan ». Il s’appuie sur la bibliothèque opcua-asyncio contrairement au module msf-opcua qui utilise la librairie python-opcua déclarée comme dépréciée par ses auteurs.
L’outil est accessible via ce lien, et met à disposition deux commandes : « hello » et « server_config », qui reprennent les fonctionnalités des scripts opcua_hello et opcua_server_config du module msf-opcua. Le script opcua_login n’a pas été repris, car il n’a pas fait l’objet d’amélioration et peut être utilisé directement.
La commande hello
Cette commande s’utilise pour détecter les applications OPC UA dans un réseau. Elle permet d’envoyer des « Hello Message » à une liste d’adresses IP, sur une liste de ports donnée, et d’en déduire la présence ou l’absence de serveurs OPC UA sur les cibles. Ensuite, le service FindServers, supposé être implémenté par tout serveur OPC UA, est utilisé pour récupérer l’ApplicationDescription du serveur (et des autres applications OPC UA connues par le serveur). Cet objet contient des informations utiles, comme le productUri, qui donne des renseignements sur le logiciel ou la bibliothèque utilisée pour faire fonctionner le serveur détecté, ou encore les discoveryUrls, qui indique les URL vers les DiscoveryEndpoints du serveur. Ces endpoints peuvent être utilisés par la commande server_config pour récupérer davantage d’informations sur la configuration du serveur.
Plusieurs options ont été ajoutées à la commande, comme la configuration du timeout pour considérer une connexion à un serveur comme échouée ou la possibilité de récupérer la liste des serveurs détectés dans un fichier de sortie JSON.
Voilà comment la commande hello pourrait s’utiliser en pratique :
$ python opcua_scan.py hello -i <IPs> -p <ports> -o hello_output.json
Exemple de résultats générés par la commande hello
Et la capture d’écran ci-dessous montre un extrait du fichier JSON généré :
Extrait d’un fichier de sortie généré par la commande hello
La documentation complète de la commande hello et de toutes ses options est disponible ici.
La commande server_config
Grâce aux DiscoveryEndpoints récupérés avec la commande hello, nous avons à présent accès à l’ensemble du Discovery Service Set du serveur. Aucune authentification ni mécanisme de chiffrement n’est nécessaire pour utiliser ces services. Parmi ces services, celui appelé GetEndpoints peut être utilisé pour récupérer les endpoints pour se connecter au serveur, ainsi que des informations sur la configuration de ces endpoints. Ces informations sont données par le biais d’objets EndpointDescriptions, qui contiennent notamment :
- Le niveau de sécurité du chiffrement accepté sur l’endpoint (None, Sign ou SignAndEncrypt)
- L’algorithme de signature ou de chiffrement utilisé
- Les types de UserIdentityToken acceptés par l’endpoint (AnonymousIdentityToken, UserNameIdentityToken, X509IdentityToken ou IssuedIdentityToken)
La commande server_config permet de récupérer les EndpointDescriptions de tous les serveurs détectés via la commande hello, et d’identifier parmi ces serveurs ceux qui acceptent les authentifications anonymes ou le niveau de sécurité None. L’ensemble de ces informations sont accessibles et récupérables pour un utilisateur non authentifié.
De plus, si un accès authentifié à un serveur est possible, la commande permet également de parcourir les nœuds du serveur et d’énumérer les droits de l’utilisateur courant sur ces nœuds. Ainsi, il est possible d’obtenir la liste des nœuds de type Variable accessibles en écriture, ou bien la liste des méthodes exécutables par l’utilisateur.
Enfin, d’autres options utiles ont été ajoutées à la commande server_config :
- -o (ou –output) permet de configurer un fichier de sortie JSON pour stocker les résultats de la commande, et les parcourir plus facilement que sur un terminal. Des informations supplémentaires y sont stockées comme la valeur de l’attribut UserWriteMask des nœuds, qui indique quels attributs des nœuds peuvent être modifiés par l’utilisateur.
- -r (ou –root_node) permet de parcourir uniquement un sous-ensemble des nœuds du serveur à partir d’un nœud de départ précisé en argument. En effet le parcours de l’ensemble des nœuds peut être long et cette option peut être utilisée pour cibler les nœuds d’intérêt.
La documentation complète de la commande server_config et de toutes ses options est disponible ici.
En pratique, voilà comment la commande server_config pourrait s’utiliser :
Le fichier de sortie de la commande hello est donné en argument (via l’option -t) et sera utilisé pour récupérer les informations sur les endpoints des serveurs détectés :
$ python opcua_scan.py server_config -t hello_output.json
Exemple de résultats générés par la commande server_config
Ici, le serveur autorise les connexions non chiffrées et anonymes ou authentifiées avec un nom d’utilisateur et un mot de passe. Si le serveur n’acceptait pas les connexions anonymes, le script opcua_login du module msf-opcua pourrait être utilisé pour essayer de trouver des identifiants valides, mais cela n’est pas nécessaire dans cet exemple
Il est possible d’accéder anonymement au serveur et parcourir ses nœuds à la recherche de nœuds intéressants (le début du résultat de la commande a volontairement été coupé, et le répertoire « TemperatureControl » a été ciblé avec l’option -r pour réduire le nombre de nœuds parcourus) :
$ python opcua_scan.py server_config -t hello_output.json -o config_output.json -nw -r ‘ns=3;s=85/0:Simulation’
Exemple de résultats obtenu lors d’une recherche de nœuds accessibles en écriture
Les nœuds accessibles en écriture peuvent ensuite être analysés plus en profondeur dans le fichier de sortie qui a été configuré dans la commande précédente :
Extrait d’un fichier de sortie généré par la commande server_config
Ici, il semble possible pour un utilisateur anonyme d’allumer ou d’éteindre des climatiseurs à distance via le serveur OPC UA détecté
Conclusion
Malgré les mécanismes de sécurité apportés par le standard OPC UA, ces derniers peuvent présenter des défauts de configuration et exposer les SI industriels de manière non-négligeable. L’outil développé par Wavestone et présenté dans cet article permet de faciliter la démarche d’audit de ces configurations et d’assurer au mieux la sécurité de ces SI industriels.
Enfin, les spécifications OPC UA proposent davantage de mécanismes de sécurité, comme la gestion des certificats par un Global Discovery Server ou de chiffrement des messages PubSub grâce à la mise en place d’un Security Key Server. Le standard OPC UA pourrait donc permettre d’effectuer des progrès supplémentaires en matière de sécurité, mais peu d’implémentations de ces mécanismes existent à ce jour.
L’outil est disponible sur notre Github : https://github.com/wavestone-cdt/opcua-scan
Cet outil a également été mis en avant lors d’une session Arsenal Lab à la conference BlackHat Asia 2023 à Singapour : https://github.com/wavestone-cdt/bhasia23-opcuhack