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
@@ -23,7 +23,7 @@ async def test_dns_engine(bbot_scanner):
23
23
  )
24
24
  result = await scan.helpers.resolve("one.one.one.one")
25
25
  assert "1.1.1.1" in result
26
- assert not "2606:4700:4700::1111" in result
26
+ assert "2606:4700:4700::1111" not in result
27
27
 
28
28
  results = [_ async for _ in scan.helpers.resolve_batch(("one.one.one.one", "1.1.1.1"))]
29
29
  pass_1 = False
@@ -85,12 +85,12 @@ async def test_dns_resolution(bbot_scanner):
85
85
  for answer in answers:
86
86
  responses += list(extract_targets(answer))
87
87
  assert ("A", "1.1.1.1") in responses
88
- assert not ("AAAA", "2606:4700:4700::1111") in responses
88
+ assert ("AAAA", "2606:4700:4700::1111") not in responses
89
89
  answers, errors = await dnsengine.resolve_raw("one.one.one.one", type="AAAA")
90
90
  responses = []
91
91
  for answer in answers:
92
92
  responses += list(extract_targets(answer))
93
- assert not ("A", "1.1.1.1") in responses
93
+ assert ("A", "1.1.1.1") not in responses
94
94
  assert ("AAAA", "2606:4700:4700::1111") in responses
95
95
  answers, errors = await dnsengine.resolve_raw("1.1.1.1")
96
96
  responses = []
@@ -106,13 +106,14 @@ async def test_dns_resolution(bbot_scanner):
106
106
  assert "2606:4700:4700::1111" in await dnsengine.resolve("one.one.one.one", type="AAAA")
107
107
  assert "one.one.one.one" in await dnsengine.resolve("1.1.1.1")
108
108
  for rdtype in ("NS", "SOA", "MX", "TXT"):
109
- assert len(await dnsengine.resolve("google.com", type=rdtype)) > 0
109
+ results = await dnsengine.resolve("google.com", type=rdtype)
110
+ assert len(results) > 0
110
111
 
111
112
  # batch resolution
112
113
  batch_results = [r async for r in dnsengine.resolve_batch(["1.1.1.1", "one.one.one.one"])]
113
114
  assert len(batch_results) == 2
114
115
  batch_results = dict(batch_results)
115
- assert any([x in batch_results["one.one.one.one"] for x in ("1.1.1.1", "1.0.0.1")])
116
+ assert any(x in batch_results["one.one.one.one"] for x in ("1.1.1.1", "1.0.0.1"))
116
117
  assert "one.one.one.one" in batch_results["1.1.1.1"]
117
118
 
118
119
  # custom batch resolution
@@ -140,11 +141,11 @@ async def test_dns_resolution(bbot_scanner):
140
141
  assert hash(("1.1.1.1", "PTR")) in dnsengine._dns_cache
141
142
  await dnsengine.resolve("one.one.one.one", type="A")
142
143
  assert hash(("one.one.one.one", "A")) in dnsengine._dns_cache
143
- assert not hash(("one.one.one.one", "AAAA")) in dnsengine._dns_cache
144
+ assert hash(("one.one.one.one", "AAAA")) not in dnsengine._dns_cache
144
145
  dnsengine._dns_cache.clear()
145
146
  await dnsengine.resolve("one.one.one.one", type="AAAA")
146
147
  assert hash(("one.one.one.one", "AAAA")) in dnsengine._dns_cache
147
- assert not hash(("one.one.one.one", "A")) in dnsengine._dns_cache
148
+ assert hash(("one.one.one.one", "A")) not in dnsengine._dns_cache
148
149
 
149
150
  await dnsengine._shutdown()
150
151
 
@@ -164,7 +165,7 @@ async def test_dns_resolution(bbot_scanner):
164
165
  assert "A" in resolved_hosts_event1.raw_dns_records
165
166
  assert "AAAA" in resolved_hosts_event1.raw_dns_records
166
167
  assert "a-record" in resolved_hosts_event1.tags
167
- assert not "a-record" in resolved_hosts_event2.tags
168
+ assert "a-record" not in resolved_hosts_event2.tags
168
169
 
169
170
  scan2 = bbot_scanner("evilcorp.com", config={"dns": {"minimal": False}})
