bbot 2.0.1.4720rc0__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 (267) 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 -4
  6. bbot/core/engine.py +9 -8
  7. bbot/core/event/base.py +131 -52
  8. bbot/core/helpers/bloom.py +10 -3
  9. bbot/core/helpers/command.py +8 -7
  10. bbot/core/helpers/depsinstaller/installer.py +31 -13
  11. bbot/core/helpers/diff.py +10 -10
  12. bbot/core/helpers/dns/brute.py +7 -4
  13. bbot/core/helpers/dns/dns.py +1 -2
  14. bbot/core/helpers/dns/engine.py +4 -6
  15. bbot/core/helpers/dns/helpers.py +2 -2
  16. bbot/core/helpers/dns/mock.py +0 -1
  17. bbot/core/helpers/files.py +1 -1
  18. bbot/core/helpers/helper.py +7 -4
  19. bbot/core/helpers/interactsh.py +3 -3
  20. bbot/core/helpers/libmagic.py +65 -0
  21. bbot/core/helpers/misc.py +65 -22
  22. bbot/core/helpers/names_generator.py +17 -3
  23. bbot/core/helpers/process.py +0 -20
  24. bbot/core/helpers/regex.py +1 -1
  25. bbot/core/helpers/regexes.py +12 -6
  26. bbot/core/helpers/validators.py +1 -2
  27. bbot/core/helpers/web/client.py +1 -1
  28. bbot/core/helpers/web/engine.py +1 -2
  29. bbot/core/helpers/web/web.py +4 -114
  30. bbot/core/helpers/wordcloud.py +5 -5
  31. bbot/core/modules.py +36 -27
  32. bbot/core/multiprocess.py +58 -0
  33. bbot/core/shared_deps.py +46 -3
  34. bbot/db/sql/models.py +147 -0
  35. bbot/defaults.yml +11 -9
  36. bbot/modules/anubisdb.py +2 -2
  37. bbot/modules/apkpure.py +63 -0
  38. bbot/modules/azure_tenant.py +2 -2
  39. bbot/modules/baddns.py +35 -19
  40. bbot/modules/baddns_direct.py +92 -0
  41. bbot/modules/baddns_zone.py +3 -8
  42. bbot/modules/badsecrets.py +4 -3
  43. bbot/modules/base.py +195 -51
  44. bbot/modules/bevigil.py +7 -7
  45. bbot/modules/binaryedge.py +7 -4
  46. bbot/modules/bufferoverrun.py +47 -0
  47. bbot/modules/builtwith.py +6 -10
  48. bbot/modules/bypass403.py +5 -5
  49. bbot/modules/c99.py +10 -7
  50. bbot/modules/censys.py +9 -13
  51. bbot/modules/certspotter.py +5 -3
  52. bbot/modules/chaos.py +9 -7
  53. bbot/modules/code_repository.py +1 -0
  54. bbot/modules/columbus.py +3 -3
  55. bbot/modules/crt.py +5 -3
  56. bbot/modules/deadly/dastardly.py +1 -1
  57. bbot/modules/deadly/ffuf.py +9 -9
  58. bbot/modules/deadly/nuclei.py +3 -3
  59. bbot/modules/deadly/vhost.py +4 -3
  60. bbot/modules/dehashed.py +1 -1
  61. bbot/modules/digitorus.py +1 -1
  62. bbot/modules/dnsbimi.py +145 -0
  63. bbot/modules/dnscaa.py +3 -3
  64. bbot/modules/dnsdumpster.py +4 -4
  65. bbot/modules/dnstlsrpt.py +144 -0
  66. bbot/modules/docker_pull.py +7 -5
  67. bbot/modules/dockerhub.py +2 -2
  68. bbot/modules/dotnetnuke.py +18 -19
  69. bbot/modules/emailformat.py +1 -1
  70. bbot/modules/extractous.py +122 -0
  71. bbot/modules/filedownload.py +9 -7
  72. bbot/modules/fullhunt.py +7 -4
  73. bbot/modules/generic_ssrf.py +5 -5
  74. bbot/modules/github_codesearch.py +3 -2
  75. bbot/modules/github_org.py +4 -4
  76. bbot/modules/github_workflows.py +4 -4
  77. bbot/modules/gitlab.py +2 -5
  78. bbot/modules/google_playstore.py +93 -0
  79. bbot/modules/gowitness.py +48 -50
  80. bbot/modules/hackertarget.py +5 -3
  81. bbot/modules/host_header.py +5 -5
  82. bbot/modules/httpx.py +1 -4
  83. bbot/modules/hunterio.py +3 -9
  84. bbot/modules/iis_shortnames.py +19 -30
  85. bbot/modules/internal/cloudcheck.py +27 -12
  86. bbot/modules/internal/dnsresolve.py +22 -20
  87. bbot/modules/internal/excavate.py +85 -48
  88. bbot/modules/internal/speculate.py +41 -32
  89. bbot/modules/internetdb.py +4 -2
  90. bbot/modules/ip2location.py +3 -5
  91. bbot/modules/ipneighbor.py +1 -1
  92. bbot/modules/ipstack.py +3 -8
  93. bbot/modules/jadx.py +87 -0
  94. bbot/modules/leakix.py +11 -10
  95. bbot/modules/myssl.py +2 -2
  96. bbot/modules/newsletters.py +2 -2
  97. bbot/modules/otx.py +5 -3
  98. bbot/modules/output/asset_inventory.py +7 -7
  99. bbot/modules/output/base.py +1 -1
  100. bbot/modules/output/csv.py +1 -1
  101. bbot/modules/output/http.py +20 -14
  102. bbot/modules/output/mysql.py +51 -0
  103. bbot/modules/output/neo4j.py +7 -2
  104. bbot/modules/output/postgres.py +49 -0
  105. bbot/modules/output/slack.py +0 -1
  106. bbot/modules/output/sqlite.py +29 -0
  107. bbot/modules/output/stdout.py +2 -2
  108. bbot/modules/output/teams.py +107 -6
  109. bbot/modules/paramminer_headers.py +5 -8
  110. bbot/modules/passivetotal.py +13 -13
  111. bbot/modules/portscan.py +32 -6
  112. bbot/modules/postman.py +50 -126
  113. bbot/modules/postman_download.py +220 -0
  114. bbot/modules/rapiddns.py +3 -8
  115. bbot/modules/report/asn.py +11 -11
  116. bbot/modules/robots.py +3 -3
  117. bbot/modules/securitytrails.py +7 -10
  118. bbot/modules/securitytxt.py +1 -1
  119. bbot/modules/shodan_dns.py +7 -9
  120. bbot/modules/sitedossier.py +1 -1
  121. bbot/modules/skymem.py +2 -2
  122. bbot/modules/social.py +2 -1
  123. bbot/modules/subdomaincenter.py +1 -1
  124. bbot/modules/subdomainradar.py +160 -0
  125. bbot/modules/telerik.py +8 -8
  126. bbot/modules/templates/bucket.py +1 -1
  127. bbot/modules/templates/github.py +22 -14
  128. bbot/modules/templates/postman.py +21 -0
  129. bbot/modules/templates/shodan.py +14 -13
  130. bbot/modules/templates/sql.py +95 -0
  131. bbot/modules/templates/subdomain_enum.py +51 -16
  132. bbot/modules/templates/webhook.py +2 -4
  133. bbot/modules/trickest.py +8 -37
  134. bbot/modules/trufflehog.py +10 -12
  135. bbot/modules/url_manipulation.py +3 -3
  136. bbot/modules/urlscan.py +1 -1
  137. bbot/modules/viewdns.py +1 -1
  138. bbot/modules/virustotal.py +8 -30
  139. bbot/modules/wafw00f.py +1 -1
  140. bbot/modules/wayback.py +1 -1
  141. bbot/modules/wpscan.py +17 -11
  142. bbot/modules/zoomeye.py +11 -6
  143. bbot/presets/baddns-thorough.yml +12 -0
  144. bbot/presets/fast.yml +16 -0
  145. bbot/presets/kitchen-sink.yml +1 -0
  146. bbot/presets/spider.yml +4 -0
  147. bbot/presets/subdomain-enum.yml +7 -7
  148. bbot/scanner/manager.py +5 -16
  149. bbot/scanner/preset/args.py +44 -26
  150. bbot/scanner/preset/environ.py +7 -2
  151. bbot/scanner/preset/path.py +7 -4
  152. bbot/scanner/preset/preset.py +36 -23
  153. bbot/scanner/scanner.py +172 -62
  154. bbot/scanner/target.py +236 -434
  155. bbot/scripts/docs.py +1 -1
  156. bbot/test/bbot_fixtures.py +13 -3
  157. bbot/test/conftest.py +132 -100
  158. bbot/test/fastapi_test.py +17 -0
  159. bbot/test/owasp_mastg.apk +0 -0
  160. bbot/test/run_tests.sh +4 -4
  161. bbot/test/test.conf +2 -0
  162. bbot/test/test_step_1/test_bbot_fastapi.py +82 -0
  163. bbot/test/test_step_1/test_bloom_filter.py +2 -0
  164. bbot/test/test_step_1/test_cli.py +138 -64
  165. bbot/test/test_step_1/test_dns.py +62 -25
  166. bbot/test/test_step_1/test_engine.py +17 -17
  167. bbot/test/test_step_1/test_events.py +183 -28
  168. bbot/test/test_step_1/test_helpers.py +64 -28
  169. bbot/test/test_step_1/test_manager_deduplication.py +1 -1
  170. bbot/test/test_step_1/test_manager_scope_accuracy.py +333 -330
  171. bbot/test/test_step_1/test_modules_basic.py +68 -70
  172. bbot/test/test_step_1/test_presets.py +184 -96
  173. bbot/test/test_step_1/test_python_api.py +7 -2
  174. bbot/test/test_step_1/test_regexes.py +35 -5
  175. bbot/test/test_step_1/test_scan.py +39 -5
  176. bbot/test/test_step_1/test_scope.py +4 -3
  177. bbot/test/test_step_1/test_target.py +243 -145
  178. bbot/test/test_step_1/test_web.py +14 -8
  179. bbot/test/test_step_2/module_tests/base.py +15 -7
  180. bbot/test/test_step_2/module_tests/test_module_anubisdb.py +1 -1
  181. bbot/test/test_step_2/module_tests/test_module_apkpure.py +71 -0
  182. bbot/test/test_step_2/module_tests/test_module_asset_inventory.py +0 -1
  183. bbot/test/test_step_2/module_tests/test_module_azure_realm.py +1 -1
  184. bbot/test/test_step_2/module_tests/test_module_baddns.py +6 -6
  185. bbot/test/test_step_2/module_tests/test_module_baddns_direct.py +62 -0
  186. bbot/test/test_step_2/module_tests/test_module_bevigil.py +29 -2
  187. bbot/test/test_step_2/module_tests/test_module_binaryedge.py +4 -2
  188. bbot/test/test_step_2/module_tests/test_module_bucket_amazon.py +2 -2
  189. bbot/test/test_step_2/module_tests/test_module_bucket_azure.py +1 -1
  190. bbot/test/test_step_2/module_tests/test_module_bufferoverrun.py +35 -0
  191. bbot/test/test_step_2/module_tests/test_module_builtwith.py +2 -2
  192. bbot/test/test_step_2/module_tests/test_module_bypass403.py +1 -1
  193. bbot/test/test_step_2/module_tests/test_module_c99.py +126 -0
  194. bbot/test/test_step_2/module_tests/test_module_censys.py +4 -1
  195. bbot/test/test_step_2/module_tests/test_module_cloudcheck.py +4 -0
  196. bbot/test/test_step_2/module_tests/test_module_code_repository.py +11 -1
  197. bbot/test/test_step_2/module_tests/test_module_columbus.py +1 -1
  198. bbot/test/test_step_2/module_tests/test_module_credshed.py +3 -3
  199. bbot/test/test_step_2/module_tests/test_module_dastardly.py +2 -1
  200. bbot/test/test_step_2/module_tests/test_module_dehashed.py +2 -2
  201. bbot/test/test_step_2/module_tests/test_module_digitorus.py +1 -1
  202. bbot/test/test_step_2/module_tests/test_module_discord.py +1 -1
  203. bbot/test/test_step_2/module_tests/test_module_dnsbimi.py +103 -0
  204. bbot/test/test_step_2/module_tests/test_module_dnsbrute.py +9 -10
  205. bbot/test/test_step_2/module_tests/test_module_dnsbrute_mutations.py +1 -2
  206. bbot/test/test_step_2/module_tests/test_module_dnscommonsrv.py +1 -2
  207. bbot/test/test_step_2/module_tests/test_module_dnsdumpster.py +4 -4
  208. bbot/test/test_step_2/module_tests/test_module_dnstlsrpt.py +64 -0
  209. bbot/test/test_step_2/module_tests/test_module_dotnetnuke.py +0 -8
  210. bbot/test/test_step_2/module_tests/test_module_excavate.py +17 -37
  211. bbot/test/test_step_2/module_tests/test_module_extractous.py +54 -0
  212. bbot/test/test_step_2/module_tests/test_module_ffuf_shortnames.py +1 -1
  213. bbot/test/test_step_2/module_tests/test_module_filedownload.py +14 -14
  214. bbot/test/test_step_2/module_tests/test_module_git_clone.py +2 -2
  215. bbot/test/test_step_2/module_tests/test_module_github_org.py +19 -8
  216. bbot/test/test_step_2/module_tests/test_module_github_workflows.py +1 -1
  217. bbot/test/test_step_2/module_tests/test_module_gitlab.py +9 -4
  218. bbot/test/test_step_2/module_tests/test_module_google_playstore.py +83 -0
  219. bbot/test/test_step_2/module_tests/test_module_gowitness.py +4 -4
  220. bbot/test/test_step_2/module_tests/test_module_host_header.py +1 -1
  221. bbot/test/test_step_2/module_tests/test_module_http.py +4 -4
  222. bbot/test/test_step_2/module_tests/test_module_httpx.py +10 -8
  223. bbot/test/test_step_2/module_tests/test_module_hunterio.py +68 -4
  224. bbot/test/test_step_2/module_tests/test_module_jadx.py +55 -0
  225. bbot/test/test_step_2/module_tests/test_module_json.py +22 -9
  226. bbot/test/test_step_2/module_tests/test_module_leakix.py +7 -3
  227. bbot/test/test_step_2/module_tests/test_module_mysql.py +76 -0
  228. bbot/test/test_step_2/module_tests/test_module_myssl.py +1 -1
  229. bbot/test/test_step_2/module_tests/test_module_neo4j.py +1 -1
  230. bbot/test/test_step_2/module_tests/test_module_newsletters.py +6 -6
  231. bbot/test/test_step_2/module_tests/test_module_ntlm.py +7 -7
  232. bbot/test/test_step_2/module_tests/test_module_oauth.py +1 -1
  233. bbot/test/test_step_2/module_tests/test_module_otx.py +1 -1
  234. bbot/test/test_step_2/module_tests/test_module_paramminer_cookies.py +1 -2
  235. bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py +0 -6
  236. bbot/test/test_step_2/module_tests/test_module_paramminer_headers.py +2 -9
  237. bbot/test/test_step_2/module_tests/test_module_passivetotal.py +3 -1
  238. bbot/test/test_step_2/module_tests/test_module_portscan.py +9 -8
  239. bbot/test/test_step_2/module_tests/test_module_postgres.py +74 -0
  240. bbot/test/test_step_2/module_tests/test_module_postman.py +84 -253
  241. bbot/test/test_step_2/module_tests/test_module_postman_download.py +439 -0
  242. bbot/test/test_step_2/module_tests/test_module_rapiddns.py +93 -1
  243. bbot/test/test_step_2/module_tests/test_module_shodan_dns.py +20 -1
  244. bbot/test/test_step_2/module_tests/test_module_sitedossier.py +2 -2
  245. bbot/test/test_step_2/module_tests/test_module_smuggler.py +1 -1
  246. bbot/test/test_step_2/module_tests/test_module_social.py +11 -1
  247. bbot/test/test_step_2/module_tests/test_module_speculate.py +2 -6
  248. bbot/test/test_step_2/module_tests/test_module_splunk.py +4 -4
  249. bbot/test/test_step_2/module_tests/test_module_sqlite.py +18 -0
  250. bbot/test/test_step_2/module_tests/test_module_sslcert.py +1 -1
  251. bbot/test/test_step_2/module_tests/test_module_stdout.py +5 -3
  252. bbot/test/test_step_2/module_tests/test_module_subdomaincenter.py +1 -1
  253. bbot/test/test_step_2/module_tests/test_module_subdomainradar.py +208 -0
  254. bbot/test/test_step_2/module_tests/test_module_subdomains.py +1 -1
  255. bbot/test/test_step_2/module_tests/test_module_teams.py +8 -6
  256. bbot/test/test_step_2/module_tests/test_module_telerik.py +1 -1
  257. bbot/test/test_step_2/module_tests/test_module_trufflehog.py +317 -14
  258. bbot/test/test_step_2/module_tests/test_module_wayback.py +1 -1
  259. bbot/test/test_step_2/template_tests/test_template_subdomain_enum.py +2 -2
  260. {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/METADATA +48 -18
  261. bbot-2.3.0.5397rc0.dist-info/RECORD +421 -0
  262. {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/WHEEL +1 -1
  263. bbot/modules/unstructured.py +0 -163
  264. bbot/test/test_step_2/module_tests/test_module_unstructured.py +0 -102
  265. bbot-2.0.1.4720rc0.dist-info/RECORD +0 -387
  266. {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/LICENSE +0 -0
  267. {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5397rc0.dist-info}/entry_points.txt +0 -0
bbot/core/shared_deps.py CHANGED
@@ -79,13 +79,23 @@ DEP_CHROMIUM = [
79
79
  "ignore_errors": True,
80
80
  },
81
81
  {
82
- "name": "Install Chromium dependencies (Debian)",
82
+ "name": "Install Chromium dependencies (Ubuntu 24.04)",
83
83
  "package": {
84
- "name": "libasound2,libatk-bridge2.0-0,libatk1.0-0,libcairo2,libcups2,libdrm2,libgbm1,libnss3,libpango-1.0-0,libxcomposite1,libxdamage1,libxfixes3,libxkbcommon0,libxrandr2",
84
+ "name": "libasound2t64,libatk-bridge2.0-0,libatk1.0-0,libcairo2,libcups2,libdrm2,libgbm1,libnss3,libpango-1.0-0,libglib2.0-0,libxcomposite1,libxdamage1,libxfixes3,libxkbcommon0,libxrandr2",
85
85
  "state": "present",
86
86
  },
87
87
  "become": True,
88
- "when": "ansible_facts['os_family'] == 'Debian'",
88
+ "when": "ansible_facts['distribution'] == 'Ubuntu' and ansible_facts['distribution_version'] == '24.04'",
89
+ "ignore_errors": True,
90
+ },
91
+ {
92
+ "name": "Install Chromium dependencies (Other Debian-based)",
93
+ "package": {
94
+ "name": "libasound2,libatk-bridge2.0-0,libatk1.0-0,libcairo2,libcups2,libdrm2,libgbm1,libnss3,libpango-1.0-0,libglib2.0-0,libxcomposite1,libxdamage1,libxfixes3,libxkbcommon0,libxrandr2",
95
+ "state": "present",
96
+ },
97
+ "become": True,
98
+ "when": "ansible_facts['os_family'] == 'Debian' and not (ansible_facts['distribution'] == 'Ubuntu' and ansible_facts['distribution_version'] == '24.04')",
89
99
  "ignore_errors": True,
90
100
  },
91
101
  {
@@ -149,6 +159,39 @@ DEP_MASSCAN = [
149
159
  },
150
160
  ]
151
161
 
162
+ DEP_JAVA = [
163
+ {
164
+ "name": "Check if Java is installed",
165
+ "command": "which java",
166
+ "register": "java_installed",
167
+ "ignore_errors": True,
168
+ },
169
+ {
170
+ "name": "Install latest JRE (Debian)",
171
+ "package": {"name": ["default-jre"], "state": "present"},
172
+ "become": True,
173
+ "when": "ansible_facts['os_family'] == 'Debian' and java_installed.rc != 0",
174
+ },
175
+ {
176
+ "name": "Install latest JRE (Arch)",
177
+ "package": {"name": ["jre-openjdk"], "state": "present"},
178
+ "become": True,
179
+ "when": "ansible_facts['os_family'] == 'Archlinux' and java_installed.rc != 0",
180
+ },
181
+ {
182
+ "name": "Install latest JRE (Fedora)",
183
+ "package": {"name": ["which", "java-latest-openjdk-headless"], "state": "present"},
184
+ "become": True,
185
+ "when": "ansible_facts['os_family'] == 'RedHat' and java_installed.rc != 0",
186
+ },
187
+ {
188
+ "name": "Install latest JRE (Alpine)",
189
+ "package": {"name": ["openjdk11"], "state": "present"},
190
+ "become": True,
191
+ "when": "ansible_facts['os_family'] == 'Alpine' and java_installed.rc != 0",
192
+ },
193
+ ]
194
+
152
195
  # shared module dependencies -- ffuf, massdns, chromium, etc.
153
196
  SHARED_DEPS = {}
154
197
  for var, val in list(locals().items()):
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)
@@ -109,11 +112,18 @@ engine:
109
112
  deps:
110
113
  ffuf:
111
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
112
122
 
113
123
  ### ADVANCED OPTIONS ###
114
124
 
115
125
  # Load BBOT modules from these custom paths
116
- module_paths: []
126
+ module_dirs: []
117
127
 
118
128
  # Infer certain events from others, e.g. IPs from IP ranges, DNS_NAMEs from URLs, etc.
119
129
  speculate: True
@@ -126,14 +136,6 @@ dnsresolve: True
126
136
  # Cloud provider tagging
127
137
  cloudcheck: True
128
138
 
129
- # How to handle installation of module dependencies
130
- # Choices are:
131
- # - abort_on_failure (default) - if a module dependency fails to install, abort the scan
132
- # - retry_failed - try again to install failed dependencies
133
- # - ignore_failed - run the scan regardless of what happens with dependency installation
134
- # - disable - completely disable BBOT's dependency system (you are responsible for installing tools, pip packages, etc.)
135
- deps_behavior: abort_on_failure
136
-
137
139
  # Strip querystring from URLs by default
138
140
  url_querystring_remove: True
139
141
  # When query string is retained, by default collapse parameter values down to a single value per parameter
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