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.
- moriarty/__init__.py +5 -0
- moriarty/adapters/__init__.py +0 -0
- moriarty/agent/__init__.py +0 -0
- moriarty/assets/modules/.gitkeep +0 -0
- moriarty/assets/modules/asia/douban.yaml +19 -0
- moriarty/assets/modules/asia/kakao.yaml +19 -0
- moriarty/assets/modules/asia/line.yaml +19 -0
- moriarty/assets/modules/asia/mixi.yaml +19 -0
- moriarty/assets/modules/asia/naver.yaml +19 -0
- moriarty/assets/modules/asia/qq.yaml +19 -0
- moriarty/assets/modules/asia/vk.yaml +19 -0
- moriarty/assets/modules/asia/wechat.yaml +19 -0
- moriarty/assets/modules/asia/weibo.yaml +19 -0
- moriarty/assets/modules/asia/xiaohongshu.yaml +19 -0
- moriarty/assets/modules/behance.yaml +47 -0
- moriarty/assets/modules/business/crunchbase.yaml +27 -0
- moriarty/assets/modules/business/fiverr.yaml +32 -0
- moriarty/assets/modules/business/freelancer.yaml +27 -0
- moriarty/assets/modules/business/glassdoor.yaml +27 -0
- moriarty/assets/modules/business/guru.yaml +26 -0
- moriarty/assets/modules/business/indeed.yaml +25 -0
- moriarty/assets/modules/business/monster.yaml +25 -0
- moriarty/assets/modules/business/peopleperhour.yaml +26 -0
- moriarty/assets/modules/business/toptal.yaml +28 -0
- moriarty/assets/modules/business/upwork.yaml +27 -0
- moriarty/assets/modules/business/ziprecruiter.yaml +25 -0
- moriarty/assets/modules/content/buymeacoffee.yaml +27 -0
- moriarty/assets/modules/content/gumroad.yaml +27 -0
- moriarty/assets/modules/content/ko-fi.yaml +32 -0
- moriarty/assets/modules/content/onlyfans.yaml +27 -0
- moriarty/assets/modules/content/patreon.yaml +33 -0
- moriarty/assets/modules/content/substack.yaml +32 -0
- moriarty/assets/modules/creative/500px.yaml +31 -0
- moriarty/assets/modules/creative/artstation.yaml +33 -0
- moriarty/assets/modules/creative/deviantart.yaml +32 -0
- moriarty/assets/modules/creative/flickr.yaml +31 -0
- moriarty/assets/modules/creative/pexels.yaml +26 -0
- moriarty/assets/modules/creative/unsplash.yaml +26 -0
- moriarty/assets/modules/creative/vimeo.yaml +31 -0
- moriarty/assets/modules/crypto/binance.yaml +27 -0
- moriarty/assets/modules/crypto/bitcointalk.yaml +33 -0
- moriarty/assets/modules/crypto/coinbase.yaml +26 -0
- moriarty/assets/modules/crypto/etherscan.yaml +32 -0
- moriarty/assets/modules/crypto/foundation.yaml +28 -0
- moriarty/assets/modules/crypto/kraken.yaml +27 -0
- moriarty/assets/modules/crypto/mirror.yaml +27 -0
- moriarty/assets/modules/crypto/niftygateway.yaml +26 -0
- moriarty/assets/modules/crypto/opensea.yaml +32 -0
- moriarty/assets/modules/crypto/rarible.yaml +27 -0
- moriarty/assets/modules/crypto/superrare.yaml +29 -0
- moriarty/assets/modules/dating/bumble.yaml +25 -0
- moriarty/assets/modules/dating/grindr.yaml +27 -0
- moriarty/assets/modules/dating/happn.yaml +25 -0
- moriarty/assets/modules/dating/her.yaml +27 -0
- moriarty/assets/modules/dating/hinge.yaml +25 -0
- moriarty/assets/modules/dating/match.yaml +25 -0
- moriarty/assets/modules/dating/meetme.yaml +27 -0
- moriarty/assets/modules/dating/okcupid.yaml +25 -0
- moriarty/assets/modules/dating/pof.yaml +25 -0
- moriarty/assets/modules/dating/tinder.yaml +25 -0
- moriarty/assets/modules/dating-nsfw/adultfriendfinder.yaml +28 -0
- moriarty/assets/modules/dating-nsfw/ashley-madison.yaml +26 -0
- moriarty/assets/modules/design/adobe-portfolio.yaml +27 -0
- moriarty/assets/modules/design/carbonmade.yaml +27 -0
- moriarty/assets/modules/design/cgsociety.yaml +27 -0
- moriarty/assets/modules/design/coroflot.yaml +27 -0
- moriarty/assets/modules/design/figma.yaml +27 -0
- moriarty/assets/modules/design/sketch.yaml +26 -0
- moriarty/assets/modules/dev/bitbucket.yaml +35 -0
- moriarty/assets/modules/dev/codeforces.yaml +32 -0
- moriarty/assets/modules/dev/codepen.yaml +34 -0
- moriarty/assets/modules/dev/hackerone.yaml +32 -0
- moriarty/assets/modules/dev/hackthebox.yaml +27 -0
- moriarty/assets/modules/dev/huggingface.yaml +27 -0
- moriarty/assets/modules/dev/kaggle.yaml +32 -0
- moriarty/assets/modules/dev/leetcode.yaml +32 -0
- moriarty/assets/modules/dev/replit.yaml +31 -0
- moriarty/assets/modules/dribbble.yaml +53 -0
- moriarty/assets/modules/ecommerce/etsy.yaml +32 -0
- moriarty/assets/modules/education/duolingo.yaml +32 -0
- moriarty/assets/modules/education/edx.yaml +26 -0
- moriarty/assets/modules/education/khanacademy.yaml +26 -0
- moriarty/assets/modules/education/lynda.yaml +27 -0
- moriarty/assets/modules/education/memrise.yaml +27 -0
- moriarty/assets/modules/education/pluralsight.yaml +27 -0
- moriarty/assets/modules/education/skillshare.yaml +27 -0
- moriarty/assets/modules/education/udacity.yaml +27 -0
- moriarty/assets/modules/email/github_email.yaml +40 -0
- moriarty/assets/modules/email/gravatar.yaml +23 -0
- moriarty/assets/modules/europe/badoo.yaml +19 -0
- moriarty/assets/modules/europe/lovoo.yaml +19 -0
- moriarty/assets/modules/europe/myspace.yaml +19 -0
- moriarty/assets/modules/europe/netlog.yaml +19 -0
- moriarty/assets/modules/europe/ok.yaml +19 -0
- moriarty/assets/modules/europe/skyrock.yaml +19 -0
- moriarty/assets/modules/europe/studivz.yaml +19 -0
- moriarty/assets/modules/europe/tuenti.yaml +19 -0
- moriarty/assets/modules/europe/viadeo.yaml +19 -0
- moriarty/assets/modules/europe/xing.yaml +19 -0
- moriarty/assets/modules/fitness/fitbit.yaml +27 -0
- moriarty/assets/modules/fitness/garmin.yaml +27 -0
- moriarty/assets/modules/fitness/myfitnesspal.yaml +27 -0
- moriarty/assets/modules/fitness/strava.yaml +33 -0
- moriarty/assets/modules/fitness/zwift.yaml +28 -0
- moriarty/assets/modules/food/allrecipes.yaml +27 -0
- moriarty/assets/modules/food/tasty.yaml +27 -0
- moriarty/assets/modules/food/yelp.yaml +32 -0
- moriarty/assets/modules/food/zomato.yaml +28 -0
- moriarty/assets/modules/forums/4chan.yaml +26 -0
- moriarty/assets/modules/forums/8kun.yaml +26 -0
- moriarty/assets/modules/forums/9gag.yaml +26 -0
- moriarty/assets/modules/forums/discourse.yaml +26 -0
- moriarty/assets/modules/forums/disqus.yaml +31 -0
- moriarty/assets/modules/forums/hackernews.yaml +32 -0
- moriarty/assets/modules/forums/launchpad.yaml +27 -0
- moriarty/assets/modules/forums/phpbb.yaml +25 -0
- moriarty/assets/modules/forums/quora.yaml +32 -0
- moriarty/assets/modules/forums/serverfault.yaml +27 -0
- moriarty/assets/modules/forums/slashdot.yaml +28 -0
- moriarty/assets/modules/forums/stackexchange.yaml +32 -0
- moriarty/assets/modules/forums/superuser.yaml +27 -0
- moriarty/assets/modules/forums/vbulletin.yaml +25 -0
- moriarty/assets/modules/forums/xenforo.yaml +25 -0
- moriarty/assets/modules/forums-nsfw/kiwifarms.yaml +25 -0
- moriarty/assets/modules/forums-nsfw/lolcow.yaml +26 -0
- moriarty/assets/modules/gaming/apextracker.yaml +27 -0
- moriarty/assets/modules/gaming/battlenet.yaml +26 -0
- moriarty/assets/modules/gaming/chess.yaml +30 -0
- moriarty/assets/modules/gaming/discord-public.yaml +27 -0
- moriarty/assets/modules/gaming/dotabuff.yaml +32 -0
- moriarty/assets/modules/gaming/epicgames.yaml +25 -0
- moriarty/assets/modules/gaming/faceit.yaml +33 -0
- moriarty/assets/modules/gaming/fortnitetracker.yaml +32 -0
- moriarty/assets/modules/gaming/gog.yaml +26 -0
- moriarty/assets/modules/gaming/itch.yaml +32 -0
- moriarty/assets/modules/gaming/kongregate.yaml +25 -0
- moriarty/assets/modules/gaming/minecraft.yaml +31 -0
- moriarty/assets/modules/gaming/opgg.yaml +32 -0
- moriarty/assets/modules/gaming/origin.yaml +26 -0
- moriarty/assets/modules/gaming/playstation.yaml +30 -0
- moriarty/assets/modules/gaming/roblox.yaml +31 -0
- moriarty/assets/modules/gaming/xbox.yaml +25 -0
- moriarty/assets/modules/github.yaml +68 -0
- moriarty/assets/modules/gitlab.yaml +60 -0
- moriarty/assets/modules/instagram.yaml +48 -0
- moriarty/assets/modules/latam/fotolog.yaml +27 -0
- moriarty/assets/modules/latam/orkut.yaml +26 -0
- moriarty/assets/modules/latam/taringa.yaml +27 -0
- moriarty/assets/modules/learning/coursera.yaml +26 -0
- moriarty/assets/modules/learning/udemy.yaml +26 -0
- moriarty/assets/modules/linkedin.yaml +40 -0
- moriarty/assets/modules/marketplaces/depop.yaml +28 -0
- moriarty/assets/modules/marketplaces/ebay.yaml +32 -0
- moriarty/assets/modules/marketplaces/grailed.yaml +27 -0
- moriarty/assets/modules/marketplaces/mercari.yaml +26 -0
- moriarty/assets/modules/marketplaces/poshmark.yaml +27 -0
- moriarty/assets/modules/marketplaces/reverb.yaml +27 -0
- moriarty/assets/modules/marketplaces/vinted.yaml +28 -0
- moriarty/assets/modules/medium.yaml +44 -0
- moriarty/assets/modules/music/audiomack.yaml +26 -0
- moriarty/assets/modules/music/bandcamp.yaml +30 -0
- moriarty/assets/modules/music/beatport.yaml +28 -0
- moriarty/assets/modules/music/deezer.yaml +26 -0
- moriarty/assets/modules/music/discogs.yaml +32 -0
- moriarty/assets/modules/music/genius.yaml +26 -0
- moriarty/assets/modules/music/lastfm.yaml +30 -0
- moriarty/assets/modules/music/mixcloud.yaml +26 -0
- moriarty/assets/modules/music/reverbnation.yaml +31 -0
- moriarty/assets/modules/music/soundcloud.yaml +31 -0
- moriarty/assets/modules/music/spotify.yaml +26 -0
- moriarty/assets/modules/music/tidal.yaml +26 -0
- moriarty/assets/modules/nsfw/adultwork.yaml +27 -0
- moriarty/assets/modules/nsfw/bongacams.yaml +28 -0
- moriarty/assets/modules/nsfw/cam4.yaml +28 -0
- moriarty/assets/modules/nsfw/chaturbate.yaml +28 -0
- moriarty/assets/modules/nsfw/clips4sale.yaml +27 -0
- moriarty/assets/modules/nsfw/extralunchmoney.yaml +27 -0
- moriarty/assets/modules/nsfw/fansly.yaml +28 -0
- moriarty/assets/modules/nsfw/fetlife.yaml +28 -0
- moriarty/assets/modules/nsfw/iwantclips.yaml +27 -0
- moriarty/assets/modules/nsfw/justforfans.yaml +28 -0
- moriarty/assets/modules/nsfw/loyalfans.yaml +28 -0
- moriarty/assets/modules/nsfw/manyvids.yaml +27 -0
- moriarty/assets/modules/nsfw/myfreecams.yaml +28 -0
- moriarty/assets/modules/nsfw/niteflirt.yaml +26 -0
- moriarty/assets/modules/nsfw/pornhub.yaml +32 -0
- moriarty/assets/modules/nsfw/redtube.yaml +27 -0
- moriarty/assets/modules/nsfw/stripchat.yaml +28 -0
- moriarty/assets/modules/nsfw/xhamster.yaml +27 -0
- moriarty/assets/modules/nsfw/xvideos.yaml +27 -0
- moriarty/assets/modules/nsfw/youporn.yaml +27 -0
- moriarty/assets/modules/photography/eyeem.yaml +25 -0
- moriarty/assets/modules/photography/fotki.yaml +25 -0
- moriarty/assets/modules/photography/photobucket.yaml +26 -0
- moriarty/assets/modules/photography/smugmug.yaml +25 -0
- moriarty/assets/modules/photography/vsco.yaml +27 -0
- moriarty/assets/modules/pinterest.yaml +40 -0
- moriarty/assets/modules/podcasts/anchor.yaml +26 -0
- moriarty/assets/modules/podcasts/castbox.yaml +26 -0
- moriarty/assets/modules/podcasts/podbean.yaml +26 -0
- moriarty/assets/modules/professional/about.yaml +31 -0
- moriarty/assets/modules/professional/academia.yaml +27 -0
- moriarty/assets/modules/professional/angellist.yaml +27 -0
- moriarty/assets/modules/professional/calendly.yaml +26 -0
- moriarty/assets/modules/professional/issuu.yaml +27 -0
- moriarty/assets/modules/professional/mendeley.yaml +27 -0
- moriarty/assets/modules/professional/notion.yaml +27 -0
- moriarty/assets/modules/professional/orcid.yaml +27 -0
- moriarty/assets/modules/professional/producthunt.yaml +31 -0
- moriarty/assets/modules/professional/researchgate.yaml +32 -0
- moriarty/assets/modules/professional/scribd.yaml +27 -0
- moriarty/assets/modules/professional/slideshare.yaml +31 -0
- moriarty/assets/modules/professional/trello.yaml +26 -0
- moriarty/assets/modules/professional/typeform.yaml +27 -0
- moriarty/assets/modules/reddit.yaml +46 -0
- moriarty/assets/modules/regional/amino.yaml +27 -0
- moriarty/assets/modules/regional/ask-fm.yaml +32 -0
- moriarty/assets/modules/regional/babycenter.yaml +26 -0
- moriarty/assets/modules/regional/cafemom.yaml +27 -0
- moriarty/assets/modules/regional/care2.yaml +27 -0
- moriarty/assets/modules/regional/diaspora.yaml +26 -0
- moriarty/assets/modules/regional/ello.yaml +27 -0
- moriarty/assets/modules/regional/gaia.yaml +27 -0
- moriarty/assets/modules/regional/habbo.yaml +27 -0
- moriarty/assets/modules/regional/imvu.yaml +27 -0
- moriarty/assets/modules/regional/lemmy.yaml +27 -0
- moriarty/assets/modules/regional/peertube.yaml +26 -0
- moriarty/assets/modules/regional/pixelfed.yaml +27 -0
- moriarty/assets/modules/regional/plurk.yaml +26 -0
- moriarty/assets/modules/regional/recroom.yaml +27 -0
- moriarty/assets/modules/regional/secondlife.yaml +26 -0
- moriarty/assets/modules/regional/vine-archive.yaml +27 -0
- moriarty/assets/modules/regional/vrchat.yaml +27 -0
- moriarty/assets/modules/regional/weheartit.yaml +27 -0
- moriarty/assets/modules/social/anilist.yaml +27 -0
- moriarty/assets/modules/social/beacons.yaml +26 -0
- moriarty/assets/modules/social/blogger.yaml +27 -0
- moriarty/assets/modules/social/crunchyroll.yaml +27 -0
- moriarty/assets/modules/social/discord.yaml +27 -0
- moriarty/assets/modules/social/dreamwidth.yaml +26 -0
- moriarty/assets/modules/social/facebook.yaml +34 -0
- moriarty/assets/modules/social/goodreads.yaml +32 -0
- moriarty/assets/modules/social/imdb.yaml +27 -0
- moriarty/assets/modules/social/kitsu.yaml +27 -0
- moriarty/assets/modules/social/letterboxd.yaml +32 -0
- moriarty/assets/modules/social/linktree.yaml +26 -0
- moriarty/assets/modules/social/livejournal.yaml +27 -0
- moriarty/assets/modules/social/mastodon.yaml +30 -0
- moriarty/assets/modules/social/minds.yaml +25 -0
- moriarty/assets/modules/social/myanimelist.yaml +32 -0
- moriarty/assets/modules/social/ravelry.yaml +27 -0
- moriarty/assets/modules/social/snapchat.yaml +25 -0
- moriarty/assets/modules/social/telegram.yaml +35 -0
- moriarty/assets/modules/social/tiktok.yaml +35 -0
- moriarty/assets/modules/social/trakt.yaml +28 -0
- moriarty/assets/modules/social/wattpad.yaml +32 -0
- moriarty/assets/modules/social/wordpress-com.yaml +26 -0
- moriarty/assets/modules/sports/espn.yaml +26 -0
- moriarty/assets/modules/sports/untappd.yaml +32 -0
- moriarty/assets/modules/stackoverflow.yaml +47 -0
- moriarty/assets/modules/steam.yaml +47 -0
- moriarty/assets/modules/streaming/caffeine.yaml +25 -0
- moriarty/assets/modules/streaming/dlive.yaml +27 -0
- moriarty/assets/modules/streaming/trovo.yaml +25 -0
- moriarty/assets/modules/travel/airbnb.yaml +26 -0
- moriarty/assets/modules/travel/booking.yaml +26 -0
- moriarty/assets/modules/travel/couchsurfing.yaml +27 -0
- moriarty/assets/modules/travel/tripadvisor.yaml +32 -0
- moriarty/assets/modules/tumblr.yaml +40 -0
- moriarty/assets/modules/twitch.yaml +48 -0
- moriarty/assets/modules/twitter.yaml +39 -0
- moriarty/assets/modules/youtube.yaml +42 -0
- moriarty/assets/templates/cves/CVE-2017-5638.yaml +27 -0
- moriarty/assets/templates/cves/CVE-2018-7600.yaml +30 -0
- moriarty/assets/templates/cves/CVE-2019-11510.yaml +27 -0
- moriarty/assets/templates/cves/CVE-2019-19781.yaml +28 -0
- moriarty/assets/templates/cves/CVE-2020-14882.yaml +28 -0
- moriarty/assets/templates/cves/CVE-2020-14883.yaml +29 -0
- moriarty/assets/templates/cves/CVE-2020-3452.yaml +28 -0
- moriarty/assets/templates/cves/CVE-2020-5902.yaml +28 -0
- moriarty/assets/templates/cves/CVE-2021-21972.yaml +31 -0
- moriarty/assets/templates/cves/CVE-2021-21985.yaml +28 -0
- moriarty/assets/templates/cves/CVE-2021-26084.yaml +30 -0
- moriarty/assets/templates/cves/CVE-2021-41773.yaml +25 -0
- moriarty/assets/templates/cves/CVE-2021-42013.yaml +28 -0
- moriarty/assets/templates/cves/CVE-2021-44228.yaml +27 -0
- moriarty/assets/templates/cves/CVE-2022-0185.yaml +21 -0
- moriarty/assets/templates/cves/CVE-2022-1388.yaml +36 -0
- moriarty/assets/templates/cves/CVE-2022-22954.yaml +28 -0
- moriarty/assets/templates/cves/CVE-2022-22965.yaml +31 -0
- moriarty/assets/templates/cves/CVE-2022-26134.yaml +27 -0
- moriarty/assets/templates/cves/CVE-2023-22515.yaml +27 -0
- moriarty/assets/templates/cves/CVE-2023-22527.yaml +29 -0
- moriarty/assets/templates/cves/CVE-2023-23752.yaml +33 -0
- moriarty/assets/templates/cves/CVE-2023-27350.yaml +27 -0
- moriarty/assets/templates/cves/CVE-2023-2868.yaml +27 -0
- moriarty/assets/templates/cves/CVE-2023-34362.yaml +27 -0
- moriarty/assets/templates/cves/CVE-2023-3519.yaml +28 -0
- moriarty/assets/templates/cves/CVE-2023-4966.yaml +27 -0
- moriarty/assets/templates/default-logins/admin-weak.yaml +40 -0
- moriarty/assets/templates/default-logins/wordpress-default.yaml +38 -0
- moriarty/assets/templates/exposures/aws-credentials.yaml +35 -0
- moriarty/assets/templates/exposures/backup-files.yaml +36 -0
- moriarty/assets/templates/exposures/database-files.yaml +34 -0
- moriarty/assets/templates/exposures/docker-exposed.yaml +31 -0
- moriarty/assets/templates/exposures/env-exposed.yaml +41 -0
- moriarty/assets/templates/exposures/git-exposed.yaml +41 -0
- moriarty/assets/templates/exposures/phpinfo.yaml +36 -0
- moriarty/assets/templates/exposures/svn-exposed.yaml +28 -0
- moriarty/assets/templates/fuzzing/api-endpoints.yaml +39 -0
- moriarty/assets/templates/fuzzing/common-files.yaml +37 -0
- moriarty/assets/templates/fuzzing/open-redirect-fuzz.yaml +35 -0
- moriarty/assets/templates/fuzzing/xss-search-fuzz.yaml +29 -0
- moriarty/assets/templates/git-config.yaml +18 -0
- moriarty/assets/templates/misconfigurations/cors-misconfiguration.yaml +30 -0
- moriarty/assets/templates/misconfigurations/debug-enabled.yaml +29 -0
- moriarty/assets/templates/misconfigurations/directory-listing.yaml +33 -0
- moriarty/assets/templates/misconfigurations/jwt-none-algo.yaml +30 -0
- moriarty/assets/templates/misconfigurations/ssl-tls-weak.yaml +23 -0
- moriarty/assets/templates/vulnerabilities/lfi-basic.yaml +31 -0
- moriarty/assets/templates/vulnerabilities/open-redirect.yaml +31 -0
- moriarty/assets/templates/vulnerabilities/rce-basic.yaml +34 -0
- moriarty/assets/templates/vulnerabilities/sqli-error.yaml +39 -0
- moriarty/assets/templates/vulnerabilities/ssrf-basic.yaml +31 -0
- moriarty/assets/templates/vulnerabilities/xss-reflected.yaml +38 -0
- moriarty/assets/templates/vulnerabilities/xxe-basic.yaml +30 -0
- moriarty/assets/wordlists/subdomains-1000.txt +1063 -0
- moriarty/cli/__init__.py +3 -0
- moriarty/cli/app.py +120 -0
- moriarty/cli/async_utils.py +19 -0
- moriarty/cli/dns.py +83 -0
- moriarty/cli/domain_cmd.py +572 -0
- moriarty/cli/email.py +383 -0
- moriarty/cli/email_investigate.py +224 -0
- moriarty/cli/intelligence.py +329 -0
- moriarty/cli/output.py +62 -0
- moriarty/cli/rdap.py +94 -0
- moriarty/cli/state.py +38 -0
- moriarty/cli/tls.py +91 -0
- moriarty/cli/user.py +227 -0
- moriarty/core/cache_backend.py +223 -0
- moriarty/core/config_manager.py +303 -0
- moriarty/correlator/__init__.py +0 -0
- moriarty/data/__init__.py +81 -0
- moriarty/data/ioc/__init__.py +142 -0
- moriarty/data/ioc/matcher.py +254 -0
- moriarty/data/ioc/types.py +267 -0
- moriarty/data/local_intelligence.py +507 -0
- moriarty/data/signature_loaders/__init__.py +103 -0
- moriarty/data/signature_loaders/base.py +54 -0
- moriarty/data/signature_loaders/ioc_feed.py +356 -0
- moriarty/data/signature_loaders/wappalyzer.py +112 -0
- moriarty/dsl/__init__.py +0 -0
- moriarty/dsl/loader.py +99 -0
- moriarty/dsl/schema.py +47 -0
- moriarty/export/__init__.py +0 -0
- moriarty/intelligence/__init__.py +27 -0
- moriarty/intelligence/__main__.py +150 -0
- moriarty/intelligence/config.py +395 -0
- moriarty/intelligence/ioc.py +267 -0
- moriarty/intelligence/signatures.py +550 -0
- moriarty/intelligence/storage.py +501 -0
- moriarty/interop/__init__.py +0 -0
- moriarty/logging/__init__.py +0 -0
- moriarty/logging/config.py +47 -0
- moriarty/models/__init__.py +16 -0
- moriarty/models/assertion.py +24 -0
- moriarty/models/entity.py +22 -0
- moriarty/models/evidence.py +37 -0
- moriarty/models/relation.py +24 -0
- moriarty/models/types.py +28 -0
- moriarty/modules/__init__.py +0 -0
- moriarty/modules/avatar_hash.py +184 -0
- moriarty/modules/directory_fuzzer.py +322 -0
- moriarty/modules/dns_scan.py +40 -0
- moriarty/modules/domain_scanner.py +620 -0
- moriarty/modules/email_check.py +98 -0
- moriarty/modules/email_investigate.py +267 -0
- moriarty/modules/email_security.py +274 -0
- moriarty/modules/googlemaps_lookup.py +106 -0
- moriarty/modules/headless_executor.py +201 -0
- moriarty/modules/orchestrator.py +60 -0
- moriarty/modules/passive_recon.py +444 -0
- moriarty/modules/phone_extractor.py +151 -0
- moriarty/modules/pipeline_orchestrator.py +726 -0
- moriarty/modules/port_scanner.py +129 -0
- moriarty/modules/rdap.py +61 -0
- moriarty/modules/rdap_extended.py +188 -0
- moriarty/modules/stealth_mode.py +610 -0
- moriarty/modules/subdomain_discovery.py +595 -0
- moriarty/modules/technology_profiler.py +361 -0
- moriarty/modules/template_executor.py +239 -0
- moriarty/modules/template_scanner.py +1048 -0
- moriarty/modules/tls_scan.py +46 -0
- moriarty/modules/tls_validator.py +188 -0
- moriarty/modules/vuln_scanner.py +483 -0
- moriarty/modules/waf_detector.py +585 -0
- moriarty/modules/wayback_discovery.py +234 -0
- moriarty/modules/web_crawler.py +163 -0
- moriarty/net/__init__.py +0 -0
- moriarty/net/dns_cache.py +175 -0
- moriarty/net/dns_client.py +188 -0
- moriarty/net/rdap_client.py +52 -0
- moriarty/net/smtp_client.py +114 -0
- moriarty/net/tls_client.py +111 -0
- moriarty/parsers/__init__.py +0 -0
- moriarty/parsers/html_parser.py +136 -0
- moriarty/tests/__init__.py +0 -0
- moriarty/tests/test_email_service.py +17 -0
- moriarty/tests/test_models.py +46 -0
- moriarty/tests/test_orchestrator.py +30 -0
- moriarty/tests/test_tls_client.py +18 -0
- moriarty_project-0.1.6.dist-info/METADATA +388 -0
- moriarty_project-0.1.6.dist-info/RECORD +418 -0
- moriarty_project-0.1.6.dist-info/WHEEL +4 -0
- moriarty_project-0.1.6.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,444 @@
|
|
1
|
+
"""Coleta passiva de inteligência sobre domínios (Passive Recon)."""
|
2
|
+
from __future__ import annotations
|
3
|
+
|
4
|
+
import asyncio
|
5
|
+
import base64
|
6
|
+
import json
|
7
|
+
from dataclasses import dataclass, field
|
8
|
+
from typing import Any, Dict, List, Optional
|
9
|
+
|
10
|
+
import httpx
|
11
|
+
import structlog
|
12
|
+
|
13
|
+
from .technology_profiler import profile_domain
|
14
|
+
from ..net.rdap_client import RDAPClient
|
15
|
+
|
16
|
+
logger = structlog.get_logger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
@dataclass
|
20
|
+
class PassiveReconResult:
|
21
|
+
"""Estrutura consolidada dos artefatos coletados."""
|
22
|
+
|
23
|
+
domain: str
|
24
|
+
subdomains: Dict[str, List[str]] = field(default_factory=dict)
|
25
|
+
credentials: Dict[str, Any] = field(default_factory=dict)
|
26
|
+
leaks: Dict[str, Any] = field(default_factory=dict)
|
27
|
+
technologies: Dict[str, Any] = field(default_factory=dict)
|
28
|
+
reputation: Dict[str, Any] = field(default_factory=dict)
|
29
|
+
rdap: Optional[Dict[str, Any]] = None
|
30
|
+
whois: Optional[str] = None
|
31
|
+
|
32
|
+
def to_dict(self) -> Dict[str, Any]:
|
33
|
+
return {
|
34
|
+
"domain": self.domain,
|
35
|
+
"subdomains": self.subdomains,
|
36
|
+
"credentials": self.credentials,
|
37
|
+
"leaks": self.leaks,
|
38
|
+
"technologies": self.technologies,
|
39
|
+
"reputation": self.reputation,
|
40
|
+
"whois": self.whois,
|
41
|
+
"rdap": self.rdap,
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
class PassiveRecon:
|
46
|
+
"""Orquestra integrações passivas usando as credenciais do ConfigManager."""
|
47
|
+
|
48
|
+
def __init__(self, domain: str, timeout: float = 15.0):
|
49
|
+
self.domain = domain.lower().strip()
|
50
|
+
self.timeout = timeout
|
51
|
+
|
52
|
+
try:
|
53
|
+
from moriarty.core.config_manager import config_manager
|
54
|
+
|
55
|
+
self.config = config_manager
|
56
|
+
except Exception: # pragma: no cover - fallback ao rodar fora do cli
|
57
|
+
self.config = None
|
58
|
+
|
59
|
+
self.session = httpx.AsyncClient(timeout=self.timeout, follow_redirects=True)
|
60
|
+
|
61
|
+
async def close(self) -> None:
|
62
|
+
await self.session.aclose()
|
63
|
+
|
64
|
+
async def collect(self) -> PassiveReconResult:
|
65
|
+
"""Executa todas as coletas em paralelo."""
|
66
|
+
result = PassiveReconResult(domain=self.domain)
|
67
|
+
|
68
|
+
tasks = [
|
69
|
+
self._gather_subdomains(result),
|
70
|
+
self._gather_credentials(result),
|
71
|
+
self._fingerprint_http(result),
|
72
|
+
self._gather_reputation(result),
|
73
|
+
self._collect_whois(result),
|
74
|
+
self._collect_rdap(result),
|
75
|
+
]
|
76
|
+
|
77
|
+
await asyncio.gather(*tasks)
|
78
|
+
return result
|
79
|
+
|
80
|
+
# ------------------------------------------------------------------
|
81
|
+
# Subdomains / Passive Sources
|
82
|
+
# ------------------------------------------------------------------
|
83
|
+
async def _gather_subdomains(self, result: PassiveReconResult) -> None:
|
84
|
+
collectors = [
|
85
|
+
self._from_passivetotal,
|
86
|
+
self._from_certspotter,
|
87
|
+
self._from_censys,
|
88
|
+
self._from_securitytrails,
|
89
|
+
self._from_spyse,
|
90
|
+
self._from_leakix,
|
91
|
+
]
|
92
|
+
|
93
|
+
subdomains: Dict[str, List[str]] = {}
|
94
|
+
|
95
|
+
async def run_collector(handler):
|
96
|
+
name = handler.__name__.replace("_from_", "")
|
97
|
+
try:
|
98
|
+
data = await handler()
|
99
|
+
if data:
|
100
|
+
subdomains[name] = sorted(set(data))
|
101
|
+
except Exception as exc: # pragma: no cover - tolera falhas pontuais
|
102
|
+
logger.debug("passiverecon.source.error", source=name, error=str(exc))
|
103
|
+
|
104
|
+
await asyncio.gather(*(run_collector(c) for c in collectors))
|
105
|
+
result.subdomains = subdomains
|
106
|
+
|
107
|
+
async def _from_passivetotal(self) -> List[str]:
|
108
|
+
creds = self._get_credentials("passivetotal")
|
109
|
+
if not creds or not creds.get("username") or not creds.get("key"):
|
110
|
+
return []
|
111
|
+
|
112
|
+
auth = base64.b64encode(f"{creds['username']}:{creds['key']}".encode()).decode()
|
113
|
+
url = "https://api.passivetotal.org/v2/enrichment/subdomains"
|
114
|
+
params = {"query": self.domain}
|
115
|
+
headers = {"Authorization": f"Basic {auth}"}
|
116
|
+
|
117
|
+
response = await self.session.get(url, params=params, headers=headers)
|
118
|
+
if response.status_code != 200:
|
119
|
+
logger.debug("passiverecon.passivetotal.http", status=response.status_code)
|
120
|
+
return []
|
121
|
+
|
122
|
+
payload = response.json()
|
123
|
+
return [f"{sub}.{self.domain}" for sub in payload.get("subdomains", [])]
|
124
|
+
|
125
|
+
async def _from_certspotter(self) -> List[str]:
|
126
|
+
url = "https://api.certspotter.com/v1/issuances"
|
127
|
+
params = {
|
128
|
+
"domain": self.domain,
|
129
|
+
"include_subdomains": "true",
|
130
|
+
"expand": "dns_names",
|
131
|
+
}
|
132
|
+
|
133
|
+
response = await self.session.get(url, params=params)
|
134
|
+
if response.status_code != 200:
|
135
|
+
logger.debug("passiverecon.certspotter.http", status=response.status_code)
|
136
|
+
return []
|
137
|
+
|
138
|
+
hosts: List[str] = []
|
139
|
+
for entry in response.json():
|
140
|
+
hosts.extend(entry.get("dns_names", []))
|
141
|
+
return [h for h in hosts if h.endswith(self.domain)]
|
142
|
+
|
143
|
+
async def _from_censys(self) -> List[str]:
|
144
|
+
creds = self._get_credentials("censys")
|
145
|
+
if not creds or not creds.get("id") or not creds.get("secret"):
|
146
|
+
return []
|
147
|
+
|
148
|
+
query = f"services.tls.certificates.leaf_data.subject_dn: {self.domain}"
|
149
|
+
url = "https://search.censys.io/api/v2/hosts/search"
|
150
|
+
auth = (creds["id"], creds["secret"])
|
151
|
+
payload = {"q": query, "per_page": 50}
|
152
|
+
|
153
|
+
response = await self.session.post(url, auth=auth, json=payload)
|
154
|
+
if response.status_code != 200:
|
155
|
+
logger.debug("passiverecon.censys.http", status=response.status_code)
|
156
|
+
return []
|
157
|
+
|
158
|
+
hosts = []
|
159
|
+
for entry in response.json().get("result", {}).get("hits", []):
|
160
|
+
ip = entry.get("ip")
|
161
|
+
if ip:
|
162
|
+
hosts.append(ip)
|
163
|
+
for service in entry.get("services", []):
|
164
|
+
hostname = service.get("observed_dns_names") or []
|
165
|
+
hosts.extend([h for h in hostname if h.endswith(self.domain)])
|
166
|
+
return hosts
|
167
|
+
|
168
|
+
async def _from_securitytrails(self) -> List[str]:
|
169
|
+
creds = self._get_credentials("securitytrails")
|
170
|
+
if not creds or not creds.get("key"):
|
171
|
+
return []
|
172
|
+
|
173
|
+
headers = {"APIKEY": creds["key"]}
|
174
|
+
url = f"https://api.securitytrails.com/v1/history/{self.domain}/dns/a"
|
175
|
+
response = await self.session.get(url, headers=headers)
|
176
|
+
if response.status_code != 200:
|
177
|
+
logger.debug("passiverecon.securitytrails.http", status=response.status_code)
|
178
|
+
return []
|
179
|
+
|
180
|
+
hosts = []
|
181
|
+
data = response.json()
|
182
|
+
for record in data.get("records", []):
|
183
|
+
hosts.extend(record.get("values", []))
|
184
|
+
return hosts
|
185
|
+
|
186
|
+
async def _from_spyse(self) -> List[str]:
|
187
|
+
creds = self._get_credentials("spyse")
|
188
|
+
if not creds or not creds.get("key"):
|
189
|
+
return []
|
190
|
+
|
191
|
+
headers = {"Authorization": f"Bearer {creds['key']}"}
|
192
|
+
payload = {"search_params": {"domain": {"equals": self.domain}}}
|
193
|
+
url = "https://api.spyse.com/v4/data/domain/search"
|
194
|
+
|
195
|
+
response = await self.session.post(url, headers=headers, json=payload)
|
196
|
+
if response.status_code != 200:
|
197
|
+
logger.debug("passiverecon.spyse.http", status=response.status_code)
|
198
|
+
return []
|
199
|
+
|
200
|
+
hosts = []
|
201
|
+
for entry in response.json().get("data", {}).get("items", []):
|
202
|
+
hosts.extend(entry.get("related_domains", []))
|
203
|
+
return [h for h in hosts if h.endswith(self.domain)]
|
204
|
+
|
205
|
+
async def _from_leakix(self) -> List[str]:
|
206
|
+
creds = self._get_credentials("leakix")
|
207
|
+
if not creds or not creds.get("key"):
|
208
|
+
return []
|
209
|
+
|
210
|
+
headers = {"Authorization": f"Bearer {creds['key']}"}
|
211
|
+
url = f"https://leakix.net/api/v1/lookup/{self.domain}"
|
212
|
+
response = await self.session.get(url, headers=headers)
|
213
|
+
if response.status_code != 200:
|
214
|
+
logger.debug("passiverecon.leakix.http", status=response.status_code)
|
215
|
+
return []
|
216
|
+
|
217
|
+
results = response.json().get("results", [])
|
218
|
+
hosts = []
|
219
|
+
for entry in results:
|
220
|
+
host = entry.get("host")
|
221
|
+
if host:
|
222
|
+
hosts.append(host)
|
223
|
+
return hosts
|
224
|
+
|
225
|
+
# ------------------------------------------------------------------
|
226
|
+
# Credenciais e vazamentos
|
227
|
+
# ------------------------------------------------------------------
|
228
|
+
async def _gather_credentials(self, result: PassiveReconResult) -> None:
|
229
|
+
leaks: Dict[str, Any] = {}
|
230
|
+
|
231
|
+
hibp = await self._from_hibp()
|
232
|
+
if hibp:
|
233
|
+
leaks["haveibeenpwned"] = hibp
|
234
|
+
|
235
|
+
leakpeek = await self._from_leakpeek()
|
236
|
+
if leakpeek:
|
237
|
+
leaks["leakpeek"] = leakpeek
|
238
|
+
|
239
|
+
result.leaks = leaks
|
240
|
+
|
241
|
+
async def _from_hibp(self) -> Optional[Dict[str, Any]]:
|
242
|
+
creds = self._get_credentials("hibp")
|
243
|
+
if not creds or not creds.get("key"):
|
244
|
+
return None
|
245
|
+
|
246
|
+
headers = {
|
247
|
+
"hibp-api-key": creds["key"],
|
248
|
+
"user-agent": "moriarty-osint",
|
249
|
+
}
|
250
|
+
url = f"https://haveibeenpwned.com/api/v3/breachedaccount/{self.domain}"
|
251
|
+
|
252
|
+
response = await self.session.get(url, headers=headers)
|
253
|
+
if response.status_code == 404:
|
254
|
+
return {"breaches": []}
|
255
|
+
if response.status_code != 200:
|
256
|
+
logger.debug("passiverecon.hibp.http", status=response.status_code)
|
257
|
+
return None
|
258
|
+
return {"breaches": response.json()}
|
259
|
+
|
260
|
+
async def _from_leakpeek(self) -> Optional[List[Dict[str, Any]]]:
|
261
|
+
creds = self._get_credentials("leakpeek")
|
262
|
+
if not creds or not creds.get("key"):
|
263
|
+
return None
|
264
|
+
|
265
|
+
headers = {"Authorization": f"Bearer {creds['key']}"}
|
266
|
+
url = f"https://api.leakpeek.com/v1/leaks/{self.domain}"
|
267
|
+
response = await self.session.get(url, headers=headers)
|
268
|
+
if response.status_code != 200:
|
269
|
+
logger.debug("passiverecon.leakpeek.http", status=response.status_code)
|
270
|
+
return None
|
271
|
+
return response.json().get("results", [])
|
272
|
+
|
273
|
+
# ------------------------------------------------------------------
|
274
|
+
# Fingerprinting HTTP / tecnologia
|
275
|
+
# ------------------------------------------------------------------
|
276
|
+
async def _fingerprint_http(self, result: PassiveReconResult) -> None:
|
277
|
+
try:
|
278
|
+
profile = await profile_domain(self.domain, session=self.session)
|
279
|
+
except Exception as exc: # pragma: no cover - tolerate fingerprint failures
|
280
|
+
logger.debug("passiverecon.fingerprint.error", domain=self.domain, error=str(exc))
|
281
|
+
profile = {}
|
282
|
+
result.technologies = profile
|
283
|
+
|
284
|
+
# ------------------------------------------------------------------
|
285
|
+
# Reputação / ameaças
|
286
|
+
# ------------------------------------------------------------------
|
287
|
+
async def _gather_reputation(self, result: PassiveReconResult) -> None:
|
288
|
+
rep: Dict[str, Any] = {}
|
289
|
+
|
290
|
+
otx = await self._from_alienvault()
|
291
|
+
if otx:
|
292
|
+
rep["alienvault"] = otx
|
293
|
+
|
294
|
+
threatfox = await self._from_threatfox()
|
295
|
+
if threatfox:
|
296
|
+
rep["threatfox"] = threatfox
|
297
|
+
|
298
|
+
urlhaus = await self._from_urlhaus()
|
299
|
+
if urlhaus:
|
300
|
+
rep["urlhaus"] = urlhaus
|
301
|
+
|
302
|
+
result.reputation = rep
|
303
|
+
|
304
|
+
async def _from_alienvault(self) -> Optional[Dict[str, Any]]:
|
305
|
+
url = f"https://otx.alienvault.com/api/v1/indicators/domain/{self.domain}/general"
|
306
|
+
response = await self.session.get(url)
|
307
|
+
if response.status_code != 200:
|
308
|
+
logger.debug("passiverecon.alienvault.http", status=response.status_code)
|
309
|
+
return None
|
310
|
+
data = response.json()
|
311
|
+
pulses = [pulse.get("name") for pulse in data.get("pulse_info", {}).get("pulses", [])]
|
312
|
+
return {
|
313
|
+
"pulses": pulses,
|
314
|
+
"alexa": data.get("alexa"),
|
315
|
+
"asn": data.get("asn"),
|
316
|
+
}
|
317
|
+
|
318
|
+
async def _from_threatfox(self) -> Optional[Dict[str, Any]]:
|
319
|
+
"""Consulta threat intel da Abuse.ch (ThreatFox)."""
|
320
|
+
url = "https://threatfox-api.abuse.ch/api/v1/"
|
321
|
+
payload = {"query": "search_ioc", "search_term": self.domain}
|
322
|
+
try:
|
323
|
+
response = await self.session.post(url, json=payload)
|
324
|
+
except Exception as exc: # pragma: no cover
|
325
|
+
logger.debug("passiverecon.threatfox.error", error=str(exc))
|
326
|
+
return None
|
327
|
+
|
328
|
+
if response.status_code != 200:
|
329
|
+
logger.debug("passiverecon.threatfox.http", status=response.status_code)
|
330
|
+
return None
|
331
|
+
|
332
|
+
data = response.json()
|
333
|
+
if data.get("query_status") != "ok":
|
334
|
+
return None
|
335
|
+
|
336
|
+
records = data.get("data", [])
|
337
|
+
families = sorted({entry.get("malware", "") for entry in records if entry.get("malware")})
|
338
|
+
return {
|
339
|
+
"count": len(records),
|
340
|
+
"malware_families": families,
|
341
|
+
}
|
342
|
+
|
343
|
+
async def _from_urlhaus(self) -> Optional[Dict[str, Any]]:
|
344
|
+
"""Consulta URLs maliciosas hospedadas no domínio (URLHaus)."""
|
345
|
+
url = "https://urlhaus-api.abuse.ch/v1/host/"
|
346
|
+
data = {"host": self.domain}
|
347
|
+
try:
|
348
|
+
response = await self.session.post(url, data=data)
|
349
|
+
except Exception as exc: # pragma: no cover
|
350
|
+
logger.debug("passiverecon.urlhaus.error", error=str(exc))
|
351
|
+
return None
|
352
|
+
|
353
|
+
if response.status_code != 200:
|
354
|
+
logger.debug("passiverecon.urlhaus.http", status=response.status_code)
|
355
|
+
return None
|
356
|
+
|
357
|
+
payload = response.json()
|
358
|
+
if payload.get("query_status") != "ok":
|
359
|
+
return None
|
360
|
+
|
361
|
+
entries = payload.get("urls", [])
|
362
|
+
latest = None
|
363
|
+
if entries:
|
364
|
+
latest_record = entries[0]
|
365
|
+
latest = {
|
366
|
+
"url": latest_record.get("url"),
|
367
|
+
"status": latest_record.get("url_status"),
|
368
|
+
"threat": latest_record.get("threat"),
|
369
|
+
"reporter": latest_record.get("reporter"),
|
370
|
+
}
|
371
|
+
|
372
|
+
return {
|
373
|
+
"count": len(entries),
|
374
|
+
"latest": latest,
|
375
|
+
}
|
376
|
+
|
377
|
+
# ------------------------------------------------------------------
|
378
|
+
# WHOIS
|
379
|
+
# ------------------------------------------------------------------
|
380
|
+
async def _collect_whois(self, result: PassiveReconResult) -> None:
|
381
|
+
cmd = await asyncio.create_subprocess_exec(
|
382
|
+
"whois",
|
383
|
+
self.domain,
|
384
|
+
stdout=asyncio.subprocess.PIPE,
|
385
|
+
stderr=asyncio.subprocess.PIPE,
|
386
|
+
)
|
387
|
+
stdout, _ = await cmd.communicate()
|
388
|
+
result.whois = stdout.decode(errors="ignore") if stdout else None
|
389
|
+
|
390
|
+
async def _collect_rdap(self, result: PassiveReconResult) -> None:
|
391
|
+
"""Consulta RDAP oficial para complementar WHOIS."""
|
392
|
+
try:
|
393
|
+
client = RDAPClient(timeout=self.timeout, http2=True)
|
394
|
+
rdap = await client.fetch(f"domain/{self.domain}")
|
395
|
+
except Exception as exc: # pragma: no cover - RDAP falhas toleradas
|
396
|
+
logger.debug("passiverecon.rdap.error", domain=self.domain, error=str(exc))
|
397
|
+
return
|
398
|
+
|
399
|
+
payload = rdap.payload.copy()
|
400
|
+
result.rdap = {
|
401
|
+
"url": rdap.url,
|
402
|
+
"status": rdap.status,
|
403
|
+
"latency_ms": round(rdap.latency_ms, 2),
|
404
|
+
"handle": payload.get("handle"),
|
405
|
+
"registrar": payload.get("registrar", {}).get("name")
|
406
|
+
if isinstance(payload.get("registrar"), dict)
|
407
|
+
else payload.get("registrar"),
|
408
|
+
"events": payload.get("events", []),
|
409
|
+
"raw": payload,
|
410
|
+
}
|
411
|
+
|
412
|
+
# ------------------------------------------------------------------
|
413
|
+
# Utilidades
|
414
|
+
# ------------------------------------------------------------------
|
415
|
+
def _get_credentials(self, service: str) -> Dict[str, Any]:
|
416
|
+
if not self.config:
|
417
|
+
return {}
|
418
|
+
|
419
|
+
api_keys = getattr(self.config, "api_keys", None)
|
420
|
+
if not api_keys:
|
421
|
+
return {}
|
422
|
+
|
423
|
+
data = getattr(api_keys, service, None)
|
424
|
+
if isinstance(data, dict):
|
425
|
+
return data
|
426
|
+
if isinstance(data, str):
|
427
|
+
return {"key": data}
|
428
|
+
|
429
|
+
# Suporte a nomes específicos (ex.: censys_id/secret)
|
430
|
+
if service == "censys":
|
431
|
+
return {
|
432
|
+
"id": getattr(api_keys, "censys_id", None),
|
433
|
+
"secret": getattr(api_keys, "censys_secret", None),
|
434
|
+
}
|
435
|
+
if service == "passivetotal":
|
436
|
+
return {
|
437
|
+
"username": getattr(api_keys, "passivetotal_username", None),
|
438
|
+
"key": getattr(api_keys, "passivetotal_key", None),
|
439
|
+
}
|
440
|
+
|
441
|
+
return {}
|
442
|
+
|
443
|
+
|
444
|
+
__all__ = ["PassiveRecon", "PassiveReconResult"]
|
@@ -0,0 +1,151 @@
|
|
1
|
+
"""Extração de telefones de perfis sociais."""
|
2
|
+
import re
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from typing import List, Optional
|
5
|
+
|
6
|
+
import phonenumbers
|
7
|
+
import structlog
|
8
|
+
|
9
|
+
logger = structlog.get_logger(__name__)
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class PhoneNumber:
|
14
|
+
"""Número de telefone normalizado."""
|
15
|
+
raw: str
|
16
|
+
normalized: str # E.164 format
|
17
|
+
country_code: str
|
18
|
+
national_number: str
|
19
|
+
is_valid: bool
|
20
|
+
is_mobile: bool
|
21
|
+
carrier: Optional[str] = None
|
22
|
+
location: Optional[str] = None
|
23
|
+
|
24
|
+
|
25
|
+
class PhoneExtractor:
|
26
|
+
"""Extrai e valida números de telefone."""
|
27
|
+
|
28
|
+
# Padrões comuns de telefone
|
29
|
+
PHONE_PATTERNS = [
|
30
|
+
r'\+\d{1,3}[\s\-]?\(?\d{1,4}\)?[\s\-]?\d{1,4}[\s\-]?\d{1,9}', # Internacional
|
31
|
+
r'\(?\d{3}\)?[\s\-]?\d{3}[\s\-]?\d{4}', # US/BR format
|
32
|
+
r'\d{2}[\s\-]?\d{4,5}[\s\-]?\d{4}', # BR format
|
33
|
+
r'\d{10,15}', # Números longos
|
34
|
+
]
|
35
|
+
|
36
|
+
def extract_from_text(self, text: str, default_region: str = "US") -> List[PhoneNumber]:
|
37
|
+
"""
|
38
|
+
Extrai números de telefone de um texto.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
text: Texto para buscar
|
42
|
+
default_region: Código do país padrão (ISO 3166-1 alpha-2)
|
43
|
+
"""
|
44
|
+
if not text:
|
45
|
+
return []
|
46
|
+
|
47
|
+
logger.debug("phone.extract.start", text_length=len(text))
|
48
|
+
|
49
|
+
phones = []
|
50
|
+
seen = set()
|
51
|
+
|
52
|
+
for pattern in self.PHONE_PATTERNS:
|
53
|
+
matches = re.finditer(pattern, text)
|
54
|
+
|
55
|
+
for match in matches:
|
56
|
+
raw = match.group(0)
|
57
|
+
|
58
|
+
# Remove duplicatas
|
59
|
+
if raw in seen:
|
60
|
+
continue
|
61
|
+
seen.add(raw)
|
62
|
+
|
63
|
+
# Tenta parsear com phonenumbers
|
64
|
+
try:
|
65
|
+
parsed = phonenumbers.parse(raw, default_region)
|
66
|
+
|
67
|
+
if phonenumbers.is_valid_number(parsed):
|
68
|
+
normalized = phonenumbers.format_number(
|
69
|
+
parsed,
|
70
|
+
phonenumbers.PhoneNumberFormat.E164
|
71
|
+
)
|
72
|
+
|
73
|
+
national = phonenumbers.format_number(
|
74
|
+
parsed,
|
75
|
+
phonenumbers.PhoneNumberFormat.NATIONAL
|
76
|
+
)
|
77
|
+
|
78
|
+
is_mobile = phonenumbers.number_type(parsed) in [
|
79
|
+
phonenumbers.PhoneNumberType.MOBILE,
|
80
|
+
phonenumbers.PhoneNumberType.FIXED_LINE_OR_MOBILE,
|
81
|
+
]
|
82
|
+
|
83
|
+
# Tenta obter carrier (operadora)
|
84
|
+
carrier = None
|
85
|
+
try:
|
86
|
+
from phonenumbers import carrier as carrier_module
|
87
|
+
carrier = carrier_module.name_for_number(parsed, "en")
|
88
|
+
except:
|
89
|
+
pass
|
90
|
+
|
91
|
+
# Tenta obter localização
|
92
|
+
location = None
|
93
|
+
try:
|
94
|
+
from phonenumbers import geocoder
|
95
|
+
location = geocoder.description_for_number(parsed, "en")
|
96
|
+
except:
|
97
|
+
pass
|
98
|
+
|
99
|
+
phone = PhoneNumber(
|
100
|
+
raw=raw,
|
101
|
+
normalized=normalized,
|
102
|
+
country_code=f"+{parsed.country_code}",
|
103
|
+
national_number=national,
|
104
|
+
is_valid=True,
|
105
|
+
is_mobile=is_mobile,
|
106
|
+
carrier=carrier if carrier else None,
|
107
|
+
location=location if location else None,
|
108
|
+
)
|
109
|
+
|
110
|
+
phones.append(phone)
|
111
|
+
logger.debug("phone.extracted", phone=normalized, location=location)
|
112
|
+
|
113
|
+
except phonenumbers.NumberParseException:
|
114
|
+
# Número inválido, ignora
|
115
|
+
continue
|
116
|
+
|
117
|
+
logger.info("phone.extract.complete", count=len(phones))
|
118
|
+
return phones
|
119
|
+
|
120
|
+
def extract_from_profile(self, profile_data: dict, default_region: str = "US") -> List[PhoneNumber]:
|
121
|
+
"""
|
122
|
+
Extrai telefones de dados de perfil.
|
123
|
+
|
124
|
+
Busca em campos comuns: bio, description, contact, etc.
|
125
|
+
"""
|
126
|
+
phones = []
|
127
|
+
|
128
|
+
# Campos comuns onde telefones aparecem
|
129
|
+
fields_to_check = [
|
130
|
+
"bio", "description", "about", "contact", "phone",
|
131
|
+
"contact_info", "business_phone", "mobile", "tel",
|
132
|
+
]
|
133
|
+
|
134
|
+
for field in fields_to_check:
|
135
|
+
if field in profile_data and profile_data[field]:
|
136
|
+
value = str(profile_data[field])
|
137
|
+
extracted = self.extract_from_text(value, default_region)
|
138
|
+
phones.extend(extracted)
|
139
|
+
|
140
|
+
# Remove duplicatas
|
141
|
+
unique_phones = {}
|
142
|
+
for phone in phones:
|
143
|
+
unique_phones[phone.normalized] = phone
|
144
|
+
|
145
|
+
return list(unique_phones.values())
|
146
|
+
|
147
|
+
|
148
|
+
__all__ = [
|
149
|
+
"PhoneExtractor",
|
150
|
+
"PhoneNumber",
|
151
|
+
]
|