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
@@ -10,10 +10,11 @@ from pathlib import Path
10
10
  from threading import Lock
11
11
  from itertools import chain
12
12
  from contextlib import suppress
13
+ from secrets import token_bytes
13
14
  from ansible_runner.interface import run
14
15
  from subprocess import CalledProcessError
15
16
 
16
- from ..misc import can_sudo_without_password, os_platform
17
+ from ..misc import can_sudo_without_password, os_platform, rm_at_exit, get_python_constraints
17
18
 
18
19
  log = logging.getLogger("bbot.core.helpers.depsinstaller")
19
20
 
@@ -29,14 +30,13 @@ class DepsInstaller:
29
30
  http_timeout = self.web_config.get("http_timeout", 30)
30
31
  os.environ["ANSIBLE_TIMEOUT"] = str(http_timeout)
31
32
 
33
+ # cache encrypted sudo pass
32
34
  self.askpass_filename = "sudo_askpass.py"
35
+ self._sudo_password = None
36
+ self._sudo_cache_setup = False
37
+ self._setup_sudo_cache()
33
38
  self._installed_sudo_askpass = False
34
- self._sudo_password = os.environ.get("BBOT_SUDO_PASS", None)
35
- if self._sudo_password is None:
36
- if self.core.bbot_sudo_pass is not None:
37
- self._sudo_password = self.core.bbot_sudo_pass
38
- elif can_sudo_without_password():
39
- self._sudo_password = ""
39
+
40
40
  self.data_dir = self.parent_helper.cache_dir / "depsinstaller"
41
41
  self.parent_helper.mkdir(self.data_dir)
42
42
  self.setup_status_cache = self.data_dir / "setup_status.json"
@@ -44,7 +44,13 @@ class DepsInstaller:
44
44
  self.parent_helper.mkdir(self.command_status)
45
45
  self.setup_status = self.read_setup_status()
46
46
 
47
- self.deps_behavior = self.parent_helper.config.get("deps_behavior", "abort_on_failure").lower()
47
+ # make sure we're using a minimal git config
48
+ self.minimal_git_config = self.data_dir / "minimal_git.config"
49
+ self.minimal_git_config.touch()
50
+ os.environ["GIT_CONFIG_GLOBAL"] = str(self.minimal_git_config)
51
+
52
+ self.deps_config = self.parent_helper.config.get("deps", {})
53
+ self.deps_behavior = self.deps_config.get("behavior", "abort_on_failure").lower()
48
54
  self.ansible_debug = self.core.logger.log_level <= logging.DEBUG
49
55
  self.venv = ""
50
56
  if sys.prefix != sys.base_prefix:
@@ -91,11 +97,11 @@ class DepsInstaller:
91
97
  or self.deps_behavior == "force_install"
92
98
  ):
93
99
  if not notified:
94
- log.hugeinfo(f"Installing module dependencies. Please be patient, this may take a while.")
100
+ log.hugeinfo("Installing module dependencies. Please be patient, this may take a while.")
95
101
  notified = True
96
102
  log.verbose(f'Installing dependencies for module "{m}"')
97
103
  # get sudo access if we need it
98
- if preloaded.get("sudo", False) == True:
104
+ if preloaded.get("sudo", False) is True:
99
105
  self.ensure_root(f'Module "{m}" needs root privileges to install its dependencies.')
100
106
  success = await self.install_module(m)
101
107
  self.setup_status[module_hash] = success
@@ -153,7 +159,7 @@ class DepsInstaller:
153
159
  deps_common = preloaded["deps"]["common"]
154
160
  if deps_common:
155
161
  for dep_common in deps_common:
156
- if self.setup_status.get(dep_common, False) == True:
162
+ if self.setup_status.get(dep_common, False) is True:
157
163
  log.debug(
158
164
  f'Skipping installation of dependency "{dep_common}" for module "{module}" since it is already installed'
159
165
  )
@@ -171,10 +177,13 @@ class DepsInstaller:
171
177
 
172
178
  command = [sys.executable, "-m", "pip", "install", "--upgrade"] + packages
173
179
 
