Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/skillspector/yara_rules/webshells.yar
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,15 @@ rule php_webshell_known
$c99v2 = "c99_sess_put" nocase
$r57 = "r57shell" nocase
$wso = "Web Shell by oRb" nocase
$wso2 = "WSO " nocase
$wso_ex = "wsoEx(" ascii
$wso_ver = "WSO_VERSION" ascii
$wso_sec = "wsoSecParam" ascii
$b374k = "b374k" nocase
$alfa = "STARTER ALFA" nocase
$weevely = "weevely" nocase
$p0wny = "p0wny" nocase
$antsword = "antSword" nocase
$behinder = "behinder" nocase
$behinder_key = { 65 34 35 65 33 32 39 66 65 62 35 64 39 32 35 62 } // AES key, md5("rebeyond")[:16]
$godzilla = "GodzillaShell" nocase
$china_chopper = "China Chopper" nocase
condition:
Expand Down
37 changes: 37 additions & 0 deletions tests/nodes/analyzers/test_static_yara.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ def _reverse_shell_fixture() -> str:
return base64.b64decode("YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx").decode()


_WEBSHELL_FIXTURES = {
"behinder_php": "PD9waHAgQGVycm9yX3JlcG9ydGluZygwKTsgc2Vzc2lvbl9zdGFydCgpOyAka2V5PSJlNDVlMzI5ZmViNWQ5MjViIjsKJF9TRVNTSU9OWydrJ109JGtleTsgJHBvc3Q9ZmlsZV9nZXRfY29udGVudHMoInBocDovL2lucHV0Iik7CiRwb3N0PW9wZW5zc2xfZGVjcnlwdCgkcG9zdCwgIkFFUzEyOCIsICRrZXkpOyBldmFsKCRwb3N0KTsgPz4K",
"behinder_jsp": "PCVAcGFnZSBpbXBvcnQ9ImphdmEudXRpbC4qLGphdmF4LmNyeXB0by4qIiU+CjwlIFN0cmluZyBrPSJlNDVlMzI5ZmViNWQ5MjViIjsgc2Vzc2lvbi5wdXRWYWx1ZSgidSIsayk7CkNpcGhlciBjPUNpcGhlci5nZXRJbnN0YW5jZSgiQUVTIik7ICU+Cg==",
"wso_php": "PD9waHAgZGVmaW5lKCdXU09fVkVSU0lPTicsICcyLjUnKTsKZnVuY3Rpb24gd3NvRXgoJGluKSB7ICRvdXQ9Jyc7IGlmKGZ1bmN0aW9uX2V4aXN0cygnZXhlYycpKSB7IEBleGVjKCRpbiwkb3V0KTsgfQpyZXR1cm4gJG91dDsgfQo=",
}


def _webshell_fixture(name: str) -> str:
return base64.b64decode(_WEBSHELL_FIXTURES[name]).decode()


def _has_rule(findings: list, rule_name: str) -> bool:
"""Return True when a finding message references a specific YARA rule."""
return any(rule_name in f.message for f in findings)
Expand Down Expand Up @@ -553,6 +564,32 @@ def test_credential_webhook_requires_collection_and_transmission(self):
findings = _run_builtin(content, "README.md")
assert not _has_rule(findings, "agent_skill_credential_exfiltration_webhook")

@pytest.mark.parametrize(
"content",
[
"Zu kleine Schrift behindert das Lesen. Menschen mit Behinderung\n"
"brauchen ausreichende Kontraste.\n",
"We deploy the API on WSO 2 Micro Integrator.\n",
"This skill detects Behinder and WSO webshells in uploaded files.\n",
],
ids=["german_prose", "wso2_product_name", "family_names_in_docs"],
)
def test_known_webshell_rule_ignores_prose(self, content):
findings = _run_builtin(content, "SKILL.md")
assert not _has_rule(findings, "php_webshell_known")

@pytest.mark.parametrize(
("fixture", "filename"),
[
("behinder_php", "shell.php"),
("behinder_jsp", "shell.jsp"),
("wso_php", "shell.php"),
],
)
def test_known_webshell_rule_matches_family_markers(self, fixture, filename):
findings = _run_builtin(_webshell_fixture(fixture), filename)
assert _has_rule(findings, "php_webshell_known")


# ── Rule caching ──────────────────────────────────────────────────────

Expand Down
Loading