Skip to content

Commit 2d75a30

Browse files
Merge pull request #99 from OSINT-TECHNOLOGIES/rolling
Stabilized v1.2.1
2 parents 8ae989d + 574e546 commit 2d75a30

28 files changed

+621
-327
lines changed

README.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
</p>
1515

1616
<p align="center">
17-
<img alt="Static Badge" src="https://img.shields.io/badge/supports-virustotal_api-red?style=for-the-badge"> <img alt="Static Badge" src="https://img.shields.io/badge/supports-securitytrails_api-red?style=for-the-badge">
18-
17+
<img alt="Static Badge" src="https://img.shields.io/badge/supports-virustotal_api (key required)-red?style=for-the-badge"> <img alt="Static Badge" src="https://img.shields.io/badge/supports-securitytrails_api (key required)-red?style=for-the-badge"> <img alt="Static Badge" src="https://img.shields.io/badge/supports-hudsonrock_api (no key required)-red?style=for-the-badge">
1918
</p>
2019

2120
> Attention! DPULSE is a research tool. It is not intended for criminal activities! Use DPULSE only on allowed domains and for legal purposes!
@@ -58,7 +57,7 @@ DPULSE is a software solution for conducting OSINT research in relation to a cer
5857
- CPEs, used web-technologies and so on
5958
- It also can download sitemap.xml and robots.txt files from a domain
6059

61-
2. ***PageSearch standard scan:*** extended subdomains deep search function, which starts in addition to basic scan and which can find:
60+
2. ***PageSearch scan:*** extended subdomains deep search function, which starts in addition to basic scan and which can find:
6261
- more e-mail addresses
6362
- API keys
6463
- exposed passwords
@@ -67,18 +66,17 @@ DPULSE is a software solution for conducting OSINT research in relation to a cer
6766
- documents, config files, databases files (and PageSearch can download them!)
6867
- specified words by user in PDF files
6968

70-
3. ***PageSearch Sitemap inspection scan:*** sitemap links crawler which starts in addition to basic scan and which can find even more e-mails
71-
72-
4. ***Dorking scan:*** extended domain research function with prepared Google Dorking databases for different purposes, such as:
69+
3. ***Dorking scan:*** extended domain research function with prepared Google Dorking databases for different purposes, such as:
7370
- IoT dorking
7471
- files dorking
7572
- admin panels dorking
7673
- web elements dorking
7774
- Moreover, this mode allows you to create your own custom Google Dorking database
7875

79-
6. ***API scan:*** extended domain research function with prepared functions for 3rd party APIs usage. Currently DPULSE supports these API:
76+
4. ***API scan:*** extended domain research function with prepared functions for 3rd party APIs usage. Currently DPULSE supports these API:
8077
- VirusTotal API (for brief domain information gathering)
8178
- SecurityTrails API (deep subdomains and DNS enumeration)
79+
- HudsonRock API (for querying a database with exposed computers which were compromised through global info-stealer campaigns)
8280

8381
Finally, DPULSE compiles all found data into an easy-to-read HTML or XLSX report by category. It also saves all information about scan in local report storage database, which can be restored later.
8482