174
- if constraints:
175
- constraints_tempfile = self.parent_helper.tempfile(constraints, pipe=False)
176
- command.append("--constraint")
177
- command.append(constraints_tempfile)
180
+ # if no custom constraints are provided, use the constraints of the currently installed version of bbot
181
+ if constraints is not None:
182
+ constraints = get_python_constraints()
183
+
184
+ constraints_tempfile = self.parent_helper.tempfile(constraints, pipe=False)
185
+ command.append("--constraint")
186
+ command.append(constraints_tempfile)
178
187
 
179
188
  process = None
180
189
  try:
@@ -235,7 +244,7 @@ class DepsInstaller:
235
244
  if success:
236
245
  log.info(f"Successfully ran {len(commands):,} shell commands")
237
246
  else:
238
- log.warning(f"Failed to run shell dependencies")
247
+ log.warning("Failed to run shell dependencies")
239
248
  return success
240
249
 
241
250
  def tasks(self, module, tasks):
@@ -248,7 +257,7 @@ class DepsInstaller:
248
257
  return success
249
258
 
250
259
  def ansible_run(self, tasks=None, module=None, args=None, ansible_args=None):
251
- _ansible_args = {"ansible_connection": "local"}
260
+ _ansible_args = {"ansible_connection": "local", "ansible_python_interpreter": sys.executable}
252
261
  if ansible_args is not None:
253
262
  _ansible_args.update(ansible_args)
254
263
  module_args = None
@@ -301,7 +310,7 @@ class DepsInstaller:
301
310
  return success, err
302
311
 
303
312
  def read_setup_status(self):
304
- setup_status = dict()
313
+ setup_status = {}
305
314
  if self.setup_status_cache.is_file():
306
315
  with open(self.setup_status_cache) as f:
307
316
  with suppress(Exception):
@@ -314,20 +323,27 @@ class DepsInstaller:
314
323
 
315
324
  def ensure_root(self, message=""):
316
325
  self._install_sudo_askpass()
326
+ # skip if we've already done this
327
+ if self._sudo_password is not None:
328
+ return
317
329
  with self.ensure_root_lock:
318
- if os.geteuid() != 0 and self._sudo_password is None:
319
- if message:
320
- log.warning(message)
321
- while not self._sudo_password:
322
- # sleep for a split second to flush previous log messages
323
- sleep(0.1)
324
- password = getpass.getpass(prompt="[USER] Please enter sudo password: ")
325
- if self.parent_helper.verify_sudo_password(password):
326
- log.success("Authentication successful")
327
- self._sudo_password = password
328
- self.core.bbot_sudo_pass = password
329
- else:
330
- log.warning("Incorrect password")
330
+ # first check if the environment variable is set
331
+ _sudo_password = os.environ.get("BBOT_SUDO_PASS", None)
332
+ if _sudo_password is not None or os.geteuid() == 0 or can_sudo_without_password():
333
+ # if we're already root or we can sudo without a password, there's no need to prompt
334
+ return
335
+
336
+ if message:
337
+ log.warning(message)
338
+ while not self._sudo_password:
339
+ # sleep for a split second to flush previous log messages
340
+ sleep(0.1)
341
+ _sudo_password = getpass.getpass(prompt="[USER] Please enter sudo password: ")
342
+ if self.parent_helper.verify_sudo_password(_sudo_password):
343
+ log.success("Authentication successful")
344
+ self._sudo_password = _sudo_password
345
+ else:
346
+ log.warning("Incorrect password")
331
347
 
332
348
  def install_core_deps(self):
333
349
  to_install = set()
@@ -335,7 +351,16 @@ class DepsInstaller:
335
351
  # ensure tldextract data is cached
336
352
  self.parent_helper.tldextract("evilcorp.co.uk")
337
353
  # command: package_name
338
- core_deps = {"unzip": "unzip", "curl": "curl"}
354
+ core_deps = {
355
+ "unzip": "unzip",
356
+ "zipinfo": "unzip",
357
+ "curl": "curl",
358
+ "git": "git",
359
+ "make": "make",
360
+ "gcc": "gcc",
361
+ "bash": "bash",
362
+ "which": "which",
363
+ }
339
364
  for command, package_name in core_deps.items():
