bbot 2.0.1.4720rc0__py3-none-any.whl → 2.3.0.5401rc0__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 (278) 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 +12 -10
  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 +20 -21
  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 +29 -12
  86. bbot/modules/internal/dnsresolve.py +22 -22
  87. bbot/modules/internal/excavate.py +97 -59
  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 +8 -11
  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 +18 -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 -2
  146. bbot/presets/spider.yml +4 -0
  147. bbot/presets/subdomain-enum.yml +7 -7
  148. bbot/presets/web/dotnet-audit.yml +0 -1
  149. bbot/scanner/manager.py +5 -16
  150. bbot/scanner/preset/args.py +46 -26
  151. bbot/scanner/preset/environ.py +7 -2
  152. bbot/scanner/preset/path.py +7 -4
  153. bbot/scanner/preset/preset.py +36 -23
  154. bbot/scanner/scanner.py +172 -62
  155. bbot/scanner/target.py +236 -434
  156. bbot/scripts/docs.py +1 -1
  157. bbot/test/bbot_fixtures.py +13 -3
  158. bbot/test/conftest.py +132 -100
  159. bbot/test/fastapi_test.py +17 -0
  160. bbot/test/owasp_mastg.apk +0 -0
  161. bbot/test/run_tests.sh +4 -4
  162. bbot/test/test.conf +2 -0
  163. bbot/test/test_step_1/test__module__tests.py +0 -1
  164. bbot/test/test_step_1/test_bbot_fastapi.py +79 -0
  165. bbot/test/test_step_1/test_bloom_filter.py +2 -1
  166. bbot/test/test_step_1/test_cli.py +138 -64
  167. bbot/test/test_step_1/test_dns.py +61 -27
  168. bbot/test/test_step_1/test_engine.py +17 -19
  169. bbot/test/test_step_1/test_events.py +183 -30
  170. bbot/test/test_step_1/test_helpers.py +64 -29
  171. bbot/test/test_step_1/test_manager_deduplication.py +1 -1
  172. bbot/test/test_step_1/test_manager_scope_accuracy.py +333 -330
  173. bbot/test/test_step_1/test_modules_basic.py +68 -70
  174. bbot/test/test_step_1/test_presets.py +183 -100
  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 +4 -3
  179. bbot/test/test_step_1/test_target.py +242 -145
  180. bbot/test/test_step_1/test_web.py +14 -10
  181. bbot/test/test_step_2/module_tests/base.py +15 -7
  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 +28 -48
  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 -6
  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 +22 -9
  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 +16 -16
  233. bbot/test/test_step_2/module_tests/test_module_ntlm.py +8 -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_pgp.py +2 -2
  241. bbot/test/test_step_2/module_tests/test_module_portscan.py +9 -8
  242. bbot/test/test_step_2/module_tests/test_module_postgres.py +74 -0
  243. bbot/test/test_step_2/module_tests/test_module_postman.py +84 -253
  244. bbot/test/test_step_2/module_tests/test_module_postman_download.py +439 -0
  245. bbot/test/test_step_2/module_tests/test_module_rapiddns.py +93 -1
  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 +14 -14
  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 +4 -8
  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 -14
  261. bbot/test/test_step_2/module_tests/test_module_viewdns.py +1 -1
  262. bbot/test/test_step_2/module_tests/test_module_wayback.py +1 -1
  263. bbot/test/test_step_2/template_tests/test_template_subdomain_enum.py +2 -2
  264. bbot/wordlists/devops_mutations.txt +1 -1
  265. bbot/wordlists/ffuf_shortname_candidates.txt +1 -1
  266. bbot/wordlists/nameservers.txt +1 -1
  267. bbot/wordlists/paramminer_headers.txt +1 -1
  268. bbot/wordlists/paramminer_parameters.txt +1 -1
  269. bbot/wordlists/raft-small-extensions-lowercase_CLEANED.txt +1 -1
  270. bbot/wordlists/valid_url_schemes.txt +1 -1
  271. {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/METADATA +48 -18
  272. bbot-2.3.0.5401rc0.dist-info/RECORD +421 -0
  273. {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/WHEEL +1 -1
  274. bbot/modules/unstructured.py +0 -163
  275. bbot/test/test_step_2/module_tests/test_module_unstructured.py +0 -102
  276. bbot-2.0.1.4720rc0.dist-info/RECORD +0 -387
  277. {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/LICENSE +0 -0
  278. {bbot-2.0.1.4720rc0.dist-info → bbot-2.3.0.5401rc0.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,6 @@ from ..bbot_fixtures import *
6
6
 
7
7
  @pytest.mark.asyncio
8
8
  async def test_web_engine(bbot_scanner, bbot_httpserver, httpx_mock):
9
-
10
9
  from werkzeug.wrappers import Response
11
10
 
12
11
  def server_handler(request):
@@ -29,7 +28,7 @@ async def test_web_engine(bbot_scanner, bbot_httpserver, httpx_mock):
29
28
  urls = [f"{base_url}{i}" for i in range(num_urls)]
30
29
  responses = [r async for r in scan.helpers.request_batch(urls)]
31
30
  assert len(responses) == 100
32
- assert all([r[1].status_code == 200 and r[1].text.startswith(f"{r[0]}: ") for r in responses])
31
+ assert all(r[1].status_code == 200 and r[1].text.startswith(f"{r[0]}: ") for r in responses)
33
32
 
34
33
  # request_batch w/ cancellation
35
34
  agen = scan.helpers.request_batch(urls)
@@ -134,7 +133,6 @@ async def test_request_batch_cancellation(bbot_scanner, bbot_httpserver, httpx_m
134
133
 
135
134
  @pytest.mark.asyncio
136
135
  async def test_web_helpers(bbot_scanner, bbot_httpserver, httpx_mock):
137
-
138
136
  # json conversion
139
137
  scan = bbot_scanner("evilcorp.com")
140
138
  url = "http://www.evilcorp.com/json_test?a=b"
@@ -153,9 +151,12 @@ async def test_web_helpers(bbot_scanner, bbot_httpserver, httpx_mock):
153
151
 
154
152
  await scan._cleanup()
155
153
 
156
- scan1 = bbot_scanner("8.8.8.8")
154
+ scan1 = bbot_scanner("8.8.8.8", modules=["ipneighbor"])
157
155
  scan2 = bbot_scanner("127.0.0.1")
158
156
 
157
+ await scan1._prep()
158
+ module = scan1.modules["ipneighbor"]
159
+
159
160
  web_config = CORE.config.get("web", {})
160
161
  user_agent = web_config.get("user_agent", "")
161
162
  headers = {"User-Agent": user_agent}
@@ -208,14 +209,14 @@ async def test_web_helpers(bbot_scanner, bbot_httpserver, httpx_mock):
208
209
  url = bbot_httpserver.url_for(path)
209
210
  bbot_httpserver.expect_request(uri=path).respond_with_data(download_content, status=200)
210
211
  webpage = await scan1.helpers.request(url)
211
- assert webpage, f"Webpage is False"
212
+ assert webpage, "Webpage is False"
212
213
  soup = scan1.helpers.beautifulsoup(webpage, "html.parser")
213
- assert soup, f"Soup is False"
214
+ assert soup, "Soup is False"
214
215
  # pretty_print = soup.prettify()
215
216
  # assert pretty_print, f"PrettyPrint is False"
216
217
  # scan1.helpers.log.info(f"{pretty_print}")
217
218
  html_text = soup.find(text="Example Domain")
218
- assert html_text, f"Find HTML Text is False"
219
+ assert html_text, "Find HTML Text is False"
219
220
 
220
221
  # 404
221
222
  path = "/test_http_helpers_download_404"
@@ -252,7 +253,7 @@ async def test_web_helpers(bbot_scanner, bbot_httpserver, httpx_mock):
252
253
  uri=f"{base_path}/3", query_string={"page_size": "100", "offset": "200"}
253
254
  ).respond_with_data("page3")
254
255
  results = []
255
- agen = scan1.helpers.api_page_iter(template_url)
256
+ agen = module.api_page_iter(template_url)
256
257
  try:
257
258
  async for result in agen:
258
259
  if result and result.text.startswith("page"):
@@ -262,7 +263,7 @@ async def test_web_helpers(bbot_scanner, bbot_httpserver, httpx_mock):
262
263
  finally:
263
264
  await agen.aclose()
264
265
  assert not results
265
- agen = scan1.helpers.api_page_iter(template_url, json=False)
266
+ agen = module.api_page_iter(template_url, json=False)
266
267
  try:
267
268
  async for result in agen:
268
269
  if result and result.text.startswith("page"):
@@ -386,7 +387,7 @@ async def test_web_http_compare(httpx_mock, bbot_scanner):
386
387
  await compare_helper.compare("http://www.example.com", check_reflection=True)
387
388
  compare_helper.compare_body({"asdf": "fdsa"}, {"fdsa": "asdf"})
388
389
  for mode in ("getparam", "header", "cookie"):
389
- assert await compare_helper.canary_check("http://www.example.com", mode=mode) == True
390
+ assert await compare_helper.canary_check("http://www.example.com", mode=mode) is True
390
391
 
391
392
  await scan._cleanup()
392
393
 
@@ -468,6 +469,9 @@ async def test_web_cookies(bbot_scanner, httpx_mock):
468
469
  # but that they're not sent in the response
469
470
  with pytest.raises(httpx.TimeoutException):
470
471
  r = await client2.get(url="http://www2.evilcorp.com/cookies/test")
472
+ # make sure cookies are sent
473
+ r = await client2.get(url="http://www2.evilcorp.com/cookies/test", cookies={"wats": "fdsa"})
474
+ assert r.status_code == 200
471
475
  # make sure we can manually send cookies
472
476
  httpx_mock.add_response(url="http://www2.evilcorp.com/cookies/test2", match_headers={"Cookie": "fdsa=wats"})
473
477
  r = await client2.get(url="http://www2.evilcorp.com/cookies/test2", cookies={"fdsa": "wats"})
@@ -20,6 +20,8 @@ class ModuleTestBase:
20
20
  config_overrides = {}
21
21
  modules_overrides = None
22
22
  log = logging.getLogger("bbot")
23
+ # if True, the test will be skipped (useful for tests that require docker)
24
+ skip_distro_tests = False
23
25
 
24
26
  class ModuleTest:
25
27
  def __init__(
@@ -89,33 +91,39 @@ class ModuleTestBase:
89
91
  async def module_test(
90
92
  self, httpx_mock, bbot_httpserver, bbot_httpserver_ssl, monkeypatch, request, caplog, capsys
91
93
  ):
94
+ # Skip dastardly test if we're in the distro tests (because dastardly uses docker)
95
+ if os.getenv("BBOT_DISTRO_TESTS") and self.skip_distro_tests:
96
+ pytest.skip("Skipping module_test for dastardly module due to BBOT_DISTRO_TESTS environment variable")
97
+
92
98
  self.log.info(f"Starting {self.name} module test")
93
99
  module_test = self.ModuleTest(
94
100
  self, httpx_mock, bbot_httpserver, bbot_httpserver_ssl, monkeypatch, request, caplog, capsys
95
101
  )
96
- self.log.debug(f"Mocking DNS")
102
+ self.log.debug("Mocking DNS")
97
103
  await module_test.mock_dns({"blacklanternsecurity.com": {"A": ["127.0.0.88"]}})
98
- self.log.debug(f"Executing setup_before_prep()")
104
+ self.log.debug("Executing setup_before_prep()")
99
105
  await self.setup_before_prep(module_test)
100
- self.log.debug(f"Executing scan._prep()")
106
+ self.log.debug("Executing scan._prep()")
101
107
  await module_test.scan._prep()
102
- self.log.debug(f"Executing setup_after_prep()")
108
+ self.log.debug("Executing setup_after_prep()")
103
109
  await self.setup_after_prep(module_test)
104
- self.log.debug(f"Starting scan")
110
+ self.log.debug("Starting scan")
105
111
  module_test.events = [e async for e in module_test.scan.async_start()]
106
112
  self.log.debug(f"Finished {module_test.name} module test")
107
113
  yield module_test
108
114
 
109
115
  @pytest.mark.asyncio
110
116
  async def test_module_run(self, module_test):
111
- self.check(module_test, module_test.events)
117
+ from bbot.core.helpers.misc import execute_sync_or_async
118
+
119
+ await execute_sync_or_async(self.check, module_test, module_test.events)
112
120
  module_test.log.info(f"Finished {self.name} module test")
113
121
  current_task = asyncio.current_task()
114
122
  tasks = [t for t in asyncio.all_tasks() if t != current_task]
115
123
  if len(tasks):
116
124
  module_test.log.info(f"Unfinished tasks detected: {tasks}")
117
125
  else:
118
- module_test.log.info(f"No unfinished tasks detected")
126
+ module_test.log.info("No unfinished tasks detected")
119
127
 
120
128
  def check(self, module_test, events):
121
129
  assert False, f"Must override {self.name}.check()"
@@ -5,7 +5,7 @@ class TestAnubisdb(ModuleTestBase):
5
5
  async def setup_after_prep(self, module_test):
6
6
  module_test.module.abort_if = lambda e: False
7
7
  module_test.httpx_mock.add_response(
8
- url=f"https://jldc.me/anubis/subdomains/blacklanternsecurity.com",
8
+ url="https://jldc.me/anubis/subdomains/blacklanternsecurity.com",
9
9
  json=["asdf.blacklanternsecurity.com", "zzzz.blacklanternsecurity.com"],
10
10
  )
11
11
 
@@ -0,0 +1,71 @@
1
+ from pathlib import Path
2
+ from .base import ModuleTestBase, tempapkfile
3
+
4
+
5
+ class TestAPKPure(ModuleTestBase):
6
+ modules_overrides = ["apkpure", "google_playstore", "speculate"]
7
+ apk_file = tempapkfile()
8
+
9
+ async def setup_after_prep(self, module_test):
10
+ await module_test.mock_dns({"blacklanternsecurity.com": {"A": ["127.0.0.99"]}})
11
+ module_test.httpx_mock.add_response(
12
+ url="https://play.google.com/store/search?q=blacklanternsecurity&c=apps",
13
+ text="""<!DOCTYPE html>
14
+ <html>
15
+ <head>
16
+ <title>"blacklanternsecurity" - Android Apps on Google Play</title>
17
+ </head>
18
+ <body>
19
+ <a href="/store/apps/details?id=com.bbot.test&pcampaignid=dontmatchme&pli=1"/>
20
+ </body>
21
+ </html>""",
22
+ )
23
+ module_test.httpx_mock.add_response(
24
+ url="https://play.google.com/store/apps/details?id=com.bbot.test",
25
+ text="""<!DOCTYPE html>
26
+ <html>
27
+ <head>
28
+ <title>BBOT</title>
29
+ </head>
30
+ <body>
31
+ <meta name="appstore:developer_url" content="https://www.blacklanternsecurity.com">
32
+ </div>
33
+ </div>
34
+ </body>
35
+ </html>""",
36
+ )
37
+ module_test.httpx_mock.add_response(
38
+ url="https://d.apkpure.com/b/XAPK/com.bbot.test?version=latest",
39
+ content=self.apk_file,
40
+ headers={
41
+ "Content-Type": "application/vnd.android.package-archive",
42
+ "Content-Disposition": "attachment; filename=com.bbot.test.apk",
43
+ },
44
+ )
45
+
46
+ def check(self, module_test, events):
47
+ assert len(events) == 6
48
+ assert 1 == len(
49
+ [
50
+ e
51
+ for e in events
52
+ if e.type == "DNS_NAME" and e.data == "blacklanternsecurity.com" and e.scope_distance == 0
53
+ ]
54
+ ), "Failed to emit target DNS_NAME"
55
+ assert 1 == len(
56
+ [e for e in events if e.type == "ORG_STUB" and e.data == "blacklanternsecurity" and e.scope_distance == 0]
57
+ ), "Failed to find ORG_STUB"
58
+ assert 1 == len(
59
+ [
60
+ e
61
+ for e in events
62
+ if e.type == "MOBILE_APP"
63
+ and "android" in e.tags
64
+ and e.data["id"] == "com.bbot.test"
65
+ and e.data["url"] == "https://play.google.com/store/apps/details?id=com.bbot.test"
66
+ ]
67
+ ), "Failed to find bbot android app"
68
+ filesystem_event = [e for e in events if e.type == "FILESYSTEM" and "com.bbot.test.apk" in e.data["path"]]
69
+ assert 1 == len(filesystem_event), "Failed to download apk"
70
+ file = Path(filesystem_event[0].data["path"])
71
+ assert file.is_file(), "Destination apk doesn't exist"
@@ -10,7 +10,6 @@ class TestAsset_Inventory(ModuleTestBase):
10
10
  masscan_output = """{ "ip": "127.0.0.1", "timestamp": "1680197558", "ports": [ {"port": 9999, "proto": "tcp", "status": "open", "reason": "syn-ack", "ttl": 54} ] }"""
11
11
 
12
12
  async def setup_before_prep(self, module_test):
13
-
14
13
  async def run_masscan(command, *args, **kwargs):
15
14
  if "masscan" in command[:2]:
16
15
  targets = open(command[11]).read().splitlines()
@@ -22,7 +22,7 @@ class TestAzure_Realm(ModuleTestBase):
22
22
  async def setup_after_prep(self, module_test):
23
23
  await module_test.mock_dns({"evilcorp.com": {"A": ["127.0.0.5"]}})
24
24
  module_test.httpx_mock.add_response(
25
- url=f"https://login.microsoftonline.com/getuserrealm.srf?login=test@evilcorp.com",
25
+ url="https://login.microsoftonline.com/getuserrealm.srf?login=test@evilcorp.com",
26
26
  json=self.response_json,
27
27
  )
28
28
 
@@ -31,9 +31,9 @@ class TestBaddns_cname_nxdomain(BaseTestBaddns):
31
31
  module_test.monkeypatch.setattr(WhoisManager, "dispatchWHOIS", self.dispatchWHOIS)
32
32
 
33
33
  def check(self, module_test, events):
34
- assert any([e.data == "baddns.azurewebsites.net" for e in events]), "CNAME detection failed"
35
- assert any([e.type == "VULNERABILITY" for e in events]), "Failed to emit VULNERABILITY"
36
- assert any(["baddns-cname" in e.tags for e in events]), "Failed to add baddns tag"
34
+ assert any(e.data == "baddns.azurewebsites.net" for e in events), "CNAME detection failed"
35
+ assert any(e.type == "VULNERABILITY" for e in events), "Failed to emit VULNERABILITY"
36
+ assert any("baddns-cname" in e.tags for e in events), "Failed to add baddns tag"
37
37
 
38
38
 
39
39
  class TestBaddns_cname_signature(BaseTestBaddns):
@@ -60,8 +60,8 @@ class TestBaddns_cname_signature(BaseTestBaddns):
60
60
  module_test.monkeypatch.setattr(WhoisManager, "dispatchWHOIS", self.dispatchWHOIS)
61
61
 
62
62
  def check(self, module_test, events):
63
- assert any([e for e in events])
63
+ assert any(e for e in events)
64
64
  assert any(
65
- [e.type == "VULNERABILITY" and "bigcartel.com" in e.data["description"] for e in events]
65
+ e.type == "VULNERABILITY" and "bigcartel.com" in e.data["description"] for e in events
66
66
  ), "Failed to emit VULNERABILITY"
67
- assert any(["baddns-cname" in e.tags for e in events]), "Failed to add baddns tag"
67
+ assert any("baddns-cname" in e.tags for e in events), "Failed to add baddns tag"
@@ -0,0 +1,62 @@
1
+ from .base import ModuleTestBase
2
+ from bbot.modules.base import BaseModule
3
+
4
+
5
+ class BaseTestBaddns(ModuleTestBase):
6
+ modules_overrides = ["baddns_direct"]
7
+ targets = ["bad.dns"]
8
+ config_overrides = {"dns": {"minimal": False}, "cloudcheck": True}
9
+
10
+
11
+ class TestBaddns_direct_cloudflare(BaseTestBaddns):
12
+ targets = ["bad.dns:8888"]
13
+ modules_overrides = ["baddns_direct"]
14
+
15
+ async def dispatchWHOIS(self):
16
+ return None
17
+
18
+ class DummyModule(BaseModule):
19
+ watched_events = ["DNS_NAME"]
20
+ _name = "dummy_module"
21
+ events_seen = []
22
+
23
+ async def handle_event(self, event):
24
+ if event.data == "bad.dns":
25
+ await self.helpers.sleep(0.5)
26
+ self.events_seen.append(event.data)
27
+ url = "http://bad.dns:8888/"
28
+ url_event = self.scan.make_event(
29
+ url, "URL", parent=self.scan.root_event, tags=["cdn-cloudflare", "in-scope", "status-401"]
30
+ )
31
+ if url_event is not None:
32
+ await self.emit_event(url_event)
33
+
34
+ async def setup_after_prep(self, module_test):
35
+ from baddns.base import BadDNS_base
36
+ from baddns.lib.whoismanager import WhoisManager
37
+
38
+ def set_target(self, target):
39
+ return "127.0.0.1:8888"
40
+
41
+ self.module_test = module_test
42
+
43
+ self.dummy_module = self.DummyModule(module_test.scan)
44
+ module_test.scan.modules["dummy_module"] = self.dummy_module
45
+
46
+ expect_args = {"method": "GET", "uri": "/"}
47
+ respond_args = {"response_data": "The specified bucket does not exist", "status": 401}
48
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
49
+
50
+ await module_test.mock_dns({"bad.dns": {"A": ["127.0.0.1"]}})
51
+
52
+ module_test.monkeypatch.setattr(BadDNS_base, "set_target", set_target)
53
+ module_test.monkeypatch.setattr(WhoisManager, "dispatchWHOIS", self.dispatchWHOIS)
54
+
55
+ def check(self, module_test, events):
56
+ assert any(
57
+ e.type == "FINDING"
58
+ and "Possible [AWS Bucket Takeover Detection] via direct BadDNS analysis. Indicator: [[Words: The specified bucket does not exist | Condition: and | Part: body] Matchers-Condition: and] Trigger: [self] baddns Module: [CNAME]"
59
+ in e.data["description"]
60
+ for e in events
61
+ ), "Failed to emit FINDING"
62
+ assert any("baddns-cname" in e.tags for e in events), "Failed to add baddns tag"
@@ -1,12 +1,16 @@
1
+ import random
2
+
1
3
  from .base import ModuleTestBase
2
4
 
3
5
 
4
6
  class TestBeVigil(ModuleTestBase):
7
+ module_name = "bevigil"
5
8
  config_overrides = {"modules": {"bevigil": {"api_key": "asdf", "urls": True}}}
6
9
 
7
10
  async def setup_after_prep(self, module_test):
8
11
  module_test.httpx_mock.add_response(
9
- url=f"https://osint.bevigil.com/api/blacklanternsecurity.com/subdomains/",
12
+ url="https://osint.bevigil.com/api/blacklanternsecurity.com/subdomains/",
13
+ match_headers={"X-Access-Token": "asdf"},
10
14
  json={
11
15
  "domain": "blacklanternsecurity.com",
12
16
  "subdomains": [
@@ -15,10 +19,33 @@ class TestBeVigil(ModuleTestBase):
15
19
  },
16
20
  )
17
21
  module_test.httpx_mock.add_response(
18
- url=f"https://osint.bevigil.com/api/blacklanternsecurity.com/urls/",
22
+ url="https://osint.bevigil.com/api/blacklanternsecurity.com/urls/",
19
23
  json={"domain": "blacklanternsecurity.com", "urls": ["https://asdf.blacklanternsecurity.com"]},
20
24
  )
21
25
 
22
26
  def check(self, module_test, events):
23
27
  assert any(e.data == "asdf.blacklanternsecurity.com" for e in events), "Failed to detect subdomain"
24
28
  assert any(e.data == "https://asdf.blacklanternsecurity.com/" for e in events), "Failed to detect url"
29
+
30
+
31
+ class TestBeVigilMultiKey(TestBeVigil):
32
+ api_keys = ["1234", "4321", "asdf", "fdsa"]
33
+ random.shuffle(api_keys)
34
+ config_overrides = {"modules": {"bevigil": {"api_key": api_keys, "urls": True}}}
35
+
36
+ async def setup_after_prep(self, module_test):
37
+ module_test.httpx_mock.add_response(
38
+ url="https://osint.bevigil.com/api/blacklanternsecurity.com/subdomains/",
39
+ match_headers={"X-Access-Token": "fdsa"},
40
+ json={
41
+ "domain": "blacklanternsecurity.com",
42
+ "subdomains": [
43
+ "asdf.blacklanternsecurity.com",
44
+ ],
45
+ },
46
+ )
47
+ module_test.httpx_mock.add_response(
48
+ match_headers={"X-Access-Token": "asdf"},
49
+ url="https://osint.bevigil.com/api/blacklanternsecurity.com/urls/",
50
+ json={"domain": "blacklanternsecurity.com", "urls": ["https://asdf.blacklanternsecurity.com"]},
51
+ )
@@ -6,7 +6,8 @@ class TestBinaryEdge(ModuleTestBase):
6
6
 
7
7
  async def setup_before_prep(self, module_test):
8
8
  module_test.httpx_mock.add_response(
9
- url=f"https://api.binaryedge.io/v2/query/domains/subdomain/blacklanternsecurity.com",
9
+ url="https://api.binaryedge.io/v2/query/domains/subdomain/blacklanternsecurity.com",
10
+ match_headers={"X-Key": "asdf"},
10
11
  json={
11
12
  "query": "blacklanternsecurity.com",
12
13
  "page": 1,
@@ -18,7 +19,8 @@ class TestBinaryEdge(ModuleTestBase):
18
19
  },
19
20
  )
20
21
  module_test.httpx_mock.add_response(
21
- url=f"https://api.binaryedge.io/v2/user/subscription",
22
+ url="https://api.binaryedge.io/v2/user/subscription",
23
+ match_headers={"X-Key": "asdf"},
22
24
  json={
23
25
  "subscription": {"name": "Free"},
24
26
  "end_date": "2023-06-17",
@@ -82,8 +82,8 @@ class Bucket_Amazon_Base(ModuleTestBase):
82
82
  if e.type == "FINDING" and str(e.module) == self.module_name:
83
83
  url = e.data.get("url", "")
84
84
  assert self.random_bucket_2 in url
85
- assert not self.random_bucket_1 in url
86
- assert not self.random_bucket_3 in url
85
+ assert self.random_bucket_1 not in url
86
+ assert self.random_bucket_3 not in url
87
87
  # make sure bucket mutations were found
88
88
  assert any(
89
89
  e.type == "STORAGE_BUCKET"
@@ -21,7 +21,7 @@ class TestBucket_Azure_NoDup(ModuleTestBase):
21
21
 
22
22
  async def setup_before_prep(self, module_test):
23
23
  module_test.httpx_mock.add_response(
24
- url=f"https://tesla.blob.core.windows.net/tesla?restype=container",
24
+ url="https://tesla.blob.core.windows.net/tesla?restype=container",
25
25
  text="",
26
26
  )
27
27
  await module_test.mock_dns(
@@ -0,0 +1,35 @@
1
+ from .base import ModuleTestBase
2
+
3
+
4
+ class TestBufferOverrun(ModuleTestBase):
5
+ config_overrides = {"modules": {"bufferoverrun": {"api_key": "asdf", "commercial": False}}}
6
+
7
+ async def setup_before_prep(self, module_test):
8
+ # Mock response for non-commercial API
9
+ module_test.httpx_mock.add_response(
10
+ url="https://tls.bufferover.run/dns?q=.blacklanternsecurity.com",
11
+ match_headers={"x-api-key": "asdf"},
12
+ json={"Results": ["1.2.3.4,example.com,*,*,sub.blacklanternsecurity.com"]},
13
+ )
14
+
15
+ def check(self, module_test, events):
16
+ assert any(e.data == "sub.blacklanternsecurity.com" for e in events), "Failed to detect subdomain for free API"
17
+
18
+
19
+ class TestBufferOverrunCommercial(ModuleTestBase):
20
+ modules_overrides = ["bufferoverrun"]
21
+ module_name = "bufferoverrun"
22
+ config_overrides = {"modules": {"bufferoverrun": {"api_key": "asdf", "commercial": True}}}
23
+
24
+ async def setup_before_prep(self, module_test):
25
+ # Mock response for commercial API
26
+ module_test.httpx_mock.add_response(
27
+ url="https://bufferover-run-tls.p.rapidapi.com/ipv4/dns?q=.blacklanternsecurity.com",
28
+ match_headers={"x-rapidapi-host": "bufferover-run-tls.p.rapidapi.com", "x-rapidapi-key": "asdf"},
29
+ json={"Results": ["5.6.7.8,blacklanternsecurity.com,*,*,sub.blacklanternsecurity.com"]},
30
+ )
31
+
32
+ def check(self, module_test, events):
33
+ assert any(
34
+ e.data == "sub.blacklanternsecurity.com" for e in events
35
+ ), "Failed to detect subdomain for commercial API"
@@ -6,7 +6,7 @@ class TestBuiltWith(ModuleTestBase):
6
6
 
7
7
  async def setup_after_prep(self, module_test):
8
8
  module_test.httpx_mock.add_response(
9
- url=f"https://api.builtwith.com/v20/api.json?KEY=asdf&LOOKUP=blacklanternsecurity.com&NOMETA=yes&NOATTR=yes&HIDETEXT=yes&HIDEDL=yes",
9
+ url="https://api.builtwith.com/v20/api.json?KEY=asdf&LOOKUP=blacklanternsecurity.com&NOMETA=yes&NOATTR=yes&HIDETEXT=yes&HIDEDL=yes",
10
10
  json={
11
11
  "Results": [
12
12
  {
@@ -91,7 +91,7 @@ class TestBuiltWith(ModuleTestBase):
91
91
  },
92
92
  )
93
93
  module_test.httpx_mock.add_response(
94
- url=f"https://api.builtwith.com/redirect1/api.json?KEY=asdf&LOOKUP=blacklanternsecurity.com",
94
+ url="https://api.builtwith.com/redirect1/api.json?KEY=asdf&LOOKUP=blacklanternsecurity.com",
95
95
  json={
96
96
  "Lookup": "blacklanternsecurity.com",
97
97
  "Inbound": [
@@ -26,7 +26,7 @@ class TestBypass403_collapsethreshold(ModuleTestBase):
26
26
  async def setup_after_prep(self, module_test):
27
27
  respond_args = {"response_data": "alive"}
28
28
 
29
- # some of these wont work outside of the module because of the complex logic. This doesn't matter, we just need to get more alerts than the threshold.
29
+ # some of these won't work outside of the module because of the complex logic. This doesn't matter, we just need to get more alerts than the threshold.
30
30
 
31
31
  query_payloads = [
32
32
  "%09",
@@ -1,7 +1,10 @@
1
+ import httpx
2
+
1
3
  from .base import ModuleTestBase
2
4
 
3
5
 
4
6
  class TestC99(ModuleTestBase):
7
+ module_name = "c99"
5
8
  config_overrides = {"modules": {"c99": {"api_key": "asdf"}}}
6
9
 
7
10
  async def setup_before_prep(self, module_test):
@@ -23,3 +26,126 @@ class TestC99(ModuleTestBase):
23
26
 
24
27
  def check(self, module_test, events):
25
28
  assert any(e.data == "asdf.blacklanternsecurity.com" for e in events), "Failed to detect subdomain"
29
+
30
+
31
+ class TestC99AbortThreshold1(TestC99):
32
+ config_overrides = {"modules": {"c99": {"api_key": ["6789", "fdsa", "1234", "4321"]}}}
33
+
34
+ async def setup_before_prep(self, module_test):
35
+ module_test.httpx_mock.add_response(
36
+ url="https://api.c99.nl/randomnumber?key=fdsa&between=1,100&json",
37
+ json={"success": True, "output": 65},
38
+ )
39
+
40
+ self.url_count = {}
41
+
42
+ async def custom_callback(request):
43
+ url = str(request.url)
44
+ try:
45
+ self.url_count[url] += 1
46
+ except KeyError:
47
+ self.url_count[url] = 1
48
+ raise httpx.TimeoutException("timeout")
49
+
50
+ module_test.httpx_mock.add_callback(custom_callback)
51
+
52
+ def check(self, module_test, events):
53
+ assert module_test.module.api_failure_abort_threshold == 13
54
+ assert module_test.module.errored is False
55
+ # assert module_test.module._api_request_failures == 4
56
+ assert module_test.module.api_retries == 4
57
+ assert {e.data for e in events if e.type == "DNS_NAME"} == {"blacklanternsecurity.com"}
58
+ assert self.url_count == {
59
+ "https://api.c99.nl/randomnumber?key=6789&between=1,100&json": 1,
60
+ "https://api.c99.nl/randomnumber?key=4321&between=1,100&json": 1,
61
+ "https://api.c99.nl/randomnumber?key=1234&between=1,100&json": 1,
62
+ "https://api.c99.nl/subdomainfinder?key=fdsa&domain=blacklanternsecurity.com&json": 1,
63
+ "https://api.c99.nl/subdomainfinder?key=6789&domain=blacklanternsecurity.com&json": 1,
64
+ "https://api.c99.nl/subdomainfinder?key=4321&domain=blacklanternsecurity.com&json": 1,
65
+ "https://api.c99.nl/subdomainfinder?key=1234&domain=blacklanternsecurity.com&json": 1,
66
+ }
67
+
68
+
69
+ class TestC99AbortThreshold2(TestC99AbortThreshold1):
70
+ targets = ["blacklanternsecurity.com", "evilcorp.com"]
71
+
72
+ async def setup_before_prep(self, module_test):
73
+ await super().setup_before_prep(module_test)
74
+ await module_test.mock_dns(
75
+ {
76
+ "blacklanternsecurity.com": {"A": ["127.0.0.88"]},
77
+ "evilcorp.com": {"A": ["127.0.0.11"]},
78
+ "evilcorp.net": {"A": ["127.0.0.22"]},
79
+ "evilcorp.co.uk": {"A": ["127.0.0.33"]},
80
+ }
81
+ )
82
+
83
+ def check(self, module_test, events):
84
+ assert module_test.module.api_failure_abort_threshold == 13
85
+ assert module_test.module.errored is False
86
+ assert module_test.module._api_request_failures == 8
87
+ assert module_test.module.api_retries == 4
88
+ assert {e.data for e in events if e.type == "DNS_NAME"} == {"blacklanternsecurity.com", "evilcorp.com"}
89
+ assert self.url_count == {
90
+ "https://api.c99.nl/randomnumber?key=6789&between=1,100&json": 1,
91
+ "https://api.c99.nl/randomnumber?key=4321&between=1,100&json": 1,
92
+ "https://api.c99.nl/randomnumber?key=1234&between=1,100&json": 1,
93
+ "https://api.c99.nl/subdomainfinder?key=fdsa&domain=blacklanternsecurity.com&json": 1,
94
+ "https://api.c99.nl/subdomainfinder?key=6789&domain=blacklanternsecurity.com&json": 1,
95
+ "https://api.c99.nl/subdomainfinder?key=4321&domain=blacklanternsecurity.com&json": 1,
96
+ "https://api.c99.nl/subdomainfinder?key=1234&domain=blacklanternsecurity.com&json": 1,
97
+ "https://api.c99.nl/subdomainfinder?key=fdsa&domain=evilcorp.com&json": 1,
98
+ "https://api.c99.nl/subdomainfinder?key=6789&domain=evilcorp.com&json": 1,
99
+ "https://api.c99.nl/subdomainfinder?key=4321&domain=evilcorp.com&json": 1,
100
+ "https://api.c99.nl/subdomainfinder?key=1234&domain=evilcorp.com&json": 1,
101
+ }
102
+
103
+
104
+ class TestC99AbortThreshold3(TestC99AbortThreshold2):
105
+ targets = ["blacklanternsecurity.com", "evilcorp.com", "evilcorp.net"]
106
+
107
+ def check(self, module_test, events):
108
+ assert module_test.module.api_failure_abort_threshold == 13
109
+ assert module_test.module.errored is False
110
+ assert module_test.module._api_request_failures == 12
111
+ assert module_test.module.api_retries == 4
112
+ assert {e.data for e in events if e.type == "DNS_NAME"} == {
113
+ "blacklanternsecurity.com",
114
+ "evilcorp.com",
115
+ "evilcorp.net",
116
+ }
117
+ assert self.url_count == {
118
+ "https://api.c99.nl/randomnumber?key=6789&between=1,100&json": 1,
119
+ "https://api.c99.nl/randomnumber?key=4321&between=1,100&json": 1,
120
+ "https://api.c99.nl/randomnumber?key=1234&between=1,100&json": 1,
121
+ "https://api.c99.nl/subdomainfinder?key=fdsa&domain=blacklanternsecurity.com&json": 1,
122
+ "https://api.c99.nl/subdomainfinder?key=6789&domain=blacklanternsecurity.com&json": 1,
123
+ "https://api.c99.nl/subdomainfinder?key=4321&domain=blacklanternsecurity.com&json": 1,
124
+ "https://api.c99.nl/subdomainfinder?key=1234&domain=blacklanternsecurity.com&json": 1,
125
+ "https://api.c99.nl/subdomainfinder?key=fdsa&domain=evilcorp.com&json": 1,
126
+ "https://api.c99.nl/subdomainfinder?key=6789&domain=evilcorp.com&json": 1,
127
+ "https://api.c99.nl/subdomainfinder?key=4321&domain=evilcorp.com&json": 1,
128
+ "https://api.c99.nl/subdomainfinder?key=1234&domain=evilcorp.com&json": 1,
129
+ "https://api.c99.nl/subdomainfinder?key=fdsa&domain=evilcorp.net&json": 1,
130
+ "https://api.c99.nl/subdomainfinder?key=6789&domain=evilcorp.net&json": 1,
131
+ "https://api.c99.nl/subdomainfinder?key=4321&domain=evilcorp.net&json": 1,
132
+ "https://api.c99.nl/subdomainfinder?key=1234&domain=evilcorp.net&json": 1,
133
+ }
134
+
135
+
136
+ class TestC99AbortThreshold4(TestC99AbortThreshold3):
137
+ targets = ["blacklanternsecurity.com", "evilcorp.com", "evilcorp.net", "evilcorp.co.uk"]
138
+
139
+ def check(self, module_test, events):
140
+ assert module_test.module.api_failure_abort_threshold == 13
141
+ assert module_test.module.errored is True
142
+ assert module_test.module._api_request_failures == 13
143
+ assert module_test.module.api_retries == 4
144
+ assert {e.data for e in events if e.type == "DNS_NAME"} == {
145
+ "blacklanternsecurity.com",
146
+ "evilcorp.com",
147
+ "evilcorp.net",
148
+ "evilcorp.co.uk",
149
+ }
150
+ assert len(self.url_count) == 16
151
+ assert all(v == 1 for v in self.url_count.values())