diff --git a/README.md b/README.md index f09edef..141c0a1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -
+
@@ -11,7 +11,7 @@ ----- +
@@ -83,21 +83,21 @@ #### APIs -| Service | Functions | Status | -|------------------------------------------------------------------ |:-----------------------------------------------------------------: |:--------------------------: | -| [HaveIBeenPwned(v3)](https://haveibeenpwned.com/) | Number of email breaches | :white_check_mark: :key: | -| [HaveIBeenPwned Pastes(v3)](https://haveibeenpwned.com/Pastes) :new: | URLs of text files mentioning targets | :white_check_mark: :key: | -| [Hunter.io](https://hunter.io/) - Public | Number of related emails | :white_check_mark: | -| [Hunter.io](https://hunter.io/) - Service (free tier) | Cleartext related emails, Chasing | :white_check_mark: :key: | -| [WeLeakInfo](https://weleakinfo.com/) - Public :new: | Number of search-able breach results | :white_check_mark: :key: | -| [WeLeakInfo](https://weleakinfo.com/) - Service :new: | Cleartext passwords, hashs and salts, usernames, IPs | :white_check_mark: :key: | -| [Snusbase](https://snusbase.com/) - Service | Cleartext passwords, hashs and salts, usernames, IPs - Fast :zap: | :white_check_mark: :key: | -| [Leak-Lookup](https://leak-lookup.com/) - Public :new: | Number of search-able breach results | :white_check_mark: (:key:) | -| [Leak-Lookup](https://leak-lookup.com/) - Service :new: | Cleartext passwords, hashs and salts, usernames, IPs | :white_check_mark: :key: | -| [Emailrep.io](https://emailrep.io/) - :new: | Last seen in breaches, social media profiles | :white_check_mark: | +| Service | Functions | Status | +|----------------------------------------------------------------|:-----------------------------------------------------------------:|:--------------------------:| +| [HaveIBeenPwned(v3)](https://haveibeenpwned.com/) | Number of email breaches | :white_check_mark: :key: | +| [HaveIBeenPwned Pastes(v3)](https://haveibeenpwned.com/Pastes) | URLs of text files mentioning targets | :white_check_mark: :key: | +| [Hunter.io](https://hunter.io/) - Public | Number of related emails | :white_check_mark: | +| [Hunter.io](https://hunter.io/) - Service (free tier) | Cleartext related emails, Chasing | :white_check_mark: :key: | +| [WeLeakInfo](https://weleakinfo.com/) - Public | Number of search-able breach results | :white_check_mark: :key: | +| [WeLeakInfo](https://weleakinfo.com/) - Service | Cleartext passwords, hashs and salts, usernames, IPs, domain | :white_check_mark: :key: | +| [Snusbase](https://snusbase.com/) - Service | Cleartext passwords, hashs and salts, usernames, IPs - Fast :zap: | :white_check_mark: :key: | +| [Leak-Lookup](https://leak-lookup.com/) - Public | Number of search-able breach results | :white_check_mark: (:key:) | +| [Leak-Lookup](https://leak-lookup.com/) - Service | Cleartext passwords, hashs and salts, usernames, IPs, domain | :white_check_mark: :key: | +| [Emailrep.io](https://emailrep.io/) - Service (free) | Last seen in breaches, social media profiles | :white_check_mark: | +| [Scylla.sh](https://scylla.sh/) - Service (free) | Cleartext passwords, hashs and salts, usernames, IPs, domain | :white_check_mark: | *:key: - API key required* -*:new: - new in h8mail v2+* ----- @@ -337,11 +337,12 @@ $ h8mail -t john.smith@evilcorp.com -k "leak-lookup_pub=1bf94ff907f68d511de9a610 ## :tangerine: Supported custom queries -| | username | domain | hash | password | ip | -|------------|:--------:|:------:|:----:|:--------:|:--------------:| -| WeLeakInfo | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| LeakLookup | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: | -| Snusbase | :white_check_mark: | | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | username | domain | hash | password | ip | +|------------|:------------------:|:------------------:|:------------------:|:------------------:|:------------------:| +| WeLeakInfo | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| LeakLookup | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: | +| Snusbase | :white_check_mark: | | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Scylla.sh | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | As of writing, some service providers are undergoing upgrades and might support additional queries. @@ -410,8 +411,8 @@ $ python3 -m h8mail -h * [WeLeakInfo](https://weleakinfo.com/) for being developer friendly * h8mail's Pypi integration is strongly based on the work of audreyr's [CookieCutter PyPackage](https://github.com/audreyr/cookiecutter-pypackage) * Logo generated using Hatchful by Shopify -* [Jake Creps](https://twitter.com/jakecreps) for his [h8mail v2 introduction](https://jakecreps.com/2019/06/21/h8mail/) - +* [Jake Creps](https://twitter.com/jakecreps) for his [h8mail v2 introduction](https://jakecreps.com/2019/06/21/h8mail/) +* [Alejandro Caceres](https://twitter.com/_hyp3ri0n) for making scylla.sh available. Be sure to [support](https://www.buymeacoffee.com/Eiw47ImnT) him if you can ----- diff --git a/h8mail/utils/classes.py b/h8mail/utils/classes.py index b65f4cf..bb02688 100644 --- a/h8mail/utils/classes.py +++ b/h8mail/utils/classes.py @@ -30,6 +30,7 @@ def dump(self): print("Path: {}".format(self.filepath)) print("Line: {}".format(self.line)) print("Content: {}".format(self.content)) + print() class target: @@ -79,34 +80,37 @@ def make_request( ) # response = requests.request(url="http://127.0.0.1:8000", headers=self.headers, method=meth, timeout=timeout, allow_redirects=redirs, data=data, params=params) if self.debug: - print( - c.fg.lightred + "\nDEBUG: Sent the following---------------------" - ) + c.debug_news("DEBUG: Sent the following---------------------") print(self.headers) print(url, meth, data, params) - print("DEBUG: Received the following---------------------") - print(response.url) - print("\nDEBUG: RESPONSE HEADER---------------") + c.debug_news("DEBUG: Received the following---------------------") + c.debug_news(response.url) + c.debug_news("DEBUG: RESPONSE HEADER---------------------") print( "\n".join( "{}: {}".format(k, v) for k, v in response.headers.items() ) ) - print("\nDEBUG: RESPONSE BODY---------------") + c.debug_news("DEBUG: RESPONSE BODY---------------------") print(json.dumps(response.json(), indent=2)) - print(c.reset) + print(response) except Exception as ex: c.bad_news("Request could not be made for " + self.target) + print(url) print(ex) print(response) return response - # Soon to be deprecated + # Deprecated def get_hibp(self): try: sleep(1.3) c.info_news(c.bold + "HIBP free tier will stop working on the 2019/08/18") - c.info_news(c.bold + "You can already use a purchased API key using h8mail (config file)" + c.reset) + c.info_news( + c.bold + + "You can already use a purchased API key using h8mail (config file)" + + c.reset + ) url = "https://haveibeenpwned.com/api/v2/breachedaccount/{}?truncateResponse=true".format( self.target ) @@ -142,7 +146,7 @@ def get_hibp(self): c.bad_news("HIBP error: " + self.target) print(ex) - # Soon to be deprecated + # Deprecated def get_hibp_pastes(self): try: sleep(1.3) @@ -291,6 +295,10 @@ def get_emailrepio(self): data = response.json() if data["details"]["credentials_leaked"] is True: self.pwned += int(data["references"]) # or inc num references + if data["references"] == 1: + self.data.append(("EMAILREP_LEAKS", "{} leaked credential".format(data["references"]))) + else: + self.data.append(("EMAILREP_LEAKS", "{} leaked credentials".format(data["references"]))) c.good_news( "Found {num} breaches for {target} using emailrep.io".format( num=data["references"], target=self.target @@ -318,6 +326,60 @@ def get_emailrepio(self): c.bad_news("emailrep.io error: " + self.target) print(ex) + def get_scylla(self, user_query="email"): + try: + sleep(0.5) + self.headers.update({"Accept": "application/json"}) + if user_query == "email": + uri_scylla = 'Email: "' + self.target + '"' + elif user_query == "password": + uri_scylla = 'Password: "' + self.target + '"' + elif user_query == "username": + uri_scylla = 'User: "' + self.target + '"' + elif user_query == "ip": + uri_scylla = 'IP: "' + self.target + '"' + elif user_query == "hash": + uri_scylla = 'Hash: "' + self.target + '"' + elif user_query == "domain": + uri_scylla = 'Email: "*@' + self.target + '"' + url = "https://scylla.sh/search?q={}".format( + requests.utils.requote_uri(uri_scylla) + ) + response = self.make_request(url) + self.headers.popitem() + if response.status_code not in [200, 404]: + c.bad_news("Could not contact scylla.sh for " + self.target) + print(response.status_code) + print(response) + return + data = response.json() + for d in data: + for field, k in d["_source"].items(): + if "User" in field and k is not None: + self.data.append(("SCYLLA_USERNAME", k)) + self.pwned += 1 + if "Email" in field and k is not None and user_query is not "email": + self.data.append(("SCYLLA_EMAIL", k)) + self.pwned += 1 + if "Password" in field and k is not None: + self.data.append(("SCYLLA_PASSWORD", k)) + self.pwned += 1 + if "PassHash" in field and k is not None: + self.data.append(("SCYLLA_HASH", k)) + self.pwned += 1 + if "PassSalt" in field and k is not None: + self.data.append(("SCYLLA_HASHSALT", k)) + self.pwned += 1 + if "IP" in field and k is not None: + self.data.append(("SCYLLA_LASTIP", k)) + self.pwned += 1 + if "Domain" in field and k is not None: + self.data.append(("SCYLLA_SOURCE", k)) + self.pwned += 1 + except Exception as ex: + c.bad_news("scylla.sh error: " + self.target) + print(ex) + def get_hunterio_public(self): try: target_domain = self.target.split("@")[1] @@ -381,8 +443,6 @@ def get_snusbase(self, api_url, api_key, user_query): ) ) for result in response["result"]: - if result["tablenr"] and self.not_exists(result["tablenr"]): - self.data.append(("SNUS_SOURCE", result["tablenr"])) if result["username"]: self.data.append(("SNUS_USERNAME", result["username"])) if result["email"] and self.not_exists(result["email"]): @@ -404,6 +464,8 @@ def get_snusbase(self, api_url, api_key, user_query): self.pwned += 1 if result["lastip"]: self.data.append(("SNUS_LASTIP", result["lastip"])) + if result["tablenr"] and self.not_exists(result["tablenr"]): + self.data.append(("SNUS_SOURCE", result["tablenr"])) except Exception as ex: c.bad_news("Snusbase error with {target}".format(target=self.target)) @@ -456,15 +518,6 @@ def get_leaklookup_priv(self, api_key, user_query): if self.not_exists(db): self.data.append(("LKLP_SOURCE", db)) for d in data: - if "password" in d.keys(): - if "plaintext" in d: - self.pwned += 1 - self.data.append(("LKLP_HASH", d["password"])) - b_counter += 1 - else: - self.pwned += 1 - self.data.append(("LKLP_PASSWORD", d["password"])) - b_counter += 1 if "username" in d.keys(): self.pwned += 1 self.data.append(("LKLP_USERNAME", d["username"])) @@ -477,6 +530,15 @@ def get_leaklookup_priv(self, api_key, user_query): self.data.append( ("LKLP_RELATED", d["email_address"].strip()) ) + if "password" in d.keys(): + if "plaintext" in d: + self.pwned += 1 + self.data.append(("LKLP_HASH", d["password"])) + b_counter += 1 + else: + self.pwned += 1 + self.data.append(("LKLP_PASSWORD", d["password"])) + b_counter += 1 c.good_news( "Found {num} entries for {target} using LeakLookup (private)".format( @@ -533,18 +595,18 @@ def get_weleakinfo_priv(self, api_key, user_query): if response["Total"] == 0: return for result in response["Data"]: + if "Username" in result: + self.data.append(("WLI_USERNAME", result["Username"])) + if "Email" in result and self.not_exists(result["Email"]): + self.data.append(("WLI_RELATED", result["Email"].strip())) if "Password" in result: self.data.append(("WLI_PASSWORD", result["Password"])) self.pwned += 1 if "Hash" in result: self.data.append(("WLI_HASH", result["Hash"])) self.pwned += 1 - if "Username" in result: - self.data.append(("WLI_USERNAME", result["Username"])) if "Database" in result and self.not_exists(result["Database"]): self.data.append(("WLI_SOURCE", result["Database"])) - if "Email" in result and self.not_exists(result["Email"]): - self.data.append(("WLI_RELATED", result["Email"].strip())) except Exception as ex: c.bad_news( "WeLeakInfo error with {target} (private)".format(target=self.target) diff --git a/h8mail/utils/colors.py b/h8mail/utils/colors.py index 422291b..de76f57 100644 --- a/h8mail/utils/colors.py +++ b/h8mail/utils/colors.py @@ -55,11 +55,12 @@ def good_news(news): print(colors.bold + colors.fg.green + "[>] " + colors.reset + news.strip()) @staticmethod - def test_news(news): + def debug_news(news): """ - Print a Success + Print a Debug """ - print(colors.bold + colors.fg.green + "[>] " + colors.reset + news.strip()) + print() + print(colors.bold + colors.fg.lightred + "[@] " + news + colors.reset) @staticmethod def bad_news(news): @@ -195,7 +196,7 @@ def print_result(target, data, source): colors.reset, ) ) - else: + elif "IP" in source: print( "{}{:15}{}|{}{:>25.25}{} > {}{}{}".format( colors.fg.lightblue, @@ -209,6 +210,20 @@ def print_result(target, data, source): colors.reset, ) ) + else: + print( + "{}{:15}{}|{}{:>25.25}{} > {}{}{}".format( + colors.fg.lightblue, + source, + colors.fg.lightgrey, + colors.fg.pink, + target, + colors.fg.lightgrey, + colors.fg.lightgrey, + data, + colors.reset, + ) + ) @staticmethod def print_res_header(target): diff --git a/h8mail/utils/helpers.py b/h8mail/utils/helpers.py index 33e1059..f6b1fa5 100644 --- a/h8mail/utils/helpers.py +++ b/h8mail/utils/helpers.py @@ -51,13 +51,12 @@ def print_banner(b_type="intro"): !_____! !_____! ;--------------------; """ # print(c.bold, c.fg.pink, banner, c.reset) - banner_tab = banner.splitlines() - code = 16 + code = 17 for b in banner_tab: clr = "\u001b[38;5;" + str(code) + "m " print(c.bold + clr + b + c.reset) - code += 5 + code += 3 elif "warn" in b_type: print( c.fg.green, @@ -67,9 +66,8 @@ def print_banner(b_type="intro"): elif "version" in b_type: print( "\t", - c.bold, - c.fg.green, - "Version " + __version__ + ' - "SEASON_PRIMER" ', + c.fg.lightgrey, + "Version " + __version__ + ' - "ECHO MIKE" ', c.reset, ) @@ -183,3 +181,17 @@ def check_latest_version(): current=__version__, latest=latest ) ) + +def check_scylla_online(): + """ + Checks if scylla.sh is online + """ + try: + re = requests.head( + url="https://scylla.sh" + ) + if re.status_code == 200: + c.good_news("scylla.sh is up") + return True + except Exception: + c.info_news("scylla.sh is down, skipping") \ No newline at end of file diff --git a/h8mail/utils/localsearch.py b/h8mail/utils/localsearch.py index 9e0ff0f..c6f56a0 100644 --- a/h8mail/utils/localsearch.py +++ b/h8mail/utils/localsearch.py @@ -11,7 +11,7 @@ from .colors import colors as c -def local_to_targets(targets, local_results): +def local_to_targets(targets, local_results, user_args): """ Appends data from local_breach_target objects using existing list of targets. Finds corresponding email in dest object list, and adds data to the t.data object variable. @@ -29,6 +29,9 @@ def local_to_targets(targets, local_results): ) ) t.pwned += 1 + if user_args.debug: + c.debug_news(f"DEBUG: Found following content matching {t.target.target}") + l.target.dump() return targets diff --git a/h8mail/utils/print_results.py b/h8mail/utils/print_results.py index ad65ccd..918e49e 100644 --- a/h8mail/utils/print_results.py +++ b/h8mail/utils/print_results.py @@ -49,3 +49,6 @@ def print_results(results, hide=False): c.print_result(t.target, t.data[i][1], t.data[i][0]) if "WLI" in t.data[i][0]: c.print_result(t.target, t.data[i][1], t.data[i][0]) + if "SCYLLA" in t.data[i][0]: + c.print_result(t.target, t.data[i][1], t.data[i][0]) + diff --git a/h8mail/utils/run.py b/h8mail/utils/run.py index eee7948..d7ab9b2 100644 --- a/h8mail/utils/run.py +++ b/h8mail/utils/run.py @@ -18,6 +18,7 @@ print_banner, save_results_csv, check_latest_version, + check_scylla_online ) from .localsearch import local_search, local_search_single, local_to_targets from .localgzipsearch import local_gzip_search, local_search_single_gzip @@ -41,20 +42,29 @@ def target_factory(targets, user_args): init_targets_len = len(targets) query = "email" + skip_default_queries = False if user_args.user_query is not None: query = user_args.user_query - user_args.skip_defaults = True - + skip_default_queries = False + + scylla_up = False + for counter, t in enumerate(targets): c.info_news("Target factory started for {target}".format(target=t)) if user_args.debug: current_target = target(t, debug=True) else: current_target = target(t) - if not user_args.skip_defaults: - current_target.get_hibp() + + if skip_default_queries: current_target.get_hunterio_public() current_target.get_emailrepio() + + if not user_args.skip_defaults: + scylla_up = check_scylla_online() + if scylla_up: + current_target.get_scylla(query) + if api_keys is not None: if "hibp" in api_keys and query == "email": current_target.get_hibp3(api_keys["hibp"]) @@ -135,7 +145,7 @@ def h8mail(user_args): else: local_found = local_search(res, targets) if local_found is not None: - breached_targets = local_to_targets(breached_targets, local_found) + breached_targets = local_to_targets(breached_targets, local_found, user_args) # Handle gzip search if user_args.local_gzip_src: for arg in user_args.local_gzip_src: @@ -145,7 +155,7 @@ def h8mail(user_args): else: local_found = local_gzip_search(res, targets) if local_found is not None: - breached_targets = local_to_targets(breached_targets, local_found) + breached_targets = local_to_targets(breached_targets, local_found, user_args) print_results(breached_targets, user_args.hide) diff --git a/h8mail/utils/version.py b/h8mail/utils/version.py index 202075f..8f1ef02 100644 --- a/h8mail/utils/version.py +++ b/h8mail/utils/version.py @@ -1 +1 @@ -__version__ = "2.3" +__version__ = "2.4"