340
365
  if not self.parent_helper.which(command):
341
366
  to_install.add(package_name)
@@ -343,6 +368,38 @@ class DepsInstaller:
343
368
  self.ensure_root()
344
369
  self.apt_install(list(to_install))
345
370
 
371
+ def _setup_sudo_cache(self):
372
+ if not self._sudo_cache_setup:
373
+ self._sudo_cache_setup = True
374
+ # write temporary encryption key, to be deleted upon scan completion
375
+ self._sudo_temp_keyfile = self.parent_helper.temp_filename()
376
+ # remove it at exit
377
+ rm_at_exit(self._sudo_temp_keyfile)
378
+ # generate random 32-byte key
379
+ random_key = token_bytes(32)
380
+ # write key to file and set secure permissions
381
+ self._sudo_temp_keyfile.write_bytes(random_key)
382
+ self._sudo_temp_keyfile.chmod(0o600)
383
+ # export path to environment variable, for use in askpass script
384
+ os.environ["BBOT_SUDO_KEYFILE"] = str(self._sudo_temp_keyfile.resolve())
385
+
386
+ @property
387
+ def encrypted_sudo_pw(self):
388
+ if self._sudo_password is None:
389
+ return ""
390
+ return self._encrypt_sudo_pw(self._sudo_password)
391
+
392
+ def _encrypt_sudo_pw(self, pw):
393
+ from Crypto.Cipher import AES
394
+ from Crypto.Util.Padding import pad
395
+
396
+ key = self._sudo_temp_keyfile.read_bytes()
397
+ cipher = AES.new(key, AES.MODE_CBC)
398
+ ct_bytes = cipher.encrypt(pad(pw.encode(), AES.block_size))
399
+ iv = cipher.iv.hex()
400
+ ct = ct_bytes.hex()
401
+ return f"{iv}:{ct}"
402
+
346
403
  def _install_sudo_askpass(self):
347
404
  if not self._installed_sudo_askpass:
348
405
  self._installed_sudo_askpass = True
@@ -1,5 +1,41 @@
1
1
  #!/usr/bin/env python3
2
-
3
2
  import os
3
+ import sys
4
+ from pathlib import Path
5
+ from Crypto.Cipher import AES
6
+ from Crypto.Util.Padding import unpad
7
+
8
+ ENV_VAR_NAME = "BBOT_SUDO_PASS"
9
+ KEY_ENV_VAR_PATH = "BBOT_SUDO_KEYFILE"
10
+
11
+
12
+ def decrypt_password(encrypted_data, key):
13
+ iv, ciphertext = encrypted_data.split(":")
14
+ iv = bytes.fromhex(iv)
15
+ ct = bytes.fromhex(ciphertext)
16
+ cipher = AES.new(key, AES.MODE_CBC, iv)
17
+ pt = unpad(cipher.decrypt(ct), AES.block_size)
18
+ return pt.decode("utf-8")
19
+
20
+
21
+ def main():
22
+ encrypted_password = os.environ.get(ENV_VAR_NAME, "")
23
+ # remove variable from environment once we've got it
24
+ os.environ.pop(ENV_VAR_NAME, None)
25
+ encryption_keypath = Path(os.environ.get(KEY_ENV_VAR_PATH, ""))
26
+
27
+ if not encrypted_password or not encryption_keypath.is_file():
28
+ print("Error: Encrypted password or encryption key not found in environment variables.", file=sys.stderr)
29
+ sys.exit(1)
30
+
31
+ try:
32
+ key = encryption_keypath.read_bytes()
33
+ decrypted_password = decrypt_password(encrypted_password, key)
34
+ print(decrypted_password, end="")
35
+ except Exception as e:
36
+ print(f'Error decrypting password "{encrypted_password}": {str(e)}', file=sys.stderr)
37
+ sys.exit(1)
38
+
4
39
 
