bbot 2.0.1.4720rc0__py3-none-any.whl → 2.3.0.5401rc0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bbot might be problematic. Click here for more details.
- bbot/__init__.py +1 -1
- bbot/cli.py +3 -7
- bbot/core/config/files.py +0 -1
- bbot/core/config/logger.py +34 -4
- bbot/core/core.py +21 -4
- bbot/core/engine.py +9 -8
- bbot/core/event/base.py +131 -52
- bbot/core/helpers/bloom.py +10 -3
- bbot/core/helpers/command.py +8 -7
- bbot/core/helpers/depsinstaller/installer.py +31 -13
- bbot/core/helpers/diff.py +10 -10
- bbot/core/helpers/dns/brute.py +7 -4
- bbot/core/helpers/dns/dns.py +1 -2
- bbot/core/helpers/dns/engine.py +4 -6
- bbot/core/helpers/dns/helpers.py +2 -2
- bbot/core/helpers/dns/mock.py +0 -1
- bbot/core/helpers/files.py +1 -1
- bbot/core/helpers/helper.py +7 -4
- bbot/core/helpers/interactsh.py +3 -3
- bbot/core/helpers/libmagic.py +65 -0
- bbot/core/helpers/misc.py +65 -22
- bbot/core/helpers/names_generator.py +17 -3
- bbot/core/helpers/process.py +0 -20
- bbot/core/helpers/regex.py +1 -1
- bbot/core/helpers/regexes.py +12 -6
- bbot/core/helpers/validators.py +1 -2
- bbot/core/helpers/web/client.py +1 -1
- bbot/core/helpers/web/engine.py +1 -2
- bbot/core/helpers/web/web.py +4 -114
- bbot/core/helpers/wordcloud.py +5 -5
- bbot/core/modules.py +36 -27
- bbot/core/multiprocess.py +58 -0
- bbot/core/shared_deps.py +46 -3
- bbot/db/sql/models.py +147 -0
- bbot/defaults.yml +12 -10
- bbot/modules/anubisdb.py +2 -2
- bbot/modules/apkpure.py +63 -0
- bbot/modules/azure_tenant.py +2 -2
- bbot/modules/baddns.py +35 -19
- bbot/modules/baddns_direct.py +92 -0
- bbot/modules/baddns_zone.py +3 -8
- bbot/modules/badsecrets.py +4 -3
- bbot/modules/base.py +195 -51
- bbot/modules/bevigil.py +7 -7
- bbot/modules/binaryedge.py +7 -4
- bbot/modules/bufferoverrun.py +47 -0
- bbot/modules/builtwith.py +6 -10
- bbot/modules/bypass403.py +5 -5
- bbot/modules/c99.py +10 -7
- bbot/modules/censys.py +9 -13
- bbot/modules/certspotter.py +5 -3
- bbot/modules/chaos.py +9 -7
- bbot/modules/code_repository.py +1 -0
- bbot/modules/columbus.py +3 -3
- bbot/modules/crt.py +5 -3
- bbot/modules/deadly/dastardly.py +1 -1
- bbot/modules/deadly/ffuf.py +9 -9
- bbot/modules/deadly/nuclei.py +3 -3
- bbot/modules/deadly/vhost.py +4 -3
- bbot/modules/dehashed.py +1 -1
- bbot/modules/digitorus.py +1 -1
- bbot/modules/dnsbimi.py +145 -0
- bbot/modules/dnscaa.py +3 -3
- bbot/modules/dnsdumpster.py +4 -4
- bbot/modules/dnstlsrpt.py +144 -0
- bbot/modules/docker_pull.py +7 -5
- bbot/modules/dockerhub.py +2 -2
- bbot/modules/dotnetnuke.py +20 -21
- bbot/modules/emailformat.py +1 -1
- bbot/modules/extractous.py +122 -0
- bbot/modules/filedownload.py +9 -7
- bbot/modules/fullhunt.py +7 -4
- bbot/modules/generic_ssrf.py +5 -5
- bbot/modules/github_codesearch.py +3 -2
- bbot/modules/github_org.py +4 -4
- bbot/modules/github_workflows.py +4 -4
- bbot/modules/gitlab.py +2 -5
- bbot/modules/google_playstore.py +93 -0
- bbot/modules/gowitness.py +48 -50
- bbot/modules/hackertarget.py +5 -3
- bbot/modules/host_header.py +5 -5
- bbot/modules/httpx.py +1 -4
- bbot/modules/hunterio.py +3 -9
- bbot/modules/iis_shortnames.py +19 -30
- bbot/modules/internal/cloudcheck.py +29 -12
- bbot/modules/internal/dnsresolve.py +22 -22
- bbot/modules/internal/excavate.py +97 -59
- bbot/modules/internal/speculate.py +41 -32
- bbot/modules/internetdb.py +4 -2
- bbot/modules/ip2location.py +3 -5
- bbot/modules/ipneighbor.py +1 -1
- bbot/modules/ipstack.py +3 -8
- bbot/modules/jadx.py +87 -0
- bbot/modules/leakix.py +11 -10
- bbot/modules/myssl.py +2 -2
- bbot/modules/newsletters.py +2 -2
- bbot/modules/otx.py +5 -3
- bbot/modules/output/asset_inventory.py +7 -7
- bbot/modules/output/base.py +1 -1
- bbot/modules/output/csv.py +1 -1
- bbot/modules/output/http.py +20 -14
- bbot/modules/output/mysql.py +51 -0
- bbot/modules/output/neo4j.py +7 -2
- bbot/modules/output/postgres.py +49 -0
- bbot/modules/output/slack.py +0 -1
- bbot/modules/output/sqlite.py +29 -0
- bbot/modules/output/stdout.py +2 -2
- bbot/modules/output/teams.py +107 -6
- bbot/modules/paramminer_headers.py +8 -11
- bbot/modules/passivetotal.py +13 -13
- bbot/modules/portscan.py +32 -6
- bbot/modules/postman.py +50 -126
- bbot/modules/postman_download.py +220 -0
- bbot/modules/rapiddns.py +3 -8
- bbot/modules/report/asn.py +18 -11
- bbot/modules/robots.py +3 -3
- bbot/modules/securitytrails.py +7 -10
- bbot/modules/securitytxt.py +1 -1
- bbot/modules/shodan_dns.py +7 -9
- bbot/modules/sitedossier.py +1 -1
- bbot/modules/skymem.py +2 -2
- bbot/modules/social.py +2 -1
- bbot/modules/subdomaincenter.py +1 -1
- bbot/modules/subdomainradar.py +160 -0
- bbot/modules/telerik.py +8 -8
- bbot/modules/templates/bucket.py +1 -1
- bbot/modules/templates/github.py +22 -14
- bbot/modules/templates/postman.py +21 -0
- bbot/modules/templates/shodan.py +14 -13
- bbot/modules/templates/sql.py +95 -0
- bbot/modules/templates/subdomain_enum.py +51 -16
- bbot/modules/templates/webhook.py +2 -4
- bbot/modules/trickest.py +8 -37
- bbot/modules/trufflehog.py +10 -12
- bbot/modules/url_manipulation.py +3 -3
- bbot/modules/urlscan.py +1 -1
- bbot/modules/viewdns.py +1 -1
- bbot/modules/virustotal.py +8 -30
- bbot/modules/wafw00f.py +1 -1
- bbot/modules/wayback.py +1 -1
- bbot/modules/wpscan.py +17 -11
- bbot/modules/zoomeye.py +11 -6
- bbot/presets/baddns-thorough.yml +12 -0
- bbot/presets/fast.yml +16 -0
- bbot/presets/kitchen-sink.yml +1 -2
- bbot/presets/spider.yml +4 -0
- bbot/presets/subdomain-enum.yml +7 -7
- bbot/presets/web/dotnet-audit.yml +0 -1
- bbot/scanner/manager.py +5 -16
- bbot/scanner/preset/args.py +46 -26
- bbot/scanner/preset/environ.py +7 -2
- bbot/scanner/preset/path.py +7 -4
- bbot/scanner/preset/preset.py +36 -23
- bbot/scanner/scanner.py +172 -62
- bbot/scanner/target.py +236 -434
- bbot/scripts/docs.py +1 -1
- bbot/test/bbot_fixtures.py +13 -3
- bbot/test/conftest.py +132 -100
- bbot/test/fastapi_test.py +17 -0
- bbot/test/owasp_mastg.apk +0 -0
- bbot/test/run_tests.sh +4 -4
- bbot/test/test.conf +2 -0
- bbot/test/test_step_1/test__module__tests.py +0 -1
- bbot/test/test_step_1/test_bbot_fastapi.py +79 -0
- bbot/test/test_step_1/test_bloom_filter.py +2 -1
- bbot/test/test_step_1/test_cli.py +138 -64
- bbot/test/test_step_1/test_dns.py +61 -27
- bbot/test/test_step_1/test_engine.py +17 -19
- bbot/test/test_step_1/test_events.py +183 -30
- bbot/test/test_step_1/test_helpers.py +64 -29
- bbot/test/test_step_1/test_manager_deduplication.py +1 -1
- bbot/test/test_step_1/test_manager_scope_accuracy.py +333 -330
- bbot/test/test_step_1/test_modules_basic.py +68 -70
- bbot/test/test_step_1/test_presets.py +183 -100
- bbot/test/test_step_1/test_python_api.py +7 -2
- bbot/test/test_step_1/test_regexes.py +35 -5
- bbot/test/test_step_1/test_scan.py +39 -5
- bbot/test/test_step_1/test_scope.py +4 -3
- bbot/test/test_step_1/test_target.py +242 -145
- bbot/test/test_step_1/test_web.py +14 -10
- bbot/test/test_step_2/module_tests/base.py +15 -7
- bbot/test/test_step_2/module_tests/test_module_anubisdb.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_apkpure.py +71 -0
- bbot/test/test_step_2/module_tests/test_module_asset_inventory.py +0 -1
- bbot/test/test_step_2/module_tests/test_module_azure_realm.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_baddns.py +6 -6
- bbot/test/test_step_2/module_tests/test_module_baddns_direct.py +62 -0
- bbot/test/test_step_2/module_tests/test_module_bevigil.py +29 -2
- bbot/test/test_step_2/module_tests/test_module_binaryedge.py +4 -2
- bbot/test/test_step_2/module_tests/test_module_bucket_amazon.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_bucket_azure.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_bufferoverrun.py +35 -0
- bbot/test/test_step_2/module_tests/test_module_builtwith.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_bypass403.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_c99.py +126 -0
- bbot/test/test_step_2/module_tests/test_module_censys.py +4 -1
- bbot/test/test_step_2/module_tests/test_module_cloudcheck.py +4 -0
- bbot/test/test_step_2/module_tests/test_module_code_repository.py +11 -1
- bbot/test/test_step_2/module_tests/test_module_columbus.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_credshed.py +3 -3
- bbot/test/test_step_2/module_tests/test_module_dastardly.py +2 -1
- bbot/test/test_step_2/module_tests/test_module_dehashed.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_digitorus.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_discord.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_dnsbimi.py +103 -0
- bbot/test/test_step_2/module_tests/test_module_dnsbrute.py +9 -10
- bbot/test/test_step_2/module_tests/test_module_dnsbrute_mutations.py +1 -2
- bbot/test/test_step_2/module_tests/test_module_dnscommonsrv.py +1 -2
- bbot/test/test_step_2/module_tests/test_module_dnsdumpster.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_dnstlsrpt.py +64 -0
- bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py +0 -8
- bbot/test/test_step_2/module_tests/test_module_excavate.py +28 -48
- bbot/test/test_step_2/module_tests/test_module_extractous.py +54 -0
- bbot/test/test_step_2/module_tests/test_module_ffuf_shortnames.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_filedownload.py +14 -14
- bbot/test/test_step_2/module_tests/test_module_git_clone.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_github_org.py +19 -8
- bbot/test/test_step_2/module_tests/test_module_github_workflows.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_gitlab.py +9 -4
- bbot/test/test_step_2/module_tests/test_module_google_playstore.py +83 -0
- bbot/test/test_step_2/module_tests/test_module_gowitness.py +4 -6
- bbot/test/test_step_2/module_tests/test_module_host_header.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_http.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_httpx.py +10 -8
- bbot/test/test_step_2/module_tests/test_module_hunterio.py +68 -4
- bbot/test/test_step_2/module_tests/test_module_jadx.py +55 -0
- bbot/test/test_step_2/module_tests/test_module_json.py +22 -9
- bbot/test/test_step_2/module_tests/test_module_leakix.py +7 -3
- bbot/test/test_step_2/module_tests/test_module_mysql.py +76 -0
- bbot/test/test_step_2/module_tests/test_module_myssl.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_neo4j.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_newsletters.py +16 -16
- bbot/test/test_step_2/module_tests/test_module_ntlm.py +8 -7
- bbot/test/test_step_2/module_tests/test_module_oauth.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_otx.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_paramminer_cookies.py +1 -2
- bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py +0 -6
- bbot/test/test_step_2/module_tests/test_module_paramminer_headers.py +2 -9
- bbot/test/test_step_2/module_tests/test_module_passivetotal.py +3 -1
- bbot/test/test_step_2/module_tests/test_module_pgp.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_portscan.py +9 -8
- bbot/test/test_step_2/module_tests/test_module_postgres.py +74 -0
- bbot/test/test_step_2/module_tests/test_module_postman.py +84 -253
- bbot/test/test_step_2/module_tests/test_module_postman_download.py +439 -0
- bbot/test/test_step_2/module_tests/test_module_rapiddns.py +93 -1
- bbot/test/test_step_2/module_tests/test_module_shodan_dns.py +20 -1
- bbot/test/test_step_2/module_tests/test_module_sitedossier.py +2 -2
- bbot/test/test_step_2/module_tests/test_module_smuggler.py +14 -14
- bbot/test/test_step_2/module_tests/test_module_social.py +11 -1
- bbot/test/test_step_2/module_tests/test_module_speculate.py +4 -8
- bbot/test/test_step_2/module_tests/test_module_splunk.py +4 -4
- bbot/test/test_step_2/module_tests/test_module_sqlite.py +18 -0
- bbot/test/test_step_2/module_tests/test_module_sslcert.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_stdout.py +5 -3
- bbot/test/test_step_2/module_tests/test_module_subdomaincenter.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_subdomainradar.py +208 -0
- bbot/test/test_step_2/module_tests/test_module_subdomains.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_teams.py +8 -6
- bbot/test/test_step_2/module_tests/test_module_telerik.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_trufflehog.py +317 -14
- bbot/test/test_step_2/module_tests/test_module_viewdns.py +1 -1
- bbot/test/test_step_2/module_tests/test_module_wayback.py +1 -1
- bbot/test/test_step_2/template_tests/test_template_subdomain_enum.py +2 -2
- bbot/wordlists/devops_mutations.txt +1 -1
- bbot/wordlists/ffuf_shortname_candidates.txt +1 -1
- bbot/wordlists/nameservers.txt +1 -1
- bbot/wordlists/paramminer_headers.txt +1 -1
- bbot/wordlists/paramminer_parameters.txt +1 -1
- bbot/wordlists/raft-small-extensions-lowercase_CLEANED.txt +1 -1
- bbot/wordlists/valid_url_schemes.txt +1 -1
- {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/METADATA +48 -18
- bbot-2.3.0.5401rc0.dist-info/RECORD +421 -0
- {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/WHEEL +1 -1
- bbot/modules/unstructured.py +0 -163
- bbot/test/test_step_2/module_tests/test_module_unstructured.py +0 -102
- bbot-2.0.1.4720rc0.dist-info/RECORD +0 -387
- {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/LICENSE +0 -0
- {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/entry_points.txt +0 -0
bbot/scanner/target.py
CHANGED
|
@@ -1,112 +1,251 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import copy
|
|
3
1
|
import logging
|
|
4
|
-
import
|
|
5
|
-
import traceback
|
|
2
|
+
import regex as re
|
|
6
3
|
from hashlib import sha1
|
|
7
|
-
from contextlib import suppress
|
|
8
4
|
from radixtarget import RadixTarget
|
|
5
|
+
from radixtarget.helpers import host_size_key
|
|
9
6
|
|
|
10
7
|
from bbot.errors import *
|
|
11
|
-
from bbot.modules.base import BaseModule
|
|
12
|
-
from bbot.core.helpers.misc import make_ip_type
|
|
13
8
|
from bbot.core.event import make_event, is_event
|
|
9
|
+
from bbot.core.helpers.misc import is_dns_name, is_ip
|
|
10
|
+
|
|
14
11
|
|
|
15
12
|
log = logging.getLogger("bbot.core.target")
|
|
16
13
|
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
def special_target_type(regex_pattern):
|
|
16
|
+
def decorator(func):
|
|
17
|
+
func._regex = re.compile(regex_pattern, re.IGNORECASE)
|
|
18
|
+
return func
|
|
19
|
+
|
|
20
|
+
return decorator
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BaseTarget(RadixTarget):
|
|
19
24
|
"""
|
|
20
|
-
A
|
|
25
|
+
A collection of BBOT events that represent a scan target.
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
Based on radixtarget, which allows extremely fast IP and DNS lookups.
|
|
28
|
+
|
|
29
|
+
This class is inherited by all three components of the BBOT target:
|
|
30
|
+
- Whitelist
|
|
31
|
+
- Blacklist
|
|
32
|
+
- Seeds
|
|
23
33
|
"""
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
special_target_types = {
|
|
36
|
+
# regex-callback pairs for handling special target types
|
|
37
|
+
# these aren't defined explicitly; instead they are decorated with @special_target_type
|
|
38
|
+
# the function must return a list of events
|
|
39
|
+
}
|
|
40
|
+
tags = []
|
|
41
|
+
|
|
42
|
+
def __init__(self, *targets, scan=None, **kwargs):
|
|
27
43
|
self.scan = scan
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
self.events = set()
|
|
45
|
+
self.inputs = set()
|
|
46
|
+
# Register decorated methods
|
|
47
|
+
for method in dir(self):
|
|
48
|
+
if callable(getattr(self, method, None)):
|
|
49
|
+
func = getattr(self, method)
|
|
50
|
+
if hasattr(func, "_regex"):
|
|
51
|
+
self.special_target_types[func._regex] = func
|
|
52
|
+
|
|
53
|
+
super().__init__(*targets, **kwargs)
|
|
54
|
+
|
|
55
|
+
def get(self, event, **kwargs):
|
|
56
|
+
"""
|
|
57
|
+
Override default .get() to accept events
|
|
58
|
+
"""
|
|
59
|
+
if is_event(event):
|
|
60
|
+
host = event.host
|
|
61
|
+
# save resources by checking if the event is an IP or DNS name
|
|
62
|
+
elif is_ip(event, include_network=True) or is_dns_name(event):
|
|
63
|
+
host = event
|
|
64
|
+
elif isinstance(event, str):
|
|
65
|
+
event = self.make_event(event)
|
|
66
|
+
host = event.host
|
|
33
67
|
else:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
68
|
+
raise ValueError(f"Invalid host/event: {event} ({type(event)})")
|
|
69
|
+
if not host:
|
|
70
|
+
if kwargs.get("raise_error", False):
|
|
71
|
+
raise KeyError(f"Host not found: '{event}'")
|
|
72
|
+
return None
|
|
73
|
+
results = super().get(host, **kwargs)
|
|
74
|
+
return results
|
|
75
|
+
|
|
76
|
+
def make_event(self, *args, **kwargs):
|
|
77
|
+
# if it's already an event, return it
|
|
78
|
+
if args and is_event(args[0]):
|
|
79
|
+
return args[0]
|
|
80
|
+
# otherwise make a new one
|
|
81
|
+
if "tags" not in kwargs:
|
|
82
|
+
kwargs["tags"] = set()
|
|
83
|
+
kwargs["tags"].update(self.tags)
|
|
84
|
+
return make_event(*args, dummy=True, scan=self.scan, **kwargs)
|
|
85
|
+
|
|
86
|
+
def add(self, targets):
|
|
87
|
+
if not isinstance(targets, (list, set, tuple)):
|
|
88
|
+
targets = [targets]
|
|
89
|
+
events = set()
|
|
90
|
+
for target in targets:
|
|
91
|
+
_events = []
|
|
92
|
+
special_target_type, _events = self.check_special_target_types(str(target))
|
|
93
|
+
if special_target_type:
|
|
94
|
+
self.inputs.add(str(target))
|
|
95
|
+
else:
|
|
96
|
+
event = self.make_event(target)
|
|
97
|
+
if event:
|
|
98
|
+
_events = [event]
|
|
99
|
+
for event in _events:
|
|
100
|
+
self.inputs.add(event.data)
|
|
101
|
+
events.add(event)
|
|
102
|
+
|
|
103
|
+
# sort by host size to ensure consistency
|
|
104
|
+
events = sorted(events, key=lambda e: (0 if not e.host else host_size_key(e.host)))
|
|
105
|
+
for event in events:
|
|
106
|
+
self.events.add(event)
|
|
107
|
+
self._add(event.host, data=event)
|
|
108
|
+
|
|
109
|
+
def check_special_target_types(self, target):
|
|
110
|
+
for regex, callback in self.special_target_types.items():
|
|
111
|
+
match = regex.match(target)
|
|
112
|
+
if match:
|
|
113
|
+
return True, callback(match)
|
|
114
|
+
return False, []
|
|
115
|
+
|
|
116
|
+
def __iter__(self):
|
|
117
|
+
yield from self.events
|
|
42
118
|
|
|
43
|
-
def add(self, *args, **kwargs):
|
|
44
|
-
self.seeds.add(*args, **kwargs)
|
|
45
|
-
self._hash = None
|
|
46
119
|
|
|
47
|
-
|
|
48
|
-
|
|
120
|
+
class ScanSeeds(BaseTarget):
|
|
121
|
+
"""
|
|
122
|
+
Initial events used to seed a scan.
|
|
49
123
|
|
|
50
|
-
|
|
51
|
-
|
|
124
|
+
These are the targets specified by the user, e.g. via `-t` on the CLI.
|
|
125
|
+
"""
|
|
52
126
|
|
|
53
|
-
|
|
54
|
-
|
|
127
|
+
tags = ["target"]
|
|
128
|
+
|
|
129
|
+
@special_target_type(r"^(?:ORG|ORG_STUB):(.*)")
|
|
130
|
+
def handle_org_stub(self, match):
|
|
131
|
+
org_stub_event = self.make_event(match.group(1), event_type="ORG_STUB")
|
|
132
|
+
if org_stub_event:
|
|
133
|
+
return [org_stub_event]
|
|
134
|
+
return []
|
|
135
|
+
|
|
136
|
+
@special_target_type(r"^(?:USER|USERNAME):(.*)")
|
|
137
|
+
def handle_username(self, match):
|
|
138
|
+
username_event = self.make_event(match.group(1), event_type="USERNAME")
|
|
139
|
+
if username_event:
|
|
140
|
+
return [username_event]
|
|
141
|
+
return []
|
|
142
|
+
|
|
143
|
+
def get(self, event, single=True, **kwargs):
|
|
144
|
+
results = super().get(event, **kwargs)
|
|
145
|
+
if results and single:
|
|
146
|
+
return next(iter(results))
|
|
147
|
+
return results
|
|
148
|
+
|
|
149
|
+
def _add(self, host, data):
|
|
150
|
+
"""
|
|
151
|
+
Overrides the base method to enable having multiple events for the same host.
|
|
55
152
|
|
|
56
|
-
|
|
57
|
-
|
|
153
|
+
The "data" attribute of the node is now a set of events.
|
|
154
|
+
"""
|
|
155
|
+
if host:
|
|
156
|
+
try:
|
|
157
|
+
event_set = self.get(host, raise_error=True, single=False)
|
|
158
|
+
event_set.add(data)
|
|
159
|
+
except KeyError:
|
|
160
|
+
event_set = {data}
|
|
161
|
+
super()._add(host, data=event_set)
|
|
58
162
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return other in self.seeds
|
|
163
|
+
def _hash_value(self):
|
|
164
|
+
# seeds get hashed by event data
|
|
165
|
+
return sorted(str(e.data).encode() for e in self.events)
|
|
63
166
|
|
|
64
|
-
def __bool__(self):
|
|
65
|
-
return bool(self.seeds)
|
|
66
167
|
|
|
67
|
-
|
|
68
|
-
|
|
168
|
+
class ACLTarget(BaseTarget):
|
|
169
|
+
def __init__(self, *args, **kwargs):
|
|
170
|
+
# ACL mode dedupes by host (and skips adding already-contained hosts) for efficiency
|
|
171
|
+
kwargs["acl_mode"] = True
|
|
172
|
+
super().__init__(*args, **kwargs)
|
|
69
173
|
|
|
70
|
-
@property
|
|
71
|
-
def hash(self):
|
|
72
|
-
"""
|
|
73
|
-
A sha1 hash representing a BBOT target and all three of its components (seeds, whitelist, blacklist)
|
|
74
174
|
|
|
75
|
-
|
|
175
|
+
class ScanWhitelist(ACLTarget):
|
|
176
|
+
"""
|
|
177
|
+
A collection of BBOT events that represent a scan's whitelist.
|
|
178
|
+
"""
|
|
76
179
|
|
|
77
|
-
|
|
78
|
-
>>> target1 = BBOTTarget("evilcorp.com", blacklist=["prod.evilcorp.com"], whitelist=["test.evilcorp.com"])
|
|
79
|
-
>>> target2 = BBOTTarget("evilcorp.com", blacklist=["prod.evilcorp.com"], whitelist=["test.evilcorp.com"])
|
|
80
|
-
>>> target3 = BBOTTarget("evilcorp.com", blacklist=["prod.evilcorp.com"])
|
|
81
|
-
>>> target1 == target2
|
|
82
|
-
True
|
|
83
|
-
>>> target1 == target3
|
|
84
|
-
False
|
|
85
|
-
"""
|
|
86
|
-
if self._hash is None:
|
|
87
|
-
# Create a new SHA-1 hash object
|
|
88
|
-
sha1_hash = sha1()
|
|
89
|
-
# Update the SHA-1 object with the hash values of each object
|
|
90
|
-
for target_hash in [t.hash for t in (self.seeds, self.whitelist, self.blacklist)]:
|
|
91
|
-
# Convert the hash value to bytes and update the SHA-1 object
|
|
92
|
-
sha1_hash.update(target_hash)
|
|
93
|
-
self._hash = sha1_hash.digest()
|
|
94
|
-
return self._hash
|
|
180
|
+
pass
|
|
95
181
|
|
|
96
|
-
@property
|
|
97
|
-
def scope_hash(self):
|
|
98
|
-
"""
|
|
99
|
-
A sha1 hash representing only the whitelist and blacklist
|
|
100
182
|
|
|
101
|
-
|
|
183
|
+
class ScanBlacklist(ACLTarget):
|
|
184
|
+
"""
|
|
185
|
+
A collection of BBOT events that represent a scan's blacklist.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
def __init__(self, *args, **kwargs):
|
|
189
|
+
self.blacklist_regexes = set()
|
|
190
|
+
super().__init__(*args, **kwargs)
|
|
191
|
+
|
|
192
|
+
@special_target_type(r"^(?:RE|REGEX):(.*)")
|
|
193
|
+
def handle_regex(self, match):
|
|
194
|
+
pattern = match.group(1)
|
|
195
|
+
log.info(f"Blacklisting by custom regex: {pattern}")
|
|
196
|
+
blacklist_regex = re.compile(pattern, re.IGNORECASE)
|
|
197
|
+
self.blacklist_regexes.add(blacklist_regex)
|
|
198
|
+
return []
|
|
199
|
+
|
|
200
|
+
def get(self, event, **kwargs):
|
|
102
201
|
"""
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
202
|
+
Here, for the blacklist, we modify this method to also consider any special regex patterns specified by the user
|
|
203
|
+
"""
|
|
204
|
+
event = self.make_event(event)
|
|
205
|
+
# first, check event's host against blacklist
|
|
206
|
+
try:
|
|
207
|
+
event_result = super().get(event, raise_error=True)
|
|
208
|
+
except KeyError:
|
|
209
|
+
event_result = None
|
|
210
|
+
if event_result is not None:
|
|
211
|
+
return event_result
|
|
212
|
+
# next, check event's host against regexes
|
|
213
|
+
host_or_url = event.host_filterable
|
|
214
|
+
if host_or_url:
|
|
215
|
+
for regex in self.blacklist_regexes:
|
|
216
|
+
if regex.search(str(host_or_url)):
|
|
217
|
+
return event
|
|
218
|
+
if kwargs.get("raise_error", False):
|
|
219
|
+
raise KeyError(f"Host not found: '{event.data}'")
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
def _hash_value(self):
|
|
223
|
+
# regexes are included in blacklist hash
|
|
224
|
+
regex_patterns = [str(r.pattern).encode() for r in self.blacklist_regexes]
|
|
225
|
+
hosts = [str(h).encode() for h in self.sorted_hosts]
|
|
226
|
+
return hosts + regex_patterns
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class BBOTTarget:
|
|
230
|
+
"""
|
|
231
|
+
A convenient abstraction of a scan target that contains three subtargets:
|
|
232
|
+
- seeds
|
|
233
|
+
- whitelist
|
|
234
|
+
- blacklist
|
|
235
|
+
|
|
236
|
+
Provides high-level functions like in_scope(), which includes both whitelist and blacklist checks.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
def __init__(self, *seeds, whitelist=None, blacklist=None, strict_scope=False, scan=None):
|
|
240
|
+
self.scan = scan
|
|
241
|
+
self.strict_scope = strict_scope
|
|
242
|
+
self.seeds = ScanSeeds(*seeds, strict_dns_scope=strict_scope, scan=scan)
|
|
243
|
+
if whitelist is None:
|
|
244
|
+
whitelist = self.seeds.hosts
|
|
245
|
+
self.whitelist = ScanWhitelist(*whitelist, strict_dns_scope=strict_scope, scan=scan)
|
|
246
|
+
if blacklist is None:
|
|
247
|
+
blacklist = []
|
|
248
|
+
self.blacklist = ScanBlacklist(*blacklist, scan=scan)
|
|
110
249
|
|
|
111
250
|
@property
|
|
112
251
|
def json(self):
|
|
@@ -122,16 +261,20 @@ class BBOTTarget:
|
|
|
122
261
|
"scope_hash": self.scope_hash.hex(),
|
|
123
262
|
}
|
|
124
263
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return
|
|
264
|
+
@property
|
|
265
|
+
def hash(self):
|
|
266
|
+
sha1_hash = sha1()
|
|
267
|
+
for target_hash in [t.hash for t in (self.seeds, self.whitelist, self.blacklist)]:
|
|
268
|
+
sha1_hash.update(target_hash)
|
|
269
|
+
return sha1_hash.digest()
|
|
131
270
|
|
|
132
271
|
@property
|
|
133
|
-
def
|
|
134
|
-
|
|
272
|
+
def scope_hash(self):
|
|
273
|
+
sha1_hash = sha1()
|
|
274
|
+
# Consider only the hash values of the whitelist and blacklist
|
|
275
|
+
for target_hash in [t.hash for t in (self.whitelist, self.blacklist)]:
|
|
276
|
+
sha1_hash.update(target_hash)
|
|
277
|
+
return sha1_hash.digest()
|
|
135
278
|
|
|
136
279
|
def in_scope(self, host):
|
|
137
280
|
"""
|
|
@@ -167,8 +310,7 @@ class BBOTTarget:
|
|
|
167
310
|
>>> preset.blacklisted("http://www.evilcorp.com")
|
|
168
311
|
True
|
|
169
312
|
"""
|
|
170
|
-
|
|
171
|
-
return e in self.blacklist
|
|
313
|
+
return host in self.blacklist
|
|
172
314
|
|
|
173
315
|
def whitelisted(self, host):
|
|
174
316
|
"""
|
|
@@ -184,360 +326,20 @@ class BBOTTarget:
|
|
|
184
326
|
>>> preset.whitelisted("http://www.evilcorp.com")
|
|
185
327
|
True
|
|
186
328
|
"""
|
|
187
|
-
|
|
188
|
-
whitelist = self.whitelist
|
|
189
|
-
if whitelist is None:
|
|
190
|
-
whitelist = self.seeds
|
|
191
|
-
return e in whitelist
|
|
329
|
+
return host in self.whitelist
|
|
192
330
|
|
|
193
331
|
@property
|
|
194
|
-
def
|
|
332
|
+
def minimal(self):
|
|
195
333
|
"""
|
|
196
334
|
A slimmer, serializable version of the target designed for simple scope checks
|
|
197
335
|
|
|
198
|
-
This version doesn't have the events, only their hosts.
|
|
336
|
+
This version doesn't have the events, only their hosts. This allows it to be passed across process boundaries.
|
|
199
337
|
"""
|
|
200
338
|
return self.__class__(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
blacklist=[e for e in self.blacklist],
|
|
339
|
+
whitelist=self.whitelist.inputs,
|
|
340
|
+
blacklist=self.blacklist.inputs,
|
|
204
341
|
strict_scope=self.strict_scope,
|
|
205
342
|
)
|
|
206
343
|
|
|
207
|
-
|
|
208
|
-
class Target:
|
|
209
|
-
"""
|
|
210
|
-
A class representing a target. Can contain an unlimited number of hosts, IP or IP ranges, URLs, etc.
|
|
211
|
-
|
|
212
|
-
Attributes:
|
|
213
|
-
strict_scope (bool): Flag indicating whether to consider child domains in-scope.
|
|
214
|
-
If set to True, only the exact hosts specified and not their children are considered part of the target.
|
|
215
|
-
|
|
216
|
-
_radix (RadixTree): Radix tree for quick IP/DNS lookups.
|
|
217
|
-
_events (set): Flat set of contained events.
|
|
218
|
-
|
|
219
|
-
Examples:
|
|
220
|
-
Basic usage
|
|
221
|
-
>>> target = Target(scan, "evilcorp.com", "1.2.3.0/24")
|
|
222
|
-
>>> len(target)
|
|
223
|
-
257
|
|
224
|
-
>>> list(t.events)
|
|
225
|
-
[
|
|
226
|
-
DNS_NAME("evilcorp.com", module=TARGET, tags={'domain', 'distance-1', 'target'}),
|
|
227
|
-
IP_RANGE("1.2.3.0/24", module=TARGET, tags={'ipv4', 'distance-1', 'target'})
|
|
228
|
-
]
|
|
229
|
-
>>> "www.evilcorp.com" in target
|
|
230
|
-
True
|
|
231
|
-
>>> "1.2.3.4" in target
|
|
232
|
-
True
|
|
233
|
-
>>> "4.3.2.1" in target
|
|
234
|
-
False
|
|
235
|
-
>>> "https://admin.evilcorp.com" in target
|
|
236
|
-
True
|
|
237
|
-
>>> "bob@evilcorp.com" in target
|
|
238
|
-
True
|
|
239
|
-
|
|
240
|
-
Event correlation
|
|
241
|
-
>>> target.get("www.evilcorp.com")
|
|
242
|
-
DNS_NAME("evilcorp.com", module=TARGET, tags={'domain', 'distance-1', 'target'})
|
|
243
|
-
>>> target.get("1.2.3.4")
|
|
244
|
-
IP_RANGE("1.2.3.0/24", module=TARGET, tags={'ipv4', 'distance-1', 'target'})
|
|
245
|
-
|
|
246
|
-
Target comparison
|
|
247
|
-
>>> target2 = Targets(scan, "www.evilcorp.com")
|
|
248
|
-
>>> target2 == target
|
|
249
|
-
False
|
|
250
|
-
>>> target2 in target
|
|
251
|
-
True
|
|
252
|
-
>>> target in target2
|
|
253
|
-
False
|
|
254
|
-
|
|
255
|
-
Notes:
|
|
256
|
-
- Targets are only precise down to the individual host. Ports and protocols are not considered in scope calculations.
|
|
257
|
-
- If you specify "https://evilcorp.com:8443" as a target, all of evilcorp.com (including subdomains and other ports and protocols) will be considered part of the target
|
|
258
|
-
- If you do not want to include child subdomains, use `strict_scope=True`
|
|
259
|
-
"""
|
|
260
|
-
|
|
261
|
-
def __init__(self, *targets, strict_scope=False, scan=None, acl_mode=False):
|
|
262
|
-
"""
|
|
263
|
-
Initialize a Target object.
|
|
264
|
-
|
|
265
|
-
Args:
|
|
266
|
-
*targets: One or more targets (e.g., domain names, IP ranges) to be included in this Target.
|
|
267
|
-
strict_scope (bool): Whether to consider subdomains of target domains in-scope
|
|
268
|
-
scan (Scan): Reference to the Scan object that instantiated the Target.
|
|
269
|
-
acl_mode (bool): Stricter deduplication for more efficient checks
|
|
270
|
-
|
|
271
|
-
Notes:
|
|
272
|
-
- If you are instantiating a target from within a BBOT module, use `self.helpers.make_target()` instead. (this removes the need to pass in a scan object.)
|
|
273
|
-
- The strict_scope flag can be set to restrict scope calculation to only exactly-matching hosts and not their child subdomains.
|
|
274
|
-
- Each target is processed and stored as an `Event` in the '_events' dictionary.
|
|
275
|
-
"""
|
|
276
|
-
self.scan = scan
|
|
277
|
-
self.strict_scope = strict_scope
|
|
278
|
-
self.acl_mode = acl_mode
|
|
279
|
-
self.special_event_types = {
|
|
280
|
-
"ORG_STUB": re.compile(r"^(?:ORG|ORG_STUB):(.*)", re.IGNORECASE),
|
|
281
|
-
"USERNAME": re.compile(r"^(?:USER|USERNAME):(.*)", re.IGNORECASE),
|
|
282
|
-
}
|
|
283
|
-
self._events = set()
|
|
284
|
-
self._radix = RadixTarget()
|
|
285
|
-
|
|
286
|
-
for target_event in self._make_events(targets):
|
|
287
|
-
self._add_event(target_event)
|
|
288
|
-
|
|
289
|
-
self._hash = None
|
|
290
|
-
|
|
291
|
-
def add(self, t, event_type=None):
|
|
292
|
-
"""
|
|
293
|
-
Add a target or merge events from another Target object into this Target.
|
|
294
|
-
|
|
295
|
-
Args:
|
|
296
|
-
t: The target to be added. It can be either a string, an event object, or another Target object.
|
|
297
|
-
|
|
298
|
-
Attributes Modified:
|
|
299
|
-
_events (dict): The dictionary is updated to include the new target's events.
|
|
300
|
-
|
|
301
|
-
Examples:
|
|
302
|
-
>>> target.add('example.com')
|
|
303
|
-
|
|
304
|
-
Notes:
|
|
305
|
-
- If `t` is of the same class as this Target, all its events are merged.
|
|
306
|
-
- If `t` is an event, it is directly added to `_events`.
|
|
307
|
-
"""
|
|
308
|
-
if not isinstance(t, (list, tuple, set)):
|
|
309
|
-
t = [t]
|
|
310
|
-
for single_target in t:
|
|
311
|
-
if isinstance(single_target, self.__class__):
|
|
312
|
-
for event in single_target.events:
|
|
313
|
-
self._add_event(event)
|
|
314
|
-
else:
|
|
315
|
-
if is_event(single_target):
|
|
316
|
-
event = single_target
|
|
317
|
-
else:
|
|
318
|
-
try:
|
|
319
|
-
event = make_event(
|
|
320
|
-
single_target, event_type=event_type, dummy=True, tags=["target"], scan=self.scan
|
|
321
|
-
)
|
|
322
|
-
except ValidationError as e:
|
|
323
|
-
# allow commented lines
|
|
324
|
-
if not str(t).startswith("#"):
|
|
325
|
-
log.trace(traceback.format_exc())
|
|
326
|
-
raise ValidationError(f'Could not add target "{t}": {e}')
|
|
327
|
-
self._add_event(event)
|
|
328
|
-
|
|
329
|
-
@property
|
|
330
|
-
def events(self):
|
|
331
|
-
"""
|
|
332
|
-
Returns all events in the target.
|
|
333
|
-
|
|
334
|
-
Yields:
|
|
335
|
-
Event object: One of the Event objects stored in the `_events` dictionary.
|
|
336
|
-
|
|
337
|
-
Examples:
|
|
338
|
-
>>> target = Target(scan, "example.com")
|
|
339
|
-
>>> for event in target.events:
|
|
340
|
-
... print(event)
|
|
341
|
-
|
|
342
|
-
Notes:
|
|
343
|
-
- This property is read-only.
|
|
344
|
-
"""
|
|
345
|
-
return self._events
|
|
346
|
-
|
|
347
|
-
@property
|
|
348
|
-
def hosts(self):
|
|
349
|
-
return [e.host for e in self.events]
|
|
350
|
-
|
|
351
|
-
def copy(self):
|
|
352
|
-
"""
|
|
353
|
-
Creates and returns a copy of the Target object, including a shallow copy of the `_events` and `_radix` attributes.
|
|
354
|
-
|
|
355
|
-
Returns:
|
|
356
|
-
Target: A new Target object with the sameattributes as the original.
|
|
357
|
-
A shallow copy of the `_events` dictionary is made.
|
|
358
|
-
|
|
359
|
-
Examples:
|
|
360
|
-
>>> original_target = Target(scan, "example.com")
|
|
361
|
-
>>> copied_target = original_target.copy()
|
|
362
|
-
>>> copied_target is original_target
|
|
363
|
-
False
|
|
364
|
-
>>> copied_target == original_target
|
|
365
|
-
True
|
|
366
|
-
>>> copied_target in original_target
|
|
367
|
-
True
|
|
368
|
-
>>> original_target in copied_target
|
|
369
|
-
True
|
|
370
|
-
|
|
371
|
-
Notes:
|
|
372
|
-
- The `scan` object reference is kept intact in the copied Target object.
|
|
373
|
-
"""
|
|
374
|
-
self_copy = self.__class__()
|
|
375
|
-
self_copy._events = set(self._events)
|
|
376
|
-
self_copy._radix = copy.copy(self._radix)
|
|
377
|
-
return self_copy
|
|
378
|
-
|
|
379
|
-
def get(self, host, single=True):
|
|
380
|
-
"""
|
|
381
|
-
Gets the event associated with the specified host from the target's radix tree.
|
|
382
|
-
|
|
383
|
-
Args:
|
|
384
|
-
host (Event, Target, or str): The hostname, IP, URL, or event to look for.
|
|
385
|
-
single (bool): Whether to return a single event. If False, return all events matching the host
|
|
386
|
-
|
|
387
|
-
Returns:
|
|
388
|
-
Event or None: Returns the Event object associated with the given host if it exists, otherwise returns None.
|
|
389
|
-
|
|
390
|
-
Examples:
|
|
391
|
-
>>> target = Target(scan, "evilcorp.com", "1.2.3.0/24")
|
|
392
|
-
>>> target.get("www.evilcorp.com")
|
|
393
|
-
DNS_NAME("evilcorp.com", module=TARGET, tags={'domain', 'distance-1', 'target'})
|
|
394
|
-
>>> target.get("1.2.3.4")
|
|
395
|
-
IP_RANGE("1.2.3.0/24", module=TARGET, tags={'ipv4', 'distance-1', 'target'})
|
|
396
|
-
|
|
397
|
-
Notes:
|
|
398
|
-
- The method returns the first event that matches the given host.
|
|
399
|
-
- If `strict_scope` is False, it will also consider parent domains and IP ranges.
|
|
400
|
-
"""
|
|
401
|
-
try:
|
|
402
|
-
event = make_event(host, dummy=True)
|
|
403
|
-
except ValidationError:
|
|
404
|
-
return
|
|
405
|
-
if event.host:
|
|
406
|
-
return self.get_host(event.host, single=single)
|
|
407
|
-
|
|
408
|
-
def get_host(self, host, single=True):
|
|
409
|
-
"""
|
|
410
|
-
A more efficient version of .get() that only accepts hostnames and IP addresses
|
|
411
|
-
"""
|
|
412
|
-
host = make_ip_type(host)
|
|
413
|
-
with suppress(KeyError, StopIteration):
|
|
414
|
-
result = self._radix.search(host)
|
|
415
|
-
if result is not None:
|
|
416
|
-
ret = set()
|
|
417
|
-
for event in result:
|
|
418
|
-
# if the result is a dns name and strict scope is enabled
|
|
419
|
-
if isinstance(event.host, str) and self.strict_scope:
|
|
420
|
-
# if the result doesn't exactly equal the host, abort
|
|
421
|
-
if event.host != host:
|
|
422
|
-
return
|
|
423
|
-
if single:
|
|
424
|
-
return event
|
|
425
|
-
else:
|
|
426
|
-
ret.add(event)
|
|
427
|
-
if ret and not single:
|
|
428
|
-
return ret
|
|
429
|
-
|
|
430
|
-
def _sort_events(self, events):
|
|
431
|
-
return sorted(events, key=lambda x: x._host_size)
|
|
432
|
-
|
|
433
|
-
def _make_events(self, targets):
|
|
434
|
-
events = []
|
|
435
|
-
for target in targets:
|
|
436
|
-
event_type = None
|
|
437
|
-
for eventtype, regex in self.special_event_types.items():
|
|
438
|
-
if isinstance(target, str):
|
|
439
|
-
match = regex.match(target)
|
|
440
|
-
if match:
|
|
441
|
-
target = match.groups()[0]
|
|
442
|
-
event_type = eventtype
|
|
443
|
-
break
|
|
444
|
-
events.append(make_event(target, event_type=event_type, dummy=True, scan=self.scan))
|
|
445
|
-
return self._sort_events(events)
|
|
446
|
-
|
|
447
|
-
def _add_event(self, event):
|
|
448
|
-
skip = False
|
|
449
|
-
if event.host:
|
|
450
|
-
radix_data = self._radix.search(event.host)
|
|
451
|
-
if self.acl_mode:
|
|
452
|
-
# skip if the hostname/IP/subnet (or its parent) has already been added
|
|
453
|
-
if radix_data is not None and not self.strict_scope:
|
|
454
|
-
skip = True
|
|
455
|
-
else:
|
|
456
|
-
event_type = "IP_RANGE" if event.type == "IP_RANGE" else "DNS_NAME"
|
|
457
|
-
event = make_event(event.host, event_type=event_type, dummy=True, scan=self.scan)
|
|
458
|
-
if not skip:
|
|
459
|
-
# if strict scope is enabled and it's not an exact host match, we add a whole new entry
|
|
460
|
-
if radix_data is None or (self.strict_scope and event.host not in radix_data):
|
|
461
|
-
radix_data = {event}
|
|
462
|
-
self._radix.insert(event.host, radix_data)
|
|
463
|
-
# otherwise, we add the event to the set
|
|
464
|
-
else:
|
|
465
|
-
radix_data.add(event)
|
|
466
|
-
# clear hash
|
|
467
|
-
self._hash = None
|
|
468
|
-
elif self.acl_mode and not self.strict_scope:
|
|
469
|
-
# skip if we're in ACL mode and there's no host
|
|
470
|
-
skip = True
|
|
471
|
-
if not skip:
|
|
472
|
-
self._events.add(event)
|
|
473
|
-
|
|
474
|
-
def _contains(self, other):
|
|
475
|
-
if self.get(other) is not None:
|
|
476
|
-
return True
|
|
477
|
-
return False
|
|
478
|
-
|
|
479
|
-
def __str__(self):
|
|
480
|
-
return ",".join([str(e.data) for e in self.events][:5])
|
|
481
|
-
|
|
482
|
-
def __iter__(self):
|
|
483
|
-
yield from self.events
|
|
484
|
-
|
|
485
|
-
def __contains__(self, other):
|
|
486
|
-
# if "other" is a Target
|
|
487
|
-
if isinstance(other, self.__class__):
|
|
488
|
-
contained_in_self = [self._contains(e) for e in other.events]
|
|
489
|
-
return all(contained_in_self)
|
|
490
|
-
else:
|
|
491
|
-
return self._contains(other)
|
|
492
|
-
|
|
493
|
-
def __bool__(self):
|
|
494
|
-
return bool(self._events)
|
|
495
|
-
|
|
496
344
|
def __eq__(self, other):
|
|
497
345
|
return self.hash == other.hash
|
|
498
|
-
|
|
499
|
-
@property
|
|
500
|
-
def hash(self):
|
|
501
|
-
if self._hash is None:
|
|
502
|
-
# Create a new SHA-1 hash object
|
|
503
|
-
sha1_hash = sha1()
|
|
504
|
-
# Update the SHA-1 object with the hash values of each object
|
|
505
|
-
for event_type, event_hash in sorted([(e.type.encode(), e.data_hash) for e in self.events]):
|
|
506
|
-
sha1_hash.update(event_type)
|
|
507
|
-
sha1_hash.update(event_hash)
|
|
508
|
-
if self.strict_scope:
|
|
509
|
-
sha1_hash.update(b"\x00")
|
|
510
|
-
self._hash = sha1_hash.digest()
|
|
511
|
-
return self._hash
|
|
512
|
-
|
|
513
|
-
def __len__(self):
|
|
514
|
-
"""
|
|
515
|
-
Calculates and returns the total number of hosts within this target, not counting duplicate events.
|
|
516
|
-
|
|
517
|
-
Returns:
|
|
518
|
-
int: The total number of unique hosts present within the target's `_events`.
|
|
519
|
-
|
|
520
|
-
Examples:
|
|
521
|
-
>>> target = Target(scan, "evilcorp.com", "1.2.3.0/24")
|
|
522
|
-
>>> len(target)
|
|
523
|
-
257
|
|
524
|
-
|
|
525
|
-
Notes:
|
|
526
|
-
- If a host is represented as an IP network, all individual IP addresses in that network are counted.
|
|
527
|
-
- For other types of hosts, each unique event is counted as one.
|
|
528
|
-
"""
|
|
529
|
-
num_hosts = 0
|
|
530
|
-
for event in self._events:
|
|
531
|
-
if isinstance(event.host, (ipaddress.IPv4Network, ipaddress.IPv6Network)):
|
|
532
|
-
num_hosts += event.host.num_addresses
|
|
533
|
-
else:
|
|
534
|
-
num_hosts += 1
|
|
535
|
-
return num_hosts
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
class TargetDummyModule(BaseModule):
|
|
539
|
-
_type = "TARGET"
|
|
540
|
-
name = "TARGET"
|
|
541
|
-
|
|
542
|
-
def __init__(self, scan):
|
|
543
|
-
self.scan = scan
|