170
171
  await scan2.helpers.dns._mock_dns(
@@ -197,7 +198,7 @@ async def test_wildcards(bbot_scanner):
197
198
  assert len(dnsengine._wildcard_cache) == len(all_rdtypes) + (len(all_rdtypes) - 2)
198
199
  for rdtype in all_rdtypes:
199
200
  assert hash(("github.io", rdtype)) in dnsengine._wildcard_cache
200
- if not rdtype in ("A", "AAAA"):
201
+ if rdtype not in ("A", "AAAA"):
201
202
  assert hash(("asdf.github.io", rdtype)) in dnsengine._wildcard_cache
202
203
  assert "github.io" in wildcard_domains
203
204
  assert "A" in wildcard_domains["github.io"]
@@ -263,7 +264,7 @@ def custom_lookup(query, rdtype):
263
264
  await scan.helpers.dns._mock_dns(mock_data, custom_lookup_fn=custom_lookup)
264
265
 
265
266
  events = [e async for e in scan.async_start()]
266
- assert len(events) == 11
267
+ assert len(events) == 12
267
268
  assert len([e for e in events if e.type == "DNS_NAME"]) == 5
268
269
  assert len([e for e in events if e.type == "RAW_DNS_RECORD"]) == 4
269
270
  assert sorted([e.data for e in events if e.type == "DNS_NAME"]) == [
@@ -320,7 +321,7 @@ def custom_lookup(query, rdtype):
320
321
  await scan.helpers.dns._mock_dns(mock_data, custom_lookup_fn=custom_lookup)
321
322
 
322
323
  events = [e async for e in scan.async_start()]
323
- assert len(events) == 11
324
+ assert len(events) == 12
324
325
  assert len([e for e in events if e.type == "DNS_NAME"]) == 5
325
326
  assert len([e for e in events if e.type == "RAW_DNS_RECORD"]) == 4
326
327
  assert sorted([e.data for e in events if e.type == "DNS_NAME"]) == [
@@ -418,7 +419,7 @@ def custom_lookup(query, rdtype):
418
419
 
419
420
  events = [e async for e in scan.async_start()]
420
421
 
421
- assert len(events) == 10
422
+ assert len(events) == 11
422
423
  assert len([e for e in events if e.type == "DNS_NAME"]) == 5
423
424
  assert len([e for e in events if e.type == "RAW_DNS_RECORD"]) == 4
424
425
  assert sorted([e.data for e in events if e.type == "DNS_NAME"]) == [
@@ -546,8 +547,8 @@ def custom_lookup(query, rdtype):
546
547
  )
547
548
  await scan2.ingress_module.queue_event(other_event, {})
548
549
  events = [e async for e in scan2.async_start()]
549
- assert len(events) == 3
550
- assert 1 == len([e for e in events if e.type == "SCAN"])
550
+ assert len(events) == 4
551
+ assert 2 == len([e for e in events if e.type == "SCAN"])
551
552
  unmodified_wildcard_events = [
552
553
  e for e in events if e.type == "DNS_NAME" and e.data == "asdfl.gashdgkjsadgsdf.github.io"
553
554
  ]
@@ -592,8 +593,8 @@ def custom_lookup(query, rdtype):
592
593
  )
593
594
  await scan2.ingress_module.queue_event(other_event, {})
594
595
  events = [e async for e in scan2.async_start()]
595
- assert len(events) == 3
596
- assert 1 == len([e for e in events if e.type == "SCAN"])
596
+ assert len(events) == 4
597
+ assert 2 == len([e for e in events if e.type == "SCAN"])
597
598
  unmodified_wildcard_events = [e for e in events if e.type == "DNS_NAME" and "_wildcard" not in e.data]
598
599
  assert len(unmodified_wildcard_events) == 2
599
600
  assert 1 == len(
@@ -631,6 +632,42 @@ def custom_lookup(query, rdtype):
631
632
  assert len(modified_wildcard_events) == 0
632
633
 
633
634
 
635
+ @pytest.mark.asyncio
636
+ async def test_wildcard_deduplication(bbot_scanner):
637
+
638
+ custom_lookup = """
639
+ def custom_lookup(query, rdtype):
640
+ if rdtype == "TXT" and query.strip(".").endswith("evilcorp.com"):
641
+ return {""}
642
+ """
643
+
644
+ mock_data = {
645
+ "evilcorp.com": {"A": ["127.0.0.1"]},
646
+ }
647
+
648
+ from bbot.modules.base import BaseModule
649
+
650
+ class DummyModule(BaseModule):
651
+ watched_events = ["DNS_NAME"]
652
+ per_domain_only = True
653
+
654
+ async def handle_event(self, event):
655
+ for i in range(30):
656
+ await self.emit_event(f"www{i}.evilcorp.com", "DNS_NAME", parent=event)
657
+
658
+ # scan without omitted event type
659
+ scan = bbot_scanner(
660
+ "evilcorp.com", config={"dns": {"minimal": False, "wildcard_ignore": []}, "omit_event_types": []}
661
+ )
662
+ await scan.helpers.dns._mock_dns(mock_data, custom_lookup_fn=custom_lookup)
663
+ dummy_module = DummyModule(scan)
664
+ scan.modules["dummy_module"] = dummy_module
665
+ events = [e async for e in scan.async_start()]
666
+ dns_name_events = [e for e in events if e.type == "DNS_NAME"]
667
+ assert len(dns_name_events) == 2
668
+ assert 1 == len([e for e in dns_name_events if e.data == "_wildcard.evilcorp.com"])
669
+
670
+
634
671
  @pytest.mark.asyncio
635
672
  async def test_dns_raw_records(bbot_scanner):
636
673
 
@@ -696,7 +733,7 @@ async def test_dns_raw_records(bbot_scanner):
696
733
  dummy_module = DummyModule(scan)
697
734
  scan.modules["dummy_module"] = dummy_module
698
735
  events = [e async for e in scan.async_start()]
699
- # no raw records should be ouptut
736
+ # no raw records should be output
700
737
  assert 0 == len([e for e in events if e.type == "RAW_DNS_RECORD"])
701
738
  # but they should still make it to the module
702
739
  assert 1 == len(
@@ -729,7 +766,7 @@ async def test_dns_graph_structure(bbot_scanner):
729
766
  }
730
767
  )
731
768
  events = [e async for e in scan.async_start()]
732
- assert len(events) == 5
769
+ assert len(events) == 6
733
770
  non_scan_events = [e for e in events if e.type != "SCAN"]
734
771
  assert sorted([e.type for e in non_scan_events]) == ["DNS_NAME", "DNS_NAME", "DNS_NAME", "URL_UNVERIFIED"]
735
772
  events_by_data = {e.data: e for e in non_scan_events}
@@ -744,16 +781,16 @@ async def test_dns_graph_structure(bbot_scanner):
744
781
 
745
782
  @pytest.mark.asyncio
746
783
  async def test_dns_helpers(bbot_scanner):
747
- assert service_record("") == False
748
- assert service_record("localhost") == False
749
- assert service_record("www.example.com") == False
750
- assert service_record("www.example.com", "SRV") == True
751
- assert service_record("_custom._service.example.com", "SRV") == True
752
- assert service_record("_custom._service.example.com", "A") == False
784
+ assert service_record("") is False
785
+ assert service_record("localhost") is False
786
+ assert service_record("www.example.com") is False
787
+ assert service_record("www.example.com", "SRV") is True
788
+ assert service_record("_custom._service.example.com", "SRV") is True
789
+ assert service_record("_custom._service.example.com", "A") is False
753
790
  # top 100 most common SRV records
754
791
  for srv_record in common_srvs[:100]:
755
792
  hostname = f"{srv_record}.example.com"
756
- assert service_record(hostname) == True
793
+ assert service_record(hostname) is True
757
794
 
758
795
  # make sure system nameservers are excluded from use by DNS brute force
759
796
  brute_nameservers = tempwordlist(["1.2.3.4", "8.8.4.4", "4.3.2.1", "8.8.8.8"])
@@ -72,7 +72,7 @@ async def test_engine():
72
72
 
73
73
  # test async generator
74
74
  assert counter == 0
75
- assert yield_cancelled == False
75
+ assert yield_cancelled is False
76
76
  yield_res = [r async for r in test_engine.yield_stuff(13)]
77
77
  assert yield_res == [f"thing{i}" for i in range(13)]
78
78
  assert len(yield_res) == 13
@@ -88,8 +88,8 @@ async def test_engine():
88
88
  await agen.aclose()
89
89
  break
90
90
  await asyncio.sleep(5)
91
- assert yield_cancelled == True
92
- assert yield_errored == False
91
+ assert yield_cancelled is True
92
+ assert yield_errored is False
93
93
  assert counter < 15
94
94
 
95
95
  # test async generator with error
@@ -99,8 +99,8 @@ async def test_engine():
99
99
  with pytest.raises(BBOTEngineError):
100
100
  async for _ in agen:
101
101
  pass
102
- assert yield_cancelled == False
103
- assert yield_errored == True
102
+ assert yield_cancelled is False
103
+ assert yield_errored is True
104
104
 
105
105
  # test return with cancellation
106
106
  return_started = False
@@ -113,10 +113,10 @@ async def test_engine():
113
113
  with pytest.raises(asyncio.CancelledError):
114
114
  await task
115
115
  await asyncio.sleep(0.1)
116
- assert return_started == True
117
- assert return_finished == False
118
- assert return_cancelled == True
119
- assert return_errored == False
116
+ assert return_started is True
117
+ assert return_finished is False
118
+ assert return_cancelled is True
119
+ assert return_errored is False
120
120
 
121
121
  # test return with late cancellation
122
122
  return_started = False
@@ -128,10 +128,10 @@ async def test_engine():
128
128
  task.cancel()
129
129
  result = await task
130
130
  assert result == "thing1"
131
- assert return_started == True
132
- assert return_finished == True
133
- assert return_cancelled == False
134
- assert return_errored == False
131
+ assert return_started is True
132
+ assert return_finished is True
133
+ assert return_cancelled is False
134
+ assert return_errored is False
135
135
 
136
136
  # test return with error
137
137
  return_started = False
@@ -140,9 +140,9 @@ async def test_engine():
140
140
  return_errored = False
141
141
  with pytest.raises(BBOTEngineError):
142
142
  result = await test_engine.return_thing(None)
143
- assert return_started == True
144
- assert return_finished == False
145
- assert return_cancelled == False
146
- assert return_errored == True
143
+ assert return_started is True
144
+ assert return_finished is False
145
+ assert return_cancelled is False
146
+ assert return_errored is True
147
147
 
148
148
  await test_engine.shutdown()
@@ -4,6 +4,7 @@ import ipaddress
4
4
 
5
5
  from ..bbot_fixtures import *
6
6
  from bbot.scanner import Scanner
7
+ from bbot.core.helpers.regexes import event_uuid_regex
7
8
 
8
9
 
9
10
  @pytest.mark.asyncio
@@ -13,10 +14,19 @@ async def test_events(events, helpers):
13
14
  await scan._prep()
14
15
 
15
16
  assert events.ipv4.type == "IP_ADDRESS"
17
+ assert events.ipv4.netloc == "8.8.8.8"
18
+ assert events.ipv4.port is None
16
19
  assert events.ipv6.type == "IP_ADDRESS"
20
+ assert events.ipv6.netloc == "[2001:4860:4860::8888]"
21
+ assert events.ipv6.port is None
22
+ assert events.ipv6_open_port.netloc == "[2001:4860:4860::8888]:443"
17
23
  assert events.netv4.type == "IP_RANGE"
24
+ assert events.netv4.netloc is None
25
+ assert "netloc" not in events.netv4.json()
18
26
  assert events.netv6.type == "IP_RANGE"
19
27
  assert events.domain.type == "DNS_NAME"
28
+ assert events.domain.netloc == "publicapis.org"
29
+ assert events.domain.port is None
20
30
  assert "domain" in events.domain.tags
21
31
  assert events.subdomain.type == "DNS_NAME"
22
32
  assert "subdomain" in events.subdomain.tags
@@ -32,6 +42,7 @@ async def test_events(events, helpers):
32
42
  # ip tests
33
43
  assert events.ipv4 == scan.make_event("8.8.8.8", dummy=True)
34
44
  assert "8.8.8.8" in events.ipv4
45
+ assert events.ipv4.host_filterable == "8.8.8.8"
35
46
  assert "8.8.8.8" == events.ipv4
36
47
  assert "8.8.8.8" in events.netv4
37
48
  assert "8.8.8.9" not in events.ipv4
@@ -49,11 +60,19 @@ async def test_events(events, helpers):
49
60
  assert events.emoji not in events.ipv4
50
61
  assert events.emoji not in events.netv6
51
62
  assert events.netv6 not in events.emoji
52
- assert "dead::c0de" == scan.make_event(" [DEaD::c0De]:88", "DNS_NAME", dummy=True)
63
+ ipv6_event = scan.make_event(" [DEaD::c0De]:88", "DNS_NAME", dummy=True)
64
+ assert "dead::c0de" == ipv6_event
65
+ assert ipv6_event.host_filterable == "dead::c0de"
66
+ range_to_ip = scan.make_event("1.2.3.4/32", dummy=True)
67
+ assert range_to_ip.type == "IP_ADDRESS"
68
+ range_to_ip = scan.make_event("dead::beef/128", dummy=True)
69
+ assert range_to_ip.type == "IP_ADDRESS"
53
70
 
54
71
  # hostname tests
55
72
  assert events.domain.host == "publicapis.org"
73
+ assert events.domain.host_filterable == "publicapis.org"
56
74
  assert events.subdomain.host == "api.publicapis.org"
75
+ assert events.subdomain.host_filterable == "api.publicapis.org"
57
76
  assert events.domain.host_stem == "publicapis"
58
77
  assert events.subdomain.host_stem == "api.publicapis"
59
78
  assert "api.publicapis.org" in events.domain
@@ -62,23 +81,39 @@ async def test_events(events, helpers):
62
81
  assert "fsocie.ty" not in events.subdomain
63
82
  assert events.subdomain in events.domain
64
83
  assert events.domain not in events.subdomain
65
- assert not events.ipv4 in events.domain
66
- assert not events.netv6 in events.domain
84
+ assert events.ipv4 not in events.domain
85
+ assert events.netv6 not in events.domain
67
86
  assert events.emoji not in events.domain
68
87
  assert events.domain not in events.emoji
69
- assert "evilcorp.com" == scan.make_event(" eViLcorp.COM.:88", "DNS_NAME", dummy=True)
70
- assert "evilcorp.com" == scan.make_event("evilcorp.com.", "DNS_NAME", dummy=True)
88
+ open_port_event = scan.make_event(" eViLcorp.COM.:88", "DNS_NAME", dummy=True)
89
+ dns_event = scan.make_event("evilcorp.com.", "DNS_NAME", dummy=True)
90
+ for e in (open_port_event, dns_event):
91
+ assert "evilcorp.com" == e
92
+ assert e.netloc == "evilcorp.com"
93
+ assert e.json()["netloc"] == "evilcorp.com"
94
+ assert e.port is None
95
+ assert "port" not in e.json()
71
96
 
72
97
  # url tests
73
- assert scan.make_event("http://evilcorp.com", dummy=True) == scan.make_event("http://evilcorp.com/", dummy=True)
98
+ url_no_trailing_slash = scan.make_event("http://evilcorp.com", dummy=True)
99
+ url_trailing_slash = scan.make_event("http://evilcorp.com/", dummy=True)
100
+ assert url_no_trailing_slash == url_trailing_slash
101
+ assert url_no_trailing_slash.host_filterable == "http://evilcorp.com/"
102
+ assert url_trailing_slash.host_filterable == "http://evilcorp.com/"
74
103
  assert events.url_unverified.host == "api.publicapis.org"
75
104
  assert events.url_unverified in events.domain
76
105
  assert events.url_unverified in events.subdomain
77
106
  assert "api.publicapis.org:443" in events.url_unverified
78
107
  assert "publicapis.org" not in events.url_unverified
79
108
  assert events.ipv4_url_unverified in events.ipv4
109
+ assert events.ipv4_url_unverified.netloc == "8.8.8.8:443"
110
+ assert events.ipv4_url_unverified.port == 443
111
+ assert events.ipv4_url_unverified.json()["port"] == 443
80
112
  assert events.ipv4_url_unverified in events.netv4
81
113
  assert events.ipv6_url_unverified in events.ipv6
114
+ assert events.ipv6_url_unverified.netloc == "[2001:4860:4860::8888]:443"
115
+ assert events.ipv6_url_unverified.port == 443
116
+ assert events.ipv6_url_unverified.json()["port"] == 443
82
117
  assert events.ipv6_url_unverified in events.netv6
83
118
  assert events.emoji not in events.url_unverified
84
119
  assert events.emoji not in events.ipv6_url_unverified
@@ -107,6 +142,7 @@ async def test_events(events, helpers):
107
142
  assert events.http_response.port == 80
108
143
  assert events.http_response.parsed_url.scheme == "http"
109
144
  assert events.http_response.with_port().geturl() == "http://example.com:80/"
145
+ assert events.http_response.host_filterable == "http://example.com/"
110
146
 
111
147
  http_response = scan.make_event(
112
148
  {
@@ -171,7 +207,7 @@ async def test_events(events, helpers):
171
207
 
172
208
  # scope distance
173
209
  event1 = scan.make_event("1.2.3.4", dummy=True)
174
- assert event1._scope_distance == None
210
+ assert event1._scope_distance is None
175
211
  event1.scope_distance = 0
176
212
  assert event1._scope_distance == 0
177
213
  event2 = scan.make_event("2.3.4.5", parent=event1)
@@ -192,6 +228,8 @@ async def test_events(events, helpers):
192
228
 
193
229
  org_stub_1 = scan.make_event("STUB1", "ORG_STUB", parent=scan.root_event)
194
230
  org_stub_1.scope_distance == 1
231
+ assert org_stub_1.netloc is None
232
+ assert "netloc" not in org_stub_1.json()
195
233
  org_stub_2 = scan.make_event("STUB2", "ORG_STUB", parent=org_stub_1)
196
234
  org_stub_2.scope_distance == 2
197
235
 
@@ -199,7 +237,7 @@ async def test_events(events, helpers):
199
237
  root_event = scan.make_event("0.0.0.0", dummy=True)
200
238
  root_event.scope_distance = 0
201
239
  internal_event1 = scan.make_event("1.2.3.4", parent=root_event, internal=True)
202
- assert internal_event1._internal == True
240
+ assert internal_event1._internal is True
203
241
  assert "internal" in internal_event1.tags
204
242
 
205
243
  # tag inheritance
@@ -231,8 +269,8 @@ async def test_events(events, helpers):
231
269
  # updating module
232
270
  event3 = scan.make_event("127.0.0.1", parent=scan.root_event)
233
271
  updated_event = scan.make_event(event3, internal=True)
234
- assert event3.internal == False
235
- assert updated_event.internal == True
272
+ assert event3.internal is False
273
+ assert updated_event.internal is True
236
274
 
237
275
  # event sorting
238
276
  parent1 = scan.make_event("127.0.0.1", parent=scan.root_event)
@@ -412,17 +450,55 @@ async def test_events(events, helpers):
412
450
  == "http://xn--12c1bik6bbd8ab6hd1b5jc6jta.com/ทดสอบ"
413
451
  )
414
452
 
453
+ # test event uuid
454
+ import uuid
455
+
456
+ parent_event1 = scan.make_event("evilcorp.com", parent=scan.root_event, context="test context")
457
+ parent_event2 = scan.make_event("evilcorp.com", parent=scan.root_event, context="test context")
458
+
459
+ event1 = scan.make_event("evilcorp.com:80", parent=parent_event1, context="test context")
460
+ assert hasattr(event1, "_uuid")
461
+ assert hasattr(event1, "uuid")
462
+ assert isinstance(event1._uuid, uuid.UUID)
463
+ assert isinstance(event1.uuid, str)
464
+ assert event1.uuid == f"{event1.type}:{event1._uuid}"
465
+ event2 = scan.make_event("evilcorp.com:80", parent=parent_event2, context="test context")
466
+ assert hasattr(event2, "_uuid")
467
+ assert hasattr(event2, "uuid")
468
+ assert isinstance(event2._uuid, uuid.UUID)
469
+ assert isinstance(event2.uuid, str)
470
+ assert event2.uuid == f"{event2.type}:{event2._uuid}"
471
+ # ids should match because the event type + data is the same
472
+ assert event1.id == event2.id
473
+ # but uuids should be unique!
474
+ assert event1.uuid != event2.uuid
475
+ # parent ids should match
476
+ assert event1.parent_id == event2.parent_id == parent_event1.id == parent_event2.id
477
+ # uuids should not
478
+ assert event1.parent_uuid == parent_event1.uuid
479
+ assert event2.parent_uuid == parent_event2.uuid
480
+ assert event1.parent_uuid != event2.parent_uuid
481
+
415
482
  # test event serialization
416
483
  from bbot.core.event import event_from_json
417
484
 
418
485
  db_event = scan.make_event("evilcorp.com:80", parent=scan.root_event, context="test context")
486
+ assert db_event.parent == scan.root_event
487
+ assert db_event.parent is scan.root_event
419
488
  db_event._resolved_hosts = {"127.0.0.1"}
420
489
  db_event.scope_distance = 1
421
490
  assert db_event.discovery_context == "test context"
422
491
  assert db_event.discovery_path == ["test context"]
423
- assert db_event.parent_chain == ["OPEN_TCP_PORT:5098b5e3fc65b13bb4a5cee4201c2e160fa4ffac"]
492
+ assert len(db_event.parent_chain) == 1
493
+ assert all(event_uuid_regex.match(u) for u in db_event.parent_chain)
494
+ assert db_event.parent_chain[0] == str(db_event.uuid)
495
+ assert db_event.parent.uuid == scan.root_event.uuid
496
+ assert db_event.parent_uuid == scan.root_event.uuid
424
497
  timestamp = db_event.timestamp.isoformat()
425
498
  json_event = db_event.json()
499
+ assert isinstance(json_event["uuid"], str)
500
+ assert json_event["uuid"] == str(db_event.uuid)
501
+ assert json_event["parent_uuid"] == str(scan.root_event.uuid)
426
502
  assert json_event["scope_distance"] == 1
427
503
  assert json_event["data"] == "evilcorp.com:80"
428
504
  assert json_event["type"] == "OPEN_TCP_PORT"
@@ -430,8 +506,14 @@ async def test_events(events, helpers):
430
506
  assert json_event["timestamp"] == timestamp
431
507
  assert json_event["discovery_context"] == "test context"
432
508
  assert json_event["discovery_path"] == ["test context"]
433
- assert json_event["parent_chain"] == ["OPEN_TCP_PORT:5098b5e3fc65b13bb4a5cee4201c2e160fa4ffac"]
509
+ assert json_event["parent_chain"] == db_event.parent_chain
510
+ assert json_event["parent_chain"][0] == str(db_event.uuid)
434
511
  reconstituted_event = event_from_json(json_event)
512
+ assert isinstance(reconstituted_event._uuid, uuid.UUID)
513
+ assert str(reconstituted_event.uuid) == json_event["uuid"]
514
+ assert str(reconstituted_event.parent_uuid) == json_event["parent_uuid"]
515
+ assert reconstituted_event.uuid == db_event.uuid
516
+ assert reconstituted_event.parent_uuid == scan.root_event.uuid
435
517
  assert reconstituted_event.scope_distance == 1
436
518
  assert reconstituted_event.timestamp.isoformat() == timestamp
437
519
  assert reconstituted_event.data == "evilcorp.com:80"
@@ -439,13 +521,13 @@ async def test_events(events, helpers):
439
521
  assert reconstituted_event.host == "evilcorp.com"
440
522
  assert reconstituted_event.discovery_context == "test context"
441
523
  assert reconstituted_event.discovery_path == ["test context"]
442
- assert reconstituted_event.parent_chain == ["OPEN_TCP_PORT:5098b5e3fc65b13bb4a5cee4201c2e160fa4ffac"]
524
+ assert reconstituted_event.parent_chain == db_event.parent_chain
443
525
  assert "127.0.0.1" in reconstituted_event.resolved_hosts
444
526
  hostless_event = scan.make_event("asdf", "ASDF", dummy=True)
445
527
  hostless_event_json = hostless_event.json()
446
528
  assert hostless_event_json["type"] == "ASDF"
447
529
  assert hostless_event_json["data"] == "asdf"
448
- assert not "host" in hostless_event_json
530
+ assert "host" not in hostless_event_json
449
531
 
450
532
  # SIEM-friendly serialize/deserialize
451
533
  json_event_siemfriendly = db_event.json(siem_friendly=True)
@@ -616,7 +698,7 @@ async def test_event_discovery_context():
616
698
  )
617
699
 
618
700
  events = [e async for e in scan.async_start()]
619
- assert len(events) == 6
701
+ assert len(events) == 7
620
702
 
621
703
  assert 1 == len(
622
704
  [
@@ -723,7 +805,7 @@ async def test_event_web_spider_distance(bbot_scanner):
723
805
  )
724
806
  assert url_event_3.web_spider_distance == 1
725
807
  assert "spider-danger" in url_event_3.tags
726
- assert not "spider-max" in url_event_3.tags
808
+ assert "spider-max" not in url_event_3.tags
727
809
  social_event = scan.make_event(
728
810
  {"platform": "github", "url": "http://www.evilcorp.com/test4"}, "SOCIAL", parent=url_event_3
729
811
  )
@@ -746,42 +828,42 @@ async def test_event_web_spider_distance(bbot_scanner):
746
828
 
747
829
  url_event = scan.make_event("http://www.evilcorp.com", "URL_UNVERIFIED", parent=scan.root_event)
748
830
  assert url_event.web_spider_distance == 0
749
- assert not "spider-danger" in url_event.tags
750
- assert not "spider-max" in url_event.tags
831
+ assert "spider-danger" not in url_event.tags
832
+ assert "spider-max" not in url_event.tags
751
833
  url_event_2 = scan.make_event(
752
834
  "http://www.evilcorp.com", "URL_UNVERIFIED", parent=scan.root_event, tags="spider-danger"
753
835
  )
754
836
  # spider distance shouldn't increment because it's not the same host
755
837
  assert url_event_2.web_spider_distance == 0
756
838
  assert "spider-danger" in url_event_2.tags
757
- assert not "spider-max" in url_event_2.tags
839
+ assert "spider-max" not in url_event_2.tags
758
840
  url_event_3 = scan.make_event(
759
841
  "http://www.evilcorp.com/3", "URL_UNVERIFIED", parent=url_event_2, tags="spider-danger"
760
842
  )
761
843
  assert url_event_3.web_spider_distance == 1
762
844
  assert "spider-danger" in url_event_3.tags
763
- assert not "spider-max" in url_event_3.tags
845
+ assert "spider-max" not in url_event_3.tags
764
846
  url_event_4 = scan.make_event("http://evilcorp.com", "URL_UNVERIFIED", parent=url_event_3)
765
847
  assert url_event_4.web_spider_distance == 0
766
- assert not "spider-danger" in url_event_4.tags
767
- assert not "spider-max" in url_event_4.tags
848
+ assert "spider-danger" not in url_event_4.tags
849
+ assert "spider-max" not in url_event_4.tags
768
850
  url_event_4.add_tag("spider-danger")
769
851
  assert url_event_4.web_spider_distance == 0
770
852
  assert "spider-danger" in url_event_4.tags
771
- assert not "spider-max" in url_event_4.tags
853
+ assert "spider-max" not in url_event_4.tags
772
854
  url_event_4.remove_tag("spider-danger")
773
855
  assert url_event_4.web_spider_distance == 0
774
- assert not "spider-danger" in url_event_4.tags
775
- assert not "spider-max" in url_event_4.tags
856
+ assert "spider-danger" not in url_event_4.tags
857
+ assert "spider-max" not in url_event_4.tags
776
858
  url_event_5 = scan.make_event("http://evilcorp.com/5", "URL_UNVERIFIED", parent=url_event_4)
777
859
  assert url_event_5.web_spider_distance == 0
778
- assert not "spider-danger" in url_event_5.tags
779
- assert not "spider-max" in url_event_5.tags
860
+ assert "spider-danger" not in url_event_5.tags
861
+ assert "spider-max" not in url_event_5.tags
780
862
  url_event_5.add_tag("spider-danger")
781
863
  # if host is the same as parent, web spider distance should auto-increment after adding spider-danger tag
782
864
  assert url_event_5.web_spider_distance == 1
783
865
  assert "spider-danger" in url_event_5.tags
784
- assert not "spider-max" in url_event_5.tags
866
+ assert "spider-max" not in url_event_5.tags
785
867
 
786
868
 
787
869
  def test_event_confidence():
@@ -856,3 +938,76 @@ def test_event_closest_host():
856
938
  vuln = scan.make_event(
857
939
  {"path": "/tmp/asdf.txt", "description": "test", "severity": "HIGH"}, "VULNERABILITY", parent=event3
858
940
  )
941
+
942
+
943
+ def test_event_magic():
944
+ from bbot.core.helpers.libmagic import get_magic_info, get_compression
945
+
946
+ import base64
947
+
948
+ zip_base64 = "UEsDBAoDAAAAAOMmZ1lR4FaHBQAAAAUAAAAIAAAAYXNkZi50eHRhc2RmClBLAQI/AwoDAAAAAOMmZ1lR4FaHBQAAAAUAAAAIACQAAAAAAAAAIICkgQAAAABhc2RmLnR4dAoAIAAAAAAAAQAYAICi2B77MNsBgKLYHvsw2wGAotge+zDbAVBLBQYAAAAAAQABAFoAAAArAAAAAAA="
949
+ zip_bytes = base64.b64decode(zip_base64)
950
+ zip_file = Path("/tmp/.bbottestzipasdkfjalsdf.zip")
951
+ with open(zip_file, "wb") as f:
952
+ f.write(zip_bytes)
953
+
954
+ # test magic helpers
955
+ extension, mime_type, description, confidence = get_magic_info(zip_file)
956
+ assert extension == ".zip"
957
+ assert mime_type == "application/zip"
958
+ assert description == "PKZIP Archive file"
959
+ assert confidence > 0
960
+ assert get_compression(mime_type) == "zip"
961
+
962
+ # test filesystem event - file
963
+ scan = Scanner()
964
+ event = scan.make_event({"path": zip_file}, "FILESYSTEM", parent=scan.root_event)
965
+ assert event.data == {
966
+ "path": "/tmp/.bbottestzipasdkfjalsdf.zip",
967
+ "magic_extension": ".zip",
968
+ "magic_mime_type": "application/zip",
969
+ "magic_description": "PKZIP Archive file",
970
+ "magic_confidence": 0.9,
971
+ "compression": "zip",
972
+ }
973
+ assert event.tags == {"file", "zip-archive", "compressed"}
974
+
975
+ # test filesystem event - folder
976
+ scan = Scanner()
977
+ event = scan.make_event({"path": "/tmp"}, "FILESYSTEM", parent=scan.root_event)
978
+ assert event.data == {"path": "/tmp"}
979
+ assert event.tags == {"folder"}
980
+
981
+ zip_file.unlink()
982
+
983
+
984
+ def test_event_hashing():
985
+ scan = Scanner("example.com")
986
+ url_event = scan.make_event("https://api.example.com/", "URL_UNVERIFIED", parent=scan.root_event)
987
+ host_event_1 = scan.make_event("www.example.com", "DNS_NAME", parent=url_event)
988
+ host_event_2 = scan.make_event("test.example.com", "DNS_NAME", parent=url_event)
989
+ finding_data = {"description": "Custom Yara Rule [find_string] Matched via identifier [str1]"}
990
+ finding1 = scan.make_event(finding_data, "FINDING", parent=host_event_1)
991
+ finding2 = scan.make_event(finding_data, "FINDING", parent=host_event_2)
992
+ finding3 = scan.make_event(finding_data, "FINDING", parent=host_event_2)
993
+
994
+ assert finding1.data == {
995
+ "description": "Custom Yara Rule [find_string] Matched via identifier [str1]",
996
+ "host": "www.example.com",
997
+ }
998
+ assert finding2.data == {
999
+ "description": "Custom Yara Rule [find_string] Matched via identifier [str1]",
1000
+ "host": "test.example.com",
1001
+ }
1002
+ assert finding3.data == {
1003
+ "description": "Custom Yara Rule [find_string] Matched via identifier [str1]",
1004
+ "host": "test.example.com",
1005
+ }
1006
+ assert finding1.id != finding2.id
1007
+ assert finding2.id == finding3.id
1008
+ assert finding1.data_id != finding2.data_id
1009
+ assert finding2.data_id == finding3.data_id
1010
+ assert finding1.data_hash != finding2.data_hash
1011
+ assert finding2.data_hash == finding3.data_hash
1012
+ assert hash(finding1) != hash(finding2)
1013
+ assert hash(finding2) == hash(finding3)