moriarty-project 0.1.6__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.
Files changed (416) hide show
  1. moriarty/__init__.py +5 -0
  2. moriarty/adapters/__init__.py +0 -0
  3. moriarty/agent/__init__.py +0 -0
  4. moriarty/assets/modules/.gitkeep +0 -0
  5. moriarty/assets/modules/asia/douban.yaml +19 -0
  6. moriarty/assets/modules/asia/kakao.yaml +19 -0
  7. moriarty/assets/modules/asia/line.yaml +19 -0
  8. moriarty/assets/modules/asia/mixi.yaml +19 -0
  9. moriarty/assets/modules/asia/naver.yaml +19 -0
  10. moriarty/assets/modules/asia/qq.yaml +19 -0
  11. moriarty/assets/modules/asia/vk.yaml +19 -0
  12. moriarty/assets/modules/asia/wechat.yaml +19 -0
  13. moriarty/assets/modules/asia/weibo.yaml +19 -0
  14. moriarty/assets/modules/asia/xiaohongshu.yaml +19 -0
  15. moriarty/assets/modules/behance.yaml +47 -0
  16. moriarty/assets/modules/business/crunchbase.yaml +27 -0
  17. moriarty/assets/modules/business/fiverr.yaml +32 -0
  18. moriarty/assets/modules/business/freelancer.yaml +27 -0
  19. moriarty/assets/modules/business/glassdoor.yaml +27 -0
  20. moriarty/assets/modules/business/guru.yaml +26 -0
  21. moriarty/assets/modules/business/indeed.yaml +25 -0
  22. moriarty/assets/modules/business/monster.yaml +25 -0
  23. moriarty/assets/modules/business/peopleperhour.yaml +26 -0
  24. moriarty/assets/modules/business/toptal.yaml +28 -0
  25. moriarty/assets/modules/business/upwork.yaml +27 -0
  26. moriarty/assets/modules/business/ziprecruiter.yaml +25 -0
  27. moriarty/assets/modules/content/buymeacoffee.yaml +27 -0
  28. moriarty/assets/modules/content/gumroad.yaml +27 -0
  29. moriarty/assets/modules/content/ko-fi.yaml +32 -0
  30. moriarty/assets/modules/content/onlyfans.yaml +27 -0
  31. moriarty/assets/modules/content/patreon.yaml +33 -0
  32. moriarty/assets/modules/content/substack.yaml +32 -0
  33. moriarty/assets/modules/creative/500px.yaml +31 -0
  34. moriarty/assets/modules/creative/artstation.yaml +33 -0
  35. moriarty/assets/modules/creative/deviantart.yaml +32 -0
  36. moriarty/assets/modules/creative/flickr.yaml +31 -0
  37. moriarty/assets/modules/creative/pexels.yaml +26 -0
  38. moriarty/assets/modules/creative/unsplash.yaml +26 -0
  39. moriarty/assets/modules/creative/vimeo.yaml +31 -0
  40. moriarty/assets/modules/crypto/binance.yaml +27 -0
  41. moriarty/assets/modules/crypto/bitcointalk.yaml +33 -0
  42. moriarty/assets/modules/crypto/coinbase.yaml +26 -0
  43. moriarty/assets/modules/crypto/etherscan.yaml +32 -0
  44. moriarty/assets/modules/crypto/foundation.yaml +28 -0
  45. moriarty/assets/modules/crypto/kraken.yaml +27 -0
  46. moriarty/assets/modules/crypto/mirror.yaml +27 -0
  47. moriarty/assets/modules/crypto/niftygateway.yaml +26 -0
  48. moriarty/assets/modules/crypto/opensea.yaml +32 -0
  49. moriarty/assets/modules/crypto/rarible.yaml +27 -0
  50. moriarty/assets/modules/crypto/superrare.yaml +29 -0
  51. moriarty/assets/modules/dating/bumble.yaml +25 -0
  52. moriarty/assets/modules/dating/grindr.yaml +27 -0
  53. moriarty/assets/modules/dating/happn.yaml +25 -0
  54. moriarty/assets/modules/dating/her.yaml +27 -0
  55. moriarty/assets/modules/dating/hinge.yaml +25 -0
  56. moriarty/assets/modules/dating/match.yaml +25 -0
  57. moriarty/assets/modules/dating/meetme.yaml +27 -0
  58. moriarty/assets/modules/dating/okcupid.yaml +25 -0
  59. moriarty/assets/modules/dating/pof.yaml +25 -0
  60. moriarty/assets/modules/dating/tinder.yaml +25 -0
  61. moriarty/assets/modules/dating-nsfw/adultfriendfinder.yaml +28 -0
  62. moriarty/assets/modules/dating-nsfw/ashley-madison.yaml +26 -0
  63. moriarty/assets/modules/design/adobe-portfolio.yaml +27 -0
  64. moriarty/assets/modules/design/carbonmade.yaml +27 -0
  65. moriarty/assets/modules/design/cgsociety.yaml +27 -0
  66. moriarty/assets/modules/design/coroflot.yaml +27 -0
  67. moriarty/assets/modules/design/figma.yaml +27 -0
  68. moriarty/assets/modules/design/sketch.yaml +26 -0
  69. moriarty/assets/modules/dev/bitbucket.yaml +35 -0
  70. moriarty/assets/modules/dev/codeforces.yaml +32 -0
  71. moriarty/assets/modules/dev/codepen.yaml +34 -0
  72. moriarty/assets/modules/dev/hackerone.yaml +32 -0
  73. moriarty/assets/modules/dev/hackthebox.yaml +27 -0
  74. moriarty/assets/modules/dev/huggingface.yaml +27 -0
  75. moriarty/assets/modules/dev/kaggle.yaml +32 -0
  76. moriarty/assets/modules/dev/leetcode.yaml +32 -0
  77. moriarty/assets/modules/dev/replit.yaml +31 -0
  78. moriarty/assets/modules/dribbble.yaml +53 -0
  79. moriarty/assets/modules/ecommerce/etsy.yaml +32 -0
  80. moriarty/assets/modules/education/duolingo.yaml +32 -0
  81. moriarty/assets/modules/education/edx.yaml +26 -0
  82. moriarty/assets/modules/education/khanacademy.yaml +26 -0
  83. moriarty/assets/modules/education/lynda.yaml +27 -0
  84. moriarty/assets/modules/education/memrise.yaml +27 -0
  85. moriarty/assets/modules/education/pluralsight.yaml +27 -0
  86. moriarty/assets/modules/education/skillshare.yaml +27 -0
  87. moriarty/assets/modules/education/udacity.yaml +27 -0
  88. moriarty/assets/modules/email/github_email.yaml +40 -0
  89. moriarty/assets/modules/email/gravatar.yaml +23 -0
  90. moriarty/assets/modules/europe/badoo.yaml +19 -0
  91. moriarty/assets/modules/europe/lovoo.yaml +19 -0
  92. moriarty/assets/modules/europe/myspace.yaml +19 -0
  93. moriarty/assets/modules/europe/netlog.yaml +19 -0
  94. moriarty/assets/modules/europe/ok.yaml +19 -0
  95. moriarty/assets/modules/europe/skyrock.yaml +19 -0
  96. moriarty/assets/modules/europe/studivz.yaml +19 -0
  97. moriarty/assets/modules/europe/tuenti.yaml +19 -0
  98. moriarty/assets/modules/europe/viadeo.yaml +19 -0
  99. moriarty/assets/modules/europe/xing.yaml +19 -0
  100. moriarty/assets/modules/fitness/fitbit.yaml +27 -0
  101. moriarty/assets/modules/fitness/garmin.yaml +27 -0
  102. moriarty/assets/modules/fitness/myfitnesspal.yaml +27 -0
  103. moriarty/assets/modules/fitness/strava.yaml +33 -0
  104. moriarty/assets/modules/fitness/zwift.yaml +28 -0
  105. moriarty/assets/modules/food/allrecipes.yaml +27 -0
  106. moriarty/assets/modules/food/tasty.yaml +27 -0
  107. moriarty/assets/modules/food/yelp.yaml +32 -0
  108. moriarty/assets/modules/food/zomato.yaml +28 -0
  109. moriarty/assets/modules/forums/4chan.yaml +26 -0
  110. moriarty/assets/modules/forums/8kun.yaml +26 -0
  111. moriarty/assets/modules/forums/9gag.yaml +26 -0
  112. moriarty/assets/modules/forums/discourse.yaml +26 -0
  113. moriarty/assets/modules/forums/disqus.yaml +31 -0
  114. moriarty/assets/modules/forums/hackernews.yaml +32 -0
  115. moriarty/assets/modules/forums/launchpad.yaml +27 -0
  116. moriarty/assets/modules/forums/phpbb.yaml +25 -0
  117. moriarty/assets/modules/forums/quora.yaml +32 -0
  118. moriarty/assets/modules/forums/serverfault.yaml +27 -0
  119. moriarty/assets/modules/forums/slashdot.yaml +28 -0
  120. moriarty/assets/modules/forums/stackexchange.yaml +32 -0
  121. moriarty/assets/modules/forums/superuser.yaml +27 -0
  122. moriarty/assets/modules/forums/vbulletin.yaml +25 -0
  123. moriarty/assets/modules/forums/xenforo.yaml +25 -0
  124. moriarty/assets/modules/forums-nsfw/kiwifarms.yaml +25 -0
  125. moriarty/assets/modules/forums-nsfw/lolcow.yaml +26 -0
  126. moriarty/assets/modules/gaming/apextracker.yaml +27 -0
  127. moriarty/assets/modules/gaming/battlenet.yaml +26 -0
  128. moriarty/assets/modules/gaming/chess.yaml +30 -0
  129. moriarty/assets/modules/gaming/discord-public.yaml +27 -0
  130. moriarty/assets/modules/gaming/dotabuff.yaml +32 -0
  131. moriarty/assets/modules/gaming/epicgames.yaml +25 -0
  132. moriarty/assets/modules/gaming/faceit.yaml +33 -0
  133. moriarty/assets/modules/gaming/fortnitetracker.yaml +32 -0
  134. moriarty/assets/modules/gaming/gog.yaml +26 -0
  135. moriarty/assets/modules/gaming/itch.yaml +32 -0
  136. moriarty/assets/modules/gaming/kongregate.yaml +25 -0
  137. moriarty/assets/modules/gaming/minecraft.yaml +31 -0
  138. moriarty/assets/modules/gaming/opgg.yaml +32 -0
  139. moriarty/assets/modules/gaming/origin.yaml +26 -0
  140. moriarty/assets/modules/gaming/playstation.yaml +30 -0
  141. moriarty/assets/modules/gaming/roblox.yaml +31 -0
  142. moriarty/assets/modules/gaming/xbox.yaml +25 -0
  143. moriarty/assets/modules/github.yaml +68 -0
  144. moriarty/assets/modules/gitlab.yaml +60 -0
  145. moriarty/assets/modules/instagram.yaml +48 -0
  146. moriarty/assets/modules/latam/fotolog.yaml +27 -0
  147. moriarty/assets/modules/latam/orkut.yaml +26 -0
  148. moriarty/assets/modules/latam/taringa.yaml +27 -0
  149. moriarty/assets/modules/learning/coursera.yaml +26 -0
  150. moriarty/assets/modules/learning/udemy.yaml +26 -0
  151. moriarty/assets/modules/linkedin.yaml +40 -0
  152. moriarty/assets/modules/marketplaces/depop.yaml +28 -0
  153. moriarty/assets/modules/marketplaces/ebay.yaml +32 -0
  154. moriarty/assets/modules/marketplaces/grailed.yaml +27 -0
  155. moriarty/assets/modules/marketplaces/mercari.yaml +26 -0
  156. moriarty/assets/modules/marketplaces/poshmark.yaml +27 -0
  157. moriarty/assets/modules/marketplaces/reverb.yaml +27 -0
  158. moriarty/assets/modules/marketplaces/vinted.yaml +28 -0
  159. moriarty/assets/modules/medium.yaml +44 -0
  160. moriarty/assets/modules/music/audiomack.yaml +26 -0
  161. moriarty/assets/modules/music/bandcamp.yaml +30 -0
  162. moriarty/assets/modules/music/beatport.yaml +28 -0
  163. moriarty/assets/modules/music/deezer.yaml +26 -0
  164. moriarty/assets/modules/music/discogs.yaml +32 -0
  165. moriarty/assets/modules/music/genius.yaml +26 -0
  166. moriarty/assets/modules/music/lastfm.yaml +30 -0
  167. moriarty/assets/modules/music/mixcloud.yaml +26 -0
  168. moriarty/assets/modules/music/reverbnation.yaml +31 -0
  169. moriarty/assets/modules/music/soundcloud.yaml +31 -0
  170. moriarty/assets/modules/music/spotify.yaml +26 -0
  171. moriarty/assets/modules/music/tidal.yaml +26 -0
  172. moriarty/assets/modules/nsfw/adultwork.yaml +27 -0
  173. moriarty/assets/modules/nsfw/bongacams.yaml +28 -0
  174. moriarty/assets/modules/nsfw/cam4.yaml +28 -0
  175. moriarty/assets/modules/nsfw/chaturbate.yaml +28 -0
  176. moriarty/assets/modules/nsfw/clips4sale.yaml +27 -0
  177. moriarty/assets/modules/nsfw/extralunchmoney.yaml +27 -0
  178. moriarty/assets/modules/nsfw/fansly.yaml +28 -0
  179. moriarty/assets/modules/nsfw/fetlife.yaml +28 -0
  180. moriarty/assets/modules/nsfw/iwantclips.yaml +27 -0
  181. moriarty/assets/modules/nsfw/justforfans.yaml +28 -0
  182. moriarty/assets/modules/nsfw/loyalfans.yaml +28 -0
  183. moriarty/assets/modules/nsfw/manyvids.yaml +27 -0
  184. moriarty/assets/modules/nsfw/myfreecams.yaml +28 -0
  185. moriarty/assets/modules/nsfw/niteflirt.yaml +26 -0
  186. moriarty/assets/modules/nsfw/pornhub.yaml +32 -0
  187. moriarty/assets/modules/nsfw/redtube.yaml +27 -0
  188. moriarty/assets/modules/nsfw/stripchat.yaml +28 -0
  189. moriarty/assets/modules/nsfw/xhamster.yaml +27 -0
  190. moriarty/assets/modules/nsfw/xvideos.yaml +27 -0
  191. moriarty/assets/modules/nsfw/youporn.yaml +27 -0
  192. moriarty/assets/modules/photography/eyeem.yaml +25 -0
  193. moriarty/assets/modules/photography/fotki.yaml +25 -0
  194. moriarty/assets/modules/photography/photobucket.yaml +26 -0
  195. moriarty/assets/modules/photography/smugmug.yaml +25 -0
  196. moriarty/assets/modules/photography/vsco.yaml +27 -0
  197. moriarty/assets/modules/pinterest.yaml +40 -0
  198. moriarty/assets/modules/podcasts/anchor.yaml +26 -0
  199. moriarty/assets/modules/podcasts/castbox.yaml +26 -0
  200. moriarty/assets/modules/podcasts/podbean.yaml +26 -0
  201. moriarty/assets/modules/professional/about.yaml +31 -0
  202. moriarty/assets/modules/professional/academia.yaml +27 -0
  203. moriarty/assets/modules/professional/angellist.yaml +27 -0
  204. moriarty/assets/modules/professional/calendly.yaml +26 -0
  205. moriarty/assets/modules/professional/issuu.yaml +27 -0
  206. moriarty/assets/modules/professional/mendeley.yaml +27 -0
  207. moriarty/assets/modules/professional/notion.yaml +27 -0
  208. moriarty/assets/modules/professional/orcid.yaml +27 -0
  209. moriarty/assets/modules/professional/producthunt.yaml +31 -0
  210. moriarty/assets/modules/professional/researchgate.yaml +32 -0
  211. moriarty/assets/modules/professional/scribd.yaml +27 -0
  212. moriarty/assets/modules/professional/slideshare.yaml +31 -0
  213. moriarty/assets/modules/professional/trello.yaml +26 -0
  214. moriarty/assets/modules/professional/typeform.yaml +27 -0
  215. moriarty/assets/modules/reddit.yaml +46 -0
  216. moriarty/assets/modules/regional/amino.yaml +27 -0
  217. moriarty/assets/modules/regional/ask-fm.yaml +32 -0
  218. moriarty/assets/modules/regional/babycenter.yaml +26 -0
  219. moriarty/assets/modules/regional/cafemom.yaml +27 -0
  220. moriarty/assets/modules/regional/care2.yaml +27 -0
  221. moriarty/assets/modules/regional/diaspora.yaml +26 -0
  222. moriarty/assets/modules/regional/ello.yaml +27 -0
  223. moriarty/assets/modules/regional/gaia.yaml +27 -0
  224. moriarty/assets/modules/regional/habbo.yaml +27 -0
  225. moriarty/assets/modules/regional/imvu.yaml +27 -0
  226. moriarty/assets/modules/regional/lemmy.yaml +27 -0
  227. moriarty/assets/modules/regional/peertube.yaml +26 -0
  228. moriarty/assets/modules/regional/pixelfed.yaml +27 -0
  229. moriarty/assets/modules/regional/plurk.yaml +26 -0
  230. moriarty/assets/modules/regional/recroom.yaml +27 -0
  231. moriarty/assets/modules/regional/secondlife.yaml +26 -0
  232. moriarty/assets/modules/regional/vine-archive.yaml +27 -0
  233. moriarty/assets/modules/regional/vrchat.yaml +27 -0
  234. moriarty/assets/modules/regional/weheartit.yaml +27 -0
  235. moriarty/assets/modules/social/anilist.yaml +27 -0
  236. moriarty/assets/modules/social/beacons.yaml +26 -0
  237. moriarty/assets/modules/social/blogger.yaml +27 -0
  238. moriarty/assets/modules/social/crunchyroll.yaml +27 -0
  239. moriarty/assets/modules/social/discord.yaml +27 -0
  240. moriarty/assets/modules/social/dreamwidth.yaml +26 -0
  241. moriarty/assets/modules/social/facebook.yaml +34 -0
  242. moriarty/assets/modules/social/goodreads.yaml +32 -0
  243. moriarty/assets/modules/social/imdb.yaml +27 -0
  244. moriarty/assets/modules/social/kitsu.yaml +27 -0
  245. moriarty/assets/modules/social/letterboxd.yaml +32 -0
  246. moriarty/assets/modules/social/linktree.yaml +26 -0
  247. moriarty/assets/modules/social/livejournal.yaml +27 -0
  248. moriarty/assets/modules/social/mastodon.yaml +30 -0
  249. moriarty/assets/modules/social/minds.yaml +25 -0
  250. moriarty/assets/modules/social/myanimelist.yaml +32 -0
  251. moriarty/assets/modules/social/ravelry.yaml +27 -0
  252. moriarty/assets/modules/social/snapchat.yaml +25 -0
  253. moriarty/assets/modules/social/telegram.yaml +35 -0
  254. moriarty/assets/modules/social/tiktok.yaml +35 -0
  255. moriarty/assets/modules/social/trakt.yaml +28 -0
  256. moriarty/assets/modules/social/wattpad.yaml +32 -0
  257. moriarty/assets/modules/social/wordpress-com.yaml +26 -0
  258. moriarty/assets/modules/sports/espn.yaml +26 -0
  259. moriarty/assets/modules/sports/untappd.yaml +32 -0
  260. moriarty/assets/modules/stackoverflow.yaml +47 -0
  261. moriarty/assets/modules/steam.yaml +47 -0
  262. moriarty/assets/modules/streaming/caffeine.yaml +25 -0
  263. moriarty/assets/modules/streaming/dlive.yaml +27 -0
  264. moriarty/assets/modules/streaming/trovo.yaml +25 -0
  265. moriarty/assets/modules/travel/airbnb.yaml +26 -0
  266. moriarty/assets/modules/travel/booking.yaml +26 -0
  267. moriarty/assets/modules/travel/couchsurfing.yaml +27 -0
  268. moriarty/assets/modules/travel/tripadvisor.yaml +32 -0
  269. moriarty/assets/modules/tumblr.yaml +40 -0
  270. moriarty/assets/modules/twitch.yaml +48 -0
  271. moriarty/assets/modules/twitter.yaml +39 -0
  272. moriarty/assets/modules/youtube.yaml +42 -0
  273. moriarty/assets/templates/cves/CVE-2017-5638.yaml +27 -0
  274. moriarty/assets/templates/cves/CVE-2018-7600.yaml +30 -0
  275. moriarty/assets/templates/cves/CVE-2019-11510.yaml +27 -0
  276. moriarty/assets/templates/cves/CVE-2019-19781.yaml +28 -0
  277. moriarty/assets/templates/cves/CVE-2020-14882.yaml +28 -0
  278. moriarty/assets/templates/cves/CVE-2020-14883.yaml +29 -0
  279. moriarty/assets/templates/cves/CVE-2020-3452.yaml +28 -0
  280. moriarty/assets/templates/cves/CVE-2020-5902.yaml +28 -0
  281. moriarty/assets/templates/cves/CVE-2021-21972.yaml +31 -0
  282. moriarty/assets/templates/cves/CVE-2021-21985.yaml +28 -0
  283. moriarty/assets/templates/cves/CVE-2021-26084.yaml +30 -0
  284. moriarty/assets/templates/cves/CVE-2021-41773.yaml +25 -0
  285. moriarty/assets/templates/cves/CVE-2021-42013.yaml +28 -0
  286. moriarty/assets/templates/cves/CVE-2021-44228.yaml +27 -0
  287. moriarty/assets/templates/cves/CVE-2022-0185.yaml +21 -0
  288. moriarty/assets/templates/cves/CVE-2022-1388.yaml +36 -0
  289. moriarty/assets/templates/cves/CVE-2022-22954.yaml +28 -0
  290. moriarty/assets/templates/cves/CVE-2022-22965.yaml +31 -0
  291. moriarty/assets/templates/cves/CVE-2022-26134.yaml +27 -0
  292. moriarty/assets/templates/cves/CVE-2023-22515.yaml +27 -0
  293. moriarty/assets/templates/cves/CVE-2023-22527.yaml +29 -0
  294. moriarty/assets/templates/cves/CVE-2023-23752.yaml +33 -0
  295. moriarty/assets/templates/cves/CVE-2023-27350.yaml +27 -0
  296. moriarty/assets/templates/cves/CVE-2023-2868.yaml +27 -0
  297. moriarty/assets/templates/cves/CVE-2023-34362.yaml +27 -0
  298. moriarty/assets/templates/cves/CVE-2023-3519.yaml +28 -0
  299. moriarty/assets/templates/cves/CVE-2023-4966.yaml +27 -0
  300. moriarty/assets/templates/default-logins/admin-weak.yaml +40 -0
  301. moriarty/assets/templates/default-logins/wordpress-default.yaml +38 -0
  302. moriarty/assets/templates/exposures/aws-credentials.yaml +35 -0
  303. moriarty/assets/templates/exposures/backup-files.yaml +36 -0
  304. moriarty/assets/templates/exposures/database-files.yaml +34 -0
  305. moriarty/assets/templates/exposures/docker-exposed.yaml +31 -0
  306. moriarty/assets/templates/exposures/env-exposed.yaml +41 -0
  307. moriarty/assets/templates/exposures/git-exposed.yaml +41 -0
  308. moriarty/assets/templates/exposures/phpinfo.yaml +36 -0
  309. moriarty/assets/templates/exposures/svn-exposed.yaml +28 -0
  310. moriarty/assets/templates/fuzzing/api-endpoints.yaml +39 -0
  311. moriarty/assets/templates/fuzzing/common-files.yaml +37 -0
  312. moriarty/assets/templates/fuzzing/open-redirect-fuzz.yaml +35 -0
  313. moriarty/assets/templates/fuzzing/xss-search-fuzz.yaml +29 -0
  314. moriarty/assets/templates/git-config.yaml +18 -0
  315. moriarty/assets/templates/misconfigurations/cors-misconfiguration.yaml +30 -0
  316. moriarty/assets/templates/misconfigurations/debug-enabled.yaml +29 -0
  317. moriarty/assets/templates/misconfigurations/directory-listing.yaml +33 -0
  318. moriarty/assets/templates/misconfigurations/jwt-none-algo.yaml +30 -0
  319. moriarty/assets/templates/misconfigurations/ssl-tls-weak.yaml +23 -0
  320. moriarty/assets/templates/vulnerabilities/lfi-basic.yaml +31 -0
  321. moriarty/assets/templates/vulnerabilities/open-redirect.yaml +31 -0
  322. moriarty/assets/templates/vulnerabilities/rce-basic.yaml +34 -0
  323. moriarty/assets/templates/vulnerabilities/sqli-error.yaml +39 -0
  324. moriarty/assets/templates/vulnerabilities/ssrf-basic.yaml +31 -0
  325. moriarty/assets/templates/vulnerabilities/xss-reflected.yaml +38 -0
  326. moriarty/assets/templates/vulnerabilities/xxe-basic.yaml +30 -0
  327. moriarty/assets/wordlists/subdomains-1000.txt +1063 -0
  328. moriarty/cli/__init__.py +3 -0
  329. moriarty/cli/app.py +120 -0
  330. moriarty/cli/async_utils.py +19 -0
  331. moriarty/cli/dns.py +83 -0
  332. moriarty/cli/domain_cmd.py +572 -0
  333. moriarty/cli/email.py +383 -0
  334. moriarty/cli/email_investigate.py +224 -0
  335. moriarty/cli/intelligence.py +329 -0
  336. moriarty/cli/output.py +62 -0
  337. moriarty/cli/rdap.py +94 -0
  338. moriarty/cli/state.py +38 -0
  339. moriarty/cli/tls.py +91 -0
  340. moriarty/cli/user.py +227 -0
  341. moriarty/core/cache_backend.py +223 -0
  342. moriarty/core/config_manager.py +303 -0
  343. moriarty/correlator/__init__.py +0 -0
  344. moriarty/data/__init__.py +81 -0
  345. moriarty/data/ioc/__init__.py +142 -0
  346. moriarty/data/ioc/matcher.py +254 -0
  347. moriarty/data/ioc/types.py +267 -0
  348. moriarty/data/local_intelligence.py +507 -0
  349. moriarty/data/signature_loaders/__init__.py +103 -0
  350. moriarty/data/signature_loaders/base.py +54 -0
  351. moriarty/data/signature_loaders/ioc_feed.py +356 -0
  352. moriarty/data/signature_loaders/wappalyzer.py +112 -0
  353. moriarty/dsl/__init__.py +0 -0
  354. moriarty/dsl/loader.py +99 -0
  355. moriarty/dsl/schema.py +47 -0
  356. moriarty/export/__init__.py +0 -0
  357. moriarty/intelligence/__init__.py +27 -0
  358. moriarty/intelligence/__main__.py +150 -0
  359. moriarty/intelligence/config.py +395 -0
  360. moriarty/intelligence/ioc.py +267 -0
  361. moriarty/intelligence/signatures.py +550 -0
  362. moriarty/intelligence/storage.py +501 -0
  363. moriarty/interop/__init__.py +0 -0
  364. moriarty/logging/__init__.py +0 -0
  365. moriarty/logging/config.py +47 -0
  366. moriarty/models/__init__.py +16 -0
  367. moriarty/models/assertion.py +24 -0
  368. moriarty/models/entity.py +22 -0
  369. moriarty/models/evidence.py +37 -0
  370. moriarty/models/relation.py +24 -0
  371. moriarty/models/types.py +28 -0
  372. moriarty/modules/__init__.py +0 -0
  373. moriarty/modules/avatar_hash.py +184 -0
  374. moriarty/modules/directory_fuzzer.py +322 -0
  375. moriarty/modules/dns_scan.py +40 -0
  376. moriarty/modules/domain_scanner.py +620 -0
  377. moriarty/modules/email_check.py +98 -0
  378. moriarty/modules/email_investigate.py +267 -0
  379. moriarty/modules/email_security.py +274 -0
  380. moriarty/modules/googlemaps_lookup.py +106 -0
  381. moriarty/modules/headless_executor.py +201 -0
  382. moriarty/modules/orchestrator.py +60 -0
  383. moriarty/modules/passive_recon.py +444 -0
  384. moriarty/modules/phone_extractor.py +151 -0
  385. moriarty/modules/pipeline_orchestrator.py +726 -0
  386. moriarty/modules/port_scanner.py +129 -0
  387. moriarty/modules/rdap.py +61 -0
  388. moriarty/modules/rdap_extended.py +188 -0
  389. moriarty/modules/stealth_mode.py +610 -0
  390. moriarty/modules/subdomain_discovery.py +595 -0
  391. moriarty/modules/technology_profiler.py +361 -0
  392. moriarty/modules/template_executor.py +239 -0
  393. moriarty/modules/template_scanner.py +1048 -0
  394. moriarty/modules/tls_scan.py +46 -0
  395. moriarty/modules/tls_validator.py +188 -0
  396. moriarty/modules/vuln_scanner.py +483 -0
  397. moriarty/modules/waf_detector.py +585 -0
  398. moriarty/modules/wayback_discovery.py +234 -0
  399. moriarty/modules/web_crawler.py +163 -0
  400. moriarty/net/__init__.py +0 -0
  401. moriarty/net/dns_cache.py +175 -0
  402. moriarty/net/dns_client.py +188 -0
  403. moriarty/net/rdap_client.py +52 -0
  404. moriarty/net/smtp_client.py +114 -0
  405. moriarty/net/tls_client.py +111 -0
  406. moriarty/parsers/__init__.py +0 -0
  407. moriarty/parsers/html_parser.py +136 -0
  408. moriarty/tests/__init__.py +0 -0
  409. moriarty/tests/test_email_service.py +17 -0
  410. moriarty/tests/test_models.py +46 -0
  411. moriarty/tests/test_orchestrator.py +30 -0
  412. moriarty/tests/test_tls_client.py +18 -0
  413. moriarty_project-0.1.6.dist-info/METADATA +388 -0
  414. moriarty_project-0.1.6.dist-info/RECORD +418 -0
  415. moriarty_project-0.1.6.dist-info/WHEEL +4 -0
  416. moriarty_project-0.1.6.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,1048 @@
