Threat Intelligence

ted backdoor : un HAProxy recompilé sert de poste d'écoute à des APT nord-coréens en Corée du Sud

Admin CyberAfrik 08 September 2026 29 lectures
ted backdoor : un HAProxy recompilé sert de poste d'écoute à des APT nord-coréens en Corée du Sud

Rapport CTI technique du 8 septembre 2026

Résumé exécutif

Rapid7 Labs a documenté le 4 septembre 2026 une boîte à outils Linux jusque-là inconnue, utilisée contre des organisations sud-coréennes des secteurs automobile et média. La pièce maîtresse est un backdoor baptisé ted par ses propres chaînes de débogage, compilé directement dans les sources de HAProxy 2.8.12 chez la victime. Il ne s'agit pas d'un module chargé de l'extérieur mais d'un plugin intégré au binaire, qui enregistre un filtre HAProxy natif, accroche le parseur HTTP et lit le trafic après terminaison TLS. Le répartiteur de charge continue de servir le trafic normalement pendant que l'implant journalise les requêtes intéressantes, vole des cookies de session et remplace le corps des réponses pour des clients choisis par adresse IP ou par jeton opérateur.

Autour de ce backdoor gravitent un keylogger SSH, un stager et un RAT nommé curlRAT, greffé dans des versions trojanisées de crond, agetty, atd et polkitd. Rapid7 attribue l'ensemble avec un niveau de confiance moyen à des APT nord-coréens, sur la base du ciblage régional, des chiffres XOR et de substitution employés, et de la présence des C2 codés en dur dans des listes associées à APT37 par ThreatFox et maltrail. Les premiers échantillons sur VirusTotal remontent au milieu de 2025 et la campagne semble active depuis le début de 2025. Le point d'entrée initial n'est pas établi : les deux victimes exposaient un portail Groupware sur le port 443 et un serveur de messagerie sur le port 25, deux surfaces cohérentes avec le mode opératoire documenté de Kimsuky, mais aucune CVE n'est confirmée à ce stade.

Chronologie

DateÉvénement
22 novembre 2024Publication de HAProxy 2.8.12-0fdb194, plus ancienne date de compilation possible pour ce build trojanisé
Novembre 2024 à février 2025Operation SyncHole (Lazarus, documentée par Kaspersky), campagne de point d'eau contre des sites média sud-coréens dont la fenêtre recoupe celle-ci
Début 2025Début estimé des campagnes utilisant ted backdoor et curlRAT
Mi-2025Premiers dépôts d'échantillons sur VirusTotal
Début 2026Kimsuky observé exploitant des RCE sur des serveurs de messagerie exposés pour compromettre des éditeurs de groupware sud-coréens
Juillet 2026ENKI WhiteHat signale un vecteur d'accès initial similaire, suggérant une exploitation potentiellement toujours en cours
4 septembre 2026Publication du rapport Rapid7 Labs avec IOC et cartographie ATT&CK
7 septembre 2026Reprise par The Hacker News, SecurityWeek et GBHackers

Fiche campagne

Acteur : APT nord-coréens, attribution de confiance moyenne. Composants : ted backdoor, curlRAT, stager, keylogger SSH. Cibles observées : deux organisations sud-coréennes, secteurs automobile et média.

Les éléments d'attribution retenus par Rapid7 sont le ciblage de logiciels régionaux (portail Groupware très répandu dans les entreprises coréennes), l'usurpation du domaine de contenu statique de Naver via img.responsive.pstatic.autos, l'emploi de chiffrements XOR simples et d'un chiffre de substitution, le modèle de point d'eau déjà documenté dans Operation Code on Toast (APT37) et Operation SyncHole (Lazarus), et enfin l'association des C2 codés en dur à APT37 dans ThreatFox et maltrail. Rapid7 rappelle que APT37 et Lazarus sont deux grappes distinctes, rattachées selon Mandiant à des agences différentes, le Ministry of State Security pour la première, le Reconnaissance General Bureau pour la seconde.

La présence de routines préfixées ngx_ dans le backdoor suggère une réutilisation de code venant d'un backdoor nginx. Rapid7 note un rapprochement de nommage avec la campagne Funnull, où un filtre nginx personnalisé accrochait le trafic HTTP avec une configuration chiffrée en XOR simple, mais précise qu'aucun recouvrement de code significatif ne permet d'établir un lien plus fort.

