Skip to content

Commit e797d31

Browse files
authored
Merge pull request #113 from khast3x/2.5.5
2.5.5
2 parents 9f75390 + 4cad657 commit e797d31

11 files changed

+166
-49
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,4 @@ keysbackup.ini
112112
.vscode/launch.json
113113
file1.bin
114114
*.txt
115+
test.json

README.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
[![platforms](https://img.shields.io/badge/platforms-Windows%20%7C%20Linux%20%7C%20OSX-success.svg)](https://pypi.org/project/h8mail/) [![PyPI version](https://badge.fury.io/py/h8mail.svg)](https://badge.fury.io/py/h8mail)
66
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/h8mail.svg)](https://pypi.org/project/h8mail/) [![Downloads](https://pepy.tech/badge/h8mail)](https://pepy.tech/project/h8mail) [![travis](https://img.shields.io/travis/khast3x/h8mail.svg)](https://travis-ci.org/khast3x/h8mail)
7-
[![Docker Pulls](https://img.shields.io/docker/pulls/kh4st3x00/h8mail.svg)](https://hub.docker.com/r/kh4st3x00/h8mail) [![MicroBadger Size (tag)](https://img.shields.io/microbadger/image-size/kh4st3x00/h8mail.svg?color=ok)](https://hub.docker.com/r/kh4st3x00/h8mail/builds)
7+
[![Docker Pulls](https://img.shields.io/docker/pulls/kh4st3x00/h8mail.svg)](https://hub.docker.com/r/kh4st3x00/h8mail)
88
**h8mail** is an email OSINT and breach hunting tool using [different breach and reconnaissance services](#apis), or local breaches such as Troy Hunt's "Collection1" and the infamous "Breach Compilation" torrent.
99

1010
----
@@ -60,7 +60,6 @@
6060

6161

6262
#### APIs
63-
6463
| Service | Functions | Status |
6564
|-|-|-|
6665
| [HaveIBeenPwned(v3)](https://haveibeenpwned.com/) | Number of email breaches | :white_check_mark: :key: |
@@ -71,9 +70,10 @@
7170
| [Leak-Lookup](https://leak-lookup.com/) - Public | Number of search-able breach results | :white_check_mark: (:key:) |
7271
| [Leak-Lookup](https://leak-lookup.com/) - Service | Cleartext passwords, hashs and salts, usernames, IPs, domain | :white_check_mark: :key: |
7372
| [Emailrep.io](https://emailrep.io/) - Service (free) | Last seen in breaches, social media profiles | :white_check_mark: :key: |
74-
| [Scylla.sh](https://scylla.sh/) - Service (free) | Cleartext passwords, hashs and salts, usernames, IPs, domain | :white_check_mark: |
73+
| [scylla.so](https://scylla.so/) - Service (free) | Cleartext passwords, hashs and salts, usernames, IPs, domain | :white_check_mark: |
7574
| [Dehashed.com](https://dehashed.com/) - Service | Cleartext passwords, hashs and salts, usernames, IPs, domain | :white_check_mark: :key: |
76-
| :new: [IntelX.io](https://intelx.io/signup) - Service (free trial) | Cleartext passwords, hashs and salts, usernames, IPs, domain, Bitcoin Wallets, IBAN | :white_check_mark: :key: |
75+
| [IntelX.io](https://intelx.io/signup) - Service (free trial) | Cleartext passwords, hashs and salts, usernames, IPs, domain, Bitcoin Wallets, IBAN | :white_check_mark: :key: |
76+
| :new: [Breachdirectory.tk](Breachdirectory.tk) - Service (free) | Cleartext passwords, hashs and salts, usernames, domain | :white_check_mark: :key: |
7777

7878
*:key: - API key required*
7979

@@ -222,7 +222,7 @@ $ h8mail -u "https://pastebin.com/raw/kQ6WNKqY" "list_of_urls.txt"
222222
* h8mail's Pypi integration is strongly based on the work of audreyr's [CookieCutter PyPackage](https://github.com/audreyr/cookiecutter-pypackage)
223223
* Logo generated using Hatchful by Shopify
224224
* [Jake Creps](https://twitter.com/jakecreps) for his [h8mail v2 introduction](https://jakecreps.com/2019/06/21/h8mail/)
225-
* [Alejandro Caceres](https://twitter.com/_hyp3ri0n) for making scylla.sh available. Be sure to [support](https://www.buymeacoffee.com/Eiw47ImnT) him if you can
225+
* [Alejandro Caceres](https://twitter.com/_hyp3ri0n) for making scylla.so available. Be sure to [support](https://www.buymeacoffee.com/Eiw47ImnT) him if you can
226226
* [IntelX](https://intelx.io) for being developer friendly
227227
228228
:purple_heart: **h8mail can be found in:**

h8mail/utils/classes.py

+87-25
Original file line numberDiff line numberDiff line change
@@ -254,19 +254,23 @@ def get_intelx(self, api_keys):
254254
)
255255
# print(contents) # Contains search data
256256
for file in intel_files:
257-
if self.debug:
258-
c.info_news(
259-
"["
260-
+ self.target
261-
+ f"]>[intelx.io] [DEBUG] Keeping {file}"
262-
)
263-
else:
264-
c.info_news(
265-
"["
266-
+ self.target
267-
+ f"]>[intelx.io] Removing {file}"
268-
)
269-
remove(file)
257+
try:
258+
if self.debug:
259+
c.info_news(
260+
"["
261+
+ self.target
262+
+ f"]>[intelx.io] [DEBUG] Keeping {file}"
263+
)
264+
else:
265+
c.info_news(
266+
"["
267+
+ self.target
268+
+ f"]>[intelx.io] Removing {file}"
269+
)
270+
remove(file)
271+
except Exception as ex:
272+
c.bad_news("intelx.io cleanup error: " + self.target)
273+
print(ex)
270274

271275
except Exception as ex:
272276
c.bad_news("intelx.io error: " + self.target)
@@ -366,22 +370,22 @@ def get_emailrepio(self, api_key=""):
366370

367371
def get_scylla(self, user_query="email"):
368372
try:
369-
c.info_news("[" + self.target + "]>[scylla.sh]")
373+
c.info_news("[" + self.target + "]>[scylla.so]")
370374
sleep(0.5)
371375
self.headers.update({"Accept": "application/json"})
372376
if user_query == "email":
373-
uri_scylla = 'Email: "' + self.target + '"'
377+
uri_scylla = 'email: "' + self.target + '"'
374378
elif user_query == "password":
375-
uri_scylla = 'Password: "' + self.target + '"'
379+
uri_scylla = 'password: "' + self.target + '"'
376380
elif user_query == "username":
377-
uri_scylla = 'User: "' + self.target + '"'
381+
uri_scylla = 'name: "' + self.target + '"'
378382
elif user_query == "ip":
379-
uri_scylla = 'IP: "' + self.target + '"'
383+
uri_scylla = 'ip: "' + self.target + '"'
380384
elif user_query == "hash":
381-
uri_scylla = 'Hash: "' + self.target + '"'
385+
uri_scylla = 'passhash: "' + self.target + '"'
382386
elif user_query == "domain":
383-
uri_scylla = 'Email: "*@' + self.target + '"'
384-
url = "https://scylla.sh/search?q={}".format(
387+
uri_scylla = 'email: "*@' + self.target + '"'
388+
url = "https://scylla.so/search?q={}".format(
385389
requests.utils.requote_uri(uri_scylla)
386390
)
387391

@@ -394,7 +398,7 @@ def get_scylla(self, user_query="email"):
394398
self.headers.popitem()
395399

396400
if response.status_code not in [200, 404]:
397-
c.bad_news("Could not contact scylla.sh for " + self.target)
401+
c.bad_news("Could not contact scylla.so for " + self.target)
398402
print(response.status_code)
399403
print(response)
400404
return
@@ -405,13 +409,13 @@ def get_scylla(self, user_query="email"):
405409
if k is not None:
406410
total += 1
407411
c.good_news(
408-
"Found {num} entries for {target} using scylla.sh ".format(
412+
"Found {num} entries for {target} using scylla.so ".format(
409413
num=total, target=self.target
410414
)
411415
)
412416
for d in data:
413417
for field, k in d["fields"].items():
414-
if "user" in field and k is not None:
418+
if "name" in field and k is not None:
415419
self.data.append(("SCYLLA_USERNAME", k))
416420
self.pwned += 1
417421
elif (
@@ -435,7 +439,7 @@ def get_scylla(self, user_query="email"):
435439
self.data.append(("SCYLLA_SOURCE", k))
436440
self.pwned += 1
437441
except Exception as ex:
438-
c.bad_news("scylla.sh error: " + self.target)
442+
c.bad_news("scylla.so error: " + self.target)
439443
print(ex)
440444

441445
def get_hunterio_public(self):
@@ -821,3 +825,61 @@ def get_dehashed(self, api_email, api_key, user_query):
821825
except Exception as ex:
822826
c.bad_news(f"Dehashed error with {self.target}")
823827
print(ex)
828+
829+
def get_breachdirectory(self, user, passw, user_query):
830+
# Todo: implement password source search when email has answer
831+
c.info_news("[" + self.target + "]>[breachdirectory.tk]")
832+
if user_query not in ["email", "username", "password", "domain"]:
833+
c.bad_news("Breachdirectory does not support this option")
834+
exit(1)
835+
mode = "pastes"
836+
url = "https://breachdirectory.tk/api/index?username={user}&password={passw}&func={mode}&term={target}".format(user=user, passw=passw, mode=mode, target=self.target)
837+
try:
838+
req = self.make_request(
839+
url, timeout=60
840+
)
841+
if req.status_code == 200:
842+
response = req.json()
843+
if response["data"] is not None:
844+
for result in response["data"]:
845+
if "email" in result and "email" not in user_query:
846+
self.data.append(("BREACHDR_EMAIL", result["email"]))
847+
if "password" in result:
848+
self.data.append(("BREACHDR_PASS", result["password"]))
849+
if "hash" in result:
850+
self.data.append(("BREACHDR_HASH", result["hash"]))
851+
if "source" in result:
852+
self.data.append(("BREACHDR_SOURCE", result["source"]))
853+
self.pwned += 1
854+
# Follow up with an aggregated leak sources query
855+
url_src = "https://breachdirectory.tk/api/index?username={user}&password={passw}&func={mode}&term={target}".format(user=user, passw=passw, mode="sources", target=self.target)
856+
req = self.make_request(
857+
url_src, timeout=60
858+
)
859+
if req.status_code == 200:
860+
response = req.json()
861+
if response["sources"] is not None:
862+
for result in response["sources"]:
863+
self.data.append(("BREACHDR_EXTSRC", result))
864+
## If using the 'auto' mode instead of pastes
865+
# c.good_news(
866+
# "Found {num} entries for {target} using breachdirectory.tk".format(
867+
# num=str(response["found"]), target=self.target
868+
# )
869+
# )
870+
871+
# for result in response["result"]:
872+
# if result["has_password"] is True:
873+
# self.data.append(("BREACHDR_PASS", result["password"]))
874+
# self.data.append(("BREACHDR_MD5", result["md5"]))
875+
# if result["sources"] == "Unverified":
876+
# source = result["sources"]
877+
# elif len(result["sources"]) > 1:
878+
# source = ", ".join(result["sources"])
879+
# else:
880+
# source = result["sources"][0]
881+
# self.data.append(("BREACHDR_SOURCE", source))
882+
# self.pwned += 1
883+
except Exception as ex:
884+
c.bad_news(f"Breachdirectory error with {self.target}")
885+
print(ex)

h8mail/utils/gen_config.py

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ def gen_config_file():
2222
;dehashed_key =
2323
;intelx_key =
2424
;intelx_maxfile = 10
25+
;breachdirectory_user =
26+
;breachdirectory_pass =
2527
"""
2628
dest_config.write(config)
2729
c.good_news(

h8mail/utils/helpers.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ def print_banner(b_type="intro"):
7070
elif "version" in b_type:
7171
print(
7272
"\t",
73-
c.fg.pink,
74-
"Version " + __version__ + ' - "ROCKSMASSON.4" ',
73+
c.fg.cyan,
74+
"Version " + __version__ + ' - "ROCKSMASSON.5" ',
7575
c.reset,
7676
)
7777

@@ -190,19 +190,19 @@ def check_latest_version():
190190
c.bad_news("Could not check for updates. Is Github blocking requests?")
191191
def check_scylla_online():
192192
"""
193-
Checks if scylla.sh is online
193+
Checks if scylla.so is online
194194
"""
195195
# Supress SSL Warning on UI
196196
# https://github.com/khast3x/h8mail/issues/64
197197
try:
198198
re = requests.head(
199-
url="https://scylla.sh", verify=False, auth=requests.auth.HTTPBasicAuth("sammy", "BasicPassword!"), timeout=10,
199+
url="https://scylla.so", verify=False, auth=requests.auth.HTTPBasicAuth("sammy", "BasicPassword!"), timeout=10,
200200
)
201201
if re.status_code == 200:
202-
c.good_news("scylla.sh is up")
202+
c.good_news("scylla.so is up")
203203
return True
204204
else:
205-
c.info_news("scylla.sh is down, skipping")
205+
c.info_news("scylla.so is down, skipping")
206206
return False
207207
except Exception:
208-
c.info_news("scylla.sh is down, skipping")
208+
c.info_news("scylla.so is down, skipping")

h8mail/utils/intelx.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -492,8 +492,8 @@ def search(self, term, maxresults=100, buckets=[], timeout=5, datefrom="", datet
492492
done = False
493493
search_id = self.INTEL_SEARCH(term, maxresults, buckets, timeout, datefrom, dateto, sort, media, terminate)
494494
if(len(str(search_id)) <= 3):
495-
print(f"[!] intelx.INTEL_SEARCH() Received {self.get_error(search_id)}")
496-
sys.exit()
495+
print(f"[!] intelx.INTEL_SEARCH() Received {self.get_error(search_id)}")
496+
sys.exit()
497497
while done == False:
498498
time.sleep(1) # lets give the backend a chance to aggregate our data
499499
r = self.query_results(search_id, maxresults)
@@ -515,8 +515,8 @@ def phonebooksearch(self, term, maxresults=1000, buckets=[], timeout=5, datefrom
515515
done = False
516516
search_id = self.PHONEBOOK_SEARCH(term, maxresults, buckets, timeout, datefrom, dateto, sort, media, terminate, target)
517517
if(len(str(search_id)) <= 3):
518-
print(f"[!] intelx.PHONEBOOK_SEARCH() Received {self.get_error(search_id)}")
519-
sys.exit()
518+
print(f"[!] intelx.PHONEBOOK_SEARCH() Received {self.get_error(search_id)}")
519+
sys.exit()
520520
while done == False:
521521
time.sleep(1) # lets give the backend a chance to aggregate our data
522522
r = self.query_pb_results(search_id, maxresults)

h8mail/utils/intelx_helpers.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@ def intelx_getsearch(target, intelx, maxfile):
1919
creds=cap["paths"]["/intelligent/search"]["Credit"]
2020
)
2121
)
22+
available_buckets = []
23+
desired_buckets=["leaks.public", "leaks.private", "pastes", "darknet"],
24+
for b in cap["buckets"]:
25+
if any(b in d for d in desired_buckets):
26+
available_buckets.append(b)
27+
c.info_news("[" + target + "]>[intelx.io] Available buckets for h8mail:")
28+
print(available_buckets)
2229
c.info_news("[" + target + "]>[intelx.io] Search in progress (max results : "+ str(maxfile) + ")")
2330
search = intelx.search(
2431
target,
25-
buckets=["leaks.public", "leaks.private", "pastes", "darknet.tor"],
26-
# buckets=[],
32+
buckets=available_buckets,
2733
maxresults=maxfile,
2834
media=24,
2935
)

h8mail/utils/print_json.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# -*- coding: utf-8 -*-
2+
from .colors import colors as c
3+
import json
4+
5+
def generate_source_arrays(pwned_data):
6+
data_array = []
7+
no_src = 0 # To check for data with no explicit source
8+
temp_array = []
9+
for i in range(len(pwned_data)):
10+
if len(pwned_data[i]) == 2:
11+
temp_array.append(pwned_data[i][0] + ":" + pwned_data[i][1])
12+
no_src += 1
13+
if "SOURCE" in pwned_data[i][0]:
14+
data_array.append(temp_array)
15+
temp_array = []
16+
no_src = 0
17+
if no_src > 0:
18+
data_array.append(temp_array)
19+
return data_array
20+
21+
22+
23+
def save_results_json(dest_json, target_obj_list):
24+
data = {}
25+
data['targets'] = []
26+
for t in target_obj_list:
27+
current_target = {}
28+
current_target["target"] = t.target
29+
current_target["pwn_num"] = t.pwned
30+
current_target["data"] = generate_source_arrays(t.data)
31+
data['targets'].append(current_target)
32+
33+
with open(dest_json, 'w') as outfile:
34+
json.dump(data, outfile)

h8mail/utils/print_results.py

+2
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,6 @@ def print_results(results, hide=False):
5555
c.print_result(t.target, t.data[i][1], t.data[i][0])
5656
if "INTELX" in t.data[i][0]:
5757
c.print_result(t.target, t.data[i][1], t.data[i][0])
58+
if "BREACHDR" in t.data[i][0]:
59+
c.print_result(t.target, t.data[i][1], t.data[i][0])
5860

0 commit comments

Comments
 (0)