1
+ """Template Scanner - Sistema estilo Nuclei para detecção de vulnerabilidades."""
2
+ import asyncio
3
+ import itertools
4
+ import json
5
+ import random
6
+ import re
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List, Optional, Set, Tuple, TYPE_CHECKING
10
+ from urllib.parse import parse_qs, urlencode, urlparse, urlsplit, urlunsplit
11
+
12
+ import httpx
13
+ import structlog
14
+ import yaml
15
+ from rich.console import Console
16
+ from rich.progress import Progress
17
+
18
+ if TYPE_CHECKING: # pragma: no cover - apenas para type hints
19
+ from moriarty.modules.stealth_mode import StealthMode
20
+
21
+ logger = structlog.get_logger(__name__)
22
+ console = Console()
23
+
24
+
25
+ @dataclass
26
+ class TemplateFinding:
27
+ """Resultado de template scan."""
28
+ template_id: str
29
+ name: str
30
+ severity: str
31
+ description: str
32
+ matched_at: str
33
+ extracted_data: Dict[str, Any]
34
+
35
+
36
+ @dataclass
37
+ class Template:
38
+ """Template de vulnerabilidade."""
39
+ id: str
40
+ info: Dict[str, Any]
41
+ requests: List[Dict[str, Any]]
42
+ matchers: List[Dict[str, Any]]
43
+ extractors: Optional[List[Dict[str, Any]]] = None
44
+ matchers_condition: str = "or"
45
+ tags: Set[str] = field(default_factory=set)
46
+
47
+
48
+ class TemplateScanner:
49
+ """
50
+ Scanner baseado em templates YAML (estilo Nuclei).
51
+
52
+ Suporta:
53
+ - HTTP requests
54
+ - Matchers (status, word, regex, dsl)
55
+ - Extractors (regex, kval, json)
56
+ - Variables
57
+ - Payloads
58
+ - Raw requests
59
+ """
60
+
61
+ FUZZ_PAYLOADS: Dict[str, List[str]] = {
62
+ "sqli": ["' OR '1'='1", "' UNION SELECT NULL--", "1; DROP TABLE users"],
63
+ "xss": ["<script>alert(1)</script>", '" onmouseover="alert(1)"', "'><img src=x onerror=alert(1)>"],
64
+ "lfi": ["../../../../etc/passwd", "..\\..\\..\\..\\windows\\win.ini"],
65
+ "cmd": [";id", "| whoami", "$(whoami)", "&& dir"],
66
+ "path": ["..;/", "..%2f"],
67
+ }
68
+
69
+ def __init__(
70
+ self,
71
+ target: str,
72
+ templates_path: Optional[str] = None,
73
+ severity_filter: Optional[List[str]] = None,
74
+ threads: int = 20,
75
+ timeout: float = 10.0,
76
+ tag_filter: Optional[List[str]] = None,
77
+ stealth: Optional["StealthMode"] = None,
78
+ ):
79
+ self.target = target
80
+ self.templates_path = templates_path or self._get_default_templates_path()
81
+ self.severity_filter = severity_filter
82
+ self.threads = threads
83
+ self.timeout = timeout
84
+ self.tag_filter = {tag.lower() for tag in tag_filter} if tag_filter else None
85
+ self.stealth = stealth
86
+ self.findings: List[TemplateFinding] = []
87
+ self._finding_cache: Set[Tuple[str, str]] = set()
88
+ self.template_ids: Set[str] = set()
89
+
90
+ normalized_target = self._ensure_scheme(target)
91
+ parsed = urlparse(normalized_target)
92
+ self.scheme = parsed.scheme or "https"
93
+ self.host = parsed.netloc or parsed.path
94
+ self.base_url = f"{self.scheme}://{self.host}"
95
+
96
+ # Mantém alvo original para logging
97
+ self.target = normalized_target
98
+
99
+ def _stealth_headers(self) -> Dict[str, str]:
100
+ if not self.stealth:
101
+ return {}
102
+ headers = self.stealth.get_random_headers()
103
+ headers.setdefault("User-Agent", headers.get("User-Agent", "Mozilla/5.0 (Moriarty Recon)"))
104
+ headers.setdefault("Accept", "*/*")
105
+ return headers
106
+
107
+ async def _stealth_delay(self) -> None:
108
+ if not self.stealth:
109
+ return
110
+ config = getattr(self.stealth, "config", None)
111
+ if not config or not getattr(config, "timing_randomization", False):
112
+ return
113
+ await asyncio.sleep(random.uniform(0.05, 0.2) * max(1, self.stealth.level))
114
+
115
+ def _get_default_templates_path(self) -> str:
116
+ """Retorna path padrão de templates."""
117
+ return str(Path(__file__).parent.parent / "assets" / "templates")
118
+
119
+ def _ensure_scheme(self, target: str) -> str:
120
+ """Garante que o alvo possua esquema HTTP/S."""
121
+ if target.startswith("http://") or target.startswith("https://"):
122
+ return target
123
+ return f"https://{target}"
124
+
125
+ async def scan(self) -> List[TemplateFinding]:
126
+ """Executa scan usando templates."""
127
+ logger.info("template.scan.start", target=self.target, templates_path=self.templates_path)
128
+
129
+ # Carrega templates
130
+ templates = self._load_templates()
131
+
132
+ # Filtra por severidade
133
+ if self.severity_filter:
134
+ templates = [t for t in templates if t.info.get("severity") in self.severity_filter]
135
+
136
+ if self.tag_filter:
137
+ filtered = [t for t in templates if t.tags and (t.tags & self.tag_filter)]
138
+ if filtered:
139
+ templates = filtered
140
+ else:
141
+ logger.debug("template.scan.tag_filter_empty", tags=sorted(self.tag_filter))
142
+
143
+ console.print(f"[cyan]📝 Loaded {len(templates)} templates[/cyan]\n")
144
+
145
+ # Executa templates
146
+ async with httpx.AsyncClient(timeout=self.timeout, follow_redirects=True) as client:
147
+ semaphore = asyncio.Semaphore(self.threads)
148
+
149
+ tasks = [
150
+ self._execute_template(client, semaphore, template)
151
+ for template in templates
152
+ ]
153
+
154
+ with Progress() as progress:
155
+ task_id = progress.add_task("[cyan]Scanning...", total=len(tasks))
156
+
157
+ for coro in asyncio.as_completed(tasks):
158
+ await coro
159
+ progress.advance(task_id)
160
+
161
+ logger.info("template.scan.complete", findings=len(self.findings))
162
+ return self.findings
163
+
164
+ def _load_templates(self) -> List[Template]:
165
+ """Carrega templates de diretório."""
166
+ templates = []
167
+ templates_dir = Path(self.templates_path)
168
+
169
+ if not templates_dir.exists():
170
+ logger.warning("template.load.not_found", path=self.templates_path)
171
+ # Cria templates de exemplo
172
+ self._create_example_templates()
173
+ return self._load_templates()
174
+
175
+ self.template_ids.clear()
176
+
177
+ # Busca arquivos .yaml recursivamente
178
+ for yaml_file in templates_dir.rglob("*.yaml"):
179
+ try:
180
+ with open(yaml_file, 'r') as f:
181
+ data = yaml.safe_load(f) or {}
182
+
183
+ if not isinstance(data, dict):
184
+ logger.warning("template.load.invalid", file=str(yaml_file))
185
+ continue
186
+
187
+ if not self._validate_template(data, yaml_file):
188
+ continue
189
+
190
+ template_id = data.get("id", yaml_file.stem)
191
+ if template_id in self.template_ids:
192
+ logger.warning("template.load.duplicate", template=template_id, file=str(yaml_file))
193
+ continue
194
+
195
+ matchers_condition = (
196
+ data.get("matchers-condition")
197
+ or data.get("matchers_condition")
198
+ or "or"
199
+ ).lower()
200
+
201
+ requests = data.get("requests", data.get("http", [])) or []
202
+ if not isinstance(requests, list):
203
+ requests = [requests]
204
+
205
+ normalized_requests = [
206
+ self._normalize_request(request)
207
+ for request in requests
208
+ if isinstance(request, dict)
209
+ ]
210
+
211
+ info = data.get("info", {})
212
+ raw_tags = info.get("tags", [])
213
+ if isinstance(raw_tags, str):
214
+ tags = {raw_tags.lower()}
215
+ else:
216
+ tags = {str(tag).lower() for tag in raw_tags}
217
+
218
+ template = Template(
219
+ id=template_id,
220
+ info=info,
221
+ requests=normalized_requests,
222
+ matchers=data.get("matchers", []),
223
+ extractors=data.get("extractors", []),
224
+ matchers_condition=matchers_condition,
225
+ tags=tags,
226
+ )
227
+
228
+ templates.append(template)
229
+ self.template_ids.add(template_id)
230
+
231
+ except Exception as e:
232
+ logger.warning("template.load.error", file=str(yaml_file), error=str(e))
233
+
234
+ logger.info("template.load.complete", count=len(templates))
235
+ return templates
236
+
237
+ def _normalize_request(self, request_def: Dict[str, Any]) -> Dict[str, Any]:
238
+ """Normaliza campos comuns de requests."""
239
+ normalized = dict(request_def)
240
+
241
+ path = normalized.get("path")
242
+ if isinstance(path, str):
243
+ normalized["path"] = [path]
244
+
245
+ raw = normalized.get("raw")
246
+ if isinstance(raw, str):
247
+ normalized["raw"] = [raw]
248
+
249
+ matchers_condition = (
250
+ normalized.get("matchers-condition")
251
+ or normalized.get("matchers_condition")
252
+ )
253
+ if matchers_condition:
254
+ normalized["matchers_condition"] = matchers_condition
255
+
256
+ return normalized
257
+
258
+ def _validate_template(self, data: Dict[str, Any], file_path: Path) -> bool:
259
+ """Valida estrutura mínima de um template."""
260
+ errors = []
261
+
262
+ template_id = data.get("id")
263
+ if not template_id:
264
+ errors.append("missing id")
265
+
266
+ info = data.get("info", {})
267
+ severity = info.get("severity")
268
+ allowed_severities = {"critical", "high", "medium", "low", "info"}
269
+ if severity and severity.lower() not in allowed_severities:
270
+ errors.append(f"invalid severity '{severity}'")
271
+
272
+ requests = data.get("requests", data.get("http", [])) or []
273
+ if not isinstance(requests, list) or not requests:
274
+ errors.append("missing requests")
275
+ else:
276
+ for index, request in enumerate(requests):
277
+ if not isinstance(request, dict):
278
+ errors.append(f"request[{index}] is not a mapping")
279
+ continue
280
+ has_path = bool(request.get("path"))
281
+ has_raw = bool(request.get("raw"))
282
+ if not has_path and not has_raw:
283
+ errors.append(f"request[{index}] missing path/raw")
284
+
285
+ if errors:
286
+ logger.warning(
287
+ "template.validation.failed",
288
+ file=str(file_path),
289
+ errors=errors,
290
+ )
291
+ return False
292
+
293
+ return True
294
+
295
+ async def _execute_template(
296
+ self,
297
+ client: httpx.AsyncClient,
298
+ semaphore: asyncio.Semaphore,
299
+ template: Template,
300
+ ):
301
+ """Executa um template."""
302
+ async with semaphore:
303
+ try:
304
+ for request_def in template.requests:
305
+ matchers = request_def.get("matchers") or template.matchers
306
+ extractors = request_def.get("extractors") or template.extractors or []
307
+ matchers_condition = (
308
+ request_def.get("matchers_condition")
309
+ or request_def.get("matchers-condition")
310
+ or template.matchers_condition
311
+ or "or"
312
+ ).lower()
313
+ stop_on_match = bool(
314
+ request_def.get("stop-at-first-match")
315
+ or request_def.get("stop_at_first_match")
316
+ )
317
+
318
+ payload_maps = self._build_payload_maps(request_def)
319
+ raw_blocks = request_def.get("raw") or []
320
+ match_found = False
321
+
322
+ if raw_blocks:
323
+ for payload_map in payload_maps:
324
+ for raw in raw_blocks:
325
+ raw_payload = self._apply_payload(raw, payload_map)
326
+ method, url, headers, body = self._parse_raw_request(raw_payload)
327
+
328
+ request_kwargs = {}
329
+ if body is not None:
330
+ request_kwargs["content"] = body.encode() if isinstance(body, str) else body
331
+
332
+ merged_headers = {**self._stealth_headers(), **(headers or {})}
333
+ await self._stealth_delay()
334
+ response = await client.request(
335
+ method,
336
+ url,
337
+ headers=merged_headers,
338
+ **request_kwargs,
339
+ )
340
+
341
+ match_found = self._process_response(
342
+ template,
343
+ url,
344
+ response,
345
+ matchers,
346
+ matchers_condition,
347
+ extractors,
348
+ ) or match_found
349
+
350
+ if match_found and stop_on_match:
351
+ return
352
+
353
+ # Se requests RAW foram processados, pula para próximo request
354
+ continue
355
+
356
+ paths = request_def.get("path") or ["/"]
357
+ if isinstance(paths, str):
358
+ paths = [paths]
359
+
360
+ method = request_def.get("method", "GET")
361
+ headers_template = request_def.get("headers", {})
362
+ body_template = request_def.get("body")
363
+
364
+ for payload_map in payload_maps:
365
+ for path in paths:
366
+ final_path = self._apply_payload(path, payload_map)
367
+ url = self._build_url(final_path)
368
+
369
+ headers = self._prepare_headers(headers_template, payload_map)
370
+ headers = {**self._stealth_headers(), **headers}
371
+ body = self._prepare_body(body_template, payload_map)
372
+ request_kwargs = self._build_request_kwargs(body, request_def)
373
+
374
+ await self._stealth_delay()
375
+ response = await client.request(
376
+ method,
377
+ url,
378
+ headers=headers,
379
+ **request_kwargs,
380
+ )
381
+
382
+ match_found = self._process_response(
383
+ template,
384
+ url,
385
+ response,
386
+ matchers,
387
+ matchers_condition,
388
+ extractors,
389
+ ) or match_found
390
+
391
+ if match_found and stop_on_match:
392
+ break
393
+
394
+ fuzz_spec = request_def.get("fuzz")
395
+ if fuzz_spec:
396
+ match_found = await self._run_query_fuzz(
397
+ client,
398
+ template,
399
+ url,
400
+ headers,
401
+ method,
402
+ request_kwargs,
403
+ fuzz_spec,
404
+ matchers,
405
+ matchers_condition,
406
+ extractors,
407
+ int(request_def.get("fuzz_max", 30)),
408
+ stop_on_match,
409
+ match_found,
410
+ )
411
+ if match_found and stop_on_match:
412
+ break
413
+
414
+ if match_found and stop_on_match:
415
+ break
416
+
417
+ if match_found and stop_on_match:
418
+ break
419
+
420
+ if match_found and stop_on_match:
421
+ break
422
+
423
+ except Exception as e:
424
+ logger.debug("template.execute.error", template=template.id, error=str(e))
425
+
426
+ def _build_url(self, path: str) -> str:
427
+ """Constrói URL completa."""
428
+ path = self._resolve_placeholders(path)
429
+
430
+ if path.startswith("http://") or path.startswith("https://"):
431
+ return path
432
+
433
+ if not path.startswith("/"):
434
+ path = f"/{path}"
435
+
436
+ return f"{self.base_url}{path}"
437
+
438
+ def _check_matchers(
439
+ self,
440
+ matchers: List[Dict[str, Any]],
441
+ response: httpx.Response,
442
+ matchers_condition: str = "or",
443
+ ) -> bool:
444
+ """Verifica se matchers são satisfeitos."""
445
+ if not matchers:
446
+ return False
447
+
448
+ results: List[bool] = []
449
+
450
+ for matcher in matchers:
451
+ matcher_type = matcher.get("type", "word").lower()
452
+ condition = matcher.get("condition", "or").lower()
453
+ negative = bool(matcher.get("negative"))
454
+ result = False
455
+
456
+ if matcher_type == "status":
457
+ status_codes = matcher.get("status") or matcher.get("codes") or []
458
+ result = response.status_code in status_codes
459
+
460
+ elif matcher_type == "word":
461
+ words = matcher.get("words", [])
462
+ part = matcher.get("part", "body")
463
+ content = self._get_response_part(response, part)
464
+ matches = [word.lower() in content.lower() for word in words]
465
+ result = all(matches) if condition == "and" else any(matches)
466
+
467
+ elif matcher_type == "regex":
468
+ regexes = matcher.get("regex", [])
469
+ part = matcher.get("part", "body")
470
+ content = self._get_response_part(response, part)
471
+ matches = [bool(re.search(regex, content, re.IGNORECASE)) for regex in regexes]
472
+ result = all(matches) if condition == "and" else any(matches)
473
+
474
+ elif matcher_type == "dsl":
475
+ expressions = matcher.get("dsl", [])
476
+ result = self._evaluate_dsl(expressions, response)
477
+
478
+ if negative:
479
+ result = not result
480
+
481
+ results.append(result)
482
+
483
+ if not results:
484
+ return False
485
+
486
+ if matchers_condition == "and":
487
+ return all(results)
488
+ return any(results)
489
+
490
+ def _process_response(
491
+ self,
492
+ template: Template,
493
+ url: str,
494
+ response: httpx.Response,
495
+ matchers: List[Dict[str, Any]],
496
+ matchers_condition: str,
497
+ extractors: List[Dict[str, Any]],
498
+ ) -> bool:
499
+ """Processa response, aplicando matchers e registrando findings."""
500
+ if not matchers:
501
+ return False
502
+
503
+ if not self._check_matchers(matchers, response, matchers_condition):
504
+ return False
505
+
506
+ extracted = self._extract_data(extractors, response) if extractors else {}
507
+ cache_key = (template.id, url)
508
+ if cache_key in self._finding_cache:
509
+ return True
510
+
511
+ self._finding_cache.add(cache_key)
512
+
513
+ finding = TemplateFinding(
514
+ template_id=template.id,
515
+ name=template.info.get("name", template.id),
516
+ severity=template.info.get("severity", "info"),
517
+ description=template.info.get("description", ""),
518
+ matched_at=url,
519
+ extracted_data=extracted,
520
+ )
521
+
522
+ self.findings.append(finding)
523
+
524
+ logger.info(
525
+ "template.match",
526
+ template=template.id,
527
+ severity=finding.severity,
528
+ url=url,
529
+ )
530
+
531
+ colors = {
532
+ "critical": "red",
533
+ "high": "yellow",
534
+ "medium": "blue",
535
+ "low": "green",
536
+ "info": "dim",
537
+ }
538
+ color = colors.get(finding.severity, "white")
539
+ console.print(f"[{color}]✓ {finding.name} [{finding.severity.upper()}][/{color}]")
540
+
541
+ return True
542
+
543
+ def _build_payload_maps(self, request_def: Dict[str, Any]) -> List[Dict[str, Any]]:
544
+ """Gera combinações de payloads para placeholders."""
545
+ payloads = request_def.get("payloads") or {}
546
+ auto_fuzz = request_def.get("auto_fuzz")
547
+ if isinstance(auto_fuzz, dict):
548
+ for placeholder, entries in auto_fuzz.items():
549
+ values = self._resolve_fuzz_values(entries if isinstance(entries, list) else [entries])
550
+ if values:
551
+ payloads.setdefault(placeholder, values)
552
+ elif isinstance(auto_fuzz, list):
553
+ for idx, category in enumerate(auto_fuzz, start=1):
554
+ values = self._resolve_fuzz_values([category])
555
+ if values:
556
+ payloads.setdefault(f"fuzz_{idx}", values)
557
+ if not payloads:
558
+ return [{}]
559
+
560
+ keys = list(payloads.keys())
561
+ value_lists = [payloads[key] for key in keys]
562
+ combinations = []
563
+ max_combos = int(request_def.get("max_payload_combinations", 256))
564
+
565
+ for index, combo in enumerate(itertools.product(*value_lists)):
566
+ if index >= max_combos:
567
+ logger.debug(
568
+ "template.payloads.limited",
569
+ template=request_def.get("id"),
570
+ max=max_combos,
571
+ )
572
+ break
573
+ combinations.append(dict(zip(keys, combo)))
574
+
575
+ return combinations or [{}]
576
+
577
+ def _apply_payload(self, value: Any, payload_map: Dict[str, Any]) -> Any:
578
+ """Aplica placeholders e payloads em strings/listas/dicts."""
579
+ if value is None:
580
+ return None
581
+
582
+ if isinstance(value, str):
583
+ result = value
584
+ for key, payload in payload_map.items():
585
+ result = result.replace(f"{{{{{key}}}}}", str(payload))
586
+ return self._resolve_placeholders(result)
587
+
588
+ if isinstance(value, list):
589
+ return [self._apply_payload(item, payload_map) for item in value]
590
+
591
+ if isinstance(value, dict):
592
+ return {k: self._apply_payload(v, payload_map) for k, v in value.items()}
593
+
594
+ return value
595
+
596
+ def _resolve_fuzz_values(self, entries: List[str]) -> List[str]:
597
+ values: List[str] = []
598
+ for entry in entries:
599
+ if entry in self.FUZZ_PAYLOADS:
600
+ values.extend(self.FUZZ_PAYLOADS[entry])
601
+ else:
602
+ values.append(entry)
603
+ seen: Set[str] = set()
604
+ unique: List[str] = []
605
+ for value in values:
606
+ if value not in seen:
607
+ seen.add(value)
608
+ unique.append(value)
609
+ return unique
610
+
611
+ def _normalize_fuzz_spec(self, fuzz_spec: Any) -> Dict[str, List[str]]:
612
+ if not isinstance(fuzz_spec, dict):
613
+ return {}
614
+ normalized: Dict[str, List[str]] = {}
615
+ for param, entries in fuzz_spec.items():
616
+ if isinstance(entries, str):
617
+ entries = [entries]
618
+ if not isinstance(entries, list):
619
+ continue
620
+ values = self._resolve_fuzz_values(entries)
621
+ if values:
622
+ normalized[param] = values
623
+ return normalized
624
+
625
+ async def _run_query_fuzz(
626
+ self,
627
+ client: httpx.AsyncClient,
628
+ template: Template,
629
+ url: str,
630
+ headers: Dict[str, Any],
631
+ method: str,
632
+ request_kwargs: Dict[str, Any],
633
+ fuzz_spec: Any,
634
+ matchers: List[Dict[str, Any]],
635
+ matchers_condition: str,
636
+ extractors: List[Dict[str, Any]],
637
+ max_mutations: int,
638
+ stop_on_match: bool,
639
+ current_match: bool,
640
+ ) -> bool:
641
+ method_upper = method.upper()
642
+ if method_upper not in {"GET", "HEAD", "DELETE"}:
643
+ return current_match
644
+
645
+ fuzz_map = self._normalize_fuzz_spec(fuzz_spec)
646
+ if not fuzz_map:
647
+ return current_match
648
+
649
+ parsed = urlsplit(url)
650
+ base_params = parse_qs(parsed.query, keep_blank_values=True)
651
+ for key in fuzz_map:
652
+ base_params.setdefault(key, [""])
653
+
654
+ keys = list(fuzz_map.keys())
655
+ values_lists = [fuzz_map[key] for key in keys]
656
+
657
+ mutations = 0
658
+ for combo in itertools.product(*values_lists):
659
+ if max_mutations > 0 and mutations >= max_mutations:
660
+ logger.debug("template.fuzz.limit", template=template.id, limit=max_mutations)
661
+ break
662
+
663
+ params = {key: values[:] for key, values in base_params.items()}
664
+ for key, payload in zip(keys, combo):
665
+ params[key] = [payload]
666
+
667
+ new_query = urlencode(params, doseq=True)
668
+ fuzz_url = urlunsplit((parsed.scheme, parsed.netloc, parsed.path, new_query, parsed.fragment))
669
+
670
+ request_headers = {**self._stealth_headers(), **(headers or {})}
671
+ await self._stealth_delay()
672
+ response = await client.request(
673
+ method_upper,
674
+ fuzz_url,
675
+ headers=request_headers,
676
+ **request_kwargs,
677
+ )
678
+
679
+ current_match = self._process_response(
680
+ template,
681
+ fuzz_url,
682
+ response,
683
+ matchers,
684
+ matchers_condition,
685
+ extractors,
686
+ ) or current_match
687
+
688
+ mutations += 1
689
+ if current_match and stop_on_match:
690
+ break
691
+
692
+ return current_match
693
+
694
+ def _resolve_placeholders(self, value: str) -> str:
695
+ """Resolve placeholders padrão (BaseURL, Hostname, Target)."""
696
+ replacements = {
697
+ "{{BaseURL}}": self.base_url,
698
+ "{{base_url}}": self.base_url,
699
+ "{{RootURL}}": self.base_url,
700
+ "{{root_url}}": self.base_url,
701
+ "{{Hostname}}": self.host,
702
+ "{{hostname}}": self.host,
703
+ "{{Target}}": self.host,
704
+ "{{target}}": self.host,
705
+ }
706
+
707
+ result = value
708
+ for placeholder, replacement in replacements.items():
709
+ result = result.replace(placeholder, replacement)
710
+
711
+ return result
712
+
713
+ def _prepare_headers(
714
+ self,
715
+ headers_template: Dict[str, Any],
716
+ payload_map: Dict[str, Any],
717
+ ) -> Dict[str, str]:
718
+ """Prepara headers aplicando payloads e defaults."""
719
+ headers = {
720
+ key: self._apply_payload(str(value), payload_map)
721
+ for key, value in (headers_template or {}).items()
722
+ }
723
+
724
+ headers.setdefault("Host", self.host)
725
+ return headers
726
+
727
+ def _prepare_body(self, body_template: Any, payload_map: Dict[str, Any]) -> Any:
728
+ """Prepara body aplicando payloads."""
729
+ if body_template is None:
730
+ return None
731
+ return self._apply_payload(body_template, payload_map)
732
+
733
+ def _build_request_kwargs(self, body: Any, request_def: Dict[str, Any]) -> Dict[str, Any]:
734
+ """Monta kwargs para httpx.request."""
735
+ kwargs: Dict[str, Any] = {}
736
+
737
+ if body is None:
738
+ return kwargs
739
+
740
+ if isinstance(body, dict):
741
+ if request_def.get("json", True):
742
+ kwargs["json"] = body
743
+ else:
744
+ kwargs["data"] = body
745
+ else:
746
+ kwargs["content"] = body.encode() if isinstance(body, str) else body
747
+
748
+ return kwargs
749
+
750
+ def _parse_raw_request(self, raw_request: str) -> Tuple[str, str, Dict[str, str], Optional[str]]:
751
+ """Converte request RAW estilo Nuclei em componentes httpx."""
752
+ lines = raw_request.splitlines()
753
+ if not lines:
754
+ raise ValueError("raw request vazio")
755
+
756
+ request_line = lines[0].strip()
757
+ try:
758
+ method, path, _ = request_line.split()
759
+ except ValueError as exc:
760
+ raise ValueError(f"linha de requisição inválida: {request_line}") from exc
761
+
762
+ headers: Dict[str, str] = {}
763
+ body_lines: List[str] = []
764
+ parsing_headers = True
765
+
766
+ for line in lines[1:]:
767
+ if parsing_headers and not line.strip():
768
+ parsing_headers = False
769
+ continue
770
+
771
+ if parsing_headers:
772
+ if ":" in line:
773
+ key, value = line.split(":", 1)
774
+ headers[key.strip()] = value.strip()
775
+ else:
776
+ body_lines.append(line)
777
+
778
+ body = "\n".join(body_lines) if body_lines else None
779
+
780
+ url = path
781
+ if not url.startswith("http://") and not url.startswith("https://"):
782
+ url = self._build_url(url)
783
+
784
+ headers.setdefault("Host", self.host)
785
+
786
+ return method.upper(), url, headers, body
787
+
788
+ def _evaluate_dsl(self, expressions: List[str], response: httpx.Response) -> bool:
789
+ """Avalia expressões DSL simples."""
790
+ if not expressions:
791
+ return False
792
+
793
+ body = response.text or ""
794
+ headers = {k.lower(): v for k, v in response.headers.items()}
795
+ status_code = response.status_code
796
+ json_cache: Any = None
797
+
798
+ def _normalize(source: Any) -> str:
799
+ if source is None:
800
+ return ""
801
+ return str(source)
802
+
803
+ def _contains(source: Any, needle: str) -> bool:
804
+ return needle.lower() in _normalize(source).lower()
805
+
806
+ def _icontains(source: Any, needle: str) -> bool:
807
+ return _contains(source, needle)
808
+
809
+ def _startswith(source: Any, prefix: str) -> bool:
810
+ return _normalize(source).lower().startswith(prefix.lower())
811
+
812
+ def _endswith(source: Any, suffix: str) -> bool:
813
+ return _normalize(source).lower().endswith(suffix.lower())
814
+
815
+ def _equals(left: Any, right: Any) -> bool:
816
+ return _normalize(left).lower() == _normalize(right).lower()
817
+
818
+ def _regex(pattern: str, source: Optional[str] = None) -> bool:
819
+ target = source if source is not None else body
820
+ return bool(re.search(pattern, target or "", re.IGNORECASE))
821
+
822
+ def _header(name: str) -> Optional[str]:
823
+ return headers.get(name.lower())
824
+
825
+ def _count(source: Any, needle: str) -> int:
826
+ return _normalize(source).lower().count(needle.lower())
827
+
828
+ def _status_in(*codes: int) -> bool:
829
+ return status_code in set(int(code) for code in codes)
830
+
831
+ def _status_between(start: int, end: int) -> bool:
832
+ return start <= status_code <= end
833
+
834
+ def _json(path: str, default: Any = None) -> Any:
835
+ nonlocal json_cache
836
+ if json_cache is None:
837
+ try:
838
+ json_cache = json.loads(body)
839
+ except Exception:
840
+ json_cache = {}
841
+
842
+ current = json_cache
843
+ if not path:
844
+ return current
845
+ for part in path.split('.'):
846
+ if isinstance(current, dict) and part in current:
847
+ current = current[part]
848
+ elif isinstance(current, list):
849
+ try:
850
+ idx = int(part)
851
+ current = current[idx]
852
+ except (ValueError, IndexError):
853
+ return default
854
+ else:
855
+ return default
856
+ return current
857
+
858
+ def _present(value: Any) -> bool:
859
+ if value is None:
860
+ return False
861
+ if isinstance(value, str):
862
+ return bool(value.strip())
863
+ if isinstance(value, (list, dict, set, tuple)):
864
+ return len(value) > 0
865
+ return True
866
+
867
+ env = {
868
+ "body": body,
869
+ "headers": headers,
870
+ "status_code": status_code,
871
+ "contains": _contains,
872
+ "icontains": _icontains,
873
+ "startswith": _startswith,
874
+ "endswith": _endswith,
875
+ "equals": _equals,
876
+ "regex": _regex,
877
+ "header": _header,
878
+ "count": _count,
879
+ "status_in": _status_in,
880
+ "status_between": _status_between,
881
+ "json_get": _json,
882
+ "present": _present,
883
+ "len": len,
884
+ "any": any,
885
+ "all": all,
886
+ "status": lambda: status_code,
887
+ "lower": lambda s: s.lower() if isinstance(s, str) else s,
888
+ "upper": lambda s: s.upper() if isinstance(s, str) else s,
889
+ }
890
+
891
+ safe_globals = {"__builtins__": {}}
892
+
893
+ for expression in expressions:
894
+ expr = expression.strip()
895
+ if not expr:
896
+ continue
897
+ try:
898
+ if not bool(eval(expr, safe_globals, env)):
899
+ return False
900
+ except Exception as exc:
901
+ logger.debug("template.dsl.error", expression=expr, error=str(exc))
902
+ return False
903
+
904
+ return True
905
+
906
+ def _get_response_part(self, response: httpx.Response, part: str) -> str:
907
+ """Extrai parte específica da response."""
908
+ if part == "body":
909
+ return response.text
910
+ elif part == "header":
911
+ return str(response.headers)
912
+ elif part == "all":
913
+ return f"{response.headers}\n\n{response.text}"
914
+ else:
915
+ return response.text
916
+
917
+ def _extract_data(self, extractors: List[Dict[str, Any]], response: httpx.Response) -> Dict[str, Any]:
918
+ """Extrai dados da response."""
919
+ extracted = {}
920
+
921
+ for extractor in extractors:
922
+ extractor_type = extractor.get("type", "regex")
923
+ name = extractor.get("name", "data")
924
+ part = extractor.get("part", "body")
925
+
926
+ content = self._get_response_part(response, part)
927
+
928
+ if extractor_type == "regex":
929
+ regexes = extractor.get("regex", [])
930
+ for regex in regexes:
931
+ match = re.search(regex, content, re.IGNORECASE)
932
+ if match:
933
+ if match.groups():
934
+ extracted[name] = match.group(1)
935
+ else:
936
+ extracted[name] = match.group(0)
937
+
938
+ elif extractor_type == "kval":
939
+ # Key-value extraction
940
+ kval = extractor.get("kval", [])
941
+ # Busca padrão key=value
942
+ for key in kval:
943
+ pattern = f"{key}=([^&\\s]+)"
944
+ match = re.search(pattern, content)
945
+ if match:
946
+ extracted[key] = match.group(1)
947
+
948
+ elif extractor_type == "json":
949
+ try:
950
+ data_json = json.loads(content)
951
+ except json.JSONDecodeError:
952
+ continue
953
+
954
+ paths = extractor.get("json", [])
955
+ if isinstance(paths, str):
956
+ paths = [paths]
957
+
958
+ for path_expr in paths:
959
+ value = self._extract_from_json(data_json, path_expr)
960
+ if value is not None:
961
+ extracted[name] = value
962
+
963
+ return extracted
964
+
965
+ def _extract_from_json(self, data: Any, path_expr: str) -> Optional[Any]:
966
+ """Suporte simples para extração JSON via notação ponto."""
967
+ if not path_expr:
968
+ return None
969
+
970
+ current = data
971
+ for part in path_expr.split('.'):
972
+ if part == '':
973
+ continue
974
+ if isinstance(current, list):
975
+ try:
976
+ index = int(part)
977
+ current = current[index]
978
+ except (ValueError, IndexError):
979
+ return None
980
+ elif isinstance(current, dict):
981
+ if part not in current:
982
+ return None
983
+ current = current.get(part)
984
+ else:
985
+ return None
986
+
987
+ return current
988
+
989
+ def _create_example_templates(self):
990
+ """Cria templates de exemplo."""
991
+ templates_dir = Path(self.templates_path)
992
+ templates_dir.mkdir(parents=True, exist_ok=True)
993
+
994
+ # Template exemplo: Git exposure
995
+ git_template = {
996
+ "id": "git-config",
997
+ "info": {
998
+ "name": "Git Config Exposure",
999
+ "severity": "high",
1000
+ "description": "Detects exposed .git/config file",
1001
+ },
1002
+ "requests": [
1003
+ {
1004
+ "method": "GET",
1005
+ "path": ["/.git/config"],
1006
+ }
1007
+ ],
1008
+ "matchers": [
1009
+ {
1010
+ "type": "word",
1011
+ "words": ["[core]", "[remote"],
1012
+ "condition": "and",
1013
+ },
1014
+ {
1015
+ "type": "status",
1016
+ "status": [200],
1017
+ }
1018
+ ],
1019
+ }
1020
+
1021
+ with open(templates_dir / "git-config.yaml", 'w') as f:
1022
+ yaml.dump(git_template, f)
1023
+
1024
+ logger.info("template.examples.created", path=str(templates_dir))
1025
+
1026
+ def export(self, findings: List[TemplateFinding], output: str):
1027
+ """Exporta findings para arquivo."""
1028
+ import json
1029
+
1030
+ data = [
1031
+ {
1032
+ "template_id": f.template_id,
1033
+ "name": f.name,
1034
+ "severity": f.severity,
1035
+ "description": f.description,
1036
+ "matched_at": f.matched_at,
1037
+ "extracted_data": f.extracted_data,
1038
+ }
1039
+ for f in findings
1040
+ ]
1041
+
1042
+ with open(output, 'w') as f:
1043
+ json.dump(data, f, indent=2)
1044
+
1045
+ logger.info("template.export.complete", file=output, count=len(findings))
1046
+
1047
+
1048
+ __all__ = ["TemplateScanner", "Template", "TemplateFinding"]