Runner auto-hébergé GitHub : sécurité et forensique
Runner auto-hébergé GitHub malveillant ou compromis : les événements du journal d'audit à vérifier, les preuves dans _diag, le périmètre et la reconstruction.
En bref. Les runners auto-hébergés sont un risque GitHub qui se trouve à l'intérieur de votre réseau. Deux scénarios se présentent. Un runner malveillant : un attaquant enregistre sa propre machine (org.register_self_hosted_runner / repo.register_self_hosted_runner) et reçoit vos jobs ainsi que leurs secrets. Un runner compromis : du code non fiable s'exécute sur votre hôte persistant et s'y installe durablement. Le journal d'audit vous indique quand les runners et les groupes ont changé. Les logs _diag/Runner_* et Worker_* de l'hôte vous indiquent ce qui s'est exécuté. Supprimez les runners inconnus, reconstruisez tout hôte ayant exécuté des jobs non fiables et passez à des runners éphémères placés dans des groupes restreints.
Les runners hébergés par GitHub sont des machines virtuelles neuves, détruites après chaque job. Les runners auto-hébergés sont ce que vous leur fournissez : une VM dans votre VPC, un pod dans votre cluster ou un Mac mini sous un bureau. Ils disposent souvent d'un accès réseau aux systèmes internes et aux endpoints de métadonnées cloud, ce qui explique précisément pourquoi les attaquants les apprécient.
Pourquoi les runners intéressent un attaquant
La référence d'utilisation sécurisée de GitHub est sans ambiguïté. Rien ne garantit qu'un runner auto-hébergé s'exécute sur une machine éphémère propre, et il peut être compromis de façon persistante par du code non fiable présent dans un workflow. Ces runners ne devraient pratiquement jamais être utilisés pour des dépôts publics. Sur des dépôts privés, toute personne capable d'ouvrir une pull request depuis un fork peut potentiellement y exécuter du code.
| Scénario | Ce que l'attaquant obtient | ATT&CK |
|---|---|---|
| Runner malveillant enregistré sur l'organisation ou un dépôt | Des jobs routés vers sa machine, avec le code récupéré, les secrets et le GITHUB_TOKEN | T1072 Software Deployment Tools |
| Workflow non fiable sur un runner persistant | Exécution de code dans votre réseau, persistance entre les jobs, accès aux secrets des jobs suivants | T1677 Poisoned Pipeline Execution |
| Groupe de runners élargi | Des runners sensibles (avec accès au réseau de production) désormais accessibles à davantage de dépôts | n/a, il s'agit d'un changement de configuration |
Ce que le journal d'audit enregistre
Les événements du journal d'audit de l'organisation couvrent tout le cycle de vie des runners :
| Action | Signification |
|---|---|
org.register_self_hosted_runner, repo.register_self_hosted_runner | Un runner a été enregistré |
org.configure_self_hosted_jit_runner, repo.configure_self_hosted_jit_runner | Un runner just-in-time (limité à un seul job) a été configuré via l'API |
org.remove_self_hosted_runner, repo.remove_self_hosted_runner | Un runner a été supprimé |
org.self_hosted_runner_online / _offline, repo.… | L'application runner a démarré ou s'est arrêtée |
org.self_hosted_runner_updated | L'application runner s'est mise à jour |
org.runner_group_created, org.runner_group_updated, org.runner_group_runners_added, org.runner_group_visiblity_updated | Les groupes de runners et leurs accès ont changé |
org.update_repo_self_hosted_runners_policy | La liste des dépôts autorisés à créer des runners de niveau dépôt a changé |
L'analyseur lève self-hosted-runner-registered (gravité moyenne) pour org., repo. et enterprise.register_self_hosted_runner. Les autres actions ci-dessus ne font pas l'objet de règles. Elles apparaissent dans la table des événements et méritent un filtrage (runner dans le champ de recherche) autour de tout enregistrement que vous ne savez pas expliquer.
Analyser un enregistrement
L'enregistrement d'un runner exige un jeton d'enregistrement qui expire au bout d'une heure, obtenu par une personne disposant des droits d'administration sur le dépôt ou l'organisation, ou d'un jeton équivalent. Face à un enregistrement suspect, posez-vous donc ces questions :
- Qui est l'acteur de l'événement ? Recherchez d'autres constats sur ce compte (jeton utilisé depuis un nouveau pays, droits de propriétaire accordés récemment).
- Depuis où ? Un enregistrement depuis une adresse IP qui n'apparaît jamais dans votre infrastructure de CI est un signal d'alerte.
- Quel nom et quels labels ? Les runners malveillants réutilisent souvent des labels comme
self-hosted,linuxetx64, afin que les workflows existants avecruns-on: [self-hosted, linux]les sélectionnent sans que personne ne modifie le moindre fichier YAML. - Quel groupe ? Un runner placé dans le groupe par défaut d'une organisation est disponible pour tous les dépôts que ce groupe autorise.
Ce que seul l'hôte peut vous apprendre
Le journal d'audit n'indique pas quels jobs se sont exécutés sur quel runner. Pour cela, il vous faut l'hôte du runner :
_diag/Runner_*.log: le log de l'application runner. Un fichier par démarrage, nommé avec un horodatage UTC. Il retrace l'enregistrement, les connexions et les affectations de jobs._diag/Worker_*.log: un log détaillé pour chaque job traité par le runner, qui mentionne le dépôt et le workflow.- Le répertoire de travail (
_work/) : les dépôts récupérés, les caches d'outils et les restes des jobs précédents. Les attaquants s'en servent pour déposer des fichiers que des jobs ultérieurs exécuteront. - La télémétrie classique de l'hôte : création de processus, connexions sortantes, nouvelles tâches cron ou nouveaux services, clés SSH, et accès aux métadonnées d'instance cloud.
Pour les runners éphémères, GitHub prévient que leurs logs doivent être transférés vers un stockage externe, faute de quoi ils disparaissent avec la machine. Si vos runners tournent sur Kubernetes avec Actions Runner Controller, les logs des pods, le journal d'audit Kubernetes et le runtime de conteneurs du nœud font partie des preuves. L'outil voisin Kubernetes Forensics couvre ce volet.
Délimiter le périmètre d'un incident de runner
- Listez tous les runners (organisation, dépôts, entreprise) et comparez-les à votre inventaire. Les noms ou adresses IP inconnus sont supprimés immédiatement, après avoir consigné leurs détails.
- Corrélez les enregistrements avec le reste de l'activité de l'acteur dans l'analyseur : onglet Entités → Runners, puis pivotez sur l'acteur et l'adresse IP.
- Retrouvez les jobs. Pour un runner malveillant, utilisez les événements
workflows.completed_workflow_rundu journal d'audit concernant des workflows qui ciblent des labels auto-hébergés pendant la période, ainsi que les logs des jobs eux-mêmes : l'étape « Set up job » affiche le nom du runner et le nom de la machine. Pour un hôte compromis, utilisez les logsWorker_*. - Considérez comme exposés les secrets utilisés par ces jobs. Renouvelez-les, y compris les identifiants de déploiement du dépôt et tout rôle cloud que l'identité machine du runner pouvait assumer.
- Reconstruisez tout runner persistant ayant exécuté du code non fiable. Ne le nettoyez pas : réinstallez-le.
Durcissement
- Utilisez des runners éphémères (
./config.sh --ephemeral) ou just-in-time. GitHub ne leur attribue qu'un seul job, ce qui limite la persistance entre les jobs. Consultez la référence des runners auto-hébergés. - Placez les runners sensibles dans des groupes de runners restreints à des dépôts et workflows précis. Consultez la page sur la gestion des accès par groupes.
- Interdisez les runners de niveau dépôt sauf raison particulière. Les propriétaires de l'organisation peuvent restreindre les dépôts autorisés à en créer.
- Ne rattachez jamais de runners auto-hébergés à des dépôts publics.
- Attribuez aux hôtes de runners des identités cloud au moindre privilège et filtrez leurs flux sortants. Beaucoup de compromissions de runners se transforment en incidents cloud via le rôle de l'instance.
- Déclenchez une alerte à chaque enregistrement. C'est un événement rare dans une organisation stable.