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
@@ -3,11 +3,11 @@ from contextlib import suppress
3
3
 
4
4
  from bbot.errors import ValidationError
5
5
  from bbot.core.helpers.dns.engine import all_rdtypes
6
- from bbot.modules.base import InterceptModule, BaseModule
7
6
  from bbot.core.helpers.dns.helpers import extract_targets
7
+ from bbot.modules.base import BaseInterceptModule, BaseModule
8
8
 
9
9
 
10
- class DNSResolve(InterceptModule):
10
+ class DNSResolve(BaseInterceptModule):
11
11
  watched_events = ["*"]
12
12
  _priority = 1
13
13
  scope_distance_modifier = None
@@ -16,12 +16,6 @@ class DNSResolve(InterceptModule):
16
16
  _name = "host"
17
17
  _type = "internal"
18
18
 
19
- def _outgoing_dedup_hash(self, event):
20
- # this exists to ensure a second, more interesting host isn't passed up
21
- # because its ugly cousin spent its one dedup token before it arrived
22
- # by removing those race conditions, this makes for more consistent results
23
- return hash((event, self.name, event.always_emit))
24
-
25
19
  @property
26
20
  def module_threads(self):
27
21
  return self.dns_config.get("threads", 25)
@@ -85,11 +79,21 @@ class DNSResolve(InterceptModule):
85
79
  await self.resolve_event(main_host_event, types=non_minimal_rdtypes)
86
80
  # check for wildcards if the event is within the scan's search distance
87
81
  if new_event and main_host_event.scope_distance <= self.scan.scope_search_distance:
88
- await self.handle_wildcard_event(main_host_event)
82
+ event_data_changed = await self.handle_wildcard_event(main_host_event)
83
+ if event_data_changed:
84
+ # since data has changed, we check again whether it's a duplicate
85
+ if self.scan.ingress_module.is_incoming_duplicate(event, add=True):
86
+ if not event._graph_important:
87
+ return False, "event was already emitted by its module"
88
+ else:
89
+ self.debug(
90
+ f"Event {event} was already emitted by its module, but it's graph-important so it gets a pass"
91
+ )
89
92
 
90
93
  # if there weren't any DNS children and it's not an IP address, tag as unresolved
91
94
  if not main_host_event.raw_dns_records and not event_is_ip:
92
95
  main_host_event.add_tag("unresolved")
96
+ main_host_event.type = "DNS_NAME_UNRESOLVED"
93
97
 
94
98
  # main_host_event.add_tag(f"resolve-distance-{main_host_event.dns_resolve_distance}")
95
99
 
@@ -109,10 +113,6 @@ class DNSResolve(InterceptModule):
109
113
  if not self.minimal:
110
114
  await self.emit_dns_children(main_host_event)
111
115
 
112
- # If the event is unresolved, change its type to DNS_NAME_UNRESOLVED
113
- if main_host_event.type == "DNS_NAME" and "unresolved" in main_host_event.tags:
114
- main_host_event.type = "DNS_NAME_UNRESOLVED"
115
-
116
116
  # emit the main DNS_NAME or IP_ADDRESS
117
117
  if (
118
118
  new_event
@@ -131,9 +131,9 @@ class DNSResolve(InterceptModule):
131
131
  event.host, rdtypes=rdtypes, raw_dns_records=event.raw_dns_records
132
132
  )
133
133
  for rdtype, (is_wildcard, wildcard_host) in wildcard_rdtypes.items():
134
- if is_wildcard == False:
134
+ if is_wildcard is False:
135
135
  continue
136
- elif is_wildcard == True:
136
+ elif is_wildcard is True:
137
137
  event.add_tag("wildcard")
138
138
  wildcard_tag = "wildcard"
139
139
  else:
@@ -142,16 +142,16 @@ class DNSResolve(InterceptModule):
142
142
  event.add_tag(f"{rdtype}-{wildcard_tag}")
143
143
 
144
144
  # wildcard event modification (www.evilcorp.com --> _wildcard.evilcorp.com)
145
- if wildcard_rdtypes and not "target" in event.tags:
145
+ if wildcard_rdtypes and "target" not in event.tags:
146
146
  # these are the rdtypes that have wildcards
147
147
  wildcard_rdtypes_set = set(wildcard_rdtypes)
148
148
  # consider the event a full wildcard if all its records are wildcards
149
149
  event_is_wildcard = False
150
150
  if wildcard_rdtypes_set:
