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.

Files changed (270) hide show
  1. bbot/__init__.py +1 -1
  2. bbot/cli.py +3 -7
  3. bbot/core/config/files.py +0 -1
  4. bbot/core/config/logger.py +34 -4
  5. bbot/core/core.py +21 -6
  6. bbot/core/engine.py +9 -8
  7. bbot/core/event/base.py +162 -63
  8. bbot/core/helpers/bloom.py +10 -3
  9. bbot/core/helpers/command.py +9 -8
  10. bbot/core/helpers/depsinstaller/installer.py +89 -32
  11. bbot/core/helpers/depsinstaller/sudo_askpass.py +38 -2
  12. bbot/core/helpers/diff.py +10 -10
  13. bbot/core/helpers/dns/brute.py +18 -14
  14. bbot/core/helpers/dns/dns.py +16 -15
  15. bbot/core/helpers/dns/engine.py +159 -132
  16. bbot/core/helpers/dns/helpers.py +2 -2
  17. bbot/core/helpers/dns/mock.py +26 -8
  18. bbot/core/helpers/files.py +1 -1
  19. bbot/core/helpers/helper.py +7 -4
  20. bbot/core/helpers/interactsh.py +3 -3
  21. bbot/core/helpers/libmagic.py +65 -0
  22. bbot/core/helpers/misc.py +65 -22
  23. bbot/core/helpers/names_generator.py +17 -3
  24. bbot/core/helpers/process.py +0 -20
  25. bbot/core/helpers/regex.py +1 -1
  26. bbot/core/helpers/regexes.py +12 -6
  27. bbot/core/helpers/validators.py +1 -2
  28. bbot/core/helpers/web/client.py +1 -1
  29. bbot/core/helpers/web/engine.py +18 -13
  30. bbot/core/helpers/web/web.py +25 -116
  31. bbot/core/helpers/wordcloud.py +5 -5
  32. bbot/core/modules.py +36 -27
  33. bbot/core/multiprocess.py +58 -0
  34. bbot/core/shared_deps.py +46 -3
  35. bbot/db/sql/models.py +147 -0
  36. bbot/defaults.yml +15 -10
  37. bbot/errors.py +0 -8
  38. bbot/modules/anubisdb.py +2 -2
  39. bbot/modules/apkpure.py +63 -0
  40. bbot/modules/azure_tenant.py +2 -2
  41. bbot/modules/baddns.py +35 -19
  42. bbot/modules/baddns_direct.py +92 -0
  43. bbot/modules/baddns_zone.py +3 -8
  44. bbot/modules/badsecrets.py +4 -3
  45. bbot/modules/base.py +195 -51
  46. bbot/modules/bevigil.py +7 -7
  47. bbot/modules/binaryedge.py +7 -4
  48. bbot/modules/bufferoverrun.py +47 -0
  49. bbot/modules/builtwith.py +6 -10
  50. bbot/modules/bypass403.py +5 -5
  51. bbot/modules/c99.py +10 -7
  52. bbot/modules/censys.py +9 -13
  53. bbot/modules/certspotter.py +5 -3
  54. bbot/modules/chaos.py +9 -7
  55. bbot/modules/code_repository.py +1 -0
  56. bbot/modules/columbus.py +3 -3
  57. bbot/modules/crt.py +5 -3
  58. bbot/modules/deadly/dastardly.py +1 -1
  59. bbot/modules/deadly/ffuf.py +9 -9
  60. bbot/modules/deadly/nuclei.py +3 -3
  61. bbot/modules/deadly/vhost.py +4 -3
  62. bbot/modules/dehashed.py +1 -1
  63. bbot/modules/digitorus.py +1 -1
  64. bbot/modules/dnsbimi.py +145 -0
  65. bbot/modules/dnscaa.py +3 -3
  66. bbot/modules/dnsdumpster.py +4 -4
  67. bbot/modules/dnstlsrpt.py +144 -0
  68. bbot/modules/docker_pull.py +7 -5
  69. bbot/modules/dockerhub.py +2 -2
  70. bbot/modules/dotnetnuke.py +18 -19
  71. bbot/modules/emailformat.py +1 -1
  72. bbot/modules/extractous.py +122 -0
  73. bbot/modules/filedownload.py +9 -7
  74. bbot/modules/fullhunt.py +7 -4
  75. bbot/modules/generic_ssrf.py +5 -5
  76. bbot/modules/github_codesearch.py +3 -2
  77. bbot/modules/github_org.py +4 -4
  78. bbot/modules/github_workflows.py +4 -4
  79. bbot/modules/gitlab.py +2 -5
  80. bbot/modules/google_playstore.py +93 -0
  81. bbot/modules/gowitness.py +48 -50
  82. bbot/modules/hackertarget.py +5 -3
  83. bbot/modules/host_header.py +5 -5
  84. bbot/modules/httpx.py +1 -4
  85. bbot/modules/hunterio.py +3 -9
  86. bbot/modules/iis_shortnames.py +19 -30
  87. bbot/modules/internal/cloudcheck.py +27 -12
  88. bbot/modules/internal/dnsresolve.py +250 -276
  89. bbot/modules/internal/excavate.py +100 -64
  90. bbot/modules/internal/speculate.py +42 -33
  91. bbot/modules/internetdb.py +4 -2
  92. bbot/modules/ip2location.py +3 -5
  93. bbot/modules/ipneighbor.py +1 -1
  94. bbot/modules/ipstack.py +3 -8
  95. bbot/modules/jadx.py +87 -0
  96. bbot/modules/leakix.py +11 -10
  97. bbot/modules/myssl.py +2 -2
  98. bbot/modules/newsletters.py +2 -2
  99. bbot/modules/otx.py +5 -3
  100. bbot/modules/output/asset_inventory.py +7 -7
  101. bbot/modules/output/base.py +1 -1
  102. bbot/modules/output/csv.py +1 -2
  103. bbot/modules/output/http.py +20 -14
  104. bbot/modules/output/mysql.py +51 -0
  105. bbot/modules/output/neo4j.py +7 -2
  106. bbot/modules/output/postgres.py +49 -0
  107. bbot/modules/output/slack.py +0 -1
  108. bbot/modules/output/sqlite.py +29 -0
  109. bbot/modules/output/stdout.py +2 -2
  110. bbot/modules/output/teams.py +107 -6
  111. bbot/modules/paramminer_headers.py +5 -8
  112. bbot/modules/passivetotal.py +13 -13
  113. bbot/modules/portscan.py +32 -6
  114. bbot/modules/postman.py +50 -126
  115. bbot/modules/postman_download.py +220 -0
  116. bbot/modules/rapiddns.py +3 -8
  117. bbot/modules/report/asn.py +11 -11
  118. bbot/modules/robots.py +3 -3
  119. bbot/modules/securitytrails.py +7 -10
  120. bbot/modules/securitytxt.py +128 -0
  121. bbot/modules/shodan_dns.py +7 -9
  122. bbot/modules/sitedossier.py +1 -1
  123. bbot/modules/skymem.py +2 -2
  124. bbot/modules/social.py +2 -1
  125. bbot/modules/subdomaincenter.py +1 -1
  126. bbot/modules/subdomainradar.py +160 -0
  127. bbot/modules/telerik.py +8 -8
  128. bbot/modules/templates/bucket.py +1 -1
  129. bbot/modules/templates/github.py +22 -14
  130. bbot/modules/templates/postman.py +21 -0
  131. bbot/modules/templates/shodan.py +14 -13
  132. bbot/modules/templates/sql.py +95 -0
  133. bbot/modules/templates/subdomain_enum.py +53 -17
  134. bbot/modules/templates/webhook.py +2 -4
  135. bbot/modules/trickest.py +8 -37
  136. bbot/modules/trufflehog.py +18 -3
  137. bbot/modules/url_manipulation.py +3 -3
  138. bbot/modules/urlscan.py +1 -1
  139. bbot/modules/viewdns.py +1 -1
  140. bbot/modules/virustotal.py +8 -30
  141. bbot/modules/wafw00f.py +1 -1
  142. bbot/modules/wayback.py +1 -1
  143. bbot/modules/wpscan.py +17 -11
  144. bbot/modules/zoomeye.py +11 -6
  145. bbot/presets/baddns-thorough.yml +12 -0
  146. bbot/presets/fast.yml +16 -0
  147. bbot/presets/kitchen-sink.yml +1 -0
  148. bbot/presets/spider.yml +4 -0
  149. bbot/presets/subdomain-enum.yml +7 -7
  150. bbot/scanner/manager.py +5 -16
  151. bbot/scanner/preset/args.py +44 -26
  152. bbot/scanner/preset/environ.py +7 -2
  153. bbot/scanner/preset/path.py +7 -4
  154. bbot/scanner/preset/preset.py +36 -23
  155. bbot/scanner/scanner.py +176 -63
  156. bbot/scanner/target.py +236 -434
  157. bbot/scripts/docs.py +1 -1
  158. bbot/test/bbot_fixtures.py +22 -3
  159. bbot/test/conftest.py +132 -100
  160. bbot/test/fastapi_test.py +17 -0
  161. bbot/test/owasp_mastg.apk +0 -0
  162. bbot/test/run_tests.sh +4 -4
  163. bbot/test/test.conf +2 -0
  164. bbot/test/test_step_1/test_bbot_fastapi.py +82 -0
  165. bbot/test/test_step_1/test_bloom_filter.py +2 -0
  166. bbot/test/test_step_1/test_cli.py +138 -64
  167. bbot/test/test_step_1/test_dns.py +392 -70
  168. bbot/test/test_step_1/test_engine.py +17 -17
  169. bbot/test/test_step_1/test_events.py +203 -37
  170. bbot/test/test_step_1/test_helpers.py +64 -28
  171. bbot/test/test_step_1/test_manager_deduplication.py +1 -1
  172. bbot/test/test_step_1/test_manager_scope_accuracy.py +336 -338
  173. bbot/test/test_step_1/test_modules_basic.py +69 -71
  174. bbot/test/test_step_1/test_presets.py +184 -96
  175. bbot/test/test_step_1/test_python_api.py +7 -2
  176. bbot/test/test_step_1/test_regexes.py +35 -5
  177. bbot/test/test_step_1/test_scan.py +39 -5
  178. bbot/test/test_step_1/test_scope.py +5 -4
  179. bbot/test/test_step_1/test_target.py +243 -145
  180. bbot/test/test_step_1/test_web.py +48 -10
  181. bbot/test/test_step_2/module_tests/base.py +17 -20
  182. bbot/test/test_step_2/module_tests/test_module_anubisdb.py +1 -1
  183. bbot/test/test_step_2/module_tests/test_module_apkpure.py +71 -0
  184. bbot/test/test_step_2/module_tests/test_module_asset_inventory.py +0 -1
  185. bbot/test/test_step_2/module_tests/test_module_azure_realm.py +1 -1
  186. bbot/test/test_step_2/module_tests/test_module_baddns.py +6 -6
  187. bbot/test/test_step_2/module_tests/test_module_baddns_direct.py +62 -0
  188. bbot/test/test_step_2/module_tests/test_module_bevigil.py +29 -2
  189. bbot/test/test_step_2/module_tests/test_module_binaryedge.py +4 -2
  190. bbot/test/test_step_2/module_tests/test_module_bucket_amazon.py +2 -2
  191. bbot/test/test_step_2/module_tests/test_module_bucket_azure.py +1 -1
  192. bbot/test/test_step_2/module_tests/test_module_bufferoverrun.py +35 -0
  193. bbot/test/test_step_2/module_tests/test_module_builtwith.py +2 -2
  194. bbot/test/test_step_2/module_tests/test_module_bypass403.py +1 -1
  195. bbot/test/test_step_2/module_tests/test_module_c99.py +126 -0
  196. bbot/test/test_step_2/module_tests/test_module_censys.py +4 -1
  197. bbot/test/test_step_2/module_tests/test_module_cloudcheck.py +4 -0
  198. bbot/test/test_step_2/module_tests/test_module_code_repository.py +11 -1
  199. bbot/test/test_step_2/module_tests/test_module_columbus.py +1 -1
  200. bbot/test/test_step_2/module_tests/test_module_credshed.py +3 -3
  201. bbot/test/test_step_2/module_tests/test_module_dastardly.py +2 -1
  202. bbot/test/test_step_2/module_tests/test_module_dehashed.py +2 -2
  203. bbot/test/test_step_2/module_tests/test_module_digitorus.py +1 -1
  204. bbot/test/test_step_2/module_tests/test_module_discord.py +1 -1
  205. bbot/test/test_step_2/module_tests/test_module_dnsbimi.py +103 -0
  206. bbot/test/test_step_2/module_tests/test_module_dnsbrute.py +9 -10
  207. bbot/test/test_step_2/module_tests/test_module_dnsbrute_mutations.py +1 -2
  208. bbot/test/test_step_2/module_tests/test_module_dnscommonsrv.py +1 -2
  209. bbot/test/test_step_2/module_tests/test_module_dnsdumpster.py +4 -4
  210. bbot/test/test_step_2/module_tests/test_module_dnstlsrpt.py +64 -0
  211. bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py +0 -8
  212. bbot/test/test_step_2/module_tests/test_module_excavate.py +17 -37
  213. bbot/test/test_step_2/module_tests/test_module_extractous.py +54 -0
  214. bbot/test/test_step_2/module_tests/test_module_ffuf_shortnames.py +1 -1
  215. bbot/test/test_step_2/module_tests/test_module_filedownload.py +14 -14
  216. bbot/test/test_step_2/module_tests/test_module_git_clone.py +2 -2
  217. bbot/test/test_step_2/module_tests/test_module_github_org.py +19 -8
  218. bbot/test/test_step_2/module_tests/test_module_github_workflows.py +1 -1
  219. bbot/test/test_step_2/module_tests/test_module_gitlab.py +9 -4
  220. bbot/test/test_step_2/module_tests/test_module_google_playstore.py +83 -0
  221. bbot/test/test_step_2/module_tests/test_module_gowitness.py +4 -4
  222. bbot/test/test_step_2/module_tests/test_module_host_header.py +1 -1
  223. bbot/test/test_step_2/module_tests/test_module_http.py +4 -4
  224. bbot/test/test_step_2/module_tests/test_module_httpx.py +10 -8
  225. bbot/test/test_step_2/module_tests/test_module_hunterio.py +68 -4
  226. bbot/test/test_step_2/module_tests/test_module_jadx.py +55 -0
  227. bbot/test/test_step_2/module_tests/test_module_json.py +24 -11
  228. bbot/test/test_step_2/module_tests/test_module_leakix.py +7 -3
  229. bbot/test/test_step_2/module_tests/test_module_mysql.py +76 -0
  230. bbot/test/test_step_2/module_tests/test_module_myssl.py +1 -1
  231. bbot/test/test_step_2/module_tests/test_module_neo4j.py +1 -1
  232. bbot/test/test_step_2/module_tests/test_module_newsletters.py +6 -6
  233. bbot/test/test_step_2/module_tests/test_module_ntlm.py +7 -7
  234. bbot/test/test_step_2/module_tests/test_module_oauth.py +1 -1
  235. bbot/test/test_step_2/module_tests/test_module_otx.py +1 -1
  236. bbot/test/test_step_2/module_tests/test_module_paramminer_cookies.py +1 -2
  237. bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py +0 -6
  238. bbot/test/test_step_2/module_tests/test_module_paramminer_headers.py +2 -9
  239. bbot/test/test_step_2/module_tests/test_module_passivetotal.py +3 -1
  240. bbot/test/test_step_2/module_tests/test_module_portscan.py +9 -8
  241. bbot/test/test_step_2/module_tests/test_module_postgres.py +74 -0
  242. bbot/test/test_step_2/module_tests/test_module_postman.py +84 -253
  243. bbot/test/test_step_2/module_tests/test_module_postman_download.py +439 -0
  244. bbot/test/test_step_2/module_tests/test_module_rapiddns.py +93 -1
  245. bbot/test/test_step_2/module_tests/test_module_securitytxt.py +50 -0
  246. bbot/test/test_step_2/module_tests/test_module_shodan_dns.py +20 -1
  247. bbot/test/test_step_2/module_tests/test_module_sitedossier.py +2 -2
  248. bbot/test/test_step_2/module_tests/test_module_smuggler.py +1 -1
  249. bbot/test/test_step_2/module_tests/test_module_social.py +11 -1
  250. bbot/test/test_step_2/module_tests/test_module_speculate.py +2 -6
  251. bbot/test/test_step_2/module_tests/test_module_splunk.py +4 -4
  252. bbot/test/test_step_2/module_tests/test_module_sqlite.py +18 -0
  253. bbot/test/test_step_2/module_tests/test_module_sslcert.py +1 -1
  254. bbot/test/test_step_2/module_tests/test_module_stdout.py +5 -3
  255. bbot/test/test_step_2/module_tests/test_module_subdomaincenter.py +1 -1
  256. bbot/test/test_step_2/module_tests/test_module_subdomainradar.py +208 -0
  257. bbot/test/test_step_2/module_tests/test_module_subdomains.py +1 -1
  258. bbot/test/test_step_2/module_tests/test_module_teams.py +8 -6
  259. bbot/test/test_step_2/module_tests/test_module_telerik.py +1 -1
  260. bbot/test/test_step_2/module_tests/test_module_trufflehog.py +317 -11
  261. bbot/test/test_step_2/module_tests/test_module_wayback.py +1 -1
  262. bbot/test/test_step_2/template_tests/test_template_subdomain_enum.py +135 -0
  263. {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/METADATA +48 -18
  264. bbot-2.3.0.5397rc0.dist-info/RECORD +421 -0
  265. {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/WHEEL +1 -1
  266. bbot/modules/unstructured.py +0 -163
  267. bbot/test/test_step_2/module_tests/test_module_unstructured.py +0 -102
  268. bbot-2.0.1.4654rc0.dist-info/RECORD +0 -385
  269. {bbot-2.0.1.4654rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/LICENSE +0 -0
  270. {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: 20
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
- module_paths: []
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
@@ -38,14 +38,6 @@ class WordlistError(BBOTError):
38
38
  pass
39
39
 
40
40
 
41
- class DNSError(BBOTError):
42
- pass
43
-
44
-
45
- class DNSWildcardBreak(DNSError):
46
- pass
47
-
48
-
49
41
  class CurlError(BBOTError):
50
42
  pass
51
43
 
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.request_with_fail_count(url)
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:
@@ -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
@@ -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(), dict()
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 = dict()
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, "enable_references": 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
- "enable_references": "Enable the references module (off by default)",
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.1.815"]
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 module_list:
36
- selected_modules.append(m)
37
- return selected_modules
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, asyncio.create_task(module_instance.dispatch())))
67
-
68
- for module_instance, task in tasks:
69
- if await task:
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: {r['confidence']}")
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
@@ -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.1.815"]
19
+ deps_pip = ["baddns~=1.4.13"]
21
20
 
22
- def select_modules(self):
23
- selected_modules = []
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):
@@ -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.4.490"]
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
- if Path(custom_secrets).is_file():
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 None, "Custom secrets file not valid"
32
+ return False, "Custom secrets file not valid"
32
33
  return True
33
34
 
34
35
  @property