muaddib-scanner 2.9.0 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/iocs/builtin.yaml CHANGED
@@ -117,6 +117,32 @@ packages:
117
117
  source: shai-hulud-v3
118
118
  description: "First confirmed v3 payload - testing phase"
119
119
 
120
+ # GlassWorm hijacked packages (mars 2026)
121
+ - name: "@aifabrix/miso-client"
122
+ version: "4.7.2"
123
+ source: glassworm
124
+ - name: "@iflow-mcp/watercrawl-watercrawl-mcp"
125
+ version: "1.3.0"
126
+ source: glassworm
127
+ - name: "@iflow-mcp/watercrawl-watercrawl-mcp"
128
+ version: "1.3.1"
129
+ source: glassworm
130
+ - name: "@iflow-mcp/watercrawl-watercrawl-mcp"
131
+ version: "1.3.2"
132
+ source: glassworm
133
+ - name: "@iflow-mcp/watercrawl-watercrawl-mcp"
134
+ version: "1.3.3"
135
+ source: glassworm
136
+ - name: "@iflow-mcp/watercrawl-watercrawl-mcp"
137
+ version: "1.3.4"
138
+ source: glassworm
139
+ - name: "react-native-country-select"
140
+ version: "0.3.91"
141
+ source: glassworm
142
+ - name: "react-native-international-phone-number"
143
+ version: "0.11.8"
144
+ source: glassworm
145
+
120
146
  # Attaques historiques
121
147
  - name: "flatmap-stream"
122
148
  version: "0.1.1"
@@ -177,6 +203,9 @@ files:
177
203
  - 3nvir0nm3nt.json
178
204
  - c9nt3nts.json
179
205
  - c0nt3nts.json
206
+ # GlassWorm (mars 2026)
207
+ - i.js
208
+ - init.json
180
209
 
181
210
  hashes:
182
211
  # Shai-Hulud v2 payloads
@@ -185,6 +214,8 @@ hashes:
185
214
  - "f099c5d9ec417d4445a0328ac0ada9cde79fc37410914103ae9c609cbc0ee068"
186
215
  - "a3894003ad1d293ba96d77881ccd2071446dc3f65f434669b49b3da92421901a"
187
216
  - "4b2399646573bb737c4969563303d8ee2e9ddbd1b271f1ca9e35ea78062538db"
217
+ # GlassWorm install.js (React Native hijack)
218
+ - "59221aa9623d86c930357dba7e3f54138c7ccbd0daa9c483d766cd8ce1b6ad26"
188
219
 
189
220
  markers:
190
221
  # Shai-Hulud v1/v2
@@ -198,4 +229,9 @@ markers:
198
229
  # Protestware
199
230
  - "peacenotwar"
200
231
  # Generic malicious
201
- - "/dev/tcp"
232
+ - "/dev/tcp"
233
+ # GlassWorm (mars 2026)
234
+ - "lzcdrtfxyqiplpd"
235
+ - "28PKnu7RzizxBzFPoLp69HLXp9bJL3JFtT2s5QzHsEA2"
236
+ - "BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC"
237
+ - "6YGcuyFRJKZtcaYCCFba9fScNUvPkGXodXE1mJiSzqDJ"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.9.0",
3
+ "version": "2.9.1",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -515,6 +515,28 @@ const PLAYBOOKS = {
515
515
  'Le package execute des commandes et transmet les resultats. Verifier les commandes executees. ' +
516
516
  'Supprimer le package si non attendu. Auditer les logs reseau pour identifier les donnees exfiltrees.',
517
517
 
518
+ unicode_invisible_injection:
519
+ 'CRITIQUE: Caracteres Unicode invisibles detectes (zero-width, variation selectors). ' +
520
+ 'Technique GlassWorm: du code malveillant est encode via des variation selectors invisibles dans les editeurs. ' +
521
+ 'Analyser le fichier avec un editeur hexa. Supprimer le package immediatement. ' +
522
+ 'Verifier les autres fichiers du projet pour des injections similaires.',
523
+
524
+ unicode_variation_decoder:
525
+ 'CRITIQUE: Decodeur de payload Unicode via variation selectors detecte (.codePointAt + 0xFE00/0xE0100). ' +
526
+ 'Signature GlassWorm: le code reconstruit un payload octet par octet a partir de caracteres invisibles. ' +
527
+ 'Isoler immediatement. Decoder manuellement les variation selectors pour extraire le payload. Supprimer le package.',
528
+
529
+ blockchain_c2_resolution:
530
+ 'Import Solana/Web3 + appel API blockchain C2 (getSignaturesForAddress, getTransaction) detecte. ' +
531
+ 'Technique GlassWorm: la blockchain sert de dead drop resolver pour obtenir l\'adresse C2 via le champ memo. ' +
532
+ 'Cout de rotation: 0.000005 SOL par changement d\'adresse C2 — censorship-resistant. ' +
533
+ 'Bloquer les connexions vers les RPC Solana. Supprimer le package.',
534
+
535
+ blockchain_rpc_endpoint:
536
+ 'Endpoint RPC blockchain hardcode detecte (Solana mainnet, Infura Ethereum). ' +
537
+ 'Dans un package non-crypto, cela indique un potentiel canal C2 via blockchain. ' +
538
+ 'Verifier le contexte: si le package n\'a rien a voir avec la blockchain, supprimer immediatement.',
539
+
518
540
  bin_field_hijack:
519
541
  'CRITIQUE: Le champ "bin" de package.json shadow une commande systeme (node, npm, git, bash, etc.). ' +
520
542
  'A l\'installation, npm cree un symlink dans node_modules/.bin/ qui intercepte la commande reelle. ' +
@@ -1544,6 +1544,56 @@ const RULES = {
1544
1544
  ],
1545
1545
  mitre: 'T1059'
1546
1546
  },