5
- print(os.environ.get("BBOT_SUDO_PASS", ""), end="")
40
+ if __name__ == "__main__":
41
+ main()
bbot/core/helpers/diff.py CHANGED
@@ -94,14 +94,14 @@ class HttpCompare:
94
94
  baseline_1_json = xmltodict.parse(baseline_1.text)
95
95
  baseline_2_json = xmltodict.parse(baseline_2.text)
96
96
  except ExpatError:
97
- log.debug(f"Cant HTML parse for {self.baseline_url}. Switching to text parsing as a backup")
97
+ log.debug(f"Can't HTML parse for {self.baseline_url}. Switching to text parsing as a backup")
98
98
  baseline_1_json = baseline_1.text.split("\n")
99
99
  baseline_2_json = baseline_2.text.split("\n")
100
100
 
101
101
  ddiff = DeepDiff(baseline_1_json, baseline_2_json, ignore_order=True, view="tree")
102
102
  self.ddiff_filters = []
103
103
 
104
- for k, v in ddiff.items():
104
+ for k in ddiff.keys():
105
105
  for x in list(ddiff[k]):
106
106
  log.debug(f"Added {k} filter for path: {x.path()}")
107
107
  self.ddiff_filters.append(x.path())
@@ -140,7 +140,7 @@ class HttpCompare:
140
140
 
141
141
  ddiff = DeepDiff(headers_1, headers_2, ignore_order=True, view="tree")
142
142
 
143
- for k, v in ddiff.items():
143
+ for k in ddiff.keys():
144
144
  for x in list(ddiff[k]):
145
145
  try:
146
146
  header_value = str(x).split("'")[1]
@@ -183,7 +183,7 @@ class HttpCompare:
183
183
 
184
184
  await self._baseline()
185
185
 
186
- if timeout == None:
186
+ if timeout is None:
187
187
  timeout = self.timeout
188
188
 
189
189
  reflection = False
@@ -203,7 +203,7 @@ class HttpCompare:
203
203
  )
204
204
 
205
205
  if subject_response is None:
206
- # this can be caused by a WAF not liking the header, so we really arent interested in it
206
+ # this can be caused by a WAF not liking the header, so we really aren't interested in it
207
207
  return (True, "403", reflection, subject_response)
208
208
 
209
209
  if check_reflection:
@@ -225,7 +225,7 @@ class HttpCompare:
225
225
  subject_json = xmltodict.parse(subject_response.text)
226
226
 
227
227
  except ExpatError:
228
- log.debug(f"Cant HTML parse for {subject.split('?')[0]}. Switching to text parsing as a backup")
228
+ log.debug(f"Can't HTML parse for {subject.split('?')[0]}. Switching to text parsing as a backup")
229
229
  subject_json = subject_response.text.split("\n")
230
230
 
231
231
  diff_reasons = []
@@ -238,11 +238,11 @@ class HttpCompare:
238
238
 
239
239
  different_headers = self.compare_headers(self.baseline.headers, subject_response.headers)
240
240
  if different_headers:
241
- log.debug(f"headers were different, no match")
241
+ log.debug("headers were different, no match")
242
242
  diff_reasons.append("header")
243
243
 
244
- if self.compare_body(self.baseline_json, subject_json) == False:
245
- log.debug(f"difference in HTML body, no match")
244
+ if self.compare_body(self.baseline_json, subject_json) is False:
245
+ log.debug("difference in HTML body, no match")
246
246
 
247
247
  diff_reasons.append("body")
248
248
 
@@ -275,6 +275,6 @@ class HttpCompare:
275
275
  )
276
276
 
277
277
  # if a nonsense header "caused" a difference, we need to abort. We also need to abort if our canary was reflected
278
- if match == False or reflection == True:
278
+ if match is False or reflection is True:
279
279
  return False
280
280
  return True
@@ -15,15 +15,17 @@ class DNSBrute:
15
15
  >>> results = await self.helpers.dns.brute(self, domain, subdomains)