151
- event_is_wildcard = all(r[0] == True for r in wildcard_rdtypes.values())
151
+ event_is_wildcard = all(r[0] is True for r in wildcard_rdtypes.values())
152
152
 
153
153
  if event_is_wildcard:
154
- if event.type in ("DNS_NAME",) and not "_wildcard" in event.data.split("."):
154
+ if event.type in ("DNS_NAME",) and "_wildcard" not in event.data.split("."):
155
155
  wildcard_parent = self.helpers.parent_domain(event.host)
156
156
  for rdtype, (_is_wildcard, _parent_domain) in wildcard_rdtypes.items():
157
157
  if _is_wildcard:
@@ -161,6 +161,8 @@ class DNSResolve(InterceptModule):
161
161
  if wildcard_data != event.data:
162
162
  self.debug(f'Wildcard detected, changing event.data "{event.data}" --> "{wildcard_data}"')
163
163
  event.data = wildcard_data
164
+ return True
165
+ return False
164
166
 
165
167
  async def emit_dns_children(self, event):
166
168
  for rdtype, children in event.dns_children.items():
@@ -271,7 +273,7 @@ class DNSResolve(InterceptModule):
271
273
  # tag event with errors
272
274
  for rdtype, errors in dns_errors.items():
273
275
  # only consider it an error if there weren't any results for that rdtype
274
- if errors and not rdtype in event.dns_children:
276
+ if errors and rdtype not in event.dns_children:
275
277
  event.add_tag(f"{rdtype}-error")
276
278
 
277
279
  def get_dns_parent(self, event):
@@ -305,7 +307,7 @@ class DNSResolve(InterceptModule):
305
307
  def emit_raw_records(self):
306
308
  if self._emit_raw_records is None:
307
309
  watching_raw_records = any(
308
- ["RAW_DNS_RECORD" in m.get_watched_events() for m in self.scan.modules.values()]
310
+ "RAW_DNS_RECORD" in m.get_watched_events() for m in self.scan.modules.values()
309
311
  )
310
312
  omitted_event_types = self.scan.config.get("omit_event_types", [])
311
313
  omit_raw_records = "RAW_DNS_RECORD" in omitted_event_types
@@ -6,6 +6,7 @@ import regex as re
6
6
  from pathlib import Path
7
7
  from bbot.errors import ExcavateError
8
8
  import bbot.core.helpers.regexes as bbot_regexes
9
+ from bbot.modules.base import BaseInterceptModule
9
10
  from bbot.modules.internal.base import BaseInternalModule
10
11
  from urllib.parse import urlparse, urljoin, parse_qs, urlunparse
11
12
 
@@ -61,7 +62,6 @@ def _exclude_key(original_dict, key_to_exclude):
61
62
 
62
63
 
63
64
  def extract_params_url(parsed_url):
64
-
65
65
  params = parse_qs(parsed_url.query)
66
66
  flat_params = {k: v[0] for k, v in params.items()}
67
67
 
@@ -93,7 +93,6 @@ def extract_params_location(location_header_value, original_parsed_url):
93
93
 
94
94
 
95
95
  class YaraRuleSettings:
96
-
97
96
  def __init__(self, description, tags, emit_match):
98
97
  self.description = description
99
98
  self.tags = tags
@@ -153,7 +152,9 @@ class ExcavateRule:
153
152
  yara_rule_settings = YaraRuleSettings(description, tags, emit_match)
154
153
  yara_results = {}
155
154
  for h in r.strings:
156
- yara_results[h.identifier.lstrip("$")] = sorted(set([i.matched_data.decode("utf-8") for i in h.instances]))
155
+ yara_results[h.identifier.lstrip("$")] = sorted(
156
+ {i.matched_data.decode("utf-8", errors="ignore") for i in h.instances}
157
+ )
157
158
  await self.process(yara_results, event, yara_rule_settings, discovery_context)
158
159
 
159
160
  async def process(self, yara_results, event, yara_rule_settings, discovery_context):
@@ -179,7 +180,7 @@ class ExcavateRule:
179
180
  Returns:
180
181
  None
181
182
  """
182
- for identifier, results in yara_results.items():
183
+ for results in yara_results.values():
183
184
  for result in results:
184
185
  event_data = {"description": f"{discovery_context} {yara_rule_settings.description}"}
185
186
  if yara_rule_settings.emit_match:
@@ -260,7 +261,6 @@ class ExcavateRule:
260
261
 
261
262
 
262
263
  class CustomExtractor(ExcavateRule):
263
-
264
264
  def __init__(self, excavate):
265
265
  super().__init__(excavate)
266
266
 
@@ -279,7 +279,7 @@ class CustomExtractor(ExcavateRule):
279
279
  await self.report(event_data, event, yara_rule_settings, discovery_context)
280
280
 
281
281
 
282
- class excavate(BaseInternalModule):
282
+ class excavate(BaseInternalModule, BaseInterceptModule):
283
283
  """