@@ -164,8 +162,8 @@ If you have problems with starting installer.sh, you should try to use `dos2unix
164162

165163

166164
# Tasks to complete before new release
167-
- [ ] Add web pages snapshoting (with screenshots)
168-
- [ ] Add web pages snapshoting (with web pages copying as HTML objects)
165+
- [x] Add web pages snapshoting (with screenshots)
166+
- [x] Add web pages snapshoting (with web pages copying as HTML objects)
169167
- [ ] Add web pages snapshoting (with Wayback Machine)
170168

171169
# DPULSE mentions in social medias

apis/api_hudsonrock.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import requests
2+
from colorama import Fore, Style
3+
import re
4+
5+
def hudsonrock_html_prep(formatted_output):
6+
formatted_output = re.sub(r'\x1b\[([0-9,A-Z]{1,2}(;[0-9]{1,2})?(;[0-9]{3})?)?[m|K]?', '', formatted_output)
7+
start_marker = "=== HUDSONROCK API REPORT ==="
8+
end_marker = "[+] Email Data:"
9+
start_index = formatted_output.find(start_marker)
10+
end_index = formatted_output.find(end_marker)
11+
if start_index != -1 and end_index != -1:
12+
formatted_output = formatted_output[:start_index] + formatted_output[end_index:]
13+
return formatted_output
14+
15+
def api_hudsonrock_get(email=None, username=None, domain=None, ip=None):
16+
base_url = "https://cavalier.hudsonrock.com/api/json/v2/osint-tools/"
17+
results = {}
18+
19+
def make_request(url):
20+
try:
21+
response = requests.get(url)
22+
response.raise_for_status()
23+
return response.json()
24+
except requests.RequestException as e:
25+
return {'error': str(e)}
26+
27+
if email:
28+
email_url = f"{base_url}search-by-email?email={email}"
29+
results['email'] = make_request(email_url)
30+
31+
if username:
32+
username_url = f"{base_url}search-by-username?username={username}"
33+
results['username'] = make_request(username_url)
34+
35+
if domain:
36+
domain_url = f"{base_url}search-by-domain?domain={domain}"
37+
results['domain'] = make_request(domain_url)
38+
39+
urls_by_domain_url = f"{base_url}urls-by-domain?domain={domain}"
40+
results['urls_by_domain'] = make_request(urls_by_domain_url)
41+
42+
if ip:
43+
ip_url = f"{base_url}search-by-ip?ip={ip}"
44+
results['ip'] = make_request(ip_url)
45+
46+
return results
47+
48+
49+
def api_hudsonrock_check(domain, ip, email, username):
50+
results = api_hudsonrock_get(email, username, domain, ip)
51+
formatted_output = Fore.LIGHTBLUE_EX + "\n=== HUDSONROCK API REPORT ===\n" + Style.RESET_ALL
52+
formatted_output += f"\n{Fore.LIGHTBLUE_EX}[+] Provided Data:{Style.RESET_ALL}\n"
53+
formatted_output += f"{Fore.GREEN}Domain:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{domain}{Style.RESET_ALL}\n"
54+
formatted_output += f"{Fore.GREEN}IP:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{ip}{Style.RESET_ALL}\n"
55+
formatted_output += f"{Fore.GREEN}E-mail:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{email}{Style.RESET_ALL}\n"
56+
formatted_output += f"{Fore.GREEN}Username:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{username}{Style.RESET_ALL}\n"
57+
58+
def format_section(title, data):
59+
nonlocal formatted_output
60+
formatted_output += f"\n{Fore.LIGHTBLUE_EX}[+] {title}:{Style.RESET_ALL}\n"
61+
if 'error' in data:
62+
formatted_output += f"{Fore.RED}Error appeared when trying to get results for {title} requests. Probably given data is incorrect.{Style.RESET_ALL}\n"
63+
return
64+
65+
if title == 'Email Data':
66+
formatted_output += f"{Fore.GREEN}Message:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{data.get('message', 'No message available')}{Style.RESET_ALL}\n"
67+
for i, stealer in enumerate(data.get('stealers', []), 1):
68+
formatted_output += f"\n{Fore.GREEN}--- STEALER {i} ---{Style.RESET_ALL}\n"
69+
formatted_output += f"{Fore.GREEN}Computer Name:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('computer_name', 'Not Found')}{Style.RESET_ALL}\n"
70+
formatted_output += f"{Fore.GREEN}OS:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('operating_system', 'Not Found')}{Style.RESET_ALL}\n"
71+
formatted_output += f"{Fore.GREEN}Date Compromised:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('date_compromised', 'Not Found')}{Style.RESET_ALL}\n"
72+
formatted_output += f"{Fore.GREEN}Malware Path:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('malware_path', 'Not Found')}{Style.RESET_ALL}\n"
73+
formatted_output += f"{Fore.GREEN}IP:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('ip', 'Not Found')}{Style.RESET_ALL}\n"
74+
formatted_output += f"{Fore.GREEN}Top Passwords:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{', '.join(stealer.get('top_passwords', []))}{Style.RESET_ALL}\n"
75+
formatted_output += f"{Fore.GREEN}Top Logins:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{', '.join(stealer.get('top_logins', []))}{Style.RESET_ALL}\n"
76+
77+
elif title == 'Username Data':
78+
formatted_output += f"{Fore.GREEN}Message:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{data.get('message', 'No message available')}{Style.RESET_ALL}\n"
79+
for i, stealer in enumerate(data.get('stealers', []), 1):
80+
formatted_output += f"\n{Fore.GREEN}--- STEALER {i} ---{Style.RESET_ALL}\n"
81+
formatted_output += f"{Fore.GREEN}Stealer Family:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('stealer_family', 'Not Found')}{Style.RESET_ALL}\n"
82+
formatted_output += f"{Fore.GREEN}Computer Name:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('computer_name', 'Not Found')}{Style.RESET_ALL}\n"
83+
formatted_output += f"{Fore.GREEN}OS:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('operating_system', 'Not Found')}{Style.RESET_ALL}\n"
84+
formatted_output += f"{Fore.GREEN}Date Compromised:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('date_compromised', 'Not Found')}{Style.RESET_ALL}\n"
85+
formatted_output += f"{Fore.GREEN}Malware Path:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('malware_path', 'Not Found')}{Style.RESET_ALL}\n"
86+
formatted_output += f"{Fore.GREEN}IP:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('ip', 'Not Found')}{Style.RESET_ALL}\n"
87+
formatted_output += f"{Fore.GREEN}Top Passwords:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{', '.join(stealer.get('top_passwords', []))}{Style.RESET_ALL}\n"
88+
formatted_output += f"{Fore.GREEN}Top Logins:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{', '.join(stealer.get('top_logins', []))}{Style.RESET_ALL}\n"
89+
90+
elif title == 'Domain Data':
91+
formatted_output += f"{Fore.GREEN}Total Entries:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{data.get('total', 0)}{Style.RESET_ALL}\n"
92+
formatted_output += f"{Fore.GREEN}Total Stealers:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{data.get('totalStealers', 0)}{Style.RESET_ALL}\n"
93+
formatted_output += f"\n{Fore.GREEN}Sample Employee URLs:{Style.RESET_ALL}\n"
94+
employee_urls = data.get('data', {}).get('employees_urls', [])
95+
if employee_urls:
96+
for url_data in employee_urls[:10]:
97+
formatted_output += f"{Fore.GREEN}Type:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{url_data.get('type', 'N/A')}{Style.RESET_ALL}"
98+
formatted_output += f" {Fore.GREEN}| URL:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{url_data.get('url', 'N/A')}{Style.RESET_ALL}"
99+
formatted_output += f" {Fore.GREEN}| Occurrence:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{url_data.get('occurrence', 'N/A')}{Style.RESET_ALL}\n"
100+
else:
101+
formatted_output += f"{Fore.RED}No employee URLs available.{Style.RESET_ALL}\n"
102+
103+
elif title == 'Attack Surface Data':
104+
formatted_output += f"{Fore.GREEN}Message:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{data.get('message', 'No message available')}{Style.RESET_ALL}\n"
105+
formatted_output += f"\n{Fore.GREEN}Sample Employee URLs:{Style.RESET_ALL}\n"
106+
employees = data.get('data', {}).get('employees_urls', [])
107+
if employees:
108+
for url_data in employees[:10]:
109+
formatted_output += f"{Fore.GREEN}Type:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{url_data.get('type', 'N/A')}{Style.RESET_ALL}"
110+
formatted_output += f" {Fore.GREEN}| URL:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{url_data.get('url', 'N/A')}{Style.RESET_ALL}"
111+
formatted_output += f" {Fore.GREEN}| Occurrence:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{url_data.get('occurrence', 'N/A')}{Style.RESET_ALL}\n"
112+
else:
113+
formatted_output += f"{Fore.RED}No employee URLs available.{Style.RESET_ALL}\n"
114+
formatted_output += f"\n{Fore.GREEN}Sample Client URLs:{Style.RESET_ALL}\n"
115+
clients = data.get('data', {}).get('clients_urls', [])
116+
if clients:
117+
for url_data in clients[:10]:
118+
formatted_output += f"{Fore.GREEN}Type:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{url_data.get('type', 'N/A')}{Style.RESET_ALL}"
119+
formatted_output += f" {Fore.GREEN}| URL:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{url_data.get('url', 'N/A')}{Style.RESET_ALL}"
120+
formatted_output += f" {Fore.GREEN}| Occurrence:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{url_data.get('occurrence', 'N/A')}{Style.RESET_ALL}\n"
121+
else:
122+
formatted_output += f"{Fore.LIGHTCYAN_EX}No client URLs available.{Style.RESET_ALL}\n"
123+
124+
elif title == 'IP Data':
125+
formatted_output += f"{Fore.GREEN}Message:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{data.get('message', 'No message available')}{Style.RESET_ALL}\n"
126+
if data.get('stealers'):
127+
for i, stealer in enumerate(data.get('stealers', []), 1):
128+
formatted_output += f"\n{Fore.GREEN}--- STEALER {i} ---{Style.RESET_ALL}\n"
129+
formatted_output += f"{Fore.GREEN}Computer Name:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('computer_name', 'Not Found')}{Style.RESET_ALL}\n"
130+
formatted_output += f"{Fore.GREEN}OS:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('operating_system', 'Not Found')}{Style.RESET_ALL}\n"
131+
formatted_output += f"{Fore.GREEN}Date Compromised:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('date_compromised', 'Not Found')}{Style.RESET_ALL}\n"
132+
formatted_output += f"{Fore.GREEN}Malware Path:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('malware_path', 'Not Found')}{Style.RESET_ALL}\n"
133+
formatted_output += f"{Fore.GREEN}IP:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{stealer.get('ip', 'Not Found')}{Style.RESET_ALL}\n"
134+
formatted_output += f"{Fore.GREEN}Top Passwords:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{', '.join(stealer.get('top_passwords', []))}{Style.RESET_ALL}\n"
135+
formatted_output += f"{Fore.GREEN}Top Logins:{Style.RESET_ALL} {Fore.LIGHTCYAN_EX}{', '.join(stealer.get('top_logins', []))}{Style.RESET_ALL}\n"
136+
formatted_output += "\n"
137+
138+
if 'email' in results:
139+
format_section('Email Data', results['email'])
140+
if 'username' in results:
141+
format_section('Username Data', results['username'])
142+
if 'domain' in results:
143+
format_section('Domain Data', results['domain'])
144+
if 'urls_by_domain' in results:
145+
format_section('Attack Surface Data', results['urls_by_domain'])
146+
if 'ip' in results:
147+
format_section('IP Data', results['ip'])
148+
149+
print(formatted_output)
150+
return formatted_output

apis/api_keys.db

0 Bytes
Binary file not shown.

apis/api_keys_reference.db

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)