1547
+
1548
+ // GlassWorm detections (mars 2026)
1549
+ unicode_invisible_injection: {
1550
+ id: 'MUADDIB-OBF-003',
1551
+ name: 'Unicode Invisible Character Injection',
1552
+ severity: 'CRITICAL',
1553
+ confidence: 'high',
1554
+ description: 'Caracteres Unicode invisibles detectes (zero-width, variation selectors). Technique GlassWorm: encodage de payload malveillant via variation selectors (U+FE00-FE0F, U+E0100-E01EF) invisible dans les editeurs.',
1555
+ references: [
1556
+ 'https://www.aikido.dev/blog/glassworm-returns-unicode-attack-github-npm-vscode',
1557
+ 'https://attack.mitre.org/techniques/T1027/'
1558
+ ],
1559
+ mitre: 'T1027'
1560
+ },
1561
+ unicode_variation_decoder: {
1562
+ id: 'MUADDIB-AST-053',
1563
+ name: 'Unicode Variation Selector Decoder',
1564
+ severity: 'CRITICAL',
1565
+ confidence: 'high',
1566
+ description: 'Decodeur de payload Unicode via variation selectors (.codePointAt + 0xFE00/0xE0100). Signature GlassWorm: le code reconstruit un payload octet par octet a partir de caracteres invisibles.',
1567
+ references: [
1568
+ 'https://www.koi.security/blog/glassworm-first-self-propagating-worm-using-invisible-code-hits-openvsx-marketplace',
1569
+ 'https://attack.mitre.org/techniques/T1140/'
1570
+ ],
1571
+ mitre: 'T1140'
1572
+ },
1573
+ blockchain_c2_resolution: {
1574
+ id: 'MUADDIB-AST-054',
1575
+ name: 'Blockchain C2 Resolution (Dead Drop)',
1576
+ severity: 'HIGH',
1577
+ confidence: 'high',
1578
+ description: 'Import Solana/Web3 + appel API C2 (getSignaturesForAddress, getTransaction). Technique GlassWorm: la blockchain sert de dead drop resolver pour obtenir l\'adresse C2 via le champ memo des transactions.',
1579
+ references: [
1580
+ 'https://www.sonatype.com/blog/hijacked-npm-packages-deliver-malware-via-solana-linked-to-glassworm',
1581
+ 'https://attack.mitre.org/techniques/T1102/'
1582
+ ],
1583
+ mitre: 'T1102'
1584
+ },
1585
+ blockchain_rpc_endpoint: {
1586
+ id: 'MUADDIB-AST-055',
1587
+ name: 'Hardcoded Blockchain RPC Endpoint',
1588
+ severity: 'MEDIUM',
1589
+ confidence: 'medium',
1590
+ description: 'Endpoint RPC blockchain hardcode (Solana mainnet, Infura Ethereum). Dans un package non-crypto, indique un potentiel canal C2 via blockchain.',
1591
+ references: [
1592
+ 'https://www.koi.security/blog/glassworm-first-self-propagating-worm-using-invisible-code-hits-openvsx-marketplace',
1593
+ 'https://attack.mitre.org/techniques/T1102/'
1594
+ ],
1595
+ mitre: 'T1102'
1596
+ },
1547
1597
  };
