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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (79.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ porthawk = portscanner.cli:main
@@ -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
@@ -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