Vulnérabilité exploitée : aucune CVE confirmée. Le vecteur d'accès initial reste indéterminé faute de preuves forensiques suffisantes, et le schéma d'attaque publié par Rapid7 pose par hypothèse une exploitation du portail Groupware exposé.

PoC disponible : sans objet, il s'agit d'une campagne d'acteur et non d'une vulnérabilité produit.

Mitigation : contrôle d'intégrité des binaires système et des composants de bordure, corrélation réseau indépendante des journaux du composant lui-même.

Diagramme de la chaîne d'attaque

Sequence attaquant et defense de la campagne ted backdoor

Reconstruction à partir du rapport Rapid7 Labs du 4 septembre 2026. Les délais de détection illustrés reprennent l'écart entre la date estimée de début de campagne et la publication.

Analyse technique

Étape 1 : pied dans la DMZ et récolte d'identifiants

Les deux victimes exposaient un serveur web de bordure avec les ports 80, 443 et 25 ouverts, le 443 servant le portail de connexion Groupware et le 25 un serveur de messagerie. L'attaquant obtient un accès sur cet hôte, y installe sa persistance et y dépose un keylogger SSH qui intercepte les mots de passe en clair dans userauth_passwd().

Le détail qui trahit l'outillage : la fonction commence par comparer les identifiants saisis à des mots de passe maîtres codés en dur, ce qui donne aux opérateurs une porte d'entrée directe en plus de la capture. Les identifiants capturés sont chiffrés avec un chiffre de substitution monoalphabétique de 67 entrées, puis encodés en base64 et écrits dans /var/lib/sshd/c8c68e629bba773a10ac80012d10bf19. Le même chiffre de substitution se retrouve dans le keylogger, dans les binaires trojanisés et dans le backdoor HAProxy, ce qui a permis de rattacher les composants entre eux.

L'hôte de bordure sert aussi de serveur de staging pour les ELF trojanisés distribués vers l'intérieur.

Étape 2 : le stager et le choix de la cible

Le stager déchiffre sa configuration avec un XOR d'un octet, vérifie qu'il tourne en root, puis profile la machine : nom d'hôte, distribution, identifiants de version, version du noyau, architecture. Ce profilage sert à sélectionner le bon binaire trojanisé parmi ceux embarqués. Les versions de crond supportées couvrent CentOS 7.7, 7.8, 7.9 et Ubuntu 22.04.

Il n'agit que si crond ou HAProxy tourne sur la machine, garde-fou qui limite le déploiement aux serveurs qui l'intéressent. Le déploiement consiste à déchiffrer le crond trojanisé en mémoire, à écraser le démon légitime et à redémarrer le service, puis à aligner l'horodatage de création du binaire sur celui de /usr/bin/ssh. Il termine par un nettoyage sélectif des journaux, en filtrant les mots clés tmp, wget, cron et crond dans /root/.bash_history, /var/log/messages, /var/log/audit/audit.log, /var/log/cmd.log, /var/log/secure, /var/log/syslog et /var/log/auth.log, via un fichier de travail nommé /tmp/jasper-log pour passer pour un artefact du moteur JSP d'un vieux Tomcat. Deux variantes du stager n'embarquent aucun payload et vont les chercher sur le serveur compromis de la victime.

Étape 3 : curlRAT, deux fils d'exécution greffés dans un démon système

Le RAT ajoute sa logique à crond sous forme de deux threads. Le premier crée un répertoire de travail snapd sous /var/lib, charge l'identifiant de victime depuis /var/lib/snapd/g580 et interroge le C2 par HTTPS avec repli en HTTP, en s'appuyant sur libcurl, d'où le nom. L'identifiant de victime est le MD5 en majuscules de la concaténation de la chaîne de version de cron, du nom d'hôte, de l'adresse IPv4 et de l'UUID matériel lu dans /sys/class/dmi/id/product_uuid, et il voyage dans l'en-tête User-token.