284
284
  Example (simple) Excavate Rules:
285
285
 
@@ -310,10 +310,11 @@ class excavate(BaseInternalModule):
310
310
  "custom_yara_rules": "Include custom Yara rules",
311
311
  }
312
312
  scope_distance_modifier = None
313
+ accept_dupes = False
313
314
 
314
315
  _module_threads = 8
315
316
 
316
- parameter_blacklist = set(
317
+ parameter_blacklist = {
317
318
  p.lower()
318
319
  for p in [
319
320
  "__VIEWSTATE",
@@ -328,7 +329,7 @@ class excavate(BaseInternalModule):
328
329
  "JSESSIONID",
329
330
  "PHPSESSID",
330
331
  ]
331
- )
332
+ }
332
333
 
333
334
  yara_rule_name_regex = re.compile(r"rule\s(\w+)\s{")
334
335
  yara_rule_regex = re.compile(r"(?s)((?:rule\s+\w+\s*{[^{}]*(?:{[^{}]*}[^{}]*)*[^{}]*(?:/\S*?}[^/]*?/)*)*})")
@@ -354,7 +355,6 @@ class excavate(BaseInternalModule):
354
355
  )
355
356
 
356
357
  class ParameterExtractor(ExcavateRule):
357
-
358
358
  yara_rules = {}
359
359
 
360
360
  class ParameterExtractorRule:
@@ -368,7 +368,6 @@ class excavate(BaseInternalModule):
368
368
  self.result = result
369
369
 
370
370
  class GetJquery(ParameterExtractorRule):
371
-
372
371
  name = "GET jquery"
373
372
  discovery_regex = r"/\$.get\([^\)].+\)/ nocase"
374
373
  extraction_regex = re.compile(r"\$.get\([\'\"](.+)[\'\"].+(\{.+\})\)")
@@ -389,8 +388,12 @@ class excavate(BaseInternalModule):
389
388
  for action, extracted_parameters in extracted_results:
390
389
  extracted_parameters_dict = self.convert_to_dict(extracted_parameters)
391
390
  for parameter_name, original_value in extracted_parameters_dict.items():
392
- yield self.output_type, parameter_name, original_value, action, _exclude_key(
393
- extracted_parameters_dict, parameter_name
391
+ yield (
392
+ self.output_type,
393
+ parameter_name,
394
+ original_value,
395
+ action,
396
+ _exclude_key(extracted_parameters_dict, parameter_name),
394
397
  )
395
398
 
396
399
  class PostJquery(GetJquery):
@@ -414,8 +417,12 @@ class excavate(BaseInternalModule):
414
417
  k: v[0] if isinstance(v, list) and len(v) == 1 else v for k, v in query_strings.items()
415
418
  }
416
419
  for parameter_name, original_value in query_strings_dict.items():
417
- yield self.output_type, parameter_name, original_value, url, _exclude_key(
418
- query_strings_dict, parameter_name
420
+ yield (
421
+ self.output_type,
422
+ parameter_name,
423
+ original_value,
424
+ url,
425
+ _exclude_key(query_strings_dict, parameter_name),
419
426
  )
420
427
 
421
428
  class GetForm(ParameterExtractorRule):
@@ -440,8 +447,12 @@ class excavate(BaseInternalModule):
440
447
  form_parameters[parameter_name] = original_value
441
448
 
442
449
  for parameter_name, original_value in form_parameters.items():
443
- yield self.output_type, parameter_name, original_value, form_action, _exclude_key(
444
- form_parameters, parameter_name
450
+ yield (
451
+ self.output_type,
452
+ parameter_name,
453
+ original_value,
454
+ form_action,
455
+ _exclude_key(form_parameters, parameter_name),
445
456
  )
446
457
 
447
458
  class PostForm(GetForm):
@@ -481,7 +492,6 @@ class excavate(BaseInternalModule):
481
492
  endpoint,
482
493
  additional_params,
483
494
  ) in extracted_params:
484
-
485
495
  self.excavate.debug(
486
496
  f"Found Parameter [{parameter_name}] in [{parameterExtractorSubModule.name}] ParameterExtractor Submodule"
487
497
  )
@@ -493,7 +503,6 @@ class excavate(BaseInternalModule):
493
503
  )
