git-subtree-dir: scanners/commons git-subtree-split: 0ffee85a4b83b3661185488c06ed9a9994c11412
177 lines
10 KiB
JSON
177 lines
10 KiB
JSON
{
|
|
"version": "0.1.0",
|
|
"id": "malware-signatures",
|
|
"description": "Known-bad-IDENTITY signatures: seven high-confidence regexes naming specific malware families - PHP webshells, reverse shells, cryptominers and offensive-security tooling. Deliberately small and tight. Complementary to the shape-based tables in this repository (entropy, taint, injection): those describe what an attack looks like, this one names things already known to be malicious. Detection data only.",
|
|
"owasp": "LLM03",
|
|
"$comment": "Extracted without behaviour change from llm-security/knowledge/signatures.json, read directly from the public remote at commit b0de0ca rather than transcribed. The seven rules are reproduced verbatim, key order included. What the seed runtime DOES with them - compiling every pattern case-insensitively, running each against a decode pipeline as well as raw bytes, filtering by an enabled-families policy, excluding certain paths, merging operator-supplied custom rules - is engine behaviour and is recorded under engine_behaviour_not_data rather than treated as part of the table.",
|
|
"provenance": {
|
|
"source_repo": "llm-security",
|
|
"source_files": [
|
|
"knowledge/signatures.json"
|
|
],
|
|
"source_consumer": "scanners/signature-scanner.mjs",
|
|
"source_version": "1.0",
|
|
"source_commit": "b0de0ca6d86ce697f39669d177c2c2654c280128",
|
|
"source_remote": "ssh://git@git.fromaitochitta.com/open/llm-security.git",
|
|
"source_bytes": 2494,
|
|
"source_sha256": "0155368f194c51e01f48e5b253067282a9c083549401a2d374afc46bb89c66bc",
|
|
"source_delivery": "read directly from the pinned commit; never transcribed through a dump",
|
|
"verified": "differentially - the seven rule objects here are compared field by field against the parsed source file, and every pattern string is byte-identical, 2026-08-09",
|
|
"evidence_limits": [
|
|
"This is an identity table, not a coverage claim. Seven signatures do not detect malware generally; they detect these seven things. A consumer that presents a clean SIG result as 'no malware' is overstating it, and the seed runtime's own header calls the table 'deliberately tight to minimize false positives'.",
|
|
"Three of the seven match on NAMES rather than on behaviour (`xmrig`, `mimikatz`, `meterpreter` and their kin). A document that merely discusses those tools matches. The seed runtime mitigates this by excluding knowledge/, tests/, docs/ and node_modules/ from the scan - a path exclusion, which is engine behaviour and does not travel with this table. A consumer without an equivalent exclusion will produce false positives this table's own severities do not warn about.",
|
|
"The provenance strings on each rule name a technique family or a public cheat sheet, not a specific sample or hash. They are attribution for the pattern's origin, not evidence that a given match is that malware."
|
|
]
|
|
},
|
|
"dialect": {
|
|
"name": "ecmascript",
|
|
"$comment": "Patterns are ECMAScript regular-expression source text exactly as the source file spells it. Unlike lexicon/injection-lexicon.json, NO rule carries a `flags` field: the engine compiles every pattern with `i` unconditionally (signature-scanner.mjs:48, `new RegExp(rule.pattern, 'i')`). Case-insensitivity is therefore engine behaviour applied to the whole table, not per-rule data. A consumer that compiles these patterns case-SENSITIVELY will silently under-match every one of them.",
|
|
"flags_applied_by_engine": "i",
|
|
"flags_declared_per_rule": "none - there is no flags field in this table",
|
|
"features_used": [
|
|
"non-capturing groups: (?:...)",
|
|
"word boundaries: \\b",
|
|
"character classes",
|
|
"negated character classes: [^...]",
|
|
"optional quantifiers: ? and *"
|
|
],
|
|
"translation_notes": [
|
|
"Python (`re`): compile with re.I. No rewriting is needed; verified by compiling all seven.",
|
|
"All seven compile in Node bare, in Node with `i`, and in Node with `iu` - 21/21 - and in Python `re` with re.I, 7/7. No pattern uses a construct that differs between the two engines.",
|
|
"No pattern contains a non-ASCII byte, and none contains the redundant `\\/` escape that appears in the other two pattern tables in this repository."
|
|
]
|
|
},
|
|
"normalisations": [],
|
|
"normalisations_note": "Empty by result, not by omission: all seven patterns are byte-identical to the source file, verified below. No escaping change was needed.",
|
|
"families": {
|
|
"$comment": "The family a rule belongs to. This is data - it is the `family` field on each rule, and the seed engine both filters on it and interpolates it into the finding title, evidence and recommendation. Note the exact spellings: `reverse_shell` with an underscore, and `cryptominer` rather than `miner`. A consumer that renames a family breaks the policy key that enables it.",
|
|
"values": [
|
|
"cryptominer",
|
|
"hacktool",
|
|
"reverse_shell",
|
|
"webshell"
|
|
],
|
|
"rule_counts": {
|
|
"cryptominer": 2,
|
|
"hacktool": 1,
|
|
"reverse_shell": 2,
|
|
"webshell": 2
|
|
},
|
|
"default_enabled": [
|
|
"webshell",
|
|
"reverse_shell",
|
|
"cryptominer",
|
|
"hacktool"
|
|
],
|
|
"default_enabled_note": "All four are enabled by default in the seed runtime (signature-scanner.mjs:30, DEFAULT_FAMILIES), in that order. Recorded because a consumer needs to know the table is fully active by default rather than opt-in; which families a given deployment enables is policy, not data."
|
|
},
|
|
"severity_vocabulary": {
|
|
"used_here": [
|
|
"critical",
|
|
"high"
|
|
],
|
|
"contract": "schema/finding.schema.json#/$defs/severity",
|
|
"$comment": "Only two of the five contract severities appear in this table. That is the table's own distribution, not a restriction on the vocabulary."
|
|
},
|
|
"rules": [
|
|
{
|
|
"id": "SIG-WEBSHELL-001",
|
|
"family": "webshell",
|
|
"severity": "critical",
|
|
"pattern": "(?:eval|assert|system|exec|passthru|shell_exec|popen|proc_open)\\s*\\(\\s*\\$_(?:POST|GET|REQUEST|COOKIE|SERVER)",
|
|
"description": "PHP webshell: executes attacker-controlled request data",
|
|
"provenance": "Classic PHP webshell pattern (c99/r57/b374k families)"
|
|
},
|
|
{
|
|
"id": "SIG-WEBSHELL-002",
|
|
"family": "webshell",
|
|
"severity": "high",
|
|
"pattern": "\\$_(?:POST|GET|REQUEST|COOKIE)\\s*\\[[^\\]]*\\]\\s*\\(",
|
|
"description": "PHP variable-function call on request data (obfuscated webshell)",
|
|
"provenance": "Variable-function webshell obfuscation"
|
|
},
|
|
{
|
|
"id": "SIG-REVSHELL-001",
|
|
"family": "reverse_shell",
|
|
"severity": "critical",
|
|
"pattern": "(?:bash|sh)\\s+-i\\s*>&?\\s*/dev/tcp/",
|
|
"description": "Bash /dev/tcp reverse shell",
|
|
"provenance": "PentestMonkey reverse-shell cheat sheet"
|
|
},
|
|
{
|
|
"id": "SIG-REVSHELL-002",
|
|
"family": "reverse_shell",
|
|
"severity": "critical",
|
|
"pattern": "\\bnc\\s+-[a-z]*e[a-z]*\\s+/(?:bin|usr/bin)/(?:sh|bash)\\b",
|
|
"description": "Netcat -e reverse shell",
|
|
"provenance": "Netcat reverse-shell one-liner"
|
|
},
|
|
{
|
|
"id": "SIG-MINER-001",
|
|
"family": "cryptominer",
|
|
"severity": "high",
|
|
"pattern": "stratum\\+(?:tcp|ssl)://",
|
|
"description": "Cryptominer stratum pool URL",
|
|
"provenance": "Stratum mining protocol"
|
|
},
|
|
{
|
|
"id": "SIG-MINER-002",
|
|
"family": "cryptominer",
|
|
"severity": "high",
|
|
"pattern": "\\b(?:xmrig|minerd|cgminer|ccminer|cpuminer)\\b",
|
|
"description": "Known cryptominer binary reference",
|
|
"provenance": "Common CPU/GPU miner binaries"
|
|
},
|
|
{
|
|
"id": "SIG-HACKTOOL-001",
|
|
"family": "hacktool",
|
|
"severity": "high",
|
|
"pattern": "\\b(?:mimikatz|meterpreter|sekurlsa::|lsadump::)\\b",
|
|
"description": "Offensive-security tooling reference",
|
|
"provenance": "Mimikatz / Metasploit Meterpreter"
|
|
}
|
|
],
|
|
"engine_behaviour_not_data": [
|
|
{
|
|
"id": "case-insensitive-compile",
|
|
"behaviour": "Every pattern is compiled with the `i` flag, unconditionally, at signature-scanner.mjs:48.",
|
|
"why_not_data": "It is applied to the table rather than declared by it. It is recorded under dialect.flags_applied_by_engine because a consumer cannot reproduce the seed behaviour without it."
|
|
},
|
|
{
|
|
"id": "decode-variant-matching",
|
|
"behaviour": "Each pattern is tested against five variants of a file's content, not just the raw bytes: raw, normalizeForScan with embedded decoding, the same on trimmed input, homoglyph-folded, and rot13 (signature-scanner.mjs:102-115). The first variant that matches wins, and a match on a non-raw variant is reported as obfuscated.",
|
|
"why_not_data": "It is the decode pipeline, which this repository does not own and deliberately does not specify. A consumer matching only raw bytes will miss obfuscated instances of these same seven signatures."
|
|
},
|
|
{
|
|
"id": "family-filter",
|
|
"behaviour": "Rules are filtered to the families named by the `sig.enabled_families` policy key before any file is read; if none are enabled the scanner returns no findings at all.",
|
|
"why_not_data": "Policy."
|
|
},
|
|
{
|
|
"id": "path-exclusion",
|
|
"behaviour": "Paths under knowledge/, tests/, docs/ and node_modules/ are skipped, because a ruleset, a fixture and a document about webshells all legitimately contain these patterns.",
|
|
"why_not_data": "Scan scoping, not detection. But see evidence_limits: without an equivalent, three of these seven rules are noisy by construction."
|
|
},
|
|
{
|
|
"id": "per-file-rule-dedup",
|
|
"behaviour": "A rule fires at most once per file, regardless of how many times it matches.",
|
|
"why_not_data": "Reporting behaviour. It means a finding count is a count of (file, rule) pairs, not of occurrences."
|
|
},
|
|
{
|
|
"id": "custom-rule-merge",
|
|
"behaviour": "Operator-supplied rules from `sig.custom_rules_path` are compiled by the same code and appended to this table at scan time.",
|
|
"why_not_data": "Runtime extension. A consumer matching only this table matches LESS than a seed deployment with custom rules loaded."
|
|
},
|
|
{
|
|
"id": "missing-field-defaults",
|
|
"behaviour": "The loader defaults a missing `family` to 'unknown', a missing `severity` to 'high', a missing `description` to the rule id, and a missing `provenance` to null, and drops any rule lacking `id` or `pattern` or whose pattern fails to compile.",
|
|
"why_not_data": "Loader tolerance. Every rule in this table carries all six fields, so no default is exercised - but a consumer writing a validator should not mistake the loader's leniency for an optional-field contract."
|
|
}
|
|
],
|
|
"totals": {
|
|
"rules": 7,
|
|
"families": 4,
|
|
"critical": 3,
|
|
"high": 4
|
|
}
|
|
}
|