16
16
  """
17
17
 
18
- nameservers_url = (
18
+ _nameservers_url = (
19
19
  "https://raw.githubusercontent.com/blacklanternsecurity/public-dns-servers/master/nameservers.txt"
20
20
  )
21
21
 
22
22
  def __init__(self, parent_helper):
23
23
  self.parent_helper = parent_helper
24
24
  self.log = logging.getLogger("bbot.helper.dns.brute")
25
+ self.dns_config = self.parent_helper.config.get("dns", {})
25
26
  self.num_canaries = 100
26
- self.max_resolvers = self.parent_helper.config.get("dns", {}).get("brute_threads", 1000)
27
+ self.max_resolvers = self.dns_config.get("brute_threads", 1000)
28
+ self.nameservers_url = self.dns_config.get("brute_nameservers", self._nameservers_url)
27
29
  self.devops_mutations = list(self.parent_helper.word_cloud.devops_mutations)
28
30
  self.digit_regex = self.parent_helper.re.compile(r"\d+")
29
31
  self._resolver_file = None
@@ -39,18 +41,15 @@ class DNSBrute:
39
41
  type = "A"
40
42
  type = str(type).strip().upper()
41
43
 
42
- domain_wildcard_rdtypes = set()
43
- for _domain, rdtypes in (await self.parent_helper.dns.is_wildcard_domain(domain)).items():
44
- for rdtype, results in rdtypes.items():
45
- if results:
46
- domain_wildcard_rdtypes.add(rdtype)
47
- if any([r in domain_wildcard_rdtypes for r in (type, "CNAME")]):
48
- self.log.info(
49
- f"Aborting massdns on {domain} because it's a wildcard domain ({','.join(domain_wildcard_rdtypes)})"
44
+ wildcard_domains = await self.parent_helper.dns.is_wildcard_domain(domain, (type, "CNAME"))
45
+ wildcard_rdtypes = set()
46
+ for domain, rdtypes in wildcard_domains.items():
47
+ wildcard_rdtypes.update(rdtypes)
48
+ if wildcard_domains:
49
+ self.log.hugewarning(
50
+ f"Aborting massdns on {domain} because it's a wildcard domain ({','.join(sorted(wildcard_rdtypes))})"
50
51
  )
51
52
  return []
52
- else:
53
- self.log.debug(f"{domain}: A is not in domain_wildcard_rdtypes:{domain_wildcard_rdtypes}")
54
53
 
55
54
  canaries = self.gen_random_subdomains(self.num_canaries)
56
55
  canaries_list = list(canaries)
@@ -148,10 +147,15 @@ class DNSBrute:
148
147
 
149
148
  async def resolver_file(self):
150
149
  if self._resolver_file is None:
151
- self._resolver_file = await self.parent_helper.wordlist(
150
+ self._resolver_file_original = await self.parent_helper.wordlist(
152
151
  self.nameservers_url,
153
152
  cache_hrs=24 * 7,
154
153
  )
154
+ nameservers = set(self.parent_helper.read_file(self._resolver_file_original))
155
+ nameservers.difference_update(self.parent_helper.dns.system_resolvers)
156
+ # exclude system nameservers from brute-force
157
+ # this helps prevent rate-limiting which might cause BBOT's main dns queries to fail
158
+ self._resolver_file = self.parent_helper.tempfile(nameservers, pipe=False)
155
159
  return self._resolver_file
156
160
 
157
161
  def gen_random_subdomains(self, n=50):
@@ -160,7 +164,7 @@ class DNSBrute:
160
164
  for i in range(0, max(0, n - 5)):
161
165
  d = delimiters[i % len(delimiters)]
162
166
  l = lengths[i % len(lengths)]
163
- segments = list(random.choice(self.devops_mutations) for _ in range(l))
167
+ segments = [random.choice(self.devops_mutations) for _ in range(l)]
164
168
  segments.append(self.parent_helper.rand_string(length=8, digits=False))
165
169
  subdomain = d.join(segments)
166
170
  yield subdomain
@@ -16,7 +16,6 @@ log = logging.getLogger("bbot.core.helpers.dns")
16
16
 
17
17
 
18
18
  class DNSHelper(EngineClient):
19
-
20
19
  SERVER_CLASS = DNSEngine
21
20
  ERROR_CLASS = DNSError
22
21
 
@@ -66,7 +65,7 @@ class DNSHelper(EngineClient):
66
65
  self.resolver.timeout = self.timeout
67
66
  self.resolver.lifetime = self.timeout
68
67
 
69
- self.runaway_limit = self.config.get("runaway_limit", 5)
68
+ self.runaway_limit = self.dns_config.get("runaway_limit", 5)
70
69
 
71
70
  # wildcard handling
72
71
  self.wildcard_disable = self.dns_config.get("wildcard_disable", False)
@@ -117,8 +116,11 @@ class DNSHelper(EngineClient):
117
116
  self._brute = DNSBrute(self.parent_helper)
118
117
  return self._brute
119
118
 
120
- @async_cachedmethod(lambda self: self._is_wildcard_cache)
121
- async def is_wildcard(self, query, ips=None, rdtype=None):
119
+ @async_cachedmethod(
120
+ lambda self: self._is_wildcard_cache,
121
+ key=lambda query, rdtypes, raw_dns_records: (query, tuple(sorted(rdtypes)), bool(raw_dns_records)),
122
+ )
123
+ async def is_wildcard(self, query, rdtypes, raw_dns_records=None):
122
124
  """