1548
1598
 
1549
1599
  function getRule(type) {
@@ -170,7 +170,11 @@ const GIT_HOOKS = [
170
170
  const SUSPICIOUS_DOMAINS_HIGH = [
171
171
  'oastify.com', 'oast.fun', 'oast.me', 'oast.live',
172
172
  'burpcollaborator.net', 'webhook.site', 'pipedream.net',
173
- 'requestbin.com', 'hookbin.com', 'canarytokens.com'
173
+ 'requestbin.com', 'hookbin.com', 'canarytokens.com',
174
+ // GlassWorm C2 IPs (mars 2026)
175
+ '217.69.3.218', '217.69.3.152',
176
+ '199.247.10.166', '199.247.13.106',
177
+ '140.82.52.31', '45.32.150.251'
174
178
  ];
175
179
 
176
180
  // Suspicious tunnel/proxy domains (MEDIUM severity)
@@ -198,6 +202,27 @@ const SANDBOX_INDICATORS = [
198
202
  '/proc/self/cgroup'
199
203
  ];
200
204
 
205
+ // Blockchain RPC endpoints — potential C2 channel via blockchain (GlassWorm)
206
+ const BLOCKCHAIN_RPC_ENDPOINTS = [
207
+ 'api.mainnet-beta.solana.com',
208
+ 'api.devnet.solana.com',
209
+ 'api.testnet.solana.com',
210
+ 'mainnet.infura.io',
211
+ 'rpc.ankr.com'
212
+ ];
213
+
214
+ // Solana/Web3 C2 methods — used for dead drop resolver (GlassWorm)
215
+ const SOLANA_C2_METHODS = [
216
+ 'getSignaturesForAddress', 'getAccountInfo', 'getTransaction',
217
+ 'getConfirmedSignaturesForAddress2', 'getParsedTransaction'
218
+ ];
219
+
220
+ // Solana/Web3 package names
221
+ const SOLANA_PACKAGES = ['@solana/web3.js', 'solana-web3.js', '@solana/web3'];
222
+
223
+ // Variation selector constants (GlassWorm decoder signature)
224
+ const VARIATION_SELECTOR_CONSTS = [0xFE00, 0xFE0F, 0xE0100, 0xE01EF];
225
+
201
226
  // ============================================
202
227
  // HELPER FUNCTIONS
203
228
  // ============================================
@@ -647,6 +672,10 @@ function handleCallExpression(node, ctx) {
647
672
  if (reqStr && /\.node\s*$/.test(reqStr)) {
648
673
  ctx.hasRequireNodeFile = true;
649
674
  }
675
+ // GlassWorm: track Solana/Web3 require imports for compound blockchain C2 detection
676
+ if (reqStr && SOLANA_PACKAGES.some(pkg => reqStr === pkg)) {
677
+ ctx.hasSolanaImport = true;
678
+ }
650
679
  }
651
680
 
652
681
  // Detect exec/execSync with dangerous shell commands (direct or via MemberExpression)
@@ -1653,6 +1682,10 @@ function handleImportExpression(node, ctx) {
1653
1682
  file: ctx.relFile
1654
1683
  });
1655
1684
  }
1685
+ // GlassWorm: track Solana/Web3 dynamic import for compound blockchain C2 detection
1686
+ if (SOLANA_PACKAGES.some(pkg => src.value === pkg)) {
1687
+ ctx.hasSolanaImport = true;
1688
+ }
1656
1689
  } else {
1657
1690
  ctx.threats.push({
1658
1691
  type: 'dynamic_import',
@@ -1821,6 +1854,34 @@ function handleLiteral(node, ctx) {
1821
1854
  file: ctx.relFile
1822
1855
  });
1823
1856
  }
1857
+
1858
+ // Blockchain RPC endpoints — potential C2 channel (GlassWorm)
1859
+ for (const endpoint of BLOCKCHAIN_RPC_ENDPOINTS) {
1860
+ if (lowerVal.includes(endpoint)) {
1861
+ ctx.threats.push({
1862
+ type: 'blockchain_rpc_endpoint',
1863
+ severity: 'MEDIUM',
1864
+ message: `Hardcoded blockchain RPC endpoint "${endpoint}" — potential blockchain C2 channel.`,
1865
+ file: ctx.relFile
1866
+ });
1867
+ break;
1868
+ }
1869
+ }
1870
+
1871
+ // Track Solana C2 method names in string literals (for compound detection)
1872
+ for (const method of SOLANA_C2_METHODS) {
1873
+ if (node.value === method || node.value.includes(method)) {
1874
+ ctx.hasSolanaC2Method = true;
1875
+ break;
1876
+ }
1877
+ }
1878
+ }
1879
+
1880
+ // Track variation selector constants in numeric literals (GlassWorm decoder)
1881
+ if (typeof node.value === 'number') {
1882
+ if (VARIATION_SELECTOR_CONSTS.includes(node.value)) {
1883
+ ctx.hasVariationSelectorConst = true;
1884
+ }
1824
1885
  }
1825
1886
  }
1826
1887
 
@@ -1995,6 +2056,16 @@ function handleMemberExpression(node, ctx) {
1995
2056
  });
1996
2057
  }
1997
2058
 
2059
+ // GlassWorm: track .codePointAt() calls (variation selector decoder pattern)
2060
+ if (node.property?.type === 'Identifier' && node.property.name === 'codePointAt') {
2061
+ ctx.hasCodePointAt = true;
2062
+ }
2063
+
2064
+ // GlassWorm: track Solana C2 method calls (e.g., connection.getSignaturesForAddress)
2065
+ if (node.property?.type === 'Identifier' && SOLANA_C2_METHODS.includes(node.property.name)) {
2066
+ ctx.hasSolanaC2Method = true;
2067
+ }
2068
+
1998
2069
  if (
1999
2070
  node.object?.object?.name === 'process' &&
2000
2071
  node.object?.property?.name === 'env'
@@ -2299,6 +2370,30 @@ function handlePostWalk(ctx) {
2299
2370
  file: ctx.relFile
2300
2371
  });
2301
2372
  }
2373
+
2374
+ // GlassWorm: Unicode variation selector decoder = .codePointAt + variation selector constants
2375
+ if (ctx.hasCodePointAt && ctx.hasVariationSelectorConst) {
2376
+ ctx.threats.push({
2377
+ type: 'unicode_variation_decoder',
2378
+ severity: 'CRITICAL',
2379
+ message: 'Unicode variation selector decoder: .codePointAt() + 0xFE00/0xE0100 constants — GlassWorm payload reconstruction from invisible characters.',
2380
+ file: ctx.relFile
2381
+ });
2382
+ }
2383
+
2384
+ // GlassWorm: Blockchain C2 resolution = Solana import + C2 method
2385
+ // CRITICAL if combined with eval/exec, HIGH otherwise
2386
+ if (ctx.hasSolanaImport && ctx.hasSolanaC2Method) {
2387
+ ctx.threats.push({
2388
+ type: 'blockchain_c2_resolution',
2389
+ severity: ctx.hasDynamicExec ? 'CRITICAL' : 'HIGH',
2390
+ message: 'Solana/Web3 import + blockchain C2 method (getSignaturesForAddress/getTransaction) — ' +
2391
+ (ctx.hasDynamicExec
2392
+ ? 'dead drop resolver with dynamic execution. GlassWorm blockchain C2 pattern confirmed.'
2393
+ : 'potential dead drop resolver. GlassWorm technique: C2 address rotated via Solana memo field.'),
2394
+ file: ctx.relFile
2395
+ });
2396
+ }
2302
2397
  }
2303
2398
 
