From 35d1eb85a1b9e5604c107e874e8df440839c1af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 7 Feb 2024 11:19:14 +0100 Subject: [PATCH 1/4] sniffer: fixed imports, naming --- honeypots/qbsniffer.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/honeypots/qbsniffer.py b/honeypots/qbsniffer.py index b0c27ef..54649af 100644 --- a/honeypots/qbsniffer.py +++ b/honeypots/qbsniffer.py @@ -10,11 +10,6 @@ // ------------------------------------------------------------- """ -from warnings import filterwarnings -from cryptography.utils import CryptographyDeprecationWarning - -filterwarnings(action="ignore", category=CryptographyDeprecationWarning) - from binascii import hexlify from multiprocessing import Process from re import compile as rcompile, search as rsearch @@ -22,9 +17,10 @@ from uuid import uuid4 from netifaces import AF_INET, AF_LINK, ifaddresses -from scapy.all import * +from scapy.layers.inet import IP, TCP +from scapy.sendrecv import send, sniff -from honeypots.helper import setup_logger +from honeypots.helper import server_arguments, setup_logger class QBSniffer: @@ -38,7 +34,7 @@ def __init__(self, filter=None, interface=None, config=""): (0, 0, "Echo/Ping reply"), (3, 0, "Destination network unreachable"), (3, 1, "Destination host unreachable"), - (3, 2, "Desination protocol unreachable"), + (3, 2, "Destination protocol unreachable"), (3, 3, "Destination port unreachable"), (3, 4, "Fragmentation required"), (3, 5, "Source route failed"), @@ -51,7 +47,7 @@ def __init__(self, filter=None, interface=None, config=""): (3, 12, "Host unreachable for TOS"), (3, 13, "Communication administratively prohibited"), (3, 14, "Host Precedence Violation"), - (3, 15, "Precendence cutoff in effect"), + (3, 15, "Precedence cutoff in effect"), (4, 0, "Source quench"), (5, 0, "Redirect Datagram for the Network"), (5, 1, "Redirect Datagram for the Host"), @@ -290,9 +286,9 @@ def kill_sniffer(self): if __name__ == "__main__": - from server_options import server_arguments - parsed = server_arguments() if parsed.docker or parsed.aws or parsed.custom: - qsniffer = QSniffer(filter=parsed.filter, interface=parsed.interface, config=parsed.config) + qsniffer = QBSniffer( + filter=parsed.filter, interface=parsed.interface, config=parsed.config + ) qsniffer.run_sniffer() From 78507794d55328f148c66b0230a7e65365b461b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 7 Feb 2024 14:12:54 +0100 Subject: [PATCH 2/4] pyproject: add upper py version limit and todo --- pyproject.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 19d98bf..968d5ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,8 @@ authors = [ ] description = "30 different honeypots in one package! (dhcp, dns, elastic, ftp, http proxy, https proxy, http, https, imap, ipp, irc, ldap, memcache, mssql, mysql, ntp, oracle, pjl, pop3, postgres, rdp, redis, sip, smb, smtp, snmp, socks5, ssh, telnet, vnc)" readme = "README.rst" -requires-python = ">=3.8" +# ToDo: fix smtp incompatibility with 3.12 +requires-python = ">=3.8,<3.12" dependencies = [ "twisted==21.7.0", "psutil==5.9.0", @@ -23,7 +24,7 @@ dependencies = [ "impacket==0.9.24", "paramiko==3.1.0", "scapy==2.4.5", - "service_identity==21.1.0", + "service-identity==21.1.0", "netifaces==0.11.0", ] license = {text = "AGPL-3.0"} From 4d263bf8b7a402b2b9afc727f0af4eff1157ee59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 7 Feb 2024 14:22:06 +0100 Subject: [PATCH 3/4] removed unused functions --- honeypots/helper.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/honeypots/helper.py b/honeypots/helper.py index 4c22c52..c0ff259 100644 --- a/honeypots/helper.py +++ b/honeypots/helper.py @@ -241,15 +241,6 @@ def kill_servers(name): process.kill() -def check_if_server_is_running(uuid): - with suppress(Exception): - for process in process_iter(): - cmdline = " ".join(process.cmdline()) - if "--custom" in cmdline and uuid in cmdline: - return True - return False - - def kill_server_wrapper(server_name, name, process): with suppress(Exception): if process is not None: @@ -300,11 +291,6 @@ def default(self, obj): return repr(obj).replace("\x00", " ") -class ComplexEncoder_db(JSONEncoder): - def default(self, obj): - return "Something wrong, deleted.." - - def serialize_object(_dict): if isinstance(_dict, Mapping): return dict((k, serialize_object(v)) for k, v in _dict.items()) From 4185b267e178b74f086022abf8ed96b5d4f1fc13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 7 Feb 2024 14:23:26 +0100 Subject: [PATCH 4/4] replaced Popen with multiprocessing to fix memory issues --- honeypots/__main__.py | 2 +- honeypots/base_server.py | 47 ++++++++++++++++++++-------------------- honeypots/helper.py | 25 ++++++++++++++++++++- tests/utils.py | 20 +++-------------- 4 files changed, 51 insertions(+), 43 deletions(-) diff --git a/honeypots/__main__.py b/honeypots/__main__.py index 76d6ed3..e7e9d17 100755 --- a/honeypots/__main__.py +++ b/honeypots/__main__.py @@ -206,7 +206,7 @@ def _set_up_honeypots(self): # noqa: C901 running_honeypots = {"good": [], "bad": []} if len(self.honeypots) > 0: for _, server_name, status in self.honeypots: - if status is False or status is None: + if not status: running_honeypots["bad"].append(server_name) else: running_honeypots["good"].append(server_name) diff --git a/honeypots/base_server.py b/honeypots/base_server.py index 1faea65..623b2a5 100644 --- a/honeypots/base_server.py +++ b/honeypots/base_server.py @@ -1,21 +1,18 @@ from __future__ import annotations -import inspect from abc import ABC, abstractmethod +from multiprocessing import Process from os import getenv -from shlex import split -from subprocess import Popen from typing import Any from uuid import uuid4 from honeypots.helper import ( - setup_logger, - set_local_vars, - set_up_error_logging, close_port_wrapper, - kill_server_wrapper, get_free_port, - check_if_server_is_running, + service_has_started, + set_local_vars, + set_up_error_logging, + setup_logger, ) @@ -26,7 +23,7 @@ class BaseServer(ABC): DEFAULT_PASSWORD = "test" def __init__(self, **kwargs): - self.auto_disabled = None + self.auto_disabled = False self.process = None self.uuid = f"honeypotslogger_{__class__.__name__}_{str(uuid4())[:8]}" self.config = kwargs.get("config", "") @@ -58,19 +55,24 @@ def __init__(self, **kwargs): or "" ) self.logger = set_up_error_logging() + self._server_process: Process | None = None def close_port(self): return close_port_wrapper(self.NAME, self.ip, self.port, self.logs) def kill_server(self): - return kill_server_wrapper(self.NAME, self.uuid, self.process) + if self._server_process: + try: + self._server_process.terminate() + self._server_process.join(timeout=5) + except TimeoutError: + self._server_process.kill() @abstractmethod def server_main(self): pass def run_server(self, process: bool = False, auto: bool = False) -> bool | None: - status = "error" run = False if not process: self.server_main() @@ -81,21 +83,10 @@ def run_server(self, process: bool = False, auto: bool = False) -> bool | None: if port > 0: self.port = port run = True - elif self.close_port() and self.kill_server(): + elif self.close_port(): run = True - if run: - file = inspect.getfile(self.__class__) - command = ( - f"python3 {file} --custom --ip {self.ip} " f"--port {self.port} --uuid {self.uuid}" - ) - if self.options: - command += f" --options '{self.options}'" - if self.config: - command += f" --config '{self.config}'" - self.process = Popen(split(command)) - if self.process.poll() is None and check_if_server_is_running(self.uuid): - status = "success" + status = self._start_server() if run else "error" self.log( { @@ -111,6 +102,14 @@ def run_server(self, process: bool = False, auto: bool = False) -> bool | None: self.kill_server() return False + def _start_server(self) -> str: + self._server_process = Process(target=self.server_main) + self._server_process.start() + if service_has_started(int(self.port)): + return "success" + self.logger.error(f"Server {self.NAME} did not start") + return "error" + def check_login(self, username: str, password: str, ip: str, port: int) -> bool: status = "success" if self._login_is_correct(username, password) else "failed" self.log( diff --git a/honeypots/helper.py b/honeypots/helper.py index c0ff259..150c65c 100644 --- a/honeypots/helper.py +++ b/honeypots/helper.py @@ -27,10 +27,11 @@ from sqlite3 import connect as sqlite3_connect from sys import stdout from tempfile import _get_candidate_names, gettempdir, NamedTemporaryFile -from time import sleep +from time import sleep, time from typing import Any, Iterator from urllib.parse import urlparse +import psutil from OpenSSL import crypto from psutil import process_iter from psycopg2 import connect as psycopg2_connect, sql @@ -728,3 +729,25 @@ def get_headers_and_ip_from_request(request, options): if client_ip == "": client_ip = request.getClientAddress().host return client_ip, headers + + +def service_has_started(port: int): + try: + wait_for_service(port) + return True + except TimeoutError: + return False + + +def wait_for_service(port: int, interval: float = 0.1, timeout: int = 5.0): + start_time = time() + while True: + if _service_runs(port): + return + sleep(interval) + if time() - start_time > timeout: + raise TimeoutError() + + +def _service_runs(port: int) -> bool: + return any(service.laddr.port == port for service in psutil.net_connections()) diff --git a/tests/utils.py b/tests/utils.py index e40eab8..9a4d4fa 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -3,10 +3,10 @@ import json from contextlib import contextmanager from socket import AF_INET, IPPROTO_UDP, SOCK_DGRAM, SOCK_STREAM, socket -from time import sleep, time +from time import sleep from typing import TYPE_CHECKING -import psutil +from honeypots.helper import wait_for_service if TYPE_CHECKING: from pathlib import Path @@ -63,20 +63,6 @@ def assert_login_is_logged(login: dict[str, str]): @contextmanager def wait_for_server(port: str | int): - _wait_for_service(int(port)) + wait_for_service(int(port)) yield sleep(0.5) # give the server process some time to write logs - - -def _wait_for_service(port: int, interval: float = 0.1, timeout: int = 5.0): - start_time = time() - while True: - if _service_runs(port): - return - sleep(interval) - if time() - start_time > timeout: - raise TimeoutError() - - -def _service_runs(port: int) -> bool: - return any(service.laddr.port == port for service in psutil.net_connections())