494
504
 
495
505
  if self.excavate.helpers.validate_parameter(parameter_name, parameter_type):
496
-
497
506
  if self.excavate.in_bl(parameter_name) == False:
498
507
  parsed_url = urlparse(url)
499
508
  description = f"HTTP Extracted Parameter [{parameter_name}] ({parameterExtractorSubModule.name} Submodule)"
@@ -523,13 +532,11 @@ class excavate(BaseInternalModule):
523
532
  async def process(self, yara_results, event, yara_rule_settings, discovery_context):
524
533
  for identifier in yara_results.keys():
525
534
  for csp_str in yara_results[identifier]:
526
- domains = await self.helpers.re.findall(bbot_regexes.dns_name_regex, csp_str)
527
- unique_domains = set(domains)
528
- for domain in unique_domains:
535
+ domains = await self.excavate.scan.extract_in_scope_hostnames(csp_str)
536
+ for domain in domains:
529
537
  await self.report(domain, event, yara_rule_settings, discovery_context, event_type="DNS_NAME")
530
538
 
531
539
  class EmailExtractor(ExcavateRule):
532
-
533
540
  yara_rules = {
534
541
  "email": 'rule email { meta: description = "contains email address" strings: $email = /[^\\W_][\\w\\-\\.\\+\']{0,100}@[a-zA-Z0-9\\-]{1,100}(\\.[a-zA-Z0-9\\-]{1,100})*\\.[a-zA-Z]{2,63}/ nocase fullword condition: $email }',
535
542
  }
@@ -548,7 +555,6 @@ class excavate(BaseInternalModule):
548
555
  }
549
556
 
550
557
  class ErrorExtractor(ExcavateRule):