2304
2399
  function handleWithStatement(node, ctx) {
@@ -160,7 +160,13 @@ function analyzeFile(content, filePath, basePath) {
160
160
  // C10: Hash verification — legitimate binary installers verify checksums
161
161
  // Requires BOTH createHash() call AND .digest() call — false positives from
162
162
  // standalone mentions of 'sha256' or 'integrity' in comments/descriptions
163
- hasHashVerification: /\bcreateHash\s*\(/.test(content) && /\.digest\s*\(/.test(content)
163
+ hasHashVerification: /\bcreateHash\s*\(/.test(content) && /\.digest\s*\(/.test(content),
164
+ // GlassWorm: variation selector decoder pattern (.codePointAt + 0xFE00/0xE0100)
165
+ hasCodePointAt: false,
166
+ hasVariationSelectorConst: false,
167
+ // GlassWorm: blockchain C2 resolution (Solana import + C2 method + dynamic exec)
168
+ hasSolanaImport: false,
169
+ hasSolanaC2Method: false
164
170
  };
165
171
 
166
172
  // Compute fetchOnlySafeDomains: check if ALL URLs in file point to known registries
@@ -70,6 +70,18 @@ function detectObfuscation(targetPath) {
70
70
  signals.push('base64_eval');
71
71
  }
72
72
 
73
+ // 7. Unicode invisible character injection (GlassWorm — mars 2026)
74
+ // Detects zero-width chars, variation selectors, tag characters embedded in source
75
+ const invisibleCount = countInvisibleUnicode(content);
76
+ if (invisibleCount >= 3) {
77
+ threats.push({
78
+ type: 'unicode_invisible_injection',
79
+ severity: isPackageOutput ? 'LOW' : 'CRITICAL',
80
+ message: `${invisibleCount} invisible Unicode characters detected (zero-width, variation selectors, tag chars). GlassWorm technique: payload encoded via invisible codepoints.`,
81
+ file: relativePath
82
+ });
83
+ }
84
+
73
85
  // Hex/unicode escapes alone are not obfuscation (e.g. lodash Unicode char tables).
74
86
  // Only count them when combined with strong obfuscation signals.
75
87
  const hasStrongSignals = signals.some(s => s !== 'hex_escapes' && s !== 'unicode_escapes');
@@ -130,4 +142,52 @@ function hasLargeStringArray(content) {
130
142
  return false;
131
143
  }
132
144
 
145
+ /**
146
+ * Count invisible Unicode codepoints in content (GlassWorm detection).
147
+ * Covers BMP zero-width chars, variation selectors, and supplementary plane
148
+ * tag characters / variation selectors supplement via codePointAt iteration.
149
+ *
150
+ * Codepoints detected:
151
+ * - U+200B, U+200C, U+200D (zero-width space/joiner/non-joiner)
152
+ * - U+FEFF (BOM — only if position > 0; pos 0 is legitimate BOM)
153
+ * - U+2060 (word joiner), U+180E (Mongolian vowel separator)
154
+ * - U+FE00-U+FE0F (variation selectors — GlassWorm 256-value encoding)
155
+ * - U+E0100-U+E01EF (variation selectors supplement)
156
+ * - U+E0001-U+E007F (tag characters)
157
+ */
158
+ function countInvisibleUnicode(content) {
159
+ let count = 0;
160
+ for (let i = 0; i < content.length; i++) {
161
+ const cp = content.codePointAt(i);
162
+ // BMP invisible chars
163
+ if (cp === 0x200B || cp === 0x200C || cp === 0x200D ||
164
+ cp === 0x2060 || cp === 0x180E) {
165
+ count++;
166
+ }
167
+ // BOM only suspicious after position 0
168
+ else if (cp === 0xFEFF && i > 0) {
169
+ count++;
170
+ }
171
+ // BMP variation selectors (U+FE00-U+FE0F)
172
+ else if (cp >= 0xFE00 && cp <= 0xFE0F) {
173
+ count++;
174
+ }
175
+ // Supplementary plane: variation selectors supplement (U+E0100-U+E01EF)
176
+ else if (cp >= 0xE0100 && cp <= 0xE01EF) {
177
+ count++;
178
+ i++; // skip surrogate pair low half
179
+ }
180
+ // Supplementary plane: tag characters (U+E0001-U+E007F)
181
+ else if (cp >= 0xE0001 && cp <= 0xE007F) {
182
+ count++;
183
+ i++; // skip surrogate pair low half
184
+ }
185
+ // Skip surrogate pair low half for other supplementary chars
186
+ else if (cp > 0xFFFF) {
187
+ i++;
188
+ }
189
+ }
190
+ return count;
191
+ }
192
+
133
193
  module.exports = { detectObfuscation };