muaddib-scanner 2.11.128 → 2.11.129
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/package.json +1 -1
- package/{self-scan-v2.11.128.json → self-scan-v2.11.129.json} +1 -1
- package/src/monitor/classify.js +1 -0
- package/src/response/playbooks.js +6 -0
- package/src/rules/index.js +14 -0
- package/src/scanner/ast-detectors/handle-call-expression.js +14 -0
- package/src/scanner/ast-detectors/handle-post-walk.js +29 -0
- package/src/scanner/ast.js +11 -0
package/package.json
CHANGED
package/src/monitor/classify.js
CHANGED
|
@@ -44,6 +44,7 @@ const HIGH_CONFIDENCE_MALICE_TYPES = new Set([
|
|
|
44
44
|
'download_exec_binary', // download+chmod+exec
|
|
45
45
|
'reverse_shell', // reverse shell (always malicious)
|
|
46
46
|
'crypto_staged_payload', // decrypt->eval staged payload chain
|
|
47
|
+
'crypto_exfil', // harvest + encrypt(RSA/AES) + network to non-benign dest (litellm/Hades)
|
|
47
48
|
'intent_credential_exfil', // intra-file credential->network
|
|
48
49
|
'intent_command_exfil', // intra-file command->network
|
|
49
50
|
'cross_file_dataflow', // proven taint cross-modules
|
|
@@ -693,6 +693,12 @@ const PLAYBOOKS = {
|
|
|
693
693
|
'puis execute le resultat via eval/Function. Pattern buildrunner-dev: payload malveillant cache dans une image PNG. ' +
|
|
694
694
|
'Isoler immediatement. Analyser le payload dechiffre. Supprimer le package.',
|
|
695
695
|
|
|
696
|
+
crypto_exfil:
|
|
697
|
+
'CRITIQUE: Exfiltration chiffree de secrets. Le code aspire des credentials/env, les chiffre (RSA publicEncrypt / AES createCipheriv, ' +
|
|
698
|
+
'souvent une enveloppe hybride avec cle publique embarquee) puis les envoie sur le reseau vers une destination non first-party. ' +
|
|
699
|
+
'Le chiffrement masque le payload aux inspections DLP/taint (pattern litellm/Hades). Isoler immediatement. Identifier la cle publique ' +
|
|
700
|
+
'et le host de destination (C2). Revoquer les secrets potentiellement exposes. Supprimer le package.',
|
|
701
|
+
|
|
696
702
|
download_exec_binary:
|
|
697
703
|
'CRITIQUE: Pattern download-execute detecte. Le code telecharge un binaire, le rend executable (chmod 755), ' +
|
|
698
704
|
'puis l\'execute via execSync. Dropper deguise en compilation native addon (NeoShadow pattern). ' +
|
package/src/rules/index.js
CHANGED
|
@@ -809,6 +809,20 @@ const RULES = {
|
|
|
809
809
|
],
|
|
810
810
|
mitre: 'T1195.002'
|
|
811
811
|
},
|
|
812
|
+
crypto_exfil: {
|
|
813
|
+
id: 'MUADDIB-COMPOUND-019',
|
|
814
|
+
name: 'Hybrid-Crypto Credential Exfiltration',
|
|
815
|
+
severity: 'CRITICAL',
|
|
816
|
+
confidence: 'high',
|
|
817
|
+
domain: 'malware',
|
|
818
|
+
description: 'Harvesting de secrets (env/credentials) + primitive de chiffrement (RSA publicEncrypt / AES createCipheriv) + envoi reseau dans le MEME fichier, vers une destination qui n\'est PAS entierement first-party/de confiance (destAllBenign faux). Les secrets voles sont chiffres AVANT exfil pour echapper a l\'inspection DLP et au taint statique — pattern litellm / Hades (juin 2026). Les libs E2E/licence/telemetry legitimes chiffrent+envoient mais n\'aspirent pas des credentials dans le blob et ne postent que vers leur propre provider (supprime par destAllBenign). Miroir cote chiffrement de fetch_decrypt_exec.',
|
|
819
|
+
references: [
|
|
820
|
+
'https://attack.mitre.org/techniques/T1560/',
|
|
821
|
+
'https://attack.mitre.org/techniques/T1041/',
|
|
822
|
+
'https://attack.mitre.org/techniques/T1552/'
|
|
823
|
+
],
|
|
824
|
+
mitre: 'T1560'
|
|
825
|
+
},
|
|
812
826
|
|
|
813
827
|
// Package.json script patterns
|
|
814
828
|
curl_pipe_sh: {
|
|
@@ -1502,6 +1502,20 @@ function handleCallExpression(node, ctx) {
|
|
|
1502
1502
|
file: ctx.relFile
|
|
1503
1503
|
});
|
|
1504
1504
|
}
|
|
1505
|
+
// crypto_exfil (encrypt side): a REAL call to an encryption primitive sets the file flag
|
|
1506
|
+
// consumed by the same-file crypto_exfil compound (handle-post-walk.js). AST-based — a string
|
|
1507
|
+
// or regex literal that merely contains "createCipheriv(" is not a CallExpression and won't trip it.
|
|
1508
|
+
if (propName === 'createCipher' || propName === 'createCipheriv' || propName === 'publicEncrypt') {
|
|
1509
|
+
ctx.hasCryptoEncipher = true;
|
|
1510
|
+
} else if (propName === 'encrypt') {
|
|
1511
|
+
// WebCrypto: crypto.subtle.encrypt(...) — gate on the `.subtle.` receiver so a generic
|
|
1512
|
+
// foo.encrypt() (ORMs, mailers, many libs expose .encrypt) does not set the flag.
|
|
1513
|
+
const obj = node.callee.object;
|
|
1514
|
+
if (obj && obj.type === 'MemberExpression' && obj.property &&
|
|
1515
|
+
(obj.property.name === 'subtle' || obj.property.value === 'subtle')) {
|
|
1516
|
+
ctx.hasCryptoEncipher = true;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1505
1519
|
if (propName === '_compile') {
|
|
1506
1520
|
// Context-aware gating: only flag _compile when the Module API is plausibly in scope.
|
|
1507
1521
|
// Custom class methods (e.g. blessed's Tput.prototype._compile) are not malware.
|
|
@@ -366,6 +366,35 @@ function handlePostWalk(ctx) {
|
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
// crypto_exfil (RSA+AES hybrid exfil — litellm/Hades 2026): secret harvesting + an encryption
|
|
370
|
+
// primitive (RSA publicEncrypt / AES createCipheriv) + a network send, in the SAME file, to a
|
|
371
|
+
// destination that is NOT entirely first-party/trusted. The encrypt step wraps the stolen
|
|
372
|
+
// secrets so DLP and static taint miss the payload. Legit E2E/license/telemetry SDKs encrypt
|
|
373
|
+
// and send, but do not also harvest credentials/env into the blob, and the benign ones post
|
|
374
|
+
// only to their own provider (suppressed by destAllBenign). Requires a non-LOW harvest threat
|
|
375
|
+
// already emitted in this file — mirror of fetch_decrypt_exec (decrypt-side loader).
|
|
376
|
+
// destAllBenign is computed once above, at the credential_regex_harvest emission site.
|
|
377
|
+
const CRYPTO_EXFIL_HARVEST_TYPES = new Set([
|
|
378
|
+
'env_access', 'env_charcode_reconstruction', 'env_harvesting_dynamic',
|
|
379
|
+
'sensitive_string', 'suspicious_dataflow', 'credential_regex_harvest',
|
|
380
|
+
'credential_command_exec', 'npm_token_steal'
|
|
381
|
+
]);
|
|
382
|
+
if (ctx.hasCryptoEncipher && ctx.hasNetworkCallInFile && !destAllBenign) {
|
|
383
|
+
const cryptoExfilHarvest = ctx.threats.some(t =>
|
|
384
|
+
t.file === ctx.relFile && t.severity !== 'LOW' && CRYPTO_EXFIL_HARVEST_TYPES.has(t.type)
|
|
385
|
+
);
|
|
386
|
+
if (cryptoExfilHarvest) {
|
|
387
|
+
ctx.threats.push({
|
|
388
|
+
type: 'crypto_exfil',
|
|
389
|
+
severity: 'CRITICAL',
|
|
390
|
+
message: 'Hybrid-crypto exfiltration: secret harvesting + ' +
|
|
391
|
+
(ctx.hasEmbeddedPublicKey ? 'embedded RSA/EC public key + ' : '') +
|
|
392
|
+
'encryption (RSA/AES) + network send in same file — harvested secrets are encrypted before exfil to evade DLP/taint inspection (litellm/Hades pattern).',
|
|
393
|
+
file: ctx.relFile
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
369
398
|
// GlassWorm: Unicode variation selector decoder = .codePointAt + variation selector constants
|
|
370
399
|
// CRITICAL if combined with eval/exec (GlassWorm always uses dynamic execution),
|
|
371
400
|
// MEDIUM otherwise (.codePointAt + 0xFE00 is legitimate Unicode processing in fonts/text libs)
|
package/src/scanner/ast.js
CHANGED
|
@@ -208,6 +208,17 @@ function analyzeFile(content, filePath, basePath) {
|
|
|
208
208
|
// Safe domain exclusion: if ALL URLs in file are from known registries, suppress download_exec_binary
|
|
209
209
|
fetchOnlySafeDomains: false, // computed below after URL extraction
|
|
210
210
|
hasCryptoDecipher: /\bcreateDecipher(iv)?\s*\(/.test(content),
|
|
211
|
+
// crypto_exfil (RSA+AES hybrid exfil — litellm/Hades 2026): encrypt-side mirror of
|
|
212
|
+
// hasCryptoDecipher. Set TRUE in handle-call-expression.js on a REAL call to an encryption
|
|
213
|
+
// primitive (createCipher(iv) = AES, publicEncrypt = RSA pubkey-wrap, subtle.encrypt =
|
|
214
|
+
// WebCrypto). AST-based on purpose, NOT a content regex: a detector/linter/doc that merely
|
|
215
|
+
// contains the STRING "createCipheriv(" must not trip it (self-scan & meta-tooling FP).
|
|
216
|
+
// Feeds the same-file crypto_exfil compound in handle-post-walk.js.
|
|
217
|
+
hasCryptoEncipher: false,
|
|
218
|
+
// Embedded RSA/EC public key (PEM SPKI/PKCS1) — an attacker's hardcoded recipient key for
|
|
219
|
+
// the exfil envelope. Enrichment signal only (a PEM public key alone — JWT/JWK verify,
|
|
220
|
+
// signature checks — is benign and extremely common, so it is never flagged on its own).
|
|
221
|
+
hasEmbeddedPublicKey: /-----BEGIN (?:RSA |EC )?PUBLIC KEY-----/.test(content),
|
|
211
222
|
// Wave 4: native addon camouflage signals
|
|
212
223
|
hasRequireNodeFile: false,
|
|
213
224
|
hasExecSyncCall: false,
|