123
125
  Use this method to check whether a *host* is a wildcard entry
124
126
 
@@ -150,9 +152,6 @@ class DNSHelper(EngineClient):
150
152
  Note:
151
153
  `is_wildcard` can be True, False, or None (indicating that wildcard detection was inconclusive)
152
154
  """
153
- if [ips, rdtype].count(None) == 1:
154
- raise ValueError("Both ips and rdtype must be specified")
155
-
156
155
  query = self._wildcard_prevalidation(query)
157
156
  if not query:
158
157
  return {}
@@ -161,15 +160,17 @@ class DNSHelper(EngineClient):
161
160
  if is_domain(query):
162
161
  return {}
163
162
 
164
- return await self.run_and_return("is_wildcard", query=query, ips=ips, rdtype=rdtype)
163
+ return await self.run_and_return("is_wildcard", query=query, rdtypes=rdtypes, raw_dns_records=raw_dns_records)
165
164
 
166
- @async_cachedmethod(lambda self: self._is_wildcard_domain_cache)
167
- async def is_wildcard_domain(self, domain, log_info=False):
165
+ @async_cachedmethod(
166
+ lambda self: self._is_wildcard_domain_cache, key=lambda domain, rdtypes: (domain, tuple(sorted(rdtypes)))
167
+ )
168
+ async def is_wildcard_domain(self, domain, rdtypes):
168
169
  domain = self._wildcard_prevalidation(domain)
169
170
  if not domain:
170
171
  return {}
171
172
 
172
- return await self.run_and_return("is_wildcard_domain", domain=domain, log_info=False)
173
+ return await self.run_and_return("is_wildcard_domain", domain=domain, rdtypes=rdtypes)
173
174
 
174
175
  def _wildcard_prevalidation(self, host):
175
176
  if self.wildcard_disable:
@@ -177,7 +178,7 @@ class DNSHelper(EngineClient):
177
178
 
178
179
  host = clean_dns_record(host)
179
180
  # skip check if it's an IP or a plain hostname
180
- if is_ip(host) or not "." in host:
181
+ if is_ip(host) or "." not in host:
181
182
  return False
182
183
 
183
184
  # skip if query isn't a dns name
@@ -192,8 +193,8 @@ class DNSHelper(EngineClient):
192
193
 
193
194
  return host
194
195
 
195
- async def _mock_dns(self, mock_data):
196
+ async def _mock_dns(self, mock_data, custom_lookup_fn=None):
196
197
  from .mock import MockResolver
197
198
 
198
- self.resolver = MockResolver(mock_data)
199
- await self.run_and_return("_mock_dns", mock_data=mock_data)
199
+ self.resolver = MockResolver(mock_data, custom_lookup_fn=custom_lookup_fn)
200
+ await self.run_and_return("_mock_dns", mock_data=mock_data, custom_lookup_fn=custom_lookup_fn)