551
-
552
558
  signatures = {
553
559
  "PHP_1": r"/\.php on line [0-9]+/",
554
560
  "PHP_2": r"/\.php<\/b> on line <b>[0-9]+/",
@@ -586,7 +592,6 @@ class excavate(BaseInternalModule):
586
592
  await self.report(event_data, event, yara_rule_settings, discovery_context, event_type="FINDING")
587
593
 
588
594
  class SerializationExtractor(ExcavateRule):
589
-
590
595
  regexes = {
591
596
  "Java": re.compile(r"[^a-zA-Z0-9\/+]rO0[a-zA-Z0-9+\/]+={0,2}"),
592
597
  "DOTNET": re.compile(r"[^a-zA-Z0-9\/+]AAEAAAD\/\/[a-zA-Z0-9\/+]+={0,2}"),
@@ -616,7 +621,6 @@ class excavate(BaseInternalModule):
616
621
  await self.report(event_data, event, yara_rule_settings, discovery_context, event_type="FINDING")
617
622
 
618
623
  class FunctionalityExtractor(ExcavateRule):
619
-
620
624
  yara_rules = {
621
625
  "File_Upload_Functionality": r'rule File_Upload_Functionality { meta: description = "contains file upload functionality" strings: $fileuploadfunc = /<input[^>]+type=["\']?file["\']?[^>]+>/ nocase condition: $fileuploadfunc }',
622
626
  "Web_Service_WSDL": r'rule Web_Service_WSDL { meta: emit_match = "True" description = "contains a web service WSDL URL" strings: $wsdl = /https?:\/\/[^\s]*\.(wsdl)/ nocase condition: $wsdl }',
@@ -630,7 +634,7 @@ class excavate(BaseInternalModule):
630
634
  scheme_blacklist = ["javascript", "mailto", "tel", "data", "vbscript", "about", "file"]
631
635
 
632
636
  async def process(self, yara_results, event, yara_rule_settings, discovery_context):
633
- for identifier, results in yara_results.items():
637
+ for results in yara_results.values():
634
638
  for url_str in results:
635
639
  scheme = url_str.split("://")[0]
636
640
  if scheme in self.scheme_blacklist:
@@ -669,15 +673,38 @@ class excavate(BaseInternalModule):
669
673
 
670
674
  class URLExtractor(ExcavateRule):
671
675
  yara_rules = {
672
- "url_full": r'rule url_full { meta: tags = "spider-danger" description = "contains full URL" strings: $url_full = /https?:\/\/([\w\.-]+)([:\/\w\.-]*)/ condition: $url_full }',
673
- "url_attr": r'rule url_attr { meta: tags = "spider-danger" description = "contains tag with src or href attribute" strings: $url_attr = /<[^>]+(href|src)=["\'][^"\']*["\'][^>]*>/ condition: $url_attr }',
676
+ "url_full": (
677
+ r"""
678
+ rule url_full {
679
+ meta:
680
+ tags = "spider-danger"
681
+ description = "contains full URL"
682
+ strings:
683
+ $url_full = /https?:\/\/([\w\.-]+)(:\d{1,5})?([\/\w\.-]*)/
684
+ condition:
685
+ $url_full
686
+ }
687
+ """
688
+ ),
689
+ "url_attr": (
690
+ r"""
691
+ rule url_attr {
692
+ meta:
693
+ tags = "spider-danger"
694
+ description = "contains tag with src or href attribute"
695
+ strings:
696
+ $url_attr = /<[^>]+(href|src)=["\'][^"\']*["\'][^>]*>/
697
+ condition:
698
+ $url_attr
699
+ }
700
+ """
701
+ ),
674
702
  }
675
703
  full_url_regex = re.compile(r"(https?)://((?:\w|\d)(?:[\d\w-]+\.?)+(?::\d{1,5})?(?:/[-\w\.\(\)]*[-\w\.]+)*/?)")
676
704
  full_url_regex_strict = re.compile(r"^(https?):\/\/([\w.-]+)(?::\d{1,5})?(\/[\w\/\.-]*)?(\?[^\s]+)?$")
677
705
  tag_attribute_regex = bbot_regexes.tag_attribute_regex
678
706
 
679
707
  async def process(self, yara_results, event, yara_rule_settings, discovery_context):
680
-
681
708
  for identifier, results in yara_results.items():
682
709
  urls_found = 0
683
710
  final_url = ""
@@ -741,20 +768,33 @@ class excavate(BaseInternalModule):
741
768
 
742
769
  def __init__(self, excavate):
743
770
  super().__init__(excavate)
744
- regexes_component_list = []
745
- if excavate.scan.dns_regexes_yara:
746
- for i, r in enumerate(excavate.scan.dns_regexes_yara):
747
- regexes_component_list.append(rf"$dns_name_{i} = /\b{r.pattern}/ nocase")
748
- regexes_component = " ".join(regexes_component_list)
749
- self.yara_rules[f"hostname_extraction"] = (
750
- f'rule hostname_extraction {{meta: description = "matches DNS hostname pattern derived from target(s)" strings: {regexes_component} condition: any of them}}'
751
- )
771
+ if excavate.scan.dns_yara_rules_uncompiled:
772
+ self.yara_rules[f"hostname_extraction"] = excavate.scan.dns_yara_rules_uncompiled
752
773
 
753
774
  async def process(self, yara_results, event, yara_rule_settings, discovery_context):
754
775
  for identifier in yara_results.keys():
755
776
  for domain_str in yara_results[identifier]:
756
777
  await self.report(domain_str, event, yara_rule_settings, discovery_context, event_type="DNS_NAME")
757
778
 
779
+ class LoginPageExtractor(ExcavateRule):
780
+ yara_rules = {
781
+ "login_page": r"""
782
+ rule login_page {
783
+ meta:
784
+ description = "Detects login pages with username and password fields"
785
+ strings:
786
+ $username_field = /<input[^>]+name=["']?(user|login|email)/ nocase
787
+ $password_field = /<input[^>]+name=["']?passw?/ nocase
788
+ condition:
789
+ $username_field and $password_field
790
+ }
791
+ """
792
+ }
793
+
794
+ async def process(self, yara_results, event, yara_rule_settings, discovery_context):
795
+ if yara_results:
796
+ event.add_tag("login-page")
797
+
758
798
  def add_yara_rule(self, rule_name, rule_content, rule_instance):
759
799
  rule_instance.name = rule_name
760
800
  self.yara_rules_dict[rule_name] = rule_content
@@ -805,9 +845,9 @@ class excavate(BaseInternalModule):
805
845
  if Path(self.custom_yara_rules).is_file():
806
846
  with open(self.custom_yara_rules) as f:
807
847
  rules_content = f.read()
808
- self.debug(f"Successfully loaded secrets file [{self.custom_yara_rules}]")
848
+ self.debug(f"Successfully loaded custom yara rules file [{self.custom_yara_rules}]")
809
849
  else:
810
- self.debug(f"Custom secrets is NOT a file. Will attempt to treat it as rule content")
850
+ self.debug(f"Custom yara rules file is NOT a file. Will attempt to treat it as rule content")
811
851
  rules_content = self.custom_yara_rules
812
852
 
813
853
  self.debug(f"Final combined yara rule contents: {rules_content}")
@@ -816,13 +856,11 @@ class excavate(BaseInternalModule):
816
856
  try:
817
857
  yara.compile(source=rule_content)
818
858
  except yara.SyntaxError as e:
819
- self.hugewarning(f"Custom Yara rule failed to compile: {e}")
820
- return False
859
+ return False, f"Custom Yara rule failed to compile: {e}"
821
860
 
822
861
  rule_match = await self.helpers.re.search(self.yara_rule_name_regex, rule_content)
823
862
  if not rule_match:
824
- self.hugewarning(f"Custom Yara formatted incorrectly: could not find rule name")
825
- return False
863
+ return False, f"Custom Yara formatted incorrectly: could not find rule name"
826
864
 
827
865
  rule_name = rule_match.groups(1)[0]
828
866
  c = CustomExtractor(self)
@@ -836,11 +874,13 @@ class excavate(BaseInternalModule):
836
874
  yara.set_config(max_match_data=yara_max_match_data)
837
875
  yara_rules_combined = "\n".join(self.yara_rules_dict.values())
838
876
  try:
877
+ self.info(f"Compiling {len(self.yara_rules_dict):,} YARA rules")
878
+ for rule_name, rule_content in self.yara_rules_dict.items():
879
+ self.debug(f" - {rule_name}")
839
880
  self.yara_rules = yara.compile(source=yara_rules_combined)
840
881
  except yara.SyntaxError as e:
841
- self.hugewarning(f"Yara Rules failed to compile with error: [{e}]")
842
882
  self.debug(yara_rules_combined)
843
- return False
883
+ return False, f"Yara Rules failed to compile with error: [{e}]"
844
884
 
845
885
  # pre-load valid URL schemes
846
886
  valid_schemes_filename = self.helpers.wordlist_dir / "valid_url_schemes.txt"
@@ -857,7 +897,6 @@ class excavate(BaseInternalModule):
857
897
  decoded_data = await self.helpers.re.recursive_decode(data)
858
898
 
859
899
  if self.parameter_extraction:
860
-
861
900
  content_type_lower = content_type.lower() if content_type else ""
862
901
  extraction_map = {
863
902
  "json": self.helpers.extract_params_json,
@@ -894,7 +933,6 @@ class excavate(BaseInternalModule):
894
933
  self.hugewarning(f"YARA Rule {rule_name} not found in pre-compiled rules")
895
934
 
896
935
  async def handle_event(self, event):
897
-
898
936
  if event.type == "HTTP_RESPONSE":
899
937
  # Harvest GET parameters from URL, if it came directly from the target, and parameter extraction is enabled
900
938
  if (
@@ -983,7 +1021,6 @@ class excavate(BaseInternalModule):
983
1021
 
984
1022
  # Try to extract parameters from the redirect URL
985
1023
  if self.parameter_extraction:
986
-
987
1024
  for (
988
1025
  method,
989
1026
  parsed_url,
@@ -32,10 +32,11 @@ class speculate(BaseInternalModule):
32
32
  "author": "@liquidsec",
33
33
  }
34
34
 
35
- options = {"max_hosts": 65536, "ports": "80,443"}
35
+ options = {"max_hosts": 65536, "ports": "80,443", "essential_only": False}
36
36
  options_desc = {
37
37
  "max_hosts": "Max number of IP_RANGE hosts to convert into IP_ADDRESS events",
38
38
  "ports": "The set of ports to speculate on",
39
+ "essential_only": "Only enable essential speculate features (no extra discovery)",
39
40
  }
40
41
  scope_distance_modifier = 1
41
42
  _priority = 4
@@ -44,14 +45,15 @@ class speculate(BaseInternalModule):
44
45
 
45
46
  async def setup(self):
46
47
  scan_modules = [m for m in self.scan.modules.values() if m._type == "scan"]
47
- self.open_port_consumers = any(["OPEN_TCP_PORT" in m.watched_events for m in scan_modules])
48
+ self.open_port_consumers = any("OPEN_TCP_PORT" in m.watched_events for m in scan_modules)
48
49
  # only consider active portscanners (still speculate if only passive ones are enabled)
49
50
  self.portscanner_enabled = any(
50
- ["portscan" in m.flags and "active" in m.flags for m in self.scan.modules.values()]
51
+ "portscan" in m.flags and "active" in m.flags for m in self.scan.modules.values()
51
52
  )
52
53
  self.emit_open_ports = self.open_port_consumers and not self.portscanner_enabled
53
54
  self.range_to_ip = True
54
55
  self.dns_disable = self.scan.config.get("dns", {}).get("disable", False)
56
+ self.essential_only = self.config.get("essential_only", False)
55
57
  self.org_stubs_seen = set()
56
58
 
57
59
  port_string = self.config.get("ports", "80,443")
@@ -63,18 +65,26 @@ class speculate(BaseInternalModule):
63
65
  if not self.portscanner_enabled:
64
66
  self.info(f"No portscanner enabled. Assuming open ports: {', '.join(str(x) for x in self.ports)}")
65
67
 
66
- target_len = len(self.scan.target)
68
+ target_len = len(self.scan.target.seeds)
67
69
  if target_len > self.config.get("max_hosts", 65536):
68
70
  if not self.portscanner_enabled:
69
71
  self.hugewarning(
70
72
  f"Selected target ({target_len:,} hosts) is too large, skipping IP_RANGE --> IP_ADDRESS speculation"
71
73
  )
72
- self.hugewarning(f'Enabling the "portscan" module is highly recommended')
74
+ self.hugewarning('Enabling the "portscan" module is highly recommended')
73
75
  self.range_to_ip = False
74
76
 
75
77
  return True
76
78
 
77
79
  async def handle_event(self, event):
80
+ ### BEGIN ESSENTIAL SPECULATION ###
81
+ # These features are required for smooth operation of bbot
82
+ # I.e. they are not "osinty" or intended to discover anything, they only compliment other modules
83
+
84
+ # we speculate on distance-1 stuff too, because distance-1 open ports are needed by certain modules like sslcert
85
+ event_in_scope_distance = event.scope_distance <= (self.scan.scope_search_distance + 1)
86
+ speculate_open_ports = self.emit_open_ports and event_in_scope_distance
87
+
78
88
  # generate individual IP addresses from IP range
79
89
  if event.type == "IP_RANGE" and self.range_to_ip:
80
90
  net = ipaddress.ip_network(event.data)
@@ -89,28 +99,46 @@ class speculate(BaseInternalModule):
89
99
  context=f"speculate converted range into individual IP_ADDRESS: {ip}",
90
100
  )
91
101
 
102
+ # IP_ADDRESS / DNS_NAME --> OPEN_TCP_PORT
103
+ if speculate_open_ports:
104
+ # don't act on unresolved DNS_NAMEs
105
+ usable_dns = False
106
+ if event.type == "DNS_NAME":
107
+ if self.dns_disable or ("a-record" in event.tags or "aaaa-record" in event.tags):
108
+ usable_dns = True
109
+
110
+ if event.type == "IP_ADDRESS" or usable_dns:
111
+ for port in self.ports:
112
+ await self.emit_event(
113
+ self.helpers.make_netloc(event.data, port),
114
+ "OPEN_TCP_PORT",
115
+ parent=event,
116
+ internal=True,
117
+ context="speculated {event.type}: {event.data}",
118
+ )
119
+
120
+ ### END ESSENTIAL SPECULATION ###
121
+ if self.essential_only:
122
+ return
123
+
92
124
  # parent domains
93
125
  if event.type.startswith("DNS_NAME"):
94
126
  parent = self.helpers.parent_domain(event.host_original)
95
127
  if parent != event.data:
96
128
  await self.emit_event(
97
- parent, "DNS_NAME", parent=event, context=f"speculated parent {{event.type}}: {{event.data}}"
129
+ parent, "DNS_NAME", parent=event, context="speculated parent {event.type}: {event.data}"
98
130
  )
99
131
 
100
- # we speculate on distance-1 stuff too, because distance-1 open ports are needed by certain modules like sslcert
101
- event_in_scope_distance = event.scope_distance <= (self.scan.scope_search_distance + 1)
102
- speculate_open_ports = self.emit_open_ports and event_in_scope_distance
103
-
104
132
  # URL --> OPEN_TCP_PORT
105
- if event.type == "URL" or (event.type == "URL_UNVERIFIED" and self.open_port_consumers):
133
+ event_is_url = event.type == "URL"
134
+ if event_is_url or (event.type == "URL_UNVERIFIED" and self.open_port_consumers):
106
135
  # only speculate port from a URL if it wouldn't be speculated naturally from the host
107
136
  if event.host and (event.port not in self.ports or not speculate_open_ports):
108
137
  await self.emit_event(
109
138
  self.helpers.make_netloc(event.host, event.port),
110
139
  "OPEN_TCP_PORT",
111
140
  parent=event,
112
- internal=True,
113
- quick=(event.type == "URL"),
141
+ internal=not event_is_url, # if the URL is verified, the port is definitely open
114
142
  context=f"speculated {{event.type}} from {event.type}: {{event.data}}",
115
143
  )
116
144
 
@@ -144,25 +172,6 @@ class speculate(BaseInternalModule):
144
172
  context="speculated {event.type}: {event.data}",
145
173
  )
146
174
 
147
- # IP_ADDRESS / DNS_NAME --> OPEN_TCP_PORT
148
- if speculate_open_ports:
149
- # don't act on unresolved DNS_NAMEs
150
- usable_dns = False
151
- if event.type == "DNS_NAME":
152
- if self.dns_disable or ("a-record" in event.tags or "aaaa-record" in event.tags):
153
- usable_dns = True
154
-
155
- if event.type == "IP_ADDRESS" or usable_dns:
156
- for port in self.ports:
157
- await self.emit_event(
158
- self.helpers.make_netloc(event.data, port),
159
- "OPEN_TCP_PORT",
160
- parent=event,
161
- internal=True,
162
- quick=True,
163
- context="speculated {event.type}: {event.data}",
164
- )
165
-
166
175
  # ORG_STUB from TLD, SOCIAL, AZURE_TENANT
167
176
  org_stubs = set()
168
177
  if event.type == "DNS_NAME" and event.scope_distance == 0:
@@ -48,6 +48,9 @@ class internetdb(BaseModule):
48
48
  "show_open_ports": "Display OPEN_TCP_PORT events in output, even if they didn't lead to an interesting discovery"
49
49
  }
50
50
 
51
+ # we get lots of 404s, that's normal
52
+ _api_failure_abort_threshold = 9999999999
53
+
51
54
  _qsize = 500
52
55
 
53
56
  base_url = "https://internetdb.shodan.io"
@@ -64,7 +67,7 @@ class internetdb(BaseModule):
64
67
  if ip is None:
65
68
  return
66
69
  url = f"{self.base_url}/{ip}"
67
- r = await self.request_with_fail_count(url)
70
+ r = await self.api_request(url)
68
71
  if r is None:
69
72
  self.debug(f"No response for {event.data}")
70
73
  return
@@ -113,7 +116,6 @@ class internetdb(BaseModule):
113
116
  "OPEN_TCP_PORT",
114
117
  parent=event,
115
118
  internal=(not self.show_open_ports),
116
- quick=True,
117
119
  context=f'{{module}} queried Shodan\'s InternetDB API for "{query_host}" and found {{event.type}}: {{event.data}}',
118
120
  )
119
121
  vulns = data.get("vulns", [])
@@ -32,12 +32,10 @@ class IP2Location(BaseModule):
32
32
 
33
33
  async def ping(self):
34
34
  url = self.build_url("8.8.8.8")
35
- r = await self.request_with_fail_count(url)
36
- resp_content = getattr(r, "text", "")
37
- assert getattr(r, "status_code", 0) == 200, resp_content
35
+ await super().ping(url)
38
36
 
39
37
  def build_url(self, data):
40
- url = f"{self.base_url}/?key={self.api_key}&ip={data}&format=json&source=bbot"
38
+ url = f"{self.base_url}/?key={{api_key}}&ip={data}&format=json&source=bbot"
41
39
  if self.lang:
42
40
  url = f"{url}&lang={self.lang}"
43
41
  return url
@@ -45,7 +43,7 @@ class IP2Location(BaseModule):
45
43
  async def handle_event(self, event):
46
44
  try:
47
45
  url = self.build_url(event.data)
48
- result = await self.request_with_fail_count(url)
46
+ result = await self.api_request(url)
49
47
  if result:
50
48
  geo_data = result.json()
51
49
  if not geo_data:
@@ -31,7 +31,7 @@ class ipneighbor(BaseModule):
31
31
  netmask = main_ip.max_prefixlen - min(main_ip.max_prefixlen, self.num_bits)
32
32
  network = ipaddress.ip_network(f"{main_ip}/{netmask}", strict=False)
33
33
  subnet_hash = hash(network)
34
- if not subnet_hash in self.processed:
34
+ if subnet_hash not in self.processed:
35
35
  self.processed.add(subnet_hash)
36
36
  for ip in network:
37
37
  if ip != main_ip: