Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Portscan CDN Detection #1970

Merged
merged 7 commits into from
Nov 21, 2024
Merged
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
30 changes: 28 additions & 2 deletions bbot/modules/portscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
from bbot.modules.base import BaseModule


# TODO: this module is getting big. It should probably be two modules: one for ping and one for SYN.


class portscan(BaseModule):
flags = ["active", "portscan", "safe"]
watched_events = ["IP_ADDRESS", "IP_RANGE", "DNS_NAME"]
Expand All @@ -27,6 +30,8 @@ class portscan(BaseModule):
"adapter_ip": "",
"adapter_mac": "",
"router_mac": "",
"cdn_tags": "cdn-",
"allowed_cdn_ports": None,
}
options_desc = {
"top_ports": "Top ports to scan (default 100) (to override, specify 'ports')",
Expand All @@ -39,6 +44,8 @@ class portscan(BaseModule):
"adapter_ip": "Send packets using this IP address. Not needed unless masscan's autodetection fails",
"adapter_mac": "Send packets using this as the source MAC address. Not needed unless masscan's autodetection fails",
"router_mac": "Send packets to this MAC address as the destination. Not needed unless masscan's autodetection fails",
"cdn_tags": "Comma-separated list of tags to skip, e.g. 'cdn,cloud'",
"allowed_cdn_ports": "Comma-separated list of ports that are allowed to be scanned for CDNs",
}
deps_common = ["masscan"]
batch_size = 1000000
Expand All @@ -60,7 +67,15 @@ async def setup(self):
try:
self.helpers.parse_port_string(self.ports)
except ValueError as e:
return False, f"Error parsing ports: {e}"
return False, f"Error parsing ports '{self.ports}': {e}"
self.cdn_tags = [t.strip() for t in self.config.get("cdn_tags", "").split(",")]
self.allowed_cdn_ports = self.config.get("allowed_cdn_ports", None)
if self.allowed_cdn_ports is not None:
try:
self.allowed_cdn_ports = [int(p.strip()) for p in self.allowed_cdn_ports.split(",")]
except Exception as e:
return False, f"Error parsing allowed CDN ports '{self.allowed_cdn_ports}': {e}"

# whether we've finished scanning our original scan targets
self.scanned_initial_targets = False
# keeps track of individual scanned IPs and their open ports
Expand Down Expand Up @@ -227,9 +242,20 @@ async def emit_open_port(self, ip, port, parent_event):
parent=parent_event,
context=f"{{module}} executed a {scan_type} scan against {parent_event.data} and found: {{event.type}}: {{event.data}}",
)
await self.emit_event(event)

await self.emit_event(event, abort_if=self.abort_if)
return event

def abort_if(self, event):
if self.allowed_cdn_ports is not None:
# if the host is a CDN
for cdn_tag in self.cdn_tags:
if any(t.startswith(str(cdn_tag)) for t in event.tags):
# and if its port isn't in the list of allowed CDN ports
if event.port not in self.allowed_cdn_ports:
return True, "event is a CDN and port is not in the allowed list"
return False

def parse_json_line(self, line):
try:
j = json.loads(line)
Expand Down
10 changes: 6 additions & 4 deletions bbot/test/test_step_2/module_tests/test_module_portscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,12 @@ def check(self, module_test, events):
if e.type == "DNS_NAME" and e.data == "dummy.asdf.evilcorp.net" and str(e.module) == "dummy_module"
]
)
assert 2 <= len([e for e in events if e.type == "IP_ADDRESS" and e.data == "8.8.8.8"]) <= 3
assert 2 <= len([e for e in events if e.type == "IP_ADDRESS" and e.data == "8.8.4.4"]) <= 3
assert 2 <= len([e for e in events if e.type == "IP_ADDRESS" and e.data == "8.8.4.5"]) <= 3
assert 2 <= len([e for e in events if e.type == "IP_ADDRESS" and e.data == "8.8.4.6"]) <= 3
# the reason these numbers aren't exactly predictable is because we can't predict which one arrives first
# to the portscan module. Sometimes, one that would normally be deduped is force-emitted because it led to a new open port.
assert 2 <= len([e for e in events if e.type == "IP_ADDRESS" and e.data == "8.8.8.8"]) <= 4
assert 2 <= len([e for e in events if e.type == "IP_ADDRESS" and e.data == "8.8.4.4"]) <= 4
assert 2 <= len([e for e in events if e.type == "IP_ADDRESS" and e.data == "8.8.4.5"]) <= 4
assert 2 <= len([e for e in events if e.type == "IP_ADDRESS" and e.data == "8.8.4.6"]) <= 4
assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "8.8.8.8:443"])
assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "8.8.4.5:80"])
assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "8.8.4.6:631"])
Expand Down
33 changes: 32 additions & 1 deletion docs/scanning/tips_and_tricks.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,38 @@ You can also pair the web spider with subdomain enumeration:
bbot -t evilcorp.com -f subdomain-enum -c spider.yml
```

### Ingesting BBOT Data Into SIEM (Elastic, Splunk)
### Exclude CDNs from Port Scan

If you want to exclude CDNs (e.g. Cloudflare) from port scanning, you can set the `allowed_cdn_ports` config option in the `portscan` module. For example, to allow only port 80 (HTTP) and 443 (HTTPS), you can do the following:

```bash
bbot -t evilcorp.com -m portscan -c modules.portscan.allowed_cdn_ports=80,443
```

By default, if you set `allowed_cdn_ports`, it will skip only providers marked as CDNs. If you want to skip cloud providers as well, you can set `cdn_tags`, which is a comma-separated list of tags to skip (matched against the beginning of each tag).

```bash
bbot -t evilcorp.com -m portscan -c modules.portscan.allowed_cdn_ports=80,443 modules.portscan.cdn_tags=cdn-,cloud-
```

...or via a preset:

```yaml title="skip_cdns.yml"
modules:
- portscan

config:
modules:
portscan:
allowed_cdn_ports: 80,443
cdn_tags: cdn-,cloud-
```

```bash
bbot -t evilcorp.com -p skip_cdns.yml
```

### Ingest BBOT Data Into SIEM (Elastic, Splunk)

If your goal is to feed BBOT data into a SIEM such as Elastic, be sure to enable this option when scanning:

Expand Down
Loading