La configuration récupérée est déchiffrée par un chiffre XOR à rétroaction dont le premier octet du fichier initialise la graine, puis parsée sur trois délimiteurs à un caractère : le point d'exclamation termine le champ d'authentification, le dièse marque la section payload, l'astérisque sépare les arguments. Un octet de mode compris entre 0 et 5 sélectionne l'un des six gestionnaires par table de saut.

ModeFonctionDescription
0Exécution de commandesDécode base64 puis XOR une liste de commandes, exécute chaque ligne via popen avec stderr redirigé, renvoie la sortie dans un tampon de 1 Mo
1Écriture de configurationDécode et valide une nouvelle configuration, fixe l'intervalle de sondage et le drapeau de sondage rapide ; sur échec de validation, retour au C2 img.monderhouse.space
2Dépôt de payloadPOST authentifié vers le C2 avec un chemin de tâche en corps, écriture en fichier temporaire, décompression puis déplacement
3Reverse shellFerme les descripteurs au-dessus de 2, appelle setuid(0) et setreuid(0, 0), puis se connecte en sortie
4BaliseRemplit une structure d'informations système de 10 Ko et l'émet en check-in
5Shell PTYShell interactif complet, payload de la forme ip:port

L'intervalle de sondage par défaut est de 43 200 secondes, soit douze heures, réduit à 30 secondes quand l'opérateur active le mode rapide. La boucle réessaie jusqu'à six fois par cycle avec cinq secondes d'intervalle. Toutes les données sortantes passent par un XOR à rétroaction avec graine aléatoire puis base64, encapsulées dans un corps application/x-www-form-urlencoded.

Le second thread est un chien de garde HAProxy. Il vérifie d'abord la présence de /usr/lib/libvirtlog.so.0 pour s'assurer d'être dans un environnement virtualisé, sinon il dort six minutes et abandonne, ce qui est une évasion de bac à sable simple mais efficace. Ensuite il lit toutes les heures le PID dans /var/run/haproxy.pid et surveille le répertoire proc correspondant, en remontant au C2 un code d'état : 0 démarré, 1 arrêté, 2 redémarré (fichier PID modifié et nouveau PID), 3 rechargé (fichier PID modifié, PID identique). Sur les variantes CentOS, les fonctions sont masquées derrière des noms préfixés atd_ pour brouiller l'analyse statique.

Étape 4 : ted backdoor, le filtre HAProxy qui efface ses propres traces

L'implant est compilé avec le reste des sources de HAProxy 2.8.12, ce qui lui donne accès à l'API de filtres native, aux pools mémoire internes, à l'ordonnanceur de tâches et à la gestion de processus. Sa configuration vit dans ~/cache/haproxy-1000.cache, chiffrée en deux couches : un XOR chaîné dont la graine est le premier octet du fichier, puis une substitution monoalphabétique construite au démarrage dans ted_init_util à partir de la même chaîne de 67 caractères que le keylogger SSH. Les listes de contrôle d'accès par IP viennent de haproxy-1001.cache et haproxy-1002.cache. Le canal C2 est sauvegardé dans les variables d'environnement HAPROXY_MWORKER_PP_READ et HAPROXY_MWORKER_PP_WRITE pour survivre aux rechargements.

Le hook principal remplace trace_chn_start_analyze() et inspecte le buffer HTX, c'est-à-dire la zone mémoire où HAProxy stocke la requête HTTP déjà déchiffrée. Si l'URL demandée correspond à l'URI d'icône codée en dur, le filtre bascule en mode commande et contrôle.

C'est là que se joue la partie la plus intéressante côté détection. Avant tout traitement, l'implant décrémente les compteurs internes de HAProxy en tapant dans la structure proxy par des offsets codés en dur pour la 2.8.12 : beconn et feconn par backend, actconn global, puis les champs 64 bits de be_counters (cum_conn, cum_req, bytes_in, bytes_out) avec garde contre le débordement, et enfin les métriques de pic 32 bits (sps_max, conn_max, cps_max) uniquement quand elles valent exactement 1. Ensuite il remet à zéro to_forward, output, buf.head et buf.data sur le canal de requête, ce qui indique à HAProxy qu'il n'y a plus rien à transmettre. La requête de l'opérateur ne parvient donc jamais à un backend, aucun serveur applicatif ne la journalise, et les statistiques du répartiteur n'en gardent pas trace.

