Comment des couleurs dans un terminal peuvent vous compromettre

Introduction

Imaginez la scène : vous lancez un outil CLI pour diagnostiquer un serveur en prod. Il affiche des informations sur un process suspect... puis, sans que vous ne compreniez vraiment pourquoi, votre écran saute, des lignes disparaissent, et un prompt apparaît : "Session expirée. Merci de ressaisir votre token :"
Vous obéissez (par réflexe), vous collez un secret, et vous venez de le donner... à une illusion. Pas un malware, pas un binaire louche : juste du texte, enrichi de séquences d'échappement ANSI interprétées par votre émulateur de terminal.
C'est précisément ce qui rend les séquences d'échappement ANSI si traîtres : on a tendance à considérer la sortie terminal comme de l'affichage, alors que c'est aussi un langage de contrôle. Si un attaquant parvient à injecter ces séquences dans une chaîne qui sera imprimée (nom de service, commande de process, variable d'environnement, logs...), il peut manipuler ce que vous voyez, vous pousser à l'erreur, et parfois même interagir avec le presse‑papier.
Bonne nouvelle : ce n'est pas théorique. Apache Tomcat est un cas réel très parlant à cause de la CVE-2025-55754.
Nous avons repéré le même problème dans l'outil open-source witr, et nous avons ouvert une (PR sur GitHub), pour le corriger.

Que sont les séquences ANSI ?

Les séquences ANSI (ou "ANSI escape sequences") sont des suites de caractères qui commencent généralement par le caractère ESC (Escape, 0x1B, souvent écrit \\\\x1b) et qui indiquent au terminal : "ne traite pas ça comme du texte, traite ça comme une instruction".
Elles servent à plein de choses utiles et légitimes :
  • Colorer du texte (rouge, vert, gras, etc.)
  • Déplacer le curseur
  • Effacer une ligne, l'écran, ou des portions d'affichage
  • Modifier des états (ex. mode "alternative screen"), titres de fenêtre, etc.
La forme la plus connue est la séquence CSI (Control Sequence Introducer) qui ressemble à :
  • ESC + [ + paramètres + lettre de commande
  • Quand on représente ça en string, on verra souvent \\\\x1b[ ... m (le m étant typique des styles/couleurs)
Exemple simple : mettre du texte en rouge puis revenir au style normal :
  • \\\\x1b[31m → rouge
  • \\\\x1b[0m → reset

Exemple avec un changement de couleur

Contrôle d'affichage

Certaines séquences permettent d'effacer une ligne ou de repositionner le curseur (très utilisé par les barres de progression). C'est pratique... et exactement le genre de mécanisme qui devient dangereux si la chaîne affichée est contrôlée par un tiers.

Le risque de sécurité

Une vulnérabilité terminal liée aux séquences ANSI ne permet pas forcément une exécution de code à distance qui fonctionne à tous les coups. Le plus souvent, c'est une attaque de manipulation : ce que l'utilisateur voit n'est plus la réalité. Et en DevSecOps, voir faux suffit souvent à prendre une mauvaise décision.
Voici les scénarios les plus courants.

1) Spoofing visuel : faux prompts, fausses erreurs, fausses validations

Si un outil CLI imprime des valeurs non fiables (ex. nom de process, arguments, env vars, champs provenant d'une API), un attaquant peut injecter des séquences pour :
  • masquer un avertissement,
  • effacer une ligne gênante pour l’attaquant,
  • déplacer le curseur et réécrire une sortie,
  • produire un affichage qui ressemble à un prompt système.
Le piège : l'utilisateur croit interagir avec une demande légitime (sudo, token, login...), alors que c'est juste une illusion imprimée par un programme. Et les humains sont des parseurs très tolérants.
Ce type de problématique a été observé à grande échelle dans l'écosystème K8s/OpenShift : l'injection de séquences ANSI dans des champs affichés au terminal a été documentée (notamment autour de la CVE-2021-25743) dans des recherches sur l'abus des émulateurs de terminal.

2) Log poisoning : quand vos logs deviennent une arme

Quand on parle d’attaques liées au terminal, on imagine presque toujours un scénario de SSH compromis, avec un attaquant en face. Mais le vecteur le plus sournois, c'est le logging :
  • logs d'applications affichés en console,
  • logs CI/CD,
  • journaux d'observabilité,
  • traces imprimées dans des dashboards qui finissent... copiés/collés dans un terminal.
Un exemple récent et très concret : la CVE-2025-58160 dans tracing-subscriber (Rust). Des entrées non fiables pouvaient injecter des séquences ANSI dans la sortie terminal via les logs, permettant de manipuler l'affichage (titres, effacement, etc.) et de tromper l'opérateur.

3) Presse‑papier, titres, liens cliquables : l'attaque "à retardement"

Certaines familles de séquences (notamment autour de ce qu'on appelle "OSC" dans beaucoup de terminaux) permettent d'interagir avec des fonctionnalités “OS-like" :
  • titre de fenêtre,
  • liens cliquables (phishing via URL trompeuse),
  • et dans certains environnements : interaction avec le presse‑papier.
Un cas d'école récent à connaître : Apache Tomcat n'échappait pas les séquences ANSI dans certains messages de log. Si Tomcat tournait dans une console (notamment sur Windows avec support ANSI), un attaquant pouvait injecter des séquences via une URL spécialement conçue afin de manipuler la console et le clipboard, et tenter de pousser un admin à exécuter une commande contrôlée par l'attaquant (attaque de type social engineering).

4) Pourquoi c'est si fréquent dans les CLI ?

Parce que les outils CLI affichent des informations fournies par le système d’exploitation ou par d’autres processus, dont une partie peut être contrôlée ou influencée par un attaquant.
  • lignes de commande (argv) de processus,
  • variables d'environnement,
  • noms de services,
  • champs provenant d'API (K8s, cloud, orchestrateurs),
  • noms de fichiers (parfois contrôlables),
  • messages de logs (souvent contrôlables).
Dans la PR que nous avons ouvert, nous résumons le problème : des chaînes "user-controlled / system-derived" peuvent contenir des caractères de contrôle, dont des escapes ANSI, et les imprimer tel quel peut altérer l'affichage, effacer des informations, ou impacter le presse‑papier.

witr, c'est quoi ?

witr ("Why is this running?") est un outil CLI orienté diagnostic : il sert à expliquer pourquoi un process/service existe et quelle chaîne de causalité (systemd, conteneur, shell, cron, etc.) le maintient en vie, avec une sortie "narrative" et lisible.
Concrètement, witr agrère et affiche des informations issues du système : ligne de commande des processus, relations parent/enfant et contexte d’exécution dans le but de donner à l’administrateur système une vision compréhensible de l’origine d’un service, sans avoir à croiser manuellement plusieurs commandes (ps, systemctl, docker, etc.).

Le problème identifié

Le cœur du sujet : witr imprime beaucoup de données issues du système (process command lines, env vars, service names...). Or ces données peuvent être influencées par un attaquant local (ou par une supply chain / un job malveillant / un conteneur compromis), et contenir des séquences ANSI.

La stratégie de correction

La correction ne repose pas sur un simple patch, mais sur un choix d’architecture côté affichage, ce qui mérite un léger détour technique.
Plutôt que d'espérer que chaque fmt.Printf(...) soit correctement “sanitizé” par chaque futur maintainer, la PR adopte une approche plus durable : centraliser la protection au point d’écriture vers le terminal.
L'idée est simple mais structurante : introduire un writer "safe" qui sanitize systématiquement tout ce qui est écrit vers la sortie standard, et n'autoriser les séquences ANSI que quand elles sont explicitement déclarées comme fiables (ex. les couleurs ajoutées volontairement par l'outil).
Dans la codebase, cette solution s'implémente avec 3 briques:
  • un SafeTerminalWriter qui implémente io.Writer et neutralise les caractères de contrôle dangereux,
  • un wrapper Printer autour des fonctions d'impression afin d’éviter l’usage direct de fmt.Print*
  • et un mécanisme "escape hatch" (via un type ansiString) pour les séquences ANSI maîtrisées par l'application (comme les couleurs).

La solution technique

Objectif : empêcher qu'une chaîne non fiable soit interprétée comme une instruction terminal.
Il existe deux approches complémentaires :
  1. Neutraliser / échapper les caractères de contrôle (dont ESC)
  2. N'autoriser les ANSI que via un canal de confiance (ex. une fonction de coloration interne)

Avant / après (exemple simplifié)

Avant : impression directe d'une chaîne issue du système
// cmdline vient de /proc ou d'une API système
fmt.Fprintf(os.Stdout, "Command: %s\\\\n", cmdline)
Après : sanitation centralisée
func SanitizeForTerminal(s string) string {
    // Approche simple : remplacer les caractères de contrôle (dont ESC)
    // en représentation visible. Conserver \\\\n et \\\\t si besoin.
    out := make([]rune, 0, len(s))
    for _, r := range s {
        if r == '\\\\n' || r == '\\\\t' {
            out = append(out, r)
            continue
        }
        if r < 0x20 || r == 0x7f {
            out = append(out, '\\\\x1b')
            continue
        }
        out = append(out, r)
    }
    return string(out)
}

fmt.Fprintf(os.Stdout, "Command: %s\\\\n", SanitizeForTerminal(cmdline))

Les exemples précédents illustrent le principe, mais ils restent volontairement simplifiés.
Dans un CLI réel, utilisé en production et amené à évoluer, la protection contre les séquences ANSI doit être plus fine et plus systématique
  • filtrer spécifiquement ESC (0x1b) et certaines séquences,
  • reconnaître et escape les principales de séquences ANSI (CSI, OSC, etc.)
  • et surtout envelopper l’écriture vers le terminal dans un composant dédié (via un safe writer), afin qu’un oubli ponctuel ne réintroduise pas la vulnérabilité.
Côté écosystèmes, il existe des bibliothèques utiles :
  • JS/TS : détection/filtrage via des libs type ansi-regex, suppression via strip-ansi, génération contrôlée via ansi-escapes
  • Python : sanitation via re + allowlist, ou wrappers d'output
  • Go : mapping de runes + writers (io.Writer) sécurisés

Bonnes pratiques et conclusion

S’il ne fallait retenir qu’un point, c’est que la sortie d’un terminal fait partie intégrante de la surface d’attaque d’un outil CLI. Toute donnée affichée sans contrôle, qu’elle provienne d’un utilisateur, d’un service ou du système, doit être traitée comme non fiable.
Vous souhaitez être accompagné pour lancer votre projet digital ?
Déposez votre projet dès maintenant
Article presentation image
Comment évaluer les performances des modèles d’IA en mathématiques ?
Le 15 mars 2016, le cinquième match de Go entre Lee Sedol, 2e joueur le plus titré de l’histoire, et AlphaGo, un programme ...
Louis Rose
Louis Rose
AI Engineer @ Galadrim
Article presentation image
Next.js App Router : le cache et ses dangers
“Il y a seulement 2 problèmes compliqués en informatique : nommer les choses, et l’invalidation de cache”. Phil Karlton. Avec ...
Valentin Gerest
Valentin Gerest
Tech Lead IA @ Galadrim
Article presentation image
Qu'est-ce que le SMS pumping et comment s'en protéger ?
La fraude appelée SMS pumping survient lorsque des fraudeurs exploitent un champ de saisie de numéro de téléphone de votre ...
Arnaud Albalat
Arnaud Albalat
CTO @ Galadrim