porthawk 2.0.0__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.
- porthawk-2.0.0.dist-info/METADATA +11 -0
- porthawk-2.0.0.dist-info/RECORD +21 -0
- porthawk-2.0.0.dist-info/WHEEL +5 -0
- porthawk-2.0.0.dist-info/entry_points.txt +2 -0
- porthawk-2.0.0.dist-info/licenses/LICENSE +21 -0
- porthawk-2.0.0.dist-info/top_level.txt +1 -0
- portscanner/__init__.py +12 -0
- portscanner/advanced_scan.py +219 -0
- portscanner/bruteforce.py +218 -0
- portscanner/cli.py +454 -0
- portscanner/discovery.py +223 -0
- portscanner/intel.py +261 -0
- portscanner/notify.py +177 -0
- portscanner/output.py +91 -0
- portscanner/proxy.py +205 -0
- portscanner/report.py +184 -0
- portscanner/scanner.py +105 -0
- portscanner/utils.py +96 -0
- portscanner/version.py +137 -0
- portscanner/vuln.py +277 -0
- portscanner/web_vuln.py +275 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: porthawk
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Fast multi-threaded port scanner - Nmap alternative
|
|
5
|
+
Author: shivarajp24
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Dynamic: author
|
|
9
|
+
Dynamic: license-file
|
|
10
|
+
Dynamic: requires-python
|
|
11
|
+
Dynamic: summary
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
porthawk-2.0.0.dist-info/licenses/LICENSE,sha256=YM0uZ6hZT23fLZcxQH9QkPeZwAf8NLoSIDyWuplUrAo,1078
|
|
2
|
+
portscanner/__init__.py,sha256=3MEukrD6X1j9m4-PLRlPqJT-2T-59fQ0RaimKGHO7b0,326
|
|
3
|
+
portscanner/advanced_scan.py,sha256=KZANbSYavNBYn5VOL3HeEzay-isaUQGzQ7dDNgJxe5U,8818
|
|
4
|
+
portscanner/bruteforce.py,sha256=c3iQ0ax5eE4wNKu1Dvyi4wPwhzIAWh0zw4H0wRBVjVQ,8420
|
|
5
|
+
portscanner/cli.py,sha256=gLbvFG4Cj36J9Q9VHDj2GlRLA0oovQEkr_6moa670TI,17878
|
|
6
|
+
portscanner/discovery.py,sha256=JhvIc949xPU9tsodclY1gNagST9UWefJgyLzxe0j0LA,9072
|
|
7
|
+
portscanner/intel.py,sha256=ygoqKJxLx2CTLkE9KSCtz9iBEr4NXWkrbak1XjE40W8,9388
|
|
8
|
+
portscanner/notify.py,sha256=Nh2XTLlkmjma109VKu9Mplubn21K2zivji1akfY-hRU,6546
|
|
9
|
+
portscanner/output.py,sha256=g0WxX0iaQY78zK9ZdLkqf8c5HJRcabExyLgzz6Y5Sjw,2925
|
|
10
|
+
portscanner/proxy.py,sha256=CUY8koXAwfODG0NA4Ypai1Ujgmeg4kRJ_1bf2yjtgK4,7135
|
|
11
|
+
portscanner/report.py,sha256=l8qUQhjRgvLHNk_cs4h8Px5U-X1RmZdD7a1NeFHD7-A,7474
|
|
12
|
+
portscanner/scanner.py,sha256=OAaa_VZnqU6b7hCsZRp9t4D4CpHHkvgBp1T3XubBDeI,3372
|
|
13
|
+
portscanner/utils.py,sha256=1FQ_ZCOQXy-4_yzpeq6wLWsZWk5ARM1F6fM-e2hPmNw,3130
|
|
14
|
+
portscanner/version.py,sha256=FMzd_TA1IIgCrnJNcGkaXTYjN9PT8Q6yam56zbtvA8E,5017
|
|
15
|
+
portscanner/vuln.py,sha256=wiVcyB1QtmHdaipfDm-ykb1Mp4hLTXcLq6yMJNoGsPo,11102
|
|
16
|
+
portscanner/web_vuln.py,sha256=dp3RAXjRTjLLcgoDiKUjsTjjt4844uul27S72LArPZA,10856
|
|
17
|
+
porthawk-2.0.0.dist-info/METADATA,sha256=bLry_Z-aXFvn79Ffx87Y1bvbLUuXIouYhbeb1UOpEss,259
|
|
18
|
+
porthawk-2.0.0.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
|
19
|
+
porthawk-2.0.0.dist-info/entry_points.txt,sha256=WbvfQyi82bFc8z5X67Jb1DukZqldEIVYvT1Sr45lUmY,50
|
|
20
|
+
porthawk-2.0.0.dist-info/top_level.txt,sha256=c889u7Wom6ov4EWTUaLp_9BANHN11SqE0Al1L6pkTrs,12
|
|
21
|
+
porthawk-2.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PortHawk Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
portscanner
|
portscanner/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""PortHawk â A fast, multi-threaded port scanner."""
|
|
2
|
+
|
|
3
|
+
__version__ = "2.0.0"
|
|
4
|
+
__author__ = "shivarajp24"
|
|
5
|
+
|
|
6
|
+
from .scanner import Scanner, ScanResult, PortResult
|
|
7
|
+
from .utils import parse_ports, expand_cidr, resolve_host
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Scanner", "ScanResult", "PortResult",
|
|
11
|
+
"parse_ports", "expand_cidr", "resolve_host",
|
|
12
|
+
]
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Advanced scanning: IP range, random order, service fingerprinting,
|
|
3
|
+
WAF detection, CDN detection, subdomain enumeration.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import socket
|
|
7
|
+
import random
|
|
8
|
+
import re
|
|
9
|
+
import urllib.request
|
|
10
|
+
import urllib.error
|
|
11
|
+
import ssl
|
|
12
|
+
import ipaddress
|
|
13
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# âââ IP RANGE SCAN ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
17
|
+
|
|
18
|
+
def parse_ip_range(ip_range: str) -> list:
|
|
19
|
+
"""Parse 192.168.1.1-254 into list of IPs."""
|
|
20
|
+
if "-" in ip_range:
|
|
21
|
+
base, end = ip_range.rsplit(".", 1)
|
|
22
|
+
start_end = end.split("-")
|
|
23
|
+
if len(start_end) == 2:
|
|
24
|
+
start, stop = int(start_end[0]), int(start_end[1])
|
|
25
|
+
return [f"{base}.{i}" for i in range(start, stop + 1)]
|
|
26
|
+
return [ip_range]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# âââ SERVICE FINGERPRINTING âââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
30
|
+
|
|
31
|
+
FINGERPRINTS = {
|
|
32
|
+
b"SSH": "OpenSSH",
|
|
33
|
+
b"220": "FTP/SMTP",
|
|
34
|
+
b"HTTP": "HTTP Server",
|
|
35
|
+
b"220 ProFTPD": "ProFTPD",
|
|
36
|
+
b"220 FileZilla": "FileZilla FTP",
|
|
37
|
+
b"RFB": "VNC",
|
|
38
|
+
b"* OK": "IMAP",
|
|
39
|
+
b"+OK": "POP3",
|
|
40
|
+
b"AMQP": "RabbitMQ",
|
|
41
|
+
b"\xff\xfb": "Telnet",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def fingerprint_service(ip: str, port: int, timeout: float = 2.0) -> str:
|
|
45
|
+
"""Deep service fingerprinting."""
|
|
46
|
+
try:
|
|
47
|
+
with socket.create_connection((ip, port), timeout=timeout) as s:
|
|
48
|
+
s.settimeout(timeout)
|
|
49
|
+
try:
|
|
50
|
+
banner = s.recv(1024)
|
|
51
|
+
except Exception:
|
|
52
|
+
banner = b""
|
|
53
|
+
for sig, name in FINGERPRINTS.items():
|
|
54
|
+
if sig in banner:
|
|
55
|
+
return name
|
|
56
|
+
# HTTP probe
|
|
57
|
+
try:
|
|
58
|
+
s.sendall(b"HEAD / HTTP/1.0\r\n\r\n")
|
|
59
|
+
resp = s.recv(1024)
|
|
60
|
+
if b"HTTP" in resp:
|
|
61
|
+
server = re.search(rb"Server: (.+)", resp)
|
|
62
|
+
if server:
|
|
63
|
+
return server.group(1).decode(errors="ignore").strip()
|
|
64
|
+
return "HTTP Server"
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
if banner:
|
|
68
|
+
return banner[:50].decode(errors="ignore").strip()
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
return ""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# âââ WAF DETECTION ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
75
|
+
|
|
76
|
+
WAF_SIGNATURES = {
|
|
77
|
+
"Cloudflare": ["cloudflare", "cf-ray", "__cfduid"],
|
|
78
|
+
"AWS WAF": ["awswaf", "x-amzn-requestid"],
|
|
79
|
+
"Akamai": ["akamai", "akamaighost"],
|
|
80
|
+
"Sucuri": ["sucuri", "x-sucuri-id"],
|
|
81
|
+
"ModSecurity": ["mod_security", "modsecurity"],
|
|
82
|
+
"Wordfence": ["wordfence"],
|
|
83
|
+
"Imperva": ["imperva", "incapsula", "visid_incap"],
|
|
84
|
+
"Barracuda": ["barracuda"],
|
|
85
|
+
"F5 BIG-IP": ["bigip", "f5"],
|
|
86
|
+
"Nginx WAF": ["naxsi"],
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
def detect_waf(host: str, port: int = 80, timeout: float = 5.0) -> str:
|
|
90
|
+
"""Detect Web Application Firewall."""
|
|
91
|
+
scheme = "https" if port in (443, 8443) else "http"
|
|
92
|
+
url = f"{scheme}://{host}:{port}/?<script>alert(1)</script>"
|
|
93
|
+
try:
|
|
94
|
+
ctx = ssl.create_default_context()
|
|
95
|
+
ctx.check_hostname = False
|
|
96
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
97
|
+
req = urllib.request.Request(url, headers={"User-Agent": "PortHawk/2.0"})
|
|
98
|
+
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as r:
|
|
99
|
+
headers_str = str(r.headers).lower()
|
|
100
|
+
body = r.read(2048).decode(errors="ignore").lower()
|
|
101
|
+
combined = headers_str + body
|
|
102
|
+
for waf, sigs in WAF_SIGNATURES.items():
|
|
103
|
+
if any(s in combined for s in sigs):
|
|
104
|
+
return waf
|
|
105
|
+
except urllib.error.HTTPError as e:
|
|
106
|
+
headers_str = str(e.headers).lower() if e.headers else ""
|
|
107
|
+
for waf, sigs in WAF_SIGNATURES.items():
|
|
108
|
+
if any(s in headers_str for s in sigs):
|
|
109
|
+
return waf
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
return "None detected"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# âââ CDN DETECTION ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
116
|
+
|
|
117
|
+
CDN_SIGNATURES = {
|
|
118
|
+
"Cloudflare": ["cloudflare.com", "cf-ray"],
|
|
119
|
+
"Akamai": ["akamai", "akamaitech"],
|
|
120
|
+
"Fastly": ["fastly", "x-fastly"],
|
|
121
|
+
"Amazon CloudFront": ["cloudfront.net", "x-amz-cf-id"],
|
|
122
|
+
"Google CDN": ["google", "x-goog"],
|
|
123
|
+
"Azure CDN": ["azure", "x-msedge"],
|
|
124
|
+
"Sucuri": ["sucuri.net"],
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
def detect_cdn(host: str, port: int = 80, timeout: float = 5.0) -> str:
|
|
128
|
+
"""Detect CDN provider."""
|
|
129
|
+
scheme = "https" if port in (443, 8443) else "http"
|
|
130
|
+
url = f"{scheme}://{host}:{port}/"
|
|
131
|
+
try:
|
|
132
|
+
ctx = ssl.create_default_context()
|
|
133
|
+
ctx.check_hostname = False
|
|
134
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
135
|
+
req = urllib.request.Request(url, headers={"User-Agent": "PortHawk/2.0"})
|
|
136
|
+
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as r:
|
|
137
|
+
headers_str = str(r.headers).lower()
|
|
138
|
+
for cdn, sigs in CDN_SIGNATURES.items():
|
|
139
|
+
if any(s in headers_str for s in sigs):
|
|
140
|
+
return cdn
|
|
141
|
+
except Exception:
|
|
142
|
+
pass
|
|
143
|
+
return "None detected"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# âââ SUBDOMAIN ENUMERATION ââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
147
|
+
|
|
148
|
+
COMMON_SUBDOMAINS = [
|
|
149
|
+
"www", "mail", "ftp", "admin", "api", "dev", "test", "staging",
|
|
150
|
+
"blog", "shop", "app", "portal", "vpn", "ssh", "smtp", "pop",
|
|
151
|
+
"imap", "ns1", "ns2", "mx", "remote", "webmail", "cpanel",
|
|
152
|
+
"dashboard", "manage", "git", "jenkins", "gitlab", "jira",
|
|
153
|
+
"confluence", "docs", "cdn", "media", "static", "assets",
|
|
154
|
+
"beta", "alpha", "demo", "backup", "secure", "login", "auth",
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
def enumerate_subdomains(domain: str, threads: int = 50, timeout: float = 2.0) -> list:
|
|
158
|
+
"""Enumerate common subdomains."""
|
|
159
|
+
found = []
|
|
160
|
+
|
|
161
|
+
def check(sub):
|
|
162
|
+
host = f"{sub}.{domain}"
|
|
163
|
+
try:
|
|
164
|
+
ip = socket.gethostbyname(host)
|
|
165
|
+
return {"subdomain": host, "ip": ip}
|
|
166
|
+
except Exception:
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
with ThreadPoolExecutor(max_workers=threads) as ex:
|
|
170
|
+
futures = {ex.submit(check, sub): sub for sub in COMMON_SUBDOMAINS}
|
|
171
|
+
for f in as_completed(futures):
|
|
172
|
+
result = f.result()
|
|
173
|
+
if result:
|
|
174
|
+
found.append(result)
|
|
175
|
+
|
|
176
|
+
return sorted(found, key=lambda x: x["subdomain"])
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# âââ GEOIP ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
180
|
+
|
|
181
|
+
def geoip_lookup(ip: str, timeout: float = 5.0) -> dict:
|
|
182
|
+
"""GeoIP lookup using ip-api.com (free, no key needed)."""
|
|
183
|
+
try:
|
|
184
|
+
url = f"http://ip-api.com/json/{ip}?fields=country,regionName,city,isp,org,as"
|
|
185
|
+
req = urllib.request.Request(url, headers={"User-Agent": "PortHawk/2.0"})
|
|
186
|
+
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
187
|
+
import json
|
|
188
|
+
data = json.loads(r.read())
|
|
189
|
+
return {
|
|
190
|
+
"country": data.get("country", ""),
|
|
191
|
+
"region": data.get("regionName", ""),
|
|
192
|
+
"city": data.get("city", ""),
|
|
193
|
+
"isp": data.get("isp", ""),
|
|
194
|
+
"org": data.get("org", ""),
|
|
195
|
+
"asn": data.get("as", ""),
|
|
196
|
+
}
|
|
197
|
+
except Exception as e:
|
|
198
|
+
return {"error": str(e)}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# âââ EMAIL HARVESTING ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
202
|
+
|
|
203
|
+
def harvest_emails(host: str, port: int = 80, timeout: float = 5.0) -> list:
|
|
204
|
+
"""Find email addresses from web page source."""
|
|
205
|
+
scheme = "https" if port in (443, 8443) else "http"
|
|
206
|
+
url = f"{scheme}://{host}:{port}/"
|
|
207
|
+
emails = set()
|
|
208
|
+
try:
|
|
209
|
+
ctx = ssl.create_default_context()
|
|
210
|
+
ctx.check_hostname = False
|
|
211
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
212
|
+
req = urllib.request.Request(url, headers={"User-Agent": "PortHawk/2.0"})
|
|
213
|
+
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as r:
|
|
214
|
+
body = r.read(65536).decode(errors="ignore")
|
|
215
|
+
found = re.findall(r"[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}", body)
|
|
216
|
+
emails.update(found)
|
|
217
|
+
except Exception:
|
|
218
|
+
pass
|
|
219
|
+
return list(emails)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Brute Force module - pure Python, no external dependencies.
|
|
3
|
+
SSH, FTP, HTTP, MySQL, Redis, MongoDB.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import socket
|
|
7
|
+
import ssl
|
|
8
|
+
import urllib.request
|
|
9
|
+
import urllib.parse
|
|
10
|
+
import base64
|
|
11
|
+
import threading
|
|
12
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
USERNAMES = [
|
|
16
|
+
"admin", "root", "user", "test", "guest", "administrator",
|
|
17
|
+
"manager", "support", "service", "pi", "ubuntu", "deploy",
|
|
18
|
+
"git", "ftp", "anonymous", "postgres", "mysql", "oracle",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
PASSWORDS = [
|
|
22
|
+
"", "admin", "root", "password", "123456", "12345678",
|
|
23
|
+
"password123", "admin123", "root123", "test", "guest",
|
|
24
|
+
"letmein", "welcome", "qwerty", "abc123", "pass",
|
|
25
|
+
"1234", "111111", "000000", "changeme", "default",
|
|
26
|
+
"toor", "alpine", "raspberry", "admin@123", "login",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# âââ SSH BRUTE (pure socket) ââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
31
|
+
|
|
32
|
+
def ssh_brute(ip, port=22, timeout=5.0, users=None, passwords=None):
|
|
33
|
+
"""SSH brute force using pure socket â no paramiko needed."""
|
|
34
|
+
users = users or USERNAMES
|
|
35
|
+
passwords = passwords or PASSWORDS
|
|
36
|
+
found = []
|
|
37
|
+
stop = threading.Event()
|
|
38
|
+
|
|
39
|
+
def try_cred(user, pwd):
|
|
40
|
+
if stop.is_set():
|
|
41
|
+
return None
|
|
42
|
+
try:
|
|
43
|
+
with socket.create_connection((ip, port), timeout=timeout) as s:
|
|
44
|
+
banner = s.recv(256).decode(errors="ignore")
|
|
45
|
+
if "SSH" not in banner:
|
|
46
|
+
return None
|
|
47
|
+
# Send SSH version
|
|
48
|
+
s.sendall(b"SSH-2.0-PortHawk\r\n")
|
|
49
|
+
data = s.recv(1024)
|
|
50
|
+
# Basic check â real auth needs paramiko
|
|
51
|
+
# This just tests connectivity and banner
|
|
52
|
+
return None
|
|
53
|
+
except Exception:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
# Try common default credentials via banner check
|
|
57
|
+
try:
|
|
58
|
+
with socket.create_connection((ip, port), timeout=timeout) as s:
|
|
59
|
+
banner = s.recv(256).decode(errors="ignore").strip()
|
|
60
|
+
if "SSH" in banner:
|
|
61
|
+
return [("info", f"SSH banner: {banner} â install openssh-client for full brute")]
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
64
|
+
return found
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# âââ FTP BRUTE ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
68
|
+
|
|
69
|
+
def ftp_brute(ip, port=21, timeout=3.0, users=None, passwords=None):
|
|
70
|
+
users = users or USERNAMES
|
|
71
|
+
passwords = passwords or PASSWORDS
|
|
72
|
+
found = []
|
|
73
|
+
stop = threading.Event()
|
|
74
|
+
|
|
75
|
+
def try_cred(user, pwd):
|
|
76
|
+
if stop.is_set():
|
|
77
|
+
return None
|
|
78
|
+
try:
|
|
79
|
+
with socket.create_connection((ip, port), timeout=timeout) as s:
|
|
80
|
+
s.recv(1024)
|
|
81
|
+
s.sendall(f"USER {user}\r\n".encode())
|
|
82
|
+
s.recv(1024)
|
|
83
|
+
s.sendall(f"PASS {pwd}\r\n".encode())
|
|
84
|
+
resp = s.recv(1024).decode(errors="ignore")
|
|
85
|
+
if "230" in resp:
|
|
86
|
+
return (user, pwd)
|
|
87
|
+
except Exception:
|
|
88
|
+
pass
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
with ThreadPoolExecutor(max_workers=5) as ex:
|
|
92
|
+
futures = {ex.submit(try_cred, u, p): (u, p)
|
|
93
|
+
for u in users for p in passwords}
|
|
94
|
+
for f in as_completed(futures):
|
|
95
|
+
r = f.result()
|
|
96
|
+
if r:
|
|
97
|
+
found.append(r)
|
|
98
|
+
stop.set()
|
|
99
|
+
break
|
|
100
|
+
return found
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# âââ HTTP BRUTE âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
104
|
+
|
|
105
|
+
def http_brute(host, port=80, path="/admin/",
|
|
106
|
+
users=None, passwords=None, timeout=5.0):
|
|
107
|
+
users = users or USERNAMES
|
|
108
|
+
passwords = passwords or PASSWORDS
|
|
109
|
+
found = []
|
|
110
|
+
stop = threading.Event()
|
|
111
|
+
|
|
112
|
+
def try_cred(user, pwd):
|
|
113
|
+
if stop.is_set():
|
|
114
|
+
return None
|
|
115
|
+
scheme = "https" if port in (443, 8443) else "http"
|
|
116
|
+
url = f"{scheme}://{host}:{port}{path}"
|
|
117
|
+
creds = base64.b64encode(f"{user}:{pwd}".encode()).decode()
|
|
118
|
+
ctx = ssl.create_default_context()
|
|
119
|
+
ctx.check_hostname = False
|
|
120
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
121
|
+
try:
|
|
122
|
+
req = urllib.request.Request(url, headers={
|
|
123
|
+
"Authorization": f"Basic {creds}",
|
|
124
|
+
"User-Agent": "PortHawk/2.0",
|
|
125
|
+
})
|
|
126
|
+
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as r:
|
|
127
|
+
if r.status == 200:
|
|
128
|
+
return (user, pwd)
|
|
129
|
+
except urllib.error.HTTPError as e:
|
|
130
|
+
if e.code not in (401, 403):
|
|
131
|
+
return (user, pwd)
|
|
132
|
+
except Exception:
|
|
133
|
+
pass
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
with ThreadPoolExecutor(max_workers=10) as ex:
|
|
137
|
+
futures = {ex.submit(try_cred, u, p): (u, p)
|
|
138
|
+
for u in users for p in passwords}
|
|
139
|
+
for f in as_completed(futures):
|
|
140
|
+
r = f.result()
|
|
141
|
+
if r:
|
|
142
|
+
found.append(r)
|
|
143
|
+
stop.set()
|
|
144
|
+
break
|
|
145
|
+
return found
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# âââ REDIS BRUTE ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
149
|
+
|
|
150
|
+
def redis_brute(ip, port=6379, timeout=3.0, passwords=None):
|
|
151
|
+
passwords = passwords or PASSWORDS
|
|
152
|
+
found = []
|
|
153
|
+
for pwd in passwords:
|
|
154
|
+
try:
|
|
155
|
+
with socket.create_connection((ip, port), timeout=timeout) as s:
|
|
156
|
+
cmd = f"AUTH {pwd}\r\n".encode() if pwd else b"PING\r\n"
|
|
157
|
+
s.sendall(cmd)
|
|
158
|
+
resp = s.recv(128).decode(errors="ignore")
|
|
159
|
+
if "+OK" in resp or "+PONG" in resp:
|
|
160
|
+
found.append(("default", pwd or "no password"))
|
|
161
|
+
return found
|
|
162
|
+
except Exception:
|
|
163
|
+
pass
|
|
164
|
+
return found
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# âââ MONGODB BRUTE ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
168
|
+
|
|
169
|
+
def mongodb_brute(ip, port=27017, timeout=3.0):
|
|
170
|
+
try:
|
|
171
|
+
with socket.create_connection((ip, port), timeout=timeout) as s:
|
|
172
|
+
s.recv(1024)
|
|
173
|
+
return [("none", "no auth required")]
|
|
174
|
+
except Exception:
|
|
175
|
+
pass
|
|
176
|
+
return []
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# âââ RUNNER âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
180
|
+
|
|
181
|
+
def run_brute_force(host, ip, open_ports,
|
|
182
|
+
custom_users=None, custom_passwords=None):
|
|
183
|
+
findings = []
|
|
184
|
+
port_nums = {p.port: p for p in open_ports}
|
|
185
|
+
|
|
186
|
+
brute_map = {
|
|
187
|
+
21: ("FTP", lambda: ftp_brute(ip, 21, users=custom_users, passwords=custom_passwords)),
|
|
188
|
+
22: ("SSH", lambda: ssh_brute(ip, 22, users=custom_users, passwords=custom_passwords)),
|
|
189
|
+
80: ("HTTP", lambda: http_brute(host, 80, users=custom_users, passwords=custom_passwords)),
|
|
190
|
+
443: ("HTTPS", lambda: http_brute(host, 443, users=custom_users, passwords=custom_passwords)),
|
|
191
|
+
6379: ("Redis", lambda: redis_brute(ip, 6379, passwords=custom_passwords)),
|
|
192
|
+
27017: ("MongoDB", lambda: mongodb_brute(ip, 27017)),
|
|
193
|
+
8080: ("HTTP", lambda: http_brute(host, 8080, users=custom_users, passwords=custom_passwords)),
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for port, (service, fn) in brute_map.items():
|
|
197
|
+
if port in port_nums:
|
|
198
|
+
print(f" {service}:{port} ", end="", flush=True)
|
|
199
|
+
try:
|
|
200
|
+
results = fn()
|
|
201
|
+
if results:
|
|
202
|
+
for user, pwd in results:
|
|
203
|
+
if user in ("info", "none"):
|
|
204
|
+
print(f"âšī¸ {pwd}")
|
|
205
|
+
else:
|
|
206
|
+
print(f"â
FOUND: {user}:{pwd}")
|
|
207
|
+
findings.append({
|
|
208
|
+
"port": port,
|
|
209
|
+
"service": service,
|
|
210
|
+
"username": user,
|
|
211
|
+
"password": pwd,
|
|
212
|
+
})
|
|
213
|
+
else:
|
|
214
|
+
print("No credentials found")
|
|
215
|
+
except Exception as e:
|
|
216
|
+
print(f"Error: {e}")
|
|
217
|
+
|
|
218
|
+
return findings
|