Skip to content
Closed
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
24 changes: 13 additions & 11 deletions packages/decepticon/decepticon/middleware/_command_targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@

_IP_RE = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")
_CIDR_RE = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}/\d{1,2}\b")
_URL_RE = re.compile(r"\b(?:https?|ftp|file|smb|nfs|ssh|rdp|ldaps?)://([^\s/:]+)", re.IGNORECASE)
_URL_RE = re.compile(
r"\b(?:https?|ftp|file|smb|nfs|ssh|rdp|ldaps?)://(?:[^\s/@]+@)?([^\s/:@]+)",
re.IGNORECASE,
)
_HOSTNAME_AFTER_VERB_RE = re.compile(
r"\b(?:curl|wget|httpx|nmap|masscan|rustscan|ssh|scp|sftp|rsync|"
r"smbclient|smbmap|crackmapexec|nxc|netexec|nikto|sqlmap|hydra|ffuf|"
Expand All @@ -47,10 +50,15 @@
)


# Final labels that mark a token as a local file argument, never a network
# target. No public DNS TLD collides with any of these, so excluding them is
# safe and prevents RoE ENFORCE mode from refusing legitimate commands whose
# option values (``-i key.pem``, ``-oA scan.txt``) look hostname-shaped.
# Final labels that mark a token as a local-file argument, never a network
# target, so RoE ENFORCE mode does not refuse legitimate commands whose option
# values (``-i key.pem``, ``-oA scan.txt``) look hostname-shaped.
#
# SECURITY: an entry here is only safe if it is NOT also a delegated DNS TLD.
# Real TLDs (``.sh`` ``.md`` ``.py`` ``.pl`` ``.pub`` ``.zip`` …) were removed:
# leaving them in silently dropped genuine hosts such as ``evil.zip`` from RoE
# scope enforcement. Per this module's design, over-extracting a spurious
# target (which the operator can override) is safer than dropping a real one.
_NON_TARGET_EXTENSIONS: frozenset[str] = frozenset(
{
"pem",
Expand All @@ -61,7 +69,6 @@
"der",
"p12",
"pfx",
"pub",
"txt",
"log",
"json",
Expand All @@ -76,12 +83,8 @@
"xml",
"html",
"htm",
"md",
"rst",
"sh",
"py",
"rb",
"pl",
"ps1",
"bat",
"pcap",
Expand All @@ -95,7 +98,6 @@
"sqlite",
"sqlite3",
"gz",
"zip",
"tar",
"tgz",
"7z",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from __future__ import annotations

import pytest

from decepticon.middleware._command_targets import _is_valid_target, extract_targets


@pytest.mark.parametrize(
"command,expected_host",
[
("curl https://user:pass@evil.example/", "evil.example"),
("curl ftp://anonymous@out.example", "out.example"),
("wget http://attacker%40svc@hidden.example/x", "hidden.example"),
("smbclient smb://user@10.20.30.40/share", "10.20.30.40"),
],
)
def test_userinfo_does_not_hide_the_real_host(command, expected_host):
assert any(target == expected_host for target in extract_targets(command))


def test_userinfo_host_still_strips_port():
targets = extract_targets("curl https://user:pass@example.com:8443/admin")
assert any(target == "example.com" for target in targets)
assert all(target != "example.com:8443" for target in targets)


def test_plain_url_host_unchanged():
assert any(
target == "example.com" for target in extract_targets("curl https://example.com/path?q=1")
)


@pytest.mark.parametrize(
"host",
["evil.zip", "target.sh", "pay.md", "exploit.py", "domain.pl", "docs.pub"],
)
def test_tld_colliding_hosts_are_extracted(host):
assert _is_valid_target(host) is True
assert any(target == host for target in extract_targets(f"curl https://{host}/"))


@pytest.mark.parametrize(
"fname",
["key.pem", "scan.txt", "creds.json", "out.log", "dump.pcap", "data.csv", "archive.tar"],
)
def test_local_file_arguments_are_still_excluded(fname):
assert _is_valid_target(fname) is False
assert all(target != fname for target in extract_targets(f"nmap -oA {fname} 10.0.0.1"))


def test_oa_output_file_excluded_but_ip_kept():
targets = extract_targets("nmap -oA report.txt 10.0.0.1")
assert targets == {"10.0.0.1"}
Loading