bbot 2.0.1.4654rc0__py3-none-any.whl → 2.3.0.5397rc0__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 -6
- bbot/core/engine.py +9 -8
- bbot/core/event/base.py +162 -63
- bbot/core/helpers/bloom.py +10 -3
- bbot/core/helpers/command.py +9 -8
- bbot/core/helpers/depsinstaller/installer.py +89 -32
- bbot/core/helpers/depsinstaller/sudo_askpass.py +38 -2
- bbot/core/helpers/diff.py +10 -10
- bbot/core/helpers/dns/brute.py +18 -14
- bbot/core/helpers/dns/dns.py +16 -15
- bbot/core/helpers/dns/engine.py +159 -132
- bbot/core/helpers/dns/helpers.py +2 -2
- bbot/core/helpers/dns/mock.py +26 -8
- 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 +18 -13
- bbot/core/helpers/web/web.py +25 -116
- 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 +15 -10
- bbot/errors.py +0 -8
- 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 +18 -19
- 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 +27 -12
- bbot/modules/internal/dnsresolve.py +250 -276
- bbot/modules/internal/excavate.py +100 -64
- bbot/modules/internal/speculate.py +42 -33
- 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 -2
- 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 +5 -8
- 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 +11 -11
- bbot/modules/robots.py +3 -3
- bbot/modules/securitytrails.py +7 -10
- bbot/modules/securitytxt.py +128 -0
- 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 +53 -17
- bbot/modules/templates/webhook.py +2 -4
- bbot/modules/trickest.py +8 -37
- bbot/modules/trufflehog.py +18 -3
- 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 -0
- bbot/presets/spider.yml +4 -0
- bbot/presets/subdomain-enum.yml +7 -7
- bbot/scanner/manager.py +5 -16
- bbot/scanner/preset/args.py +44 -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 +176 -63
- bbot/scanner/target.py +236 -434
- bbot/scripts/docs.py +1 -1
- bbot/test/bbot_fixtures.py +22 -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_bbot_fastapi.py +82 -0
- bbot/test/test_step_1/test_bloom_filter.py +2 -0
- bbot/test/test_step_1/test_cli.py +138 -64
- bbot/test/test_step_1/test_dns.py +392 -70
- bbot/test/test_step_1/test_engine.py +17 -17
- bbot/test/test_step_1/test_events.py +203 -37
- bbot/test/test_step_1/test_helpers.py +64 -28
- bbot/test/test_step_1/test_manager_deduplication.py +1 -1
- bbot/test/test_step_1/test_manager_scope_accuracy.py +336 -338
- bbot/test/test_step_1/test_modules_basic.py +69 -71
- bbot/test/test_step_1/test_presets.py +184 -96
- 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 +5 -4
- bbot/test/test_step_1/test_target.py +243 -145
- bbot/test/test_step_1/test_web.py +48 -10
- bbot/test/test_step_2/module_tests/base.py +17 -20
- 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 +17 -37
- 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 -4
- 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 +24 -11
- 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 +6 -6
- bbot/test/test_step_2/module_tests/test_module_ntlm.py +7 -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_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_securitytxt.py +50 -0
- 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 +1 -1
- bbot/test/test_step_2/module_tests/test_module_social.py +11 -1
- bbot/test/test_step_2/module_tests/test_module_speculate.py +2 -6
- 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 -11
- 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 +135 -0
- {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/METADATA +48 -18
- bbot-2.3.0.5397rc0.dist-info/RECORD +421 -0
- {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.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.4654rc0.dist-info/RECORD +0 -385
- {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/LICENSE +0 -0
- {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/entry_points.txt +0 -0
bbot/db/sql/models.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# This file contains SQLModel (Pydantic + SQLAlchemy) models for BBOT events, scans, and targets.
|
|
2
|
+
# Used by the SQL output modules, but portable for outside use.
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from pydantic import ConfigDict
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from typing_extensions import Annotated
|
|
10
|
+
from pydantic.functional_validators import AfterValidator
|
|
11
|
+
from sqlmodel import inspect, Column, Field, SQLModel, JSON, String, DateTime as SQLADateTime
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger("bbot_server.models")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def naive_datetime_validator(d: datetime):
|
|
18
|
+
"""
|
|
19
|
+
Converts all dates into UTC, then drops timezone information.
|
|
20
|
+
|
|
21
|
+
This is needed to prevent inconsistencies in sqlite, because it is timezone-naive.
|
|
22
|
+
"""
|
|
23
|
+
# drop timezone info
|
|
24
|
+
return d.replace(tzinfo=None)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
NaiveUTC = Annotated[datetime, AfterValidator(naive_datetime_validator)]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CustomJSONEncoder(json.JSONEncoder):
|
|
31
|
+
def default(self, obj):
|
|
32
|
+
# handle datetime
|
|
33
|
+
if isinstance(obj, datetime):
|
|
34
|
+
return obj.isoformat()
|
|
35
|
+
return super().default(obj)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BBOTBaseModel(SQLModel):
|
|
39
|
+
model_config = ConfigDict(extra="ignore")
|
|
40
|
+
|
|
41
|
+
def __init__(self, *args, **kwargs):
|
|
42
|
+
self._validated = None
|
|
43
|
+
super().__init__(*args, **kwargs)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def validated(self):
|
|
47
|
+
try:
|
|
48
|
+
if self._validated is None:
|
|
49
|
+
self._validated = self.__class__.model_validate(self)
|
|
50
|
+
return self._validated
|
|
51
|
+
except AttributeError:
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
def to_json(self, **kwargs):
|
|
55
|
+
return json.dumps(self.validated.model_dump(), sort_keys=True, cls=CustomJSONEncoder, **kwargs)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def _pk_column_names(cls):
|
|
59
|
+
return [column.name for column in inspect(cls).primary_key]
|
|
60
|
+
|
|
61
|
+
def __hash__(self):
|
|
62
|
+
return hash(self.to_json())
|
|
63
|
+
|
|
64
|
+
def __eq__(self, other):
|
|
65
|
+
return hash(self) == hash(other)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
### EVENT ###
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Event(BBOTBaseModel, table=True):
|
|
72
|
+
def __init__(self, *args, **kwargs):
|
|
73
|
+
super().__init__(*args, **kwargs)
|
|
74
|
+
data = self._get_data(self.data, self.type)
|
|
75
|
+
self.data = {self.type: data}
|
|
76
|
+
if self.host:
|
|
77
|
+
self.reverse_host = self.host[::-1]
|
|
78
|
+
|
|
79
|
+
def get_data(self):
|
|
80
|
+
return self._get_data(self.data, self.type)
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def _get_data(data, type):
|
|
84
|
+
# handle SIEM-friendly format
|
|
85
|
+
if isinstance(data, dict) and list(data) == [type]:
|
|
86
|
+
return data[type]
|
|
87
|
+
return data
|
|
88
|
+
|
|
89
|
+
uuid: str = Field(
|
|
90
|
+
primary_key=True,
|
|
91
|
+
index=True,
|
|
92
|
+
nullable=False,
|
|
93
|
+
)
|
|
94
|
+
id: str = Field(index=True)
|
|
95
|
+
type: str = Field(index=True)
|
|
96
|
+
scope_description: str
|
|
97
|
+
data: dict = Field(sa_type=JSON)
|
|
98
|
+
host: Optional[str]
|
|
99
|
+
port: Optional[int]
|
|
100
|
+
netloc: Optional[str]
|
|
101
|
+
# store the host in reversed form for efficient lookups by domain
|
|
102
|
+
reverse_host: Optional[str] = Field(default="", exclude=True, index=True)
|
|
103
|
+
resolved_hosts: List = Field(default=[], sa_type=JSON)
|
|
104
|
+
dns_children: dict = Field(default={}, sa_type=JSON)
|
|
105
|
+
web_spider_distance: int = 10
|
|
106
|
+
scope_distance: int = Field(default=10, index=True)
|
|
107
|
+
scan: str = Field(index=True)
|
|
108
|
+
timestamp: NaiveUTC = Field(index=True)
|
|
109
|
+
parent: str = Field(index=True)
|
|
110
|
+
tags: List = Field(default=[], sa_type=JSON)
|
|
111
|
+
module: str = Field(index=True)
|
|
112
|
+
module_sequence: str
|
|
113
|
+
discovery_context: str = ""
|
|
114
|
+
discovery_path: List[str] = Field(default=[], sa_type=JSON)
|
|
115
|
+
parent_chain: List[str] = Field(default=[], sa_type=JSON)
|
|
116
|
+
inserted_at: NaiveUTC = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
### SCAN ###
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class Scan(BBOTBaseModel, table=True):
|
|
123
|
+
id: str = Field(primary_key=True)
|
|
124
|
+
name: str
|
|
125
|
+
status: str
|
|
126
|
+
started_at: NaiveUTC = Field(index=True)
|
|
127
|
+
finished_at: Optional[NaiveUTC] = Field(default=None, sa_column=Column(SQLADateTime, nullable=True, index=True))
|
|
128
|
+
duration_seconds: Optional[float] = Field(default=None)
|
|
129
|
+
duration: Optional[str] = Field(default=None)
|
|
130
|
+
target: dict = Field(sa_type=JSON)
|
|
131
|
+
preset: dict = Field(sa_type=JSON)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
### TARGET ###
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class Target(BBOTBaseModel, table=True):
|
|
138
|
+
name: str = "Default Target"
|
|
139
|
+
strict_scope: bool = False
|
|
140
|
+
seeds: List = Field(default=[], sa_type=JSON)
|
|
141
|
+
whitelist: List = Field(default=None, sa_type=JSON)
|
|
142
|
+
blacklist: List = Field(default=[], sa_type=JSON)
|
|
143
|
+
hash: str = Field(sa_column=Column("hash", String(length=255), unique=True, primary_key=True, index=True))
|
|
144
|
+
scope_hash: str = Field(sa_column=Column("scope_hash", String(length=255), index=True))
|
|
145
|
+
seed_hash: str = Field(sa_column=Column("seed_hashhash", String(length=255), index=True))
|
|
146
|
+
whitelist_hash: str = Field(sa_column=Column("whitelist_hash", String(length=255), index=True))
|
|
147
|
+
blacklist_hash: str = Field(sa_column=Column("blacklist_hash", String(length=255), index=True))
|
bbot/defaults.yml
CHANGED
|
@@ -14,6 +14,9 @@ folder_blobs: false
|
|
|
14
14
|
### SCOPE ###
|
|
15
15
|
|
|
16
16
|
scope:
|
|
17
|
+
# strict scope means only exact DNS names are considered in-scope
|
|
18
|
+
# subdomains are not included unless they are explicitly provided in the target list
|
|
19
|
+
strict: false
|
|
17
20
|
# Filter by scope distance which events are displayed in the output
|
|
18
21
|
# 0 == show only in-scope events (affiliates are always shown)
|
|
19
22
|
# 1 == show all events up to distance-1 (1 hop from target)
|
|
@@ -30,10 +33,13 @@ dns:
|
|
|
30
33
|
# Speed up scan by not creating any new DNS events, and only resolving A and AAAA records
|
|
31
34
|
minimal: false
|
|
32
35
|
# How many instances of the dns module to run concurrently
|
|
33
|
-
threads:
|
|
36
|
+
threads: 25
|
|
34
37
|
# How many concurrent DNS resolvers to use when brute-forcing
|
|
35
38
|
# (under the hood this is passed through directly to massdns -s)
|
|
36
39
|
brute_threads: 1000
|
|
40
|
+
# nameservers to use for DNS brute-forcing
|
|
41
|
+
# default is updated weekly and contains ~10K high-quality public servers
|
|
42
|
+
brute_nameservers: https://raw.githubusercontent.com/blacklanternsecurity/public-dns-servers/master/nameservers.txt
|
|
37
43
|
# How far away from the main target to explore via DNS resolution (independent of scope.search_distance)
|
|
38
44
|
# This is safe to change
|
|
39
45
|
search_distance: 1
|
|
@@ -106,11 +112,18 @@ engine:
|
|
|
106
112
|
deps:
|
|
107
113
|
ffuf:
|
|
108
114
|
version: "2.1.0"
|
|
115
|
+
# How to handle installation of module dependencies
|
|
116
|
+
# Choices are:
|
|
117
|
+
# - abort_on_failure (default) - if a module dependency fails to install, abort the scan
|
|
118
|
+
# - retry_failed - try again to install failed dependencies
|
|
119
|
+
# - ignore_failed - run the scan regardless of what happens with dependency installation
|
|
120
|
+
# - disable - completely disable BBOT's dependency system (you are responsible for installing tools, pip packages, etc.)
|
|
121
|
+
behavior: abort_on_failure
|
|
109
122
|
|
|
110
123
|
### ADVANCED OPTIONS ###
|
|
111
124
|
|
|
112
125
|
# Load BBOT modules from these custom paths
|
|
113
|
-
|
|
126
|
+
module_dirs: []
|
|
114
127
|
|
|
115
128
|
# Infer certain events from others, e.g. IPs from IP ranges, DNS_NAMEs from URLs, etc.
|
|
116
129
|
speculate: True
|
|
@@ -123,14 +136,6 @@ dnsresolve: True
|
|
|
123
136
|
# Cloud provider tagging
|
|
124
137
|
cloudcheck: True
|
|
125
138
|
|
|
126
|
-
# How to handle installation of module dependencies
|
|
127
|
-
# Choices are:
|
|
128
|
-
# - abort_on_failure (default) - if a module dependency fails to install, abort the scan
|
|
129
|
-
# - retry_failed - try again to install failed dependencies
|
|
130
|
-
# - ignore_failed - run the scan regardless of what happens with dependency installation
|
|
131
|
-
# - disable - completely disable BBOT's dependency system (you are responsible for installing tools, pip packages, etc.)
|
|
132
|
-
deps_behavior: abort_on_failure
|
|
133
|
-
|
|
134
139
|
# Strip querystring from URLs by default
|
|
135
140
|
url_querystring_remove: True
|
|
136
141
|
# When query string is retained, by default collapse parameter values down to a single value per parameter
|
bbot/errors.py
CHANGED
bbot/modules/anubisdb.py
CHANGED
|
@@ -20,7 +20,7 @@ class anubisdb(subdomain_enum):
|
|
|
20
20
|
|
|
21
21
|
async def request_url(self, query):
|
|
22
22
|
url = f"{self.base_url}/{self.helpers.quote(query)}"
|
|
23
|
-
return await self.
|
|
23
|
+
return await self.api_request(url)
|
|
24
24
|
|
|
25
25
|
def abort_if_pre(self, hostname):
|
|
26
26
|
"""
|
|
@@ -38,7 +38,7 @@ class anubisdb(subdomain_enum):
|
|
|
38
38
|
return True, "DNS name is unresolved"
|
|
39
39
|
return await super().abort_if(event)
|
|
40
40
|
|
|
41
|
-
def parse_results(self, r, query):
|
|
41
|
+
async def parse_results(self, r, query):
|
|
42
42
|
results = set()
|
|
43
43
|
json = r.json()
|
|
44
44
|
if json:
|
bbot/modules/apkpure.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from bbot.modules.base import BaseModule
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class apkpure(BaseModule):
|
|
7
|
+
watched_events = ["MOBILE_APP"]
|
|
8
|
+
produced_events = ["FILESYSTEM"]
|
|
9
|
+
flags = ["passive", "safe", "code-enum"]
|
|
10
|
+
meta = {
|
|
11
|
+
"description": "Download android applications from apkpure.com",
|
|
12
|
+
"created_date": "2024-10-11",
|
|
13
|
+
"author": "@domwhewell-sage",
|
|
14
|
+
}
|
|
15
|
+
options = {"output_folder": ""}
|
|
16
|
+
options_desc = {"output_folder": "Folder to download apk's to"}
|
|
17
|
+
|
|
18
|
+
async def setup(self):
|
|
19
|
+
output_folder = self.config.get("output_folder")
|
|
20
|
+
if output_folder:
|
|
21
|
+
self.output_dir = Path(output_folder) / "apk_files"
|
|
22
|
+
else:
|
|
23
|
+
self.output_dir = self.scan.home / "apk_files"
|
|
24
|
+
self.helpers.mkdir(self.output_dir)
|
|
25
|
+
return await super().setup()
|
|
26
|
+
|
|
27
|
+
async def filter_event(self, event):
|
|
28
|
+
if event.type == "MOBILE_APP":
|
|
29
|
+
if "android" not in event.tags:
|
|
30
|
+
return False, "event is not an android app"
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
async def handle_event(self, event):
|
|
34
|
+
app_id = event.data.get("id", "")
|
|
35
|
+
path = await self.download_apk(app_id)
|
|
36
|
+
if path:
|
|
37
|
+
await self.emit_event(
|
|
38
|
+
{"path": str(path)},
|
|
39
|
+
"FILESYSTEM",
|
|
40
|
+
tags=["apk", "file"],
|
|
41
|
+
parent=event,
|
|
42
|
+
context=f'{{module}} downloaded the apk "{app_id}" to: {path}',
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async def download_apk(self, app_id):
|
|
46
|
+
path = None
|
|
47
|
+
url = f"https://d.apkpure.com/b/XAPK/{app_id}?version=latest"
|
|
48
|
+
self.helpers.mkdir(self.output_dir / app_id)
|
|
49
|
+
response = await self.helpers.request(url, allow_redirects=True)
|
|
50
|
+
if response:
|
|
51
|
+
attachment = response.headers.get("Content-Disposition", "")
|
|
52
|
+
if "filename" in attachment:
|
|
53
|
+
match = re.search(r'filename="?([^"]+)"?', attachment)
|
|
54
|
+
if match:
|
|
55
|
+
filename = match.group(1)
|
|
56
|
+
extension = filename.split(".")[-1]
|
|
57
|
+
content = response.content
|
|
58
|
+
file_destination = self.output_dir / app_id / f"{app_id}.{extension}"
|
|
59
|
+
with open(file_destination, "wb") as f:
|
|
60
|
+
f.write(content)
|
|
61
|
+
self.info(f'Downloaded "{app_id}" from "{url}", saved to {file_destination}')
|
|
62
|
+
path = file_destination
|
|
63
|
+
return path
|
bbot/modules/azure_tenant.py
CHANGED
|
@@ -102,7 +102,7 @@ class azure_tenant(BaseModule):
|
|
|
102
102
|
status_code = getattr(r, "status_code", 0)
|
|
103
103
|
if status_code not in (200, 421):
|
|
104
104
|
self.verbose(f'Error retrieving azure_tenant domains for "{domain}" (status code: {status_code})')
|
|
105
|
-
return set(),
|
|
105
|
+
return set(), {}
|
|
106
106
|
found_domains = list(set(await self.helpers.re.findall(self.d_xml_regex, r.text)))
|
|
107
107
|
domains = set()
|
|
108
108
|
|
|
@@ -116,7 +116,7 @@ class azure_tenant(BaseModule):
|
|
|
116
116
|
self.scan.word_cloud.absorb_word(d)
|
|
117
117
|
|
|
118
118
|
r = await openid_task
|
|
119
|
-
openid_config =
|
|
119
|
+
openid_config = {}
|
|
120
120
|
with suppress(Exception):
|
|
121
121
|
openid_config = r.json()
|
|
122
122
|
|
bbot/modules/baddns.py
CHANGED
|
@@ -15,26 +15,26 @@ class baddns(BaseModule):
|
|
|
15
15
|
"created_date": "2024-01-18",
|
|
16
16
|
"author": "@liquidsec",
|
|
17
17
|
}
|
|
18
|
-
options = {"custom_nameservers": [], "only_high_confidence": False, "
|
|
18
|
+
options = {"custom_nameservers": [], "only_high_confidence": False, "enabled_submodules": []}
|
|
19
19
|
options_desc = {
|
|
20
20
|
"custom_nameservers": "Force BadDNS to use a list of custom nameservers",
|
|
21
21
|
"only_high_confidence": "Do not emit low-confidence or generic detections",
|
|
22
|
-
"
|
|
22
|
+
"enabled_submodules": "A list of submodules to enable. Empty list (default) enables CNAME, TXT and MX Only",
|
|
23
23
|
}
|
|
24
24
|
module_threads = 8
|
|
25
|
-
deps_pip = ["baddns~=1.
|
|
25
|
+
deps_pip = ["baddns~=1.4.13"]
|
|
26
26
|
|
|
27
27
|
def select_modules(self):
|
|
28
|
-
|
|
29
|
-
module_list = ["CNAME", "NS", "MX", "TXT"]
|
|
30
|
-
if self.config.get("enable_references", False):
|
|
31
|
-
module_list.append("references")
|
|
32
|
-
|
|
33
|
-
selected_modules = []
|
|
28
|
+
selected_submodules = []
|
|
34
29
|
for m in get_all_modules():
|
|
35
|
-
if m.name in
|
|
36
|
-
|
|
37
|
-
return
|
|
30
|
+
if m.name in self.enabled_submodules:
|
|
31
|
+
selected_submodules.append(m)
|
|
32
|
+
return selected_submodules
|
|
33
|
+
|
|
34
|
+
def set_modules(self):
|
|
35
|
+
self.enabled_submodules = self.config.get("enabled_submodules", [])
|
|
36
|
+
if self.enabled_submodules == []:
|
|
37
|
+
self.enabled_submodules = ["CNAME", "MX", "TXT"]
|
|
38
38
|
|
|
39
39
|
async def setup(self):
|
|
40
40
|
self.preset.core.logger.include_logger(logging.getLogger("baddns"))
|
|
@@ -43,10 +43,18 @@ class baddns(BaseModule):
|
|
|
43
43
|
self.custom_nameservers = self.helpers.chain_lists(self.custom_nameservers)
|
|
44
44
|
self.only_high_confidence = self.config.get("only_high_confidence", False)
|
|
45
45
|
self.signatures = load_signatures()
|
|
46
|
+
self.set_modules()
|
|
47
|
+
all_submodules_list = [m.name for m in get_all_modules()]
|
|
48
|
+
for m in self.enabled_submodules:
|
|
49
|
+
if m not in all_submodules_list:
|
|
50
|
+
self.hugewarning(
|
|
51
|
+
f"Selected BadDNS submodule [{m}] does not exist. Available submodules: [{','.join(all_submodules_list)}]"
|
|
52
|
+
)
|
|
53
|
+
return False
|
|
54
|
+
self.debug(f"Enabled BadDNS Submodules: [{','.join(self.enabled_submodules)}]")
|
|
46
55
|
return True
|
|
47
56
|
|
|
48
57
|
async def handle_event(self, event):
|
|
49
|
-
|
|
50
58
|
tasks = []
|
|
51
59
|
for ModuleClass in self.select_modules():
|
|
52
60
|
kwargs = {
|
|
@@ -62,11 +70,18 @@ class baddns(BaseModule):
|
|
|
62
70
|
kwargs["raw_query_retry_wait"] = 0
|
|
63
71
|
|
|
64
72
|
module_instance = ModuleClass(event.data, **kwargs)
|
|
65
|
-
|
|
66
|
-
tasks.append((module_instance,
|
|
67
|
-
|
|
68
|
-
for
|
|
69
|
-
if
|
|
73
|
+
task = asyncio.create_task(module_instance.dispatch())
|
|
74
|
+
tasks.append((module_instance, task))
|
|
75
|
+
|
|
76
|
+
async for completed_task in self.helpers.as_completed([task for _, task in tasks]):
|
|
77
|
+
module_instance = next((m for m, t in tasks if t == completed_task), None)
|
|
78
|
+
try:
|
|
79
|
+
task_result = await completed_task
|
|
80
|
+
except Exception as e:
|
|
81
|
+
self.warning(f"Task for {module_instance} raised an error: {e}")
|
|
82
|
+
task_result = None
|
|
83
|
+
|
|
84
|
+
if task_result:
|
|
70
85
|
results = module_instance.analyze()
|
|
71
86
|
if results and len(results) > 0:
|
|
72
87
|
for r in results:
|
|
@@ -99,7 +114,7 @@ class baddns(BaseModule):
|
|
|
99
114
|
context=f'{{module}}\'s "{r_dict["module"]}" module found {{event.type}}: {r_dict["description"]}',
|
|
100
115
|
)
|
|
101
116
|
else:
|
|
102
|
-
self.warning(f"Got unrecognized confidence level: {
|
|
117
|
+
self.warning(f"Got unrecognized confidence level: {r_dict['confidence']}")
|
|
103
118
|
|
|
104
119
|
found_domains = r_dict.get("found_domains", None)
|
|
105
120
|
if found_domains:
|
|
@@ -111,3 +126,4 @@ class baddns(BaseModule):
|
|
|
111
126
|
tags=[f"baddns-{module_instance.name.lower()}"],
|
|
112
127
|
context=f'{{module}}\'s "{r_dict["module"]}" module found {{event.type}}: {{event.data}}',
|
|
113
128
|
)
|
|
129
|
+
await module_instance.cleanup()
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from baddns.base import get_all_modules
|
|
2
|
+
from baddns.lib.loader import load_signatures
|
|
3
|
+
from .base import BaseModule
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class baddns_direct(BaseModule):
|
|
9
|
+
watched_events = ["URL", "STORAGE_BUCKET"]
|
|
10
|
+
produced_events = ["FINDING", "VULNERABILITY"]
|
|
11
|
+
flags = ["active", "safe", "subdomain-enum", "baddns", "cloud-enum"]
|
|
12
|
+
meta = {
|
|
13
|
+
"description": "Check for unusual subdomain / service takeover edge cases that require direct detection",
|
|
14
|
+
"created_date": "2024-01-29",
|
|
15
|
+
"author": "@liquidsec",
|
|
16
|
+
}
|
|
17
|
+
options = {"custom_nameservers": []}
|
|
18
|
+
options_desc = {
|
|
19
|
+
"custom_nameservers": "Force BadDNS to use a list of custom nameservers",
|
|
20
|
+
}
|
|
21
|
+
module_threads = 8
|
|
22
|
+
deps_pip = ["baddns~=1.4.13"]
|
|
23
|
+
|
|
24
|
+
scope_distance_modifier = 1
|
|
25
|
+
|
|
26
|
+
async def setup(self):
|
|
27
|
+
self.preset.core.logger.include_logger(logging.getLogger("baddns"))
|
|
28
|
+
self.custom_nameservers = self.config.get("custom_nameservers", []) or None
|
|
29
|
+
if self.custom_nameservers:
|
|
30
|
+
self.custom_nameservers = self.helpers.chain_lists(self.custom_nameservers)
|
|
31
|
+
self.only_high_confidence = self.config.get("only_high_confidence", False)
|
|
32
|
+
self.signatures = load_signatures()
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
def select_modules(self):
|
|
36
|
+
selected_modules = []
|
|
37
|
+
for m in get_all_modules():
|
|
38
|
+
if m.name in ["CNAME"]:
|
|
39
|
+
selected_modules.append(m)
|
|
40
|
+
return selected_modules
|
|
41
|
+
|
|
42
|
+
async def handle_event(self, event):
|
|
43
|
+
CNAME_direct_module = self.select_modules()[0]
|
|
44
|
+
kwargs = {
|
|
45
|
+
"http_client_class": self.scan.helpers.web.AsyncClient,
|
|
46
|
+
"dns_client": self.scan.helpers.dns.resolver,
|
|
47
|
+
"custom_nameservers": self.custom_nameservers,
|
|
48
|
+
"signatures": self.signatures,
|
|
49
|
+
"direct_mode": True,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
CNAME_direct_instance = CNAME_direct_module(event.host, **kwargs)
|
|
53
|
+
if await CNAME_direct_instance.dispatch():
|
|
54
|
+
results = CNAME_direct_instance.analyze()
|
|
55
|
+
if results and len(results) > 0:
|
|
56
|
+
for r in results:
|
|
57
|
+
r_dict = r.to_dict()
|
|
58
|
+
|
|
59
|
+
data = {
|
|
60
|
+
"description": f"Possible [{r_dict['signature']}] via direct BadDNS analysis. Indicator: [{r_dict['indicator']}] Trigger: [{r_dict['trigger']}] baddns Module: [{r_dict['module']}]",
|
|
61
|
+
"host": str(event.host),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await self.emit_event(
|
|
65
|
+
data,
|
|
66
|
+
"FINDING",
|
|
67
|
+
event,
|
|
68
|
+
tags=[f"baddns-{CNAME_direct_module.name.lower()}"],
|
|
69
|
+
context=f'{{module}}\'s "{r_dict["module"]}" module found {{event.type}}: {r_dict["description"]}',
|
|
70
|
+
)
|
|
71
|
+
await CNAME_direct_instance.cleanup()
|
|
72
|
+
|
|
73
|
+
async def filter_event(self, event):
|
|
74
|
+
if event.type == "STORAGE_BUCKET":
|
|
75
|
+
if str(event.module).startswith("bucket_"):
|
|
76
|
+
return False
|
|
77
|
+
self.debug(f"Processing STORAGE_BUCKET for {event.host}")
|
|
78
|
+
if event.type == "URL":
|
|
79
|
+
if event.scope_distance > 0:
|
|
80
|
+
self.debug(
|
|
81
|
+
f"Rejecting {event.host} due to not being in scope (scope distance: {str(event.scope_distance)})"
|
|
82
|
+
)
|
|
83
|
+
return False
|
|
84
|
+
if "cdn-cloudflare" not in event.tags:
|
|
85
|
+
self.debug(f"Rejecting {event.host} due to not being behind CloudFlare")
|
|
86
|
+
return False
|
|
87
|
+
if "status-200" in event.tags or "status-301" in event.tags:
|
|
88
|
+
self.debug(f"Rejecting {event.host} due to lack of non-standard status code")
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
self.debug(f"Passed all checks and is processing {event.host}")
|
|
92
|
+
return True
|
bbot/modules/baddns_zone.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from baddns.base import get_all_modules
|
|
2
1
|
from .baddns import baddns as baddns_module
|
|
3
2
|
|
|
4
3
|
|
|
@@ -17,14 +16,10 @@ class baddns_zone(baddns_module):
|
|
|
17
16
|
"only_high_confidence": "Do not emit low-confidence or generic detections",
|
|
18
17
|
}
|
|
19
18
|
module_threads = 8
|
|
20
|
-
deps_pip = ["baddns~=1.
|
|
19
|
+
deps_pip = ["baddns~=1.4.13"]
|
|
21
20
|
|
|
22
|
-
def
|
|
23
|
-
|
|
24
|
-
for m in get_all_modules():
|
|
25
|
-
if m.name in ["NSEC", "zonetransfer"]:
|
|
26
|
-
selected_modules.append(m)
|
|
27
|
-
return selected_modules
|
|
21
|
+
def set_modules(self):
|
|
22
|
+
self.enabled_submodules = ["NSEC", "zonetransfer"]
|
|
28
23
|
|
|
29
24
|
# minimize nsec records feeding back into themselves
|
|
30
25
|
async def filter_event(self, event):
|
bbot/modules/badsecrets.py
CHANGED
|
@@ -17,18 +17,19 @@ class badsecrets(BaseModule):
|
|
|
17
17
|
options_desc = {
|
|
18
18
|
"custom_secrets": "Include custom secrets loaded from a local file",
|
|
19
19
|
}
|
|
20
|
-
deps_pip = ["badsecrets~=0.
|
|
20
|
+
deps_pip = ["badsecrets~=0.6.21"]
|
|
21
21
|
|
|
22
22
|
async def setup(self):
|
|
23
23
|
self.custom_secrets = None
|
|
24
24
|
custom_secrets = self.config.get("custom_secrets", None)
|
|
25
25
|
if custom_secrets:
|
|
26
|
-
|
|
26
|
+
secrets_path = Path(custom_secrets).expanduser()
|
|
27
|
+
if secrets_path.is_file():
|
|
27
28
|
self.custom_secrets = custom_secrets
|
|
28
29
|
self.info(f"Successfully loaded secrets file [{custom_secrets}]")
|
|
29
30
|
else:
|
|
30
31
|
self.warning(f"custom secrets file [{custom_secrets}] is not valid")
|
|
31
|
-
return
|
|
32
|
+
return False, "Custom secrets file not valid"
|
|
32
33
|
return True
|
|
33
34
|
|
|
34
35
|
@property
|