Les commandes transitent par des FIFO créées sous /tmp, avec bascule vers le processus maître si HAProxy tourne en mode master-worker. Le dispatcher accepte cinq opcodes : 0 balise renvoyant un bandeau de version avec l'identifiant de build 24112201 et la version HAProxy 2.8.12-0fdb194, 1 téléversement de fichier, 2 téléchargement de fichier, 3 exécution de commande via popen avec fusion de stdout et stderr, 9 mise à jour de configuration. Toutes les réponses portent un en-tête HTTP/1.0 200 OK écrit à la main et repartent par un send() direct sur la socket, sans passer par le chemin de réponse de HAProxy.

Étape 5 : injection sélective dans les pages servies

Hors mode C2, le filtre journalise le trafic correspondant aux expressions régulières de sa configuration, en extrayant IP source, Host, Referer et User-Agent dans un enregistrement à une ligne séparé par des points d'exclamation. Si le champ action est non nul, le chemin d'injection démarre et une requête doit franchir quatre conditions : présence d'un User-Agent correspondant au motif configuré, détection des octets x, 6 et 4 dans cet agent pour choisir entre payload 32 et 64 bits, correspondance simultanée des regex d'URL et de referer d'une règle, et enfin analyse de l'en-tête Accept-Language d'où sont extraits jusqu'à quatre champs opérateur (mrt pour une clé de 64 octets, msc pour un statut de 8 octets, mst pour un score de 8 octets, et un quatrième mot clé pour un blob d'information de 1 024 octets). Une clé mrt valide court-circuite entièrement le filtrage par IP, ce qui permet à l'opérateur d'atteindre la page depuis n'importe où. Sans clé, le filtrage retombe sur les listes blanches ou noires, testées deux fois, une fois avec le dernier octet mis à zéro pour une correspondance sur le /24 et une fois sur l'hôte exact.

La substitution elle-même se fait en trois callbacks. ted_http_headers_for_htx revérifie le Content-Type et la ligne de statut, ajuste la longueur, supprime Accept-Ranges pour empêcher le client de demander des plages d'octets et de repérer l'écart de taille, et force un 200 OK. ted_http_payload lit le fichier de payload par tranches, le déchiffre avec ngx_decrypt_script et le substitue via les appels d'édition de corps de HAProxy, en décalant les offsets des filtres restants quand la longueur change. trace_http_end ajoute le reliquat quand le payload est plus gros que le corps d'origine, en s'appuyant sur l'ordonnanceur de HAProxy pour étaler la livraison sur autant de passes que nécessaire.

L'infrastructure suit un schéma constant : domaines enregistrés sur des TLD bon marché (.store, .space, .site, .autos) avec un sous-domaine img. imitant un CDN d'images, dont img.responsive.pstatic.autos qui copie le domaine de contenu statique de Naver.

Indicateurs de compromission

TypeValeur
SHA-256 stager5db1b6d52faf60b4f32d6fd0c7c938e4d05d29a14c32ded4a9668357c08b6a91
SHA-256 stager09739441ed4599bac2f8159028f772f71e4b25c8badfff95574e56d7384f3dbe
SHA-256 stagerfea1bc36632c71e5a839803469ef60ac47595d36b2c50934ac109ade6df06e61
SHA-256 curlRAT83f7d565b0465546027052b597af46eae3a199e7a91fcc2ab936341147349130
SHA-256 curlRAT7007a78d50a993cb174c685eba96eb442c9507e38fd9d8e5dffc712f613ec110
SHA-256 curlRAT6cf1b5e92a9c0756f597a5ddefb38eba32961c52efac7ab2a0aa52c639a8fc53
SHA-256 curlRATed72f4cd8d467b5c5d95ae6aeca4aaeea14d79565d379c1ca5871a714727be16
SHA-256 curlRATfeeea9d0bf6ae7396d28271baa51ae50df5169ce5d32a516865856f91abc50b3
SHA-256 curlRAT (cronie)d53c760c23b4405eb04ad0f20ead375440344b3bdf1fb7854ed12e40d155eabe
SHA-256 curlRAT (agetty)2f02b09d61d432134e994ad671258f523bbf289ae6091fd4eae192c60bd51b6f
SHA-256 curlRAT (atd)8f30b57928934ae67478d0e690c91d046e35a638da098d02922a4a88a0fdb66c
SHA-256 curlRAT (polkitd)a1d8af3a6acb731f07f72040eccb3450c1c83d40e29f736c2a63d35388660be4
SHA-256 curlRAT12810854c8b2c391b23e2e18b013e873d0369b0637aa3cf993136c07188ba3b8
SHA-256 curlRAT009a1e2d7a582a24e50cf2ffc2a005482c8e38f22bf5ed416053855f8d054e1e
SHA-256 keylogger SSH4bb923eb040aa13ca8fd409c31ee4729c60ddff32e350efe1c5a4a9168a065f5
SHA-256 ted backdoor94630b96f628c96a6bff7904b40ffc9ad67c86f8a4ff6080c3b524831c93f402
SHA-256 ted backdoor72e70936f0dbe459142a1d867617c35f8d0cce5d18c6a49e1090a2a5adc8e558
SHA-256 ted backdoora8bfab4de81a1acb04aacdf757346946b0f5e30f0c9f402004016d0e425119c7
Domaine C2img.monderhouse.space
Domaine C2img.smartnords.site
Domaine C2img.darklights.store
Domaine C2img.responsive.pstatic.autos
Domaine C2img.socialteams.store
Domaine C2img.worksongo.store
URI déclencheur C2/favorite_list_2x_m500_ico.jpg
Chemin fichier/var/lib/sshd/c8c68e629bba773a10ac80012d10bf19
Chemin fichier/var/lib/snapd/g580 et /var/lib/snapd/g105
Chemin fichier~/cache/haproxy-1000.cache, haproxy-1001.cache, haproxy-1002.cache
Chemin fichier/tmp/jasper-log
Jeton APIapi_token/ecd427ea8330a4ff73618483e00b9b41
En-tête HTTPUser-token présent dans les requêtes sortantes vers les domaines img.

MITRE ATT&CK

Cartographie publiée par Rapid7 Labs dans le rapport d'origine, reprise ici sous forme condensée.

TactiqueTechniqueIDJustification
Accès initialExploit Public-Facing ApplicationT1190API de filtres HAProxy détournée comme point d'injection, livraison par point d'eau via le répartiteur compromis
ExécutionCommand and Scripting Interpreter: Unix ShellT1059.004popen() pour l'opcode 3 de ted, shell PTY en mode 5 et reverse shell en mode 3 de curlRAT
ExécutionNative APIT1106pthread_create et pthread_detach pour les threads détachés, pool_alloc et task_wakeup de HAProxy pour l'ordonnancement
PersistanceHijack Execution Flow: Dynamic LinkerT1574.006Implant chargé comme filtre HAProxy au démarrage du processus, persistant à travers les redémarrages
PersistanceCreate or Modify System ProcessT1543crond légitime écrasé sur place, service redémarré, horodatage aligné sur /usr/bin/ssh
Élévation de privilègesAbuse Elevation Control MechanismT1548setuid(0) et setreuid(0,0) avant le reverse shell, stager vérifiant root avant dépôt
Évasion défensiveMasquerading: Match Legitimate NameT1036.005Noms crond, polkitd, agetty, atd, fonctions masquées en routines atd_ sur CentOS
Évasion défensiveClear Linux LogsT1070.002Effacement sélectif par mots clés dans bash_history, messages, audit.log, secure, syslog, auth.log
Évasion défensiveTimestompT1070.006Horodatage de création du crond backdooré copié sur /usr/bin/ssh
Évasion défensiveDisable or Modify OS LoggingT1562.006Compteurs de connexion HAProxy nettoyés atomiquement via offsets codés en dur
Évasion défensiveObfuscated Files or InformationT1027XOR chaîné et substitution monoalphabétique sur configuration et scripts, XOR à rétroaction plus base64 sur le C2
Évasion défensiveVirtualization/Sandbox EvasionT1497.001Chien de garde vérifiant /usr/lib/libvirtlog.so.0 avant activation
Évasion défensiveExecution GuardrailsT1480Stager déployant uniquement si HAProxy ou cron est présent, validation du jeton victime, liste noire d'IP de scanners
Accès aux identifiantsModify Authentication Process: PAMT1556.003Keylogger SSH interceptant les mots de passe en clair
Accès aux identifiantsSteal Web Session CookieT1539Capture passive des sessions HTTP et des cookies par filtres regex
DécouverteSystem Information DiscoveryT1082Profilage hôte, distribution, noyau, architecture par le stager, balise de 10 Ko de curlRAT
DécouverteProcess DiscoveryT1057Chien de garde interrogeant le PID HAProxy toutes les heures
CollecteBrowser Session HijackingT1185Corps de réponse remplacé ou complété par un script déchiffré via les callbacks de filtre HAProxy
CollecteAutomated CollectionT1119Journalisation horodatée par requête correspondante
Commande et contrôleApplication Layer Protocol: Web ProtocolsT1071.001C2 de ted tunnelisé en HTTP à travers le répartiteur, curlRAT en HTTPS avec repli HTTP
Commande et contrôleData Encoding: Standard EncodingT1132.001Base64 après XOR à rétroaction pour curlRAT, XOR roulant sur le protocole de pipe de ted
Commande et contrôleWeb ServiceT1102Repli sur img.monderhouse.space, img.darklights.store comme hôte de configuration de secours
Commande et contrôleProtocol TunnelingT1572Shell interactif tunnelisé par FIFO nommées, réponse exfiltrée par send() direct hors journalisation HAProxy
ExfiltrationExfiltration Over C2 ChannelT1041Identifiants SSH et sortie de commande renvoyés par le même canal
ExfiltrationArchive Collected DataT1560Chiffrement par substitution avant écriture, XOR à rétroaction plus base64 en sortie

Remédiation : checklist opérationnelle

  1. Vérifier l'intégrité des binaires HAProxy en production par comparaison d'empreinte avec les paquets officiels de la distribution ou avec une recompilation de référence. Un binaire de 18 Mo pour un HAProxy 2.8.12 doit être considéré comme suspect en soi.
  2. Rechercher immédiatement les empreintes et domaines listés en section IOC dans les journaux DNS, proxy et EDR, en remontant au moins à début 2025 compte tenu de la durée estimée de la campagne.
  3. Chercher sur les serveurs de bordure les chemins de cache haproxy-1000 à 1002, /var/lib/snapd/g580, /var/lib/sshd/c8c68e629bba773a10ac80012d10bf19, /tmp/jasper-log et les FIFO nommées sous /tmp.
  4. Alerter sur toute requête HTTP vers /favorite_list_2x_m500_ico.jpg au niveau du pare-feu applicatif ou de la capture réseau, indépendamment des journaux du répartiteur.
  5. Contrôler l'intégrité de crond, agetty, atd, polkitd et sshd sur les serveurs internes, en comparant aux paquets de la distribution plutôt qu'aux horodatages, qui sont falsifiés.
  6. Corréler le trafic réseau sortant des serveurs de bordure hors des journaux applicatifs. Le C2 de ted ne remonte dans aucun journal HAProxy et n'atteint aucun backend : seule une capture réseau ou un flux NetFlow indépendant le voit.
  7. Traquer les balises HTTPS régulières vers des sous-domaines img. sur des TLD .store, .space, .site et .autos, en tenant compte d'un intervalle par défaut de douze heures, qui échappe aux règles de détection calibrées sur des périodicités courtes.
  8. Faire tourner tous les identifiants SSH et applicatifs susceptibles d'avoir transité par un serveur de bordure compromis, y compris ceux des comptes de service.
  9. Traiter les composants de bordure gérant le trafic, la terminaison TLS ou des modules d'exécution avec le même niveau d'exigence que les serveurs applicatifs : durcissement, journalisation externalisée et contrôle d'intégrité périodique.
  10. Sur les environnements exposant un portail Groupware ou un serveur de messagerie sur Internet, prioriser la mise à jour de ces composants et la revue de leurs journaux d'authentification, le vecteur d'accès initial restant non confirmé.

Sources

Tags : ted backdoorcurlRATHAProxyAPT37Corée du SudRapid7 LabsespionnageLinux
Partager cet article