From 62479c8e1a318dcc1bff7ab400edb4c7895e7bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 7 Feb 2024 15:58:56 +0100 Subject: [PATCH 01/35] added pre-commit github action --- .github/workflows/pre-commit.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/pre-commit.yaml diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 0000000..505623a --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,19 @@ +name: pre-commit + +on: + pull_request: + push: + branches: [main] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + # requites to grab the history of the PR + fetch-depth: 0 + - uses: actions/setup-python@v3 + - uses: pre-commit/action@v3.0.0 + with: + extra_args: --from-ref ${{ github.event.pull_request.base.sha }} --to-ref ${{ github.event.pull_request.head.sha }} From 4fc0b7a176c2197fff27252ea0acc8e29d761c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 7 Feb 2024 16:02:32 +0100 Subject: [PATCH 02/35] dhcp server: fixed ruff issues --- honeypots/dhcp_server.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/honeypots/dhcp_server.py b/honeypots/dhcp_server.py index fa9e519..05e5c20 100644 --- a/honeypots/dhcp_server.py +++ b/honeypots/dhcp_server.py @@ -12,7 +12,7 @@ from os import getenv from socket import inet_aton -from struct import unpack, error as StructError +import struct from uuid import uuid4 from twisted.internet import reactor @@ -72,7 +72,7 @@ def payload(self, value, message): siaddr, giaddr, chaddr, - ) = unpack("1s1s1s1s4s2s2s4s4s4s4s16s", message[:44]) + ) = struct.unpack("1s1s1s1s4s2s2s4s4s4s4s16s", message[:44]) # op, htype, hlen, hops, xid, secs, flags, ciaddr response = b"\x02\x01\x06\x00" + xid + b"\x00\x00\x00\x00\x00\x00\x00\x00" # yiaddr, siaddr, giaddr, chaddr @@ -99,27 +99,26 @@ def parse_options(self, raw): tag_name = None tag_size = None tag = "" - for idx, b in enumerate(raw): + for b in raw: if tag_name is None: tag_name = b elif tag_name is not None and tag_size is None: tag_size = b tag = "" - else: - if tag_size: - tag_size -= 1 - tag += chr(b) - if tag_size == 0: - options.update({check_bytes(tag_name): check_bytes(tag)}) - tag_name = None - tag_size = None - tag = "" + elif tag_size: + tag_size -= 1 + tag += chr(b) + if tag_size == 0: + options.update({check_bytes(tag_name): check_bytes(tag)}) + tag_name = None + tag_size = None + tag = "" return options - def datagramReceived(self, data, addr): + def datagramReceived(self, data, addr): # noqa: N802 try: - mac_address = unpack("!28x6s", data[:34])[0].hex(":") - except StructError: + mac_address = struct.unpack("!28x6s", data[:34])[0].hex(":") + except struct.error: mac_address = "None" data = self.parse_options(data[240:]) data.update({"mac_address": mac_address}) From ca2c375382c02478e97b224fa576b455a3726946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 7 Feb 2024 16:05:42 +0100 Subject: [PATCH 03/35] run test action on all PRs --- .github/workflows/tests.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bb178d5..cc726ab 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,11 +2,8 @@ name: Tests CI run-name: Tests CI on: pull_request: - branches: - - main push: - branches: - - main + branches: [main] workflow_dispatch: jobs: From bb7b38c7b4b5ac2ac4ea5b07bf6aef0c0a107a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 7 Feb 2024 16:10:46 +0100 Subject: [PATCH 04/35] dns server: fixed ruff issues --- honeypots/dns_server.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/honeypots/dns_server.py b/honeypots/dns_server.py index 894f1de..b9ec294 100644 --- a/honeypots/dns_server.py +++ b/honeypots/dns_server.py @@ -36,17 +36,17 @@ def server_main(self): _q_s = self class CustomClientResolver(client.Resolver): - def queryUDP(self, queries, timeout=2): + def queryUDP(self, queries, timeout=2): # noqa: N802 res = client.Resolver.queryUDP(self, queries, timeout) - def queryFailed(reason): + def queryFailed(reason): # noqa: N802,ARG001 return defer.fail(error.DomainError()) res.addErrback(queryFailed) return res class CustomDNSServerFactory(DNSServerFactory): - def gotResolverResponse(self, response, protocol, message, address): + def gotResolverResponse(self, response, protocol, message, address): # noqa: N802 if address is None: src_ip, src_port = "None", "None" else: @@ -64,7 +64,7 @@ def gotResolverResponse(self, response, protocol, message, address): return super().gotResolverResponse(response, protocol, message, address) class CustomDnsUdpProtocol(dns.DNSDatagramProtocol): - def datagramReceived(self, data: bytes, addr: tuple[str, int]): + def datagramReceived(self, data: bytes, addr: tuple[str, int]): # noqa: N802 _q_s.log( { "action": "connection", @@ -82,7 +82,7 @@ def datagramReceived(self, data: bytes, addr: tuple[str, int]): reactor.listenTCP(self.port, self.factory, interface=self.ip) reactor.run() - def test_server(self, ip=None, port=None, domain=None): + def test_server(self, *_, domain=None, **__): with suppress(Exception): from dns.resolver import Resolver @@ -90,7 +90,7 @@ def test_server(self, ip=None, port=None, domain=None): res.nameservers = [self.ip] res.port = self.port temp_domain = domain or "example.org" - r = res.query(temp_domain, "a") + res.resolve(temp_domain, "a") if __name__ == "__main__": From cb92527a231782c3cd923c90a6346af611ce1da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 7 Feb 2024 17:11:44 +0100 Subject: [PATCH 05/35] elastic server: reduce code duplication + ruff fixes --- honeypots/elastic_server.py | 366 +++++++++++++++--------------------- 1 file changed, 148 insertions(+), 218 deletions(-) diff --git a/honeypots/elastic_server.py b/honeypots/elastic_server.py index 14efa25..aa8d22e 100644 --- a/honeypots/elastic_server.py +++ b/honeypots/elastic_server.py @@ -31,7 +31,7 @@ class QElasticServer(BaseServer): DEFAULT_PORT = 9200 DEFAULT_USERNAME = "elastic" - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self class CustomElasticServerHandler(SimpleHTTPRequestHandler): @@ -74,7 +74,7 @@ def _set_response_gzip(self, content, code): self.end_headers() return gzip_compressed_data - def do_HEAD(self): + def do_HEAD(self): # noqa: N802 self.send_response(200) self.send_header("content-encoding", "gzip") self.send_header("content-type", "application/json; charset=UTF-8") @@ -92,7 +92,7 @@ def _set_response_gzip_auth(self, content, code): self.end_headers() return gzip_compressed_data - def do_GET(self): + def do_GET(self): # noqa: N802 username = "" password = "" e_name = "045dffec8b60" @@ -116,31 +116,12 @@ def do_GET(self): "password": password, } ) - auth_paylaod = bytes( - dumps( - { - "error": { - "root_cause": [ - { - "type": "security_exception", - "reason": f"unable to authenticate user [{username}] for REST request [/]", - "header": { - "WWW-Authenticate": 'Basic realm="security" charset="UTF-8"' - }, - } - ], - "type": "security_exception", - "reason": f"unable to authenticate user [{username}] for REST request [/]", - "header": { - "WWW-Authenticate": 'Basic realm="security" charset="UTF-8"' - }, - }, - "status": 401, - } - ), - "utf-8", - ) - self.wfile.write(self._set_response_gzip_auth(auth_paylaod, 401)) + error = { + "type": "security_exception", + "reason": f"unable to authenticate user [{username}] for REST request [/]", + "header": {"WWW-Authenticate": 'Basic realm="security" charset="UTF-8"'}, + } + self._send_auth_error(error, 401) elif self.headers.get("Authorization") == "Basic " + str(key): _q_s.log( { @@ -155,169 +136,124 @@ def do_GET(self): with suppress(Exception): extracted = urlparse(self.path).path if extracted == "/": - normal_payload = bytes( - dumps( - { - "name": e_name, - "cluster_name": e_cluster_name, - "cluster_uuid": "09cf5BKcTCG2U8z2ndwGEw", - "version": { - "number": "7.12.1", - "build_flavor": "default", - "build_type": e_build_type, - "build_hash": "3186837139b9c6b6d23c3200870651f10d3343b7", - "build_date": "2021-04-20T20:56:39.040728659Z", - "build_snapshot": False, - "lucene_version": "8.8.0", - "minimum_wire_compatibility_version": "6.8.0", - "minimum_index_compatibility_version": "6.0.0-beta1", - }, - "tagline": "You Know, for Search", - } - ), - "utf-8", + normal_payload = { + "name": e_name, + "cluster_name": e_cluster_name, + "cluster_uuid": "09cf5BKcTCG2U8z2ndwGEw", + "version": { + "number": "7.12.1", + "build_flavor": "default", + "build_type": e_build_type, + "build_hash": "3186837139b9c6b6d23c3200870651f10d3343b7", + "build_date": "2021-04-20T20:56:39.040728659Z", + "build_snapshot": False, + "lucene_version": "8.8.0", + "minimum_wire_compatibility_version": "6.8.0", + "minimum_index_compatibility_version": "6.0.0-beta1", + }, + "tagline": "You Know, for Search", + } + self.wfile.write( + self._set_response_gzip(dumps(normal_payload).encode("utf-8"), 200) ) - self.wfile.write(self._set_response_gzip(normal_payload, 200)) elif extracted.startswith("/_nodes"): - _nodes_payload = bytes( - dumps( - { - "_nodes": {"total": 1, "successful": 1, "failed": 0}, - "cluster_name": e_cluster_name, - "nodes": { - "rvyTV3xvTgyt74ti4u12bw": { - "name": e_name, - "transport_address": e_transport_address, - "host": e_host, - "src_ip": e_host, - "version": "7.12.1", - "build_flavor": "default", - "build_type": e_build_type, - "build_hash": "3186837139b9c6b6d23c3200870651f10d3343b7", - "roles": [ - "data", - "data_cold", - "data_content", - "data_frozen", - "data_hot", - "data_warm", - "ingest", - "master", - "ml", - "remote_cluster_client", - "transform", - ], - "attributes": { - "ml.machine_memory": "16685318144", - "xpack.installed": "true", - "transform.node": "true", - "ml.max_open_jobs": "20", - "ml.max_jvm_size": "8342470656", - }, - "process": { - "refresh_interval_in_millis": 1000, - "id": 7, - "mlockall": False, - }, - }, - "os": { - "refresh_interval_in_millis": 1000, - "name": e_os_name, - "pretty_name": e_os_pretty_name, - "arch": "amd64", - "version": e_os_version, - "available_processors": 32, - "allocated_processors": 8, - }, - "process": { - "refresh_interval_in_millis": 1000, - "id": 7, - "mlockall": False, - }, + _nodes_payload = { + "_nodes": {"total": 1, "successful": 1, "failed": 0}, + "cluster_name": e_cluster_name, + "nodes": { + "rvyTV3xvTgyt74ti4u12bw": { + "name": e_name, + "transport_address": e_transport_address, + "host": e_host, + "src_ip": e_host, + "version": "7.12.1", + "build_flavor": "default", + "build_type": e_build_type, + "build_hash": "3186837139b9c6b6d23c3200870651f10d3343b7", + "roles": [ + "data", + "data_cold", + "data_content", + "data_frozen", + "data_hot", + "data_warm", + "ingest", + "master", + "ml", + "remote_cluster_client", + "transform", + ], + "attributes": { + "ml.machine_memory": "16685318144", + "xpack.installed": "true", + "transform.node": "true", + "ml.max_open_jobs": "20", + "ml.max_jvm_size": "8342470656", }, - } - ), - "utf-8", + "process": { + "refresh_interval_in_millis": 1000, + "id": 7, + "mlockall": False, + }, + }, + "os": { + "refresh_interval_in_millis": 1000, + "name": e_os_name, + "pretty_name": e_os_pretty_name, + "arch": "amd64", + "version": e_os_version, + "available_processors": 32, + "allocated_processors": 8, + }, + "process": { + "refresh_interval_in_millis": 1000, + "id": 7, + "mlockall": False, + }, + }, + } + self.wfile.write( + self._set_response_gzip(dumps(_nodes_payload).encode("utf-8"), 200) ) - self.wfile.write(self._set_response_gzip(_nodes_payload, 200)) elif extracted.startswith("/_cluster/health"): - _cluster_health_payload = bytes( - dumps( - { - "cluster_name": e_cluster_name, - "status": "green", - "timed_out": False, - "number_of_nodes": 1, - "number_of_data_nodes": 1, - "active_primary_shards": 0, - "active_shards": 0, - "relocating_shards": 0, - "initializing_shards": 0, - "unassigned_shards": 0, - "delayed_unassigned_shards": 0, - "number_of_pending_tasks": 0, - "number_of_in_flight_fetch": 0, - "task_max_waiting_in_queue_millis": 0, - "active_shards_percent_as_number": 100.0, - } - ), - "utf-8", - ) - self.wfile.write(self._set_response_gzip(_cluster_health_payload, 200)) + _cluster_health_payload = { + "cluster_name": e_cluster_name, + "status": "green", + "timed_out": False, + "number_of_nodes": 1, + "number_of_data_nodes": 1, + "active_primary_shards": 0, + "active_shards": 0, + "relocating_shards": 0, + "initializing_shards": 0, + "unassigned_shards": 0, + "delayed_unassigned_shards": 0, + "number_of_pending_tasks": 0, + "number_of_in_flight_fetch": 0, + "task_max_waiting_in_queue_millis": 0, + "active_shards_percent_as_number": 100.0, + } + self._send_auth_payload(_cluster_health_payload, 200) elif extracted.startswith("/_"): _index = extracted.split("/")[1].lower() - _payload = bytes( - dumps( - { - "error": { - "root_cause": [ - { - "type": "invalid_index_name_exception", - "reason": f'Invalid index name [{_index}], must not start with "_".', - "index_uuid": "_na_", - "index": _index, - } - ], - "type": "invalid_index_name_exception", - "reason": f'Invalid index name [{_index}], must not start with "_".', - "index_uuid": "_na_", - "index": _index, - }, - "status": 400, - } - ), - "utf-8", - ) - self.wfile.write(self._set_response_gzip(_payload, 400)) + error = { + "type": "invalid_index_name_exception", + "reason": f'Invalid index name [{_index}], must not start with "_".', + "index_uuid": "_na_", + "index": _index, + } + self._send_auth_error(error, 400) else: _search = extracted.split("/")[1].lower() - _search_payload = bytes( - dumps( - { - "error": { - "root_cause": [ - { - "type": "index_not_found_exception", - "reason": f"no such index [{_search}]", - "resource.type": "index_or_alias", - "resource.id": _search, - "index_uuid": "_na_", - "index": _search, - } - ], - "type": "index_not_found_exception", - "reason": f"no such index [{_search}]", - "resource.type": "index_or_alias", - "resource.id": _search, - "index_uuid": "_na_", - "index": _search, - }, - "status": 404, - } - ), - "utf-8", - ) - self.wfile.write(self._set_response_gzip(_search_payload, 404)) + error = { + "type": "index_not_found_exception", + "reason": f"no such index [{_search}]", + "resource.type": "index_or_alias", + "resource.id": _search, + "index_uuid": "_na_", + "index": _search, + } + self._send_auth_error(error, 404) else: authorization_string = self.headers.get("Authorization").split(" ") basic = b64decode(authorization_string[1]).decode("utf-8") @@ -332,42 +268,36 @@ def do_GET(self): "password": password, } ) - auth_paylaod = bytes( - dumps( - { - "error": { - "root_cause": [ - { - "type": "security_exception", - "reason": "missing authentication credentials for REST request [/]", - "header": { - "WWW-Authenticate": 'Basic realm="security" charset="UTF-8"' - }, - } - ], - "type": "security_exception", - "reason": "missing authentication credentials for REST request [/]", - "header": { - "WWW-Authenticate": 'Basic realm="security" charset="UTF-8"' - }, - }, - "status": 401, - } - ), - "utf-8", - ) - self.wfile.write(self._set_response_gzip_auth(auth_paylaod, 401)) + error = { + "type": "security_exception", + "reason": "missing authentication credentials for REST request [/]", + "header": {"WWW-Authenticate": 'Basic realm="security" charset="UTF-8"'}, + } + self._send_auth_error(error, 401) + + def _send_auth_error(self, error: dict, status_code: int): + auth_payload = { + "error": {"root_cause": [error], **error}, + "status": status_code, + } + self._send_auth_payload(auth_payload, status_code) + + def _send_auth_payload(self, payload: dict, status_code: int): + compressed = self._set_response_gzip_auth( + dumps(payload).encode("utf-8"), status_code + ) + self.wfile.write(compressed) - do_POST = do_GET - do_PUT = do_GET - do_DELETE = do_GET + do_POST = do_GET # noqa: N815 + do_PUT = do_GET # noqa: N815 + do_DELETE = do_GET # noqa: N815 - def send_error(self, code, message=None): + def send_error(self, code, message=None, explain=None): self.error_message_format = "Error!" - SimpleHTTPRequestHandler.send_error(self, code, message) + super().send_error(code, message, explain) - def log_message(self, format, *args): - return + def log_message(self, *_, **__): + pass def handle_one_request(self): _q_s.log( @@ -377,13 +307,13 @@ def handle_one_request(self): "src_port": self.client_address[1], } ) - return SimpleHTTPRequestHandler.handle_one_request(self) + return super().handle_one_request() class CustomElasticServer(ThreadingHTTPServer): - key = b64encode(bytes("%s:%s" % ("elastic", "changeme"), "utf-8")).decode("ascii") + key = b64encode(b"elastic:changeme").decode("ascii") - def __init__(self, address, handlerClass=CustomElasticServerHandler): - super().__init__(address, handlerClass) + def __init__(self, address, handler_class=CustomElasticServerHandler): + super().__init__(address, handler_class) def set_auth_key(self, username, password): self.key = b64encode(f"{username}:{password}".encode()).decode("ascii") From d377f69503060c3da0ea79fea20b38989ab7018c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 7 Feb 2024 17:16:56 +0100 Subject: [PATCH 06/35] ftp server: ruff fixes --- honeypots/ftp_server.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/honeypots/ftp_server.py b/honeypots/ftp_server.py index af4b2e5..93835de 100644 --- a/honeypots/ftp_server.py +++ b/honeypots/ftp_server.py @@ -56,26 +56,38 @@ def __init__(self, **kwargs): ) self.temp_folder = TemporaryDirectory() - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self @implementer(portal.IRealm) class CustomFTPRealm: - def __init__(self, anonymousRoot): + def __init__(self, anonymousRoot): # noqa: N803 self.anonymousRoot = filepath.FilePath(anonymousRoot) - def requestAvatar(self, avatarId, mind, *interfaces): + def requestAvatar( # noqa: N802 + self, + avatarId, # noqa: ARG002,N803 + mind, # noqa: ARG002 + *interfaces, + ): for iface in interfaces: if iface is IFTPShell: avatar = FTPAnonymousShell(self.anonymousRoot) - return IFTPShell, avatar, getattr(avatar, "logout", lambda: None) + return ( + IFTPShell, + avatar, + getattr(avatar, "logout", lambda: None), + ) raise NotImplementedError("Only IFTPShell interface is supported by this realm") @implementer(ICredentialsChecker) class CustomAccess: - credentialInterfaces = (credentials.IAnonymous, credentials.IUsernamePassword) + credentialInterfaces = ( # noqa: N815 + credentials.IAnonymous, + credentials.IUsernamePassword, + ) - def requestAvatarId(self, credentials): + def requestAvatarId(self, credentials): # noqa: N802 with suppress(Exception): username = check_bytes(credentials.username) password = check_bytes(credentials.password) @@ -84,7 +96,7 @@ def requestAvatarId(self, credentials): return defer.fail(UnauthorizedLogin()) class CustomFTPProtocol(FTP): - def connectionMade(self): + def connectionMade(self): # noqa: N802 _q_s.log( { "action": "connection", @@ -96,7 +108,7 @@ def connectionMade(self): self.setTimeout(self.timeOut) self.reply("220.2", self.factory.welcomeMessage) - def processCommand(self, cmd, *params): + def processCommand(self, cmd, *params): # noqa: N802 with suppress(Exception): if "capture_commands" in _q_s.options: _q_s.log( @@ -112,7 +124,7 @@ def processCommand(self, cmd, *params): ) return super().processCommand(cmd, *params) - def ftp_PASS(self, password): + def ftp_PASS(self, password): # noqa: N802 username = check_bytes(self._user) password = check_bytes(password) peer = self.transport.getPeer() @@ -127,14 +139,14 @@ def ftp_PASS(self, password): del self._user - def _cbLogin(parsed): + def _cbLogin(parsed): # noqa: N802 self.shell = parsed[1] self.logout = parsed[2] self.workingDirectory = [] self.state = self.AUTHED return reply - def _ebLogin(failure): + def _ebLogin(failure): # noqa: N802 failure.trap(UnauthorizedLogin, UnhandledCredentials) self.state = self.UNAUTH raise AuthorizationError From 10f15cf84748c8c333cb9c41d3e86087140eaf0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 7 Feb 2024 17:28:59 +0100 Subject: [PATCH 07/35] imap server: ruff fixes --- honeypots/imap_server.py | 62 ++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/honeypots/imap_server.py b/honeypots/imap_server.py index bac350a..382a171 100644 --- a/honeypots/imap_server.py +++ b/honeypots/imap_server.py @@ -40,17 +40,16 @@ def __init__(self, **kwargs): [b"OK Microsoft Exchange Server 2003 IMAP4rev1 server version 6.5.6944.0 DC9 ready"] ) - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self class CustomIMAP4Server(IMAP4Server): def parse_command(self, line): args = line.split(None, 2) rest = None - tag = None - if len(args) == 3: + if len(args) == 3: # noqa: PLR2004 tag, cmd, rest = args - elif len(args) == 2: + elif len(args) == 2: # noqa: PLR2004 tag, cmd = args elif len(args) == 1: tag = args[0] @@ -62,20 +61,19 @@ def parse_command(self, line): cmd = cmd.upper() - with suppress(Exception): - if "capture_commands" in _q_s.options: - _q_s.log( - { - "action": "command", - "data": { - "cmd": check_bytes(cmd), - "tag": check_bytes(tag), - "data": check_bytes(rest), - }, - "src_ip": self.transport.getPeer().host, - "src_port": self.transport.getPeer().port, - } - ) + if "capture_commands" in _q_s.options: + _q_s.log( + { + "action": "command", + "data": { + "cmd": check_bytes(cmd), + "tag": check_bytes(tag), + "data": check_bytes(rest), + }, + "src_ip": self.transport.getPeer().host, + "src_port": self.transport.getPeer().port, + } + ) try: return self.dispatchCommand(tag, cmd, rest) @@ -86,7 +84,7 @@ def parse_command(self, line): except IllegalMailboxEncoding as e: self.sendNegativeResponse(tag, "Illegal mailbox name: " + str(e)) - def connectionMade(self): + def connectionMade(self): # noqa: N802 _q_s.log( { "action": "connection", @@ -96,32 +94,28 @@ def connectionMade(self): ) self.sendPositiveResponse(message=_q_s.mocking_server) - def authenticateLogin(self, user, passwd): + def authenticateLogin(self, user, passwd): # noqa: N802 username = check_bytes(user) password = check_bytes(passwd) peer = self.transport.getPeer() _q_s.check_login(username, password, ip=peer.host, port=peer.port) raise cred.error.UnauthorizedLogin() - def lineReceived(self, line): - try: - _line = line.split(b" ")[1] - if _line.lower().startswith(b"login") or _line.lower().startswith( - b"capability" - ): - IMAP4Server.lineReceived(self, line) - except BaseException: - pass + def lineReceived(self, line: bytes): # noqa: N802 + with suppress(IndexError): + _line = line.split(b" ")[1].lower() + if _line.startswith((b"login", b"capability")): + super().lineReceived(line) class CustomIMAPFactory(Factory): protocol = CustomIMAP4Server portal = None - def buildProtocol(self, address): - p = self.protocol() - p.portal = self.portal - p.factory = self - return p + def buildProtocol(self, _): # noqa: N802 + protocol = self.protocol() + protocol.portal = self.portal + protocol.factory = self + return protocol factory = CustomIMAPFactory() reactor.listenTCP(port=self.port, factory=factory, interface=self.ip) From 4d0a738ff6e530c93aa257d9f54902d018c98a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 8 Feb 2024 08:26:43 +0100 Subject: [PATCH 08/35] telnet server: ruff fixes --- honeypots/telnet_server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/honeypots/telnet_server.py b/honeypots/telnet_server.py index 61964e8..3fafa92 100644 --- a/honeypots/telnet_server.py +++ b/honeypots/telnet_server.py @@ -43,7 +43,7 @@ class CustomTelnetProtocol(TelnetProtocol): _user = None _pass = None - def connectionMade(self): + def connectionMade(self): # noqa: N802 self._state = None self._user = None self._pass = None @@ -57,7 +57,7 @@ def connectionMade(self): } ) - def dataReceived(self, data): + def dataReceived(self, data): # noqa: N802 data = data.strip() if self._state == b"Username": self._user = data @@ -72,7 +72,7 @@ def dataReceived(self, data): else: self.transport.loseConnection() - def connectionLost(self, reason): + def connectionLost(self, reason=None): # noqa: N802,ARG002 self._state = None self._user = None self._pass = None From ecc1fdce47d5f2bf5506087f4d793aab444b469d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 8 Feb 2024 09:13:28 +0100 Subject: [PATCH 09/35] socks server: ruff fixes --- honeypots/socks5_server.py | 39 +++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/honeypots/socks5_server.py b/honeypots/socks5_server.py index fba97e2..9c9e297 100644 --- a/honeypots/socks5_server.py +++ b/honeypots/socks5_server.py @@ -20,6 +20,10 @@ check_bytes, ) +USER_PW_AUTH_V1 = 1 +SOCKS_V5 = 5 +AUTH_TYPE_USER_PW = 2 + class QSOCKS5Server(BaseServer): NAME = "socks5_server" @@ -39,16 +43,15 @@ def handle(self): } ) try: - v, m = unpack("!BB", self.connection.recv(2)) - if v == 5: - if 2 in unpack("!" + "B" * m, self.connection.recv(m)): + # see RFC 1928 + version, auth_types_len = unpack("!BB", self.connection.recv(2)) + if version == SOCKS_V5: + supported_auth_methods = unpack( + "!" + "B" * auth_types_len, self.connection.recv(auth_types_len) + ) + if AUTH_TYPE_USER_PW in supported_auth_methods: self.connection.sendall(b"\x05\x02") - if 1 in unpack("B", self.connection.recv(1)): - _len = ord(self.connection.recv(1)) - username = check_bytes(self.connection.recv(_len)) - _len = ord(self.connection.recv(1)) - password = check_bytes(self.connection.recv(_len)) - _q_s.check_login(username, password, src_ip, src_port) + self._check_user_pw_auth(src_ip, src_port) except ConnectionResetError: _q_s.logger.debug( f"[{_q_s.NAME}]: Connection reset error when trying to handle connection" @@ -58,6 +61,16 @@ def handle(self): self.server.close_request(self.request) + def _check_user_pw_auth(self, ip: str, port: int): + # see RFC 1929 + auth_version = unpack("B", self.connection.recv(1))[0] + if auth_version == USER_PW_AUTH_V1: + _len = ord(self.connection.recv(1)) + username = check_bytes(self.connection.recv(_len)) + _len = ord(self.connection.recv(1)) + password = check_bytes(self.connection.recv(_len)) + _q_s.check_login(username, password, ip, port) + class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass @@ -75,10 +88,10 @@ def test_server(self, ip=None, port=None, username=None, password=None): _password = password or self.password get( "https://yahoo.com", - proxies=dict( - http=f"socks5://{_username}:{_password}@{_ip}:{_port}", - https=f"socks5://{_username}:{_password}@{_ip}:{_port}", - ), + proxies={ + "http": f"socks5://{_username}:{_password}@{_ip}:{_port}", + "https": f"socks5://{_username}:{_password}@{_ip}:{_port}", + }, ) From c4fc12599a8c414643e213ca0f91c1c19b35b703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 8 Feb 2024 09:43:42 +0100 Subject: [PATCH 10/35] snmp server: ruff fixes --- honeypots/snmp_server.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/honeypots/snmp_server.py b/honeypots/snmp_server.py index 0b099ba..e0c69e9 100644 --- a/honeypots/snmp_server.py +++ b/honeypots/snmp_server.py @@ -9,13 +9,9 @@ // contributors list qeeqbox/honeypots/graphs/contributors // ------------------------------------------------------------- """ -from contextlib import suppress -from warnings import filterwarnings -from cryptography.utils import CryptographyDeprecationWarning - -filterwarnings(action="ignore", category=CryptographyDeprecationWarning) -from scapy.all import SNMP +from scapy.error import Scapy_Exception +from scapy.layers.snmp import SNMP from twisted.internet import reactor from twisted.internet.protocol import DatagramProtocol @@ -34,17 +30,18 @@ def server_main(self): class CustomDatagramProtocolProtocol(DatagramProtocol): def parse_snmp(self, data): - version = "UnKnown" - community = "UnKnown" - oids = "UnKnown" - with suppress(Exception): + try: parsed_snmp = SNMP(data) community = parsed_snmp.community.val version = parsed_snmp.version.val oids = " ".join([item.oid.val for item in parsed_snmp.PDU.varbindlist]) + except Scapy_Exception: + version = "UnKnown" + community = "UnKnown" + oids = "UnKnown" return version, community, oids - def datagramReceived(self, data, addr): + def datagramReceived(self, data, addr): # noqa: N802 _q_s.log( { "action": "connection", @@ -71,8 +68,8 @@ def datagramReceived(self, data, addr): ) reactor.run() - def test_server(self, ip=None, port=None, username=None, password=None): - with suppress(Exception): + def test_server(self, ip=None, port=None, username=None, password=None): # noqa: ARG002 + try: from pysnmp.hlapi import ( getCmd, SnmpEngine, @@ -92,7 +89,9 @@ def test_server(self, ip=None, port=None, username=None, password=None): ContextData(), ObjectType(ObjectIdentity("1.3.6.1.4.1.9.9.618.1.4.1.0")), ) - errorIndication, errorStatus, errorIndex, varBinds = next(g) + next(g) + except Exception as error: + self.logger.exception(f"Error during test of {self.NAME} server: {error}") if __name__ == "__main__": From 11a96761712f8d7a79b345c0b134d945b33ad78c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 8 Feb 2024 10:41:24 +0100 Subject: [PATCH 11/35] smtp server: hostname leak fix --- honeypots/smtp_server.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/honeypots/smtp_server.py b/honeypots/smtp_server.py index aaae143..085a6fb 100644 --- a/honeypots/smtp_server.py +++ b/honeypots/smtp_server.py @@ -10,6 +10,7 @@ // ------------------------------------------------------------- """ +import socket from asyncore import loop from base64 import b64decode from contextlib import suppress @@ -29,6 +30,17 @@ def server_main(self): _q_s = self class CustomSMTPChannel(SMTPChannel): + def __init__(self, *args, **kwargs): + fun = None + try: + # don't leak the *actual* hostname + fun = socket.getfqdn + socket.getfqdn = lambda: "ip-127-0-0-1.ec2.internal" + super().__init__(*args, **kwargs) + finally: + if fun: + socket.getfqdn = fun + def found_terminator(self): with suppress(Exception): if "capture_commands" in _q_s.options: From fb73a7caf04fc23821a9f4745a131fa881f384f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 8 Feb 2024 10:42:55 +0100 Subject: [PATCH 12/35] smtp server: ruff fixes + depr. warn. fixes --- honeypots/smtp_server.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/honeypots/smtp_server.py b/honeypots/smtp_server.py index 085a6fb..ef6e7b7 100644 --- a/honeypots/smtp_server.py +++ b/honeypots/smtp_server.py @@ -26,7 +26,7 @@ class QSMTPServer(BaseServer): NAME = "smtp_server" DEFAULT_PORT = 25 - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self class CustomSMTPChannel(SMTPChannel): @@ -44,17 +44,11 @@ def __init__(self, *args, **kwargs): def found_terminator(self): with suppress(Exception): if "capture_commands" in _q_s.options: - line = self._emptystring.join(self.received_lines).decode() - arg = None - data = None - if line.find(" ") < 0: - command = line.upper() - else: - command = line.split(" ")[0].upper() - arg = line.split(" ")[1].strip() - if len(line.split(" ")) > 2: - data = line.split(" ", 2)[2] - if command != "HELO" and command != "EHLO": + line = self._emptystring.join(self.received_lines).decode(errors="ignore") + command, *rest = line.split(" ") + arg = rest[0] if rest else None + data = rest[1] if len(rest) > 1 else None + if command.upper() not in {"HELO", "EHLO"}: _q_s.log( { "action": "connection", @@ -65,7 +59,7 @@ def found_terminator(self): ) super().found_terminator() - def smtp_EHLO(self, arg): + def smtp_EHLO(self, arg): # noqa: N802 _q_s.log( { "action": "connection", @@ -75,16 +69,16 @@ def smtp_EHLO(self, arg): ) if not arg: self.push("501 Syntax: HELO hostname") - if self._SMTPChannel__greeting: + if self.seen_greeting: self.push("503 Duplicate HELO/EHLO") else: - self._SMTPChannel__greeting = arg - self.push(f"250-{self._SMTPChannel__fqdn} Hello {arg}") + self.seen_greeting = arg + self.push(f"250-{self.fqdn} Hello {arg}") self.push("250-8BITMIME") self.push("250-AUTH LOGIN PLAIN") self.push("250 STARTTLS") - def smtp_AUTH(self, arg): + def smtp_AUTH(self, arg): # noqa: N802 with suppress(Exception): if arg.startswith("PLAIN "): _, username, password = ( @@ -100,9 +94,7 @@ def __getattr__(self, name): self.smtp_QUIT(0) class CustomSMTPServer(SMTPServer): - def process_message( - self, peer, mailfrom, rcpttos, data, mail_options=None, rcpt_options=None - ): + def process_message(self, *_, **__): return def handle_accept(self): From 44e3fbfde65121ea725e42358e05efef51181b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 8 Feb 2024 13:25:43 +0100 Subject: [PATCH 13/35] smb server: ruff fixes + trace log fix --- honeypots/helper.py | 12 ++++ honeypots/smb_server.py | 135 ++++++++++++++++++---------------------- 2 files changed, 73 insertions(+), 74 deletions(-) diff --git a/honeypots/helper.py b/honeypots/helper.py index 150c65c..f7bc2c4 100644 --- a/honeypots/helper.py +++ b/honeypots/helper.py @@ -12,6 +12,7 @@ from __future__ import annotations import logging +import os import sys from argparse import ArgumentParser from collections.abc import Mapping @@ -751,3 +752,14 @@ def wait_for_service(port: int, interval: float = 0.1, timeout: int = 5.0): def _service_runs(port: int) -> bool: return any(service.laddr.port == port for service in psutil.net_connections()) + + +@contextmanager +def hide_stderr(): + stderr = sys.stderr + try: + with Path(os.devnull).open("w") as devnull: + sys.stderr = devnull + yield + finally: + sys.stderr = stderr diff --git a/honeypots/smb_server.py b/honeypots/smb_server.py index 96b2a06..73e09a7 100644 --- a/honeypots/smb_server.py +++ b/honeypots/smb_server.py @@ -9,15 +9,12 @@ // contributors list qeeqbox/honeypots/graphs/contributors // ------------------------------------------------------------- """ - from contextlib import suppress -from logging import DEBUG, getLogger, StreamHandler -from os import path +from pathlib import Path from random import randint -from shutil import rmtree -from tempfile import mkdtemp +from tempfile import TemporaryDirectory from threading import current_thread -from time import sleep +from unittest.mock import patch from impacket import smbserver from impacket.ntlm import compute_lmhash, compute_nthash @@ -26,6 +23,7 @@ from honeypots.base_server import BaseServer from honeypots.helper import ( server_arguments, + hide_stderr, ) @@ -37,42 +35,9 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.folders = "" - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self - class Logger: - def write(self, message): - temp = current_thread().name - if temp.startswith("thread_"): - ip = temp.split("_")[1] - port = temp.split("_")[2] - if ( - "Incoming connection" in message.strip() - or "AUTHENTICATE_MESSAGE" in message.strip() - or "authenticated successfully" in message.strip() - ): - _q_s.log( - { - "action": "connection", - "data": message.strip(), - "src_ip": ip, - "src_port": port, - } - ) - elif ":aaaaaaaaaaaaaaaa:" in message.strip(): - parsed = message.strip().split(":") - if len(parsed) == 6: - username, _, _, _, nt_res_1, nt_res_2 = parsed - _q_s.log( - { - "action": "login", - "username": username, - "src_ip": ip, - "src_port": port, - "data": {"nt_data_1": nt_res_1, "nt_data_2": nt_res_2}, - } - ) - class SMBSERVERHandler(smbserver.SMBSERVERHandler): def __init__(self, request, client_address, server, select_poll=False): self.__SMB = server @@ -84,51 +49,73 @@ def __init__(self, request, client_address, server, select_poll=False): current_thread().name = self.__connId socketserver.BaseRequestHandler.__init__(self, request, client_address, server) - class SMBSERVER(smbserver.SMBSERVER): + class SMBServer(smbserver.SMBSERVER): def __init__(self, server_address, handler_class=SMBSERVERHandler, config_parser=None): super().__init__(server_address, handler_class, config_parser) - def processRequest(self, connId, data): - x = super().processRequest(connId, data) - return x + def processRequest(self, connId, data): # noqa: N802,N803 + # hide trace logging from smbserver module + with hide_stderr(): + return super().processRequest(connId, data) + + def log(self, msg, level=None): # noqa: ARG002 + temp = current_thread().name + if not temp.startswith("thread_") or temp.count("_") < 2: # noqa: PLR2004 + return + _, ip, port, *_ = temp.split("_") + message = msg.strip() + if ( + "Incoming connection" in message + or "AUTHENTICATE_MESSAGE" in message + or "authenticated successfully" in message + ): + _q_s.log( + { + "action": "connection", + "data": message, + "src_ip": ip, + "src_port": port, + } + ) + elif ":aaaaaaaaaaaaaaaa:" in message: + with suppress(ValueError): + username, _, _, _, nt_res_1, nt_res_2 = message.split(":") + _q_s.log( + { + "action": "login", + "username": username, + "src_ip": ip, + "src_port": port, + "data": {"nt_data_1": nt_res_1, "nt_data_2": nt_res_2}, + } + ) class SimpleSMBServer(smbserver.SimpleSMBServer): - def __init__(self, listenAddress="0.0.0.0", listenPort=445, configFile=""): - super().__init__(listenAddress, listenPort, configFile) - self.__server.server_close() - sleep(randint(1, 2)) - self.__server = SMBSERVER( - (listenAddress, listenPort), config_parser=self.__smbConfig - ) - self.__server.processConfigFile() + def __init__(self, listenAddress="0.0.0.0", listenPort=445, configFile=""): # noqa: N803 + with patch("impacket.smbserver.SMBSERVER", SMBServer): + super().__init__(listenAddress, listenPort, configFile) def start(self): self.__srvsServer.start() self.__wkstServer.start() self.__server.serve_forever() - handler = StreamHandler(Logger()) - getLogger("impacket").addHandler(handler) - getLogger("impacket").setLevel(DEBUG) - - dirpath = mkdtemp() - server = SimpleSMBServer(listenAddress=self.ip, listenPort=self.port) - # server.removeShare('IPC$') - if self.folders == "" or self.folders is None: - server.addShare("C$", dirpath, "", readOnly="yes") - else: - for folder in self.folders.split(","): - name, d = folder.split(":") - if path.isdir(d) and len(name) > 0: - server.addShare(name, d, "", readOnly="yes") - - server.setSMB2Support(True) - server.addCredential( - self.username, 0, compute_lmhash(self.password), compute_nthash(self.password) - ) - server.setSMBChallenge("") - server.start() - rmtree(dirpath) + with TemporaryDirectory() as tmpdir: + server = SimpleSMBServer(listenAddress=self.ip, listenPort=self.port) + if self.folders == "" or self.folders is None: + server.addShare("C$", tmpdir, "", readOnly="yes") + else: + for folder in self.folders.split(","): + name, path = folder.split(":") + if Path(path).is_dir() and len(name) > 0: + server.addShare(name, path, "", readOnly="yes") + + server.setSMB2Support(True) + server.addCredential( + self.username, 0, compute_lmhash(self.password), compute_nthash(self.password) + ) + server.setSMBChallenge("") + server.start() def test_server(self, ip=None, port=None, username=None, password=None): with suppress(Exception): From 18bb532dd0e5a59881b0b9c9e3458dea2c78c626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 8 Feb 2024 13:29:06 +0100 Subject: [PATCH 14/35] smtp test fix --- tests/test_smtp_server.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_smtp_server.py b/tests/test_smtp_server.py index e1d3ac0..14012a4 100644 --- a/tests/test_smtp_server.py +++ b/tests/test_smtp_server.py @@ -25,11 +25,11 @@ } } EXPECTED_DATA = [ - {"arg": "FROM:", "command": "MAIL", "data": "None"}, - {"arg": "TO:", "command": "RCPT", "data": "None"}, - {"arg": "None", "command": "DATA", "data": "None"}, - {"arg": "None", "command": "NOTHING", "data": "None"}, - {"arg": "None", "command": "QUIT", "data": "None"}, + {"arg": "FROM:", "command": "mail", "data": "None"}, + {"arg": "TO:", "command": "rcpt", "data": "None"}, + {"arg": "None", "command": "data", "data": "None"}, + {"arg": "None", "command": "Nothing", "data": "None"}, + {"arg": "None", "command": "quit", "data": "None"}, ] From ac9243ea5b5c51f5f3f59920e6501a6ef941707a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 8 Feb 2024 13:31:09 +0100 Subject: [PATCH 15/35] sip server: ruff fixes --- honeypots/sip_server.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/honeypots/sip_server.py b/honeypots/sip_server.py index 87157d5..c3bdc34 100644 --- a/honeypots/sip_server.py +++ b/honeypots/sip_server.py @@ -69,7 +69,14 @@ def test_server(self, ip=None, port=None, username=None, password=None): _password = password or self.password sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) sock.sendto( - b"INVITE sip:user_1@test.test SIP/2.0\r\nTo: \r\nFrom: sip:user_3@test.test.test;tag=none\r\nCall-ID: 1@0.0.0.0\r\nCSeq: 1 INVITE\r\nContact: sip:user_3@test.test.test\r\nVia: SIP/2.0/TCP 0.0.0.0;branch=34uiddhjczqw3mq23\r\nContent-Length: 1\r\n\r\nT", + b"INVITE sip:user_1@test.test SIP/2.0\r\n" + b"To: \r\n" + b"From: sip:user_3@test.test.test;tag=none\r\n" + b"Call-ID: 1@0.0.0.0\r\n" + b"CSeq: 1 INVITE\r\n" + b"Contact: sip:user_3@test.test.test\r\n" + b"Via: SIP/2.0/TCP 0.0.0.0;branch=34uiddhjczqw3mq23\r\n" + b"Content-Length: 1\r\n\r\nT", (_ip, _port), ) sock.close() From e3084979de1ccfe34493c7f5c5f661a458278be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 8 Feb 2024 14:30:46 +0100 Subject: [PATCH 16/35] redis server: ruff fixes --- honeypots/redis_server.py | 46 ++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/honeypots/redis_server.py b/honeypots/redis_server.py index 70c9d8d..ece17d6 100644 --- a/honeypots/redis_server.py +++ b/honeypots/redis_server.py @@ -9,6 +9,7 @@ // contributors list qeeqbox/honeypots/graphs/contributors // ------------------------------------------------------------- """ +from __future__ import annotations from contextlib import suppress @@ -26,7 +27,7 @@ class QRedisServer(BaseServer): NAME = "redis_server" DEFAULT_PORT = 6379 - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self class CustomRedisProtocol(Protocol): @@ -36,34 +37,35 @@ def get_command(self, data): if _data[0][0] == "*": _count = int(_data[0][1]) - 1 _data.pop(0) - if _data[0::2][0][0] == "$" and len(_data[1::2][0]) == int( - _data[0::2][0][1] - ): + command = self._parse_field(_data, 0) + if command: return _count, _data[1::2][0] return 0, "" - def parse_data(self, c, data): + def parse_data(self, count: int, data: bytes): _data = data.decode("utf-8").split("\r\n")[3::] username, password = "", "" - if c == 2: - _ = 0 - if _data[0::2][_][0] == "$" and len(_data[1::2][_]) == int(_data[0::2][_][1]): - username = _data[1::2][_] - _ = 1 - if _data[0::2][_][0] == "$" and len(_data[1::2][_]) == int(_data[0::2][_][1]): - password = _data[1::2][_] - if c == 1: - _ = 0 - if _data[0::2][_][0] == "$" and len(_data[1::2][_]) == int(_data[0::2][_][1]): - password = _data[1::2][_] - if c == 2 or c == 1: + if count == 2: # noqa: PLR2004 + username = self._parse_field(_data, 0) + password = self._parse_field(_data, 1) + elif count == 1: + password = self._parse_field(_data, 0) + if count in {1, 2}: peer = self.transport.getPeer() _q_s.check_login( check_bytes(username), check_bytes(password), ip=peer.host, port=peer.port ) - def connectionMade(self): + @staticmethod + def _parse_field(str_list: list[str], index: int) -> str: + if str_list[0::2][index][0] == "$" and len(str_list[1::2][index]) == int( + str_list[0::2][index][1] + ): + return str_list[1::2][index] + return "" + + def connectionMade(self): # noqa: N802 self._state = 1 self._variables = {} _q_s.log( @@ -74,10 +76,10 @@ def connectionMade(self): } ) - def dataReceived(self, data): - c, command = self.get_command(data) + def dataReceived(self, data: bytes): # noqa: N802 + count, command = self.get_command(data) if command == "AUTH": - self.parse_data(c, data) + self.parse_data(count, data) self.transport.write(b"-ERR invalid password\r\n") else: self.transport.write(f'-ERR unknown command "{command}"\r\n'.encode()) @@ -97,7 +99,7 @@ def test_server(self, ip=None, port=None, username=None, password=None): _username = username or self.username _password = password or self.password r = StrictRedis.from_url(f"redis://{_username}:{_password}@{_ip}:{_port}/1") - for key in r.scan_iter("user:*"): + for _ in r.scan_iter("user:*"): pass From ccec2fa0317c8d61948d4368e1900ca4fcd12302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 8 Feb 2024 16:01:13 +0100 Subject: [PATCH 17/35] rdp server: ruff fixes --- honeypots/rdp_server.py | 71 +++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/honeypots/rdp_server.py b/honeypots/rdp_server.py index be5698f..ea553d8 100644 --- a/honeypots/rdp_server.py +++ b/honeypots/rdp_server.py @@ -28,15 +28,12 @@ class QRDPServer(BaseServer): NAME = "rdp_server" DEFAULT_PORT = 3389 - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self class ConnectionHandle(Thread): def __init__(self, sock, key, cert): - super(ConnectionHandle, self).__init__() + super().__init__() self.sock = sock self.key = key self.cert = cert @@ -44,12 +41,12 @@ def __init__(self, sock, key, cert): def get_value(self, length, data): with suppress(Exception): var = b"" - for idx, _ in enumerate(data): - if _ == 0 and data[idx + 1] == 0: + for idx, byte in enumerate(data): + if byte == 0 and data[idx + 1] == 0: break - if _ == 0: + if byte == 0: continue - var += bytes([_]) + var += bytes([byte]) if length / 2 == len(var): return var return b"" @@ -60,34 +57,27 @@ def extract_cookie(self, data: bytes) -> bytes: def extract_creds(self, data: bytes): with suppress(Exception): ( - flag, - flags, - code_page, - option_flags, domain_length, user_length, password_length, shell_length, working_dir_length, - ) = unpack("HHIIHHHHH", data[15:37]) + ) = unpack("HHHHH", data[27:37]) location = 37 - domain = self.get_value(domain_length, data[location:]) - location = location + domain_length + 2 + _ = self.get_value(domain_length, data[location:]) + location += domain_length + 2 user = self.get_value(user_length, data[location:]) - location = location + user_length + 2 + location += user_length + 2 password = self.get_value(password_length, data[location:]) - location = location + password_length + 2 - shell = self.get_value(shell_length, data[location:]) - location = location + shell_length + 2 - working_dir = self.get_value(working_dir_length, data[location:]) + location += password_length + 2 + _ = self.get_value(shell_length, data[location:]) + location += shell_length + 2 + _ = self.get_value(working_dir_length, data[location:]) return user, password def run(self): - # There is no good documentation on how RDP protocol works (It took a bit of time to figure it out - Use b1105eb1-d1f7-414b-ad68-fd0c5a7823e4 test case) - cookie = "" - rdpdr = False - cliprdr = False - rdpsnd = False + # There is no good documentation on how RDP protocol works (It took a bit of time + # to figure it out - Use b1105eb1-d1f7-414b-ad68-fd0c5a7823e4 test case) initiator = b"\x00\x06" with suppress(Exception): _q_s.log( @@ -132,19 +122,15 @@ def run(self): data = self.sock.recv(1024) - if b"rdpdr" in data: - rdpdr = True - if b"cliprdr" in data: - cliprdr = True - if b"rdpsnd" in data: - rdpsnd = True - # MCS Connect Response PDU with GCC Conference Create Response # \x03\x00\x00 # \x7c - # \x02\xf0\x80\x7f\x66\x74\x0a\x01\x00\x02\x01\x00\x30\x1a\x02\x01\x22\x02\x01\x03\x02\x01\x00\x02\x01\x01\x02\x01\x00\x02\x01\x01\x02\x03\x00\xff\xf8\x02\x01\x02\x04 + # \x02\xf0\x80\x7f\x66\x74\x0a\x01\x00\x02\x01\x00\x30\x1a\x02\x01\x22\x02 + # \x01\x03\x02\x01\x00\x02\x01\x01\x02\x01\x00\x02\x01\x01\x02\x03\x00\xff + # \xf8\x02\x01\x02\x04 # \x4e - # \x00\x05\x00\x14\x7c\x00\x01\x2a\x14\x76\x0a\x01\x01\x00\x01\xc0\x00\x4d\x63\x44\x6e + # \x00\x05\x00\x14\x7c\x00\x01\x2a\x14\x76\x0a\x01\x01\x00\x01\xc0\x00\x4d + # \x63\x44\x6e # \x38 # \x01\x0c SC_CORE # \x0e\x00\x04\x00\x08\x00\x03\x00\x00\x00\x03\x00 @@ -162,7 +148,14 @@ def run(self): # \x08\x00\x00\x00\x00\x00 self.sock.send( - b"\x03\x00\x00\x7c\x02\xf0\x80\x7f\x66\x74\x0a\x01\x00\x02\x01\x00\x30\x1a\x02\x01\x22\x02\x01\x03\x02\x01\x00\x02\x01\x01\x02\x01\x00\x02\x01\x01\x02\x03\x00\xff\xf8\x02\x01\x02\x04\x4e\x00\x05\x00\x14\x7c\x00\x01\x2a\x14\x76\x0a\x01\x01\x00\x01\xc0\x00\x4d\x63\x44\x6e\x38\x01\x0c\x0e\x00\x04\x00\x08\x00\x03\x00\x00\x00\x03\x00\x02\x0c\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x0c\x10\x00\xeb\x03\x04\x00\xec\x03\xed\x03\xee\x03\xef\x03\x04\x0c\x06\x00\xf0\x03\x08\x0c\x08\x00\x00\x00\x00\x00" + b"\x03\x00\x00\x7c\x02\xf0\x80\x7f\x66\x74\x0a\x01\x00\x02\x01\x00\x30" + b"\x1a\x02\x01\x22\x02\x01\x03\x02\x01\x00\x02\x01\x01\x02\x01\x00\x02" + b"\x01\x01\x02\x03\x00\xff\xf8\x02\x01\x02\x04\x4e\x00\x05\x00\x14\x7c" + b"\x00\x01\x2a\x14\x76\x0a\x01\x01\x00\x01\xc0\x00\x4d\x63\x44\x6e\x38" + b"\x01\x0c\x0e\x00\x04\x00\x08\x00\x03\x00\x00\x00\x03\x00\x02\x0c\x0c" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x0c\x10\x00\xeb\x03\x04\x00" + b"\xec\x03\xed\x03\xee\x03\xef\x03\x04\x0c\x06\x00\xf0\x03\x08\x0c\x08" + b"\x00\x00\x00\x00\x00" ) data = self.sock.recv(1024) @@ -178,10 +171,10 @@ def run(self): with suppress(Exception): # 7 times + 1 - for i in range(8): + for _ in range(8): data = self.sock.recv(1024) - if len(data) > 14: - if data[15] == 64: + if len(data) > 14: # noqa: PLR2004 + if data[15] == 64: # noqa: PLR2004 username, password = self.extract_creds(data) peer = self.sock.getpeername() _q_s.check_login( From 72cce96a329540a182325fee9fff133c529332a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Tue, 13 Feb 2024 10:03:30 +0100 Subject: [PATCH 18/35] ipp server: refactoring + ruff fixes --- honeypots/ipp_server.py | 477 +++++++++++++++++++++------------------- 1 file changed, 248 insertions(+), 229 deletions(-) diff --git a/honeypots/ipp_server.py b/honeypots/ipp_server.py index 80cc967..3a936ee 100644 --- a/honeypots/ipp_server.py +++ b/honeypots/ipp_server.py @@ -12,7 +12,7 @@ from __future__ import annotations from contextlib import suppress -from struct import unpack +import struct from twisted.internet import reactor from twisted.web.resource import Resource @@ -25,6 +25,153 @@ get_headers_and_ip_from_request, ) +SUPPORTED_OPERATIONS = { + 0x0001: "Reserved", + 0x0002: "Print-Job", + 0x0003: "Print-URI", + 0x0004: "Validate-Job", + 0x0005: "Create-Job", + 0x0006: "Send-Document", + 0x0007: "Send-URI", + 0x0008: "Cancel-Job", + 0x0009: "Get-Job-Attributes", + 0x000A: "Get-Jobs", + 0x000B: "Get-Printer-Attributes", + 0x000C: "Hold-Job", + 0x000D: "Release-Job", + 0x000E: "Restart-Job", + 0x000F: "Reserved", + 0x0010: "Pause-Printer", + 0x0011: "Resume-Printer", + 0x0012: "Purge-Jobs", + 0x0013: "Set-Printer-Attributes", + 0x0014: "Set-Job-Attributes", + 0x0015: "Get-Printer-Supported-Values", + 0x0016: "Create-Printer-Subscriptions", + 0x0017: "Create-Job-Subscriptions", + 0x0018: "Get-Subscription-Attributes", + 0x0019: "Get-Subscriptions", + 0x001A: "Renew-Subscription", + 0x001B: "Cancel-Subscription", + 0x001C: "Get-Notifications", + 0x001D: "ipp-indp-method", + 0x001E: "Get-Resource-Attributes", + 0x001F: "ipp-get-resources", + 0x0020: "Get-Resources", + 0x0021: "ipp-install", + 0x0022: "Enable-Printer", + 0x0023: "Disable-Printer", + 0x0024: "Pause-Printer-After-Current-Job", + 0x0025: "Hold-New-Jobs", + 0x0026: "Release-Held-New-Jobs", + 0x0027: "Deactivate-Printer", + 0x0028: "Activate-Printer", + 0x0029: "Restart-Printer", + 0x002A: "Shutdown-Printer", + 0x002B: "Startup-Printer", + 0x002C: "Reprocess-Job", + 0x002D: "Cancel-Current-Job", + 0x002E: "Suspend-Current-Job", + 0x002F: "Resume-Job", + 0x0030: "Promote-Job", + 0x0031: "Schedule-Job-After", + 0x0033: "Cancel-Document", + 0x0034: "Get-Document-Attributes", + 0x0035: "Get-Documents", + 0x0036: "Delete-Document", + 0x0037: "Set-Document-Attributes", + 0x0038: "Cancel-Jobs", + 0x0039: "Cancel-My-Jobs", + 0x003A: "Resubmit-Job", + 0x003B: "Close-Job", + 0x003C: "Identify-Printer", + 0x003D: "Validate-Document", + 0x003E: "Add-Document-Images", + 0x003F: "Acknowledge-Document", + 0x0040: "Acknowledge-Identify-Printer", + 0x0041: "Acknowledge-Job", + 0x0042: "Fetch-Document", + 0x0043: "Fetch-Job", + 0x0044: "Get-Output-Device-Attributes", + 0x0045: "Update-Active-Jobs", + 0x0046: "Deregister-Output-Device", + 0x0047: "Update-Document-Status", + 0x0048: "Update-Job-Status", + 0x0049: "Update-Output-Device-Attributes", + 0x004A: "Get-Next-Document-Data", + 0x004B: "Allocate-Printer-Resources", + 0x004C: "Create-Printer", + 0x004D: "Deallocate-Printer-Resources", + 0x004E: "Delete-Printer", + 0x004F: "Get-Printers", + 0x0050: "Shutdown-One-Printer", + 0x0051: "Startup-One-Printer", + 0x0052: "Cancel-Resource", + 0x0053: "Create-Resource", + 0x0054: "Install-Resource", + 0x0055: "Send-Resource-Data", + 0x0056: "Set-Resource-Attributes", + 0x0057: "Create-Resource-Subscriptions", + 0x0058: "Create-System-Subscriptions", + 0x0059: "Disable-All-Printers", + 0x005A: "Enable-All-Printers", + 0x005B: "Get-System-Attributes", + 0x005C: "Get-System-Supported-Values", + 0x005D: "Pause-All-Printers", + 0x005E: "Pause-All-Printers-After-Current-Job", + 0x005F: "Register-Output-Device", + 0x0060: "Restart-System", + 0x0061: "Resume-All-Printers", + 0x0062: "Set-System-Attributes", + 0x0063: "Shutdown-All-Printers", + 0x0064: "Startup-All-Printers", + 0x0065: "Get-Printer-Resources", + 0x0066: "Get-User-Printer-Attributes", + 0x0067: "Restart-One-Printer", +} +ATTRIBUTE_GROUP_TAGS = { + 0x00: "Reserved", + 0x01: "operation-attributes-tag", + 0x02: "job-attributes-tag", + 0x03: "end-of-attributes-tag", + 0x04: "printer-attributes-tag", + 0x05: "unsupported-attributes-tag", + 0x06: "subscription-attributes-tag", + 0x07: "event-notification-attributes-tag", + 0x08: "resource-attributes-tag", + 0x09: "document-attributes-tag", + 0x0A: "system-attributes-tag", +} +ATTRIBUTE_SYNTAXES = { + 0x20: "Unassigned", + 0x21: "integer", + 0x22: "boolean", + 0x23: "enum", + 0x30: "octetString", + 0x31: "dateTime", + 0x32: "resolution", + 0x33: "rangeOfInteger", + 0x34: "begCollection", + 0x35: "textWithLanguage", + 0x36: "nameWithLanguage", + 0x37: "endCollection", + 0x40: "Unassigned", + 0x41: "textWithoutLanguage", + 0x42: "nameWithoutLanguage", + 0x43: "Unassigned", + 0x44: "keyword", + 0x45: "uri", + 0x46: "uriScheme", + 0x47: "charset", + 0x48: "naturalLanguage", + 0x49: "mimeMediaType", + 0x4A: "memberAttrName", + 0x7F: "extension", +} +ATTRIBUTE_NAME_TO_VALUE_TAG = { + "attributes-charset": b"\x47", + "attributes-natural-language": b"\x48", +} STATUS_CODE_OK = b"\x00\x00" STATUS_CODE_BAD_REQUEST = b"\x04\x00" @@ -33,239 +180,28 @@ class QIPPServer(BaseServer): NAME = "ipp_server" DEFAULT_PORT = 631 - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self class MainResource(Resource): - isLeaf = True - operations_supported = { - 0x0001: "Reserved", - 0x0002: "Print-Job", - 0x0003: "Print-URI", - 0x0004: "Validate-Job", - 0x0005: "Create-Job", - 0x0006: "Send-Document", - 0x0007: "Send-URI", - 0x0008: "Cancel-Job", - 0x0009: "Get-Job-Attributes", - 0x000A: "Get-Jobs", - 0x000B: "Get-Printer-Attributes", - 0x000C: "Hold-Job", - 0x000D: "Release-Job", - 0x000E: "Restart-Job", - 0x000F: "Reserved", - 0x0010: "Pause-Printer", - 0x0011: "Resume-Printer", - 0x0012: "Purge-Jobs", - 0x0013: "Set-Printer-Attributes", - 0x0014: "Set-Job-Attributes", - 0x0015: "Get-Printer-Supported-Values", - 0x0016: "Create-Printer-Subscriptions", - 0x0017: "Create-Job-Subscriptions", - 0x0018: "Get-Subscription-Attributes", - 0x0019: "Get-Subscriptions", - 0x001A: "Renew-Subscription", - 0x001B: "Cancel-Subscription", - 0x001C: "Get-Notifications", - 0x001D: "ipp-indp-method", - 0x001E: "Get-Resource-Attributes", - 0x001F: "ipp-get-resources", - 0x0020: "Get-Resources", - 0x0021: "ipp-install", - 0x0022: "Enable-Printer", - 0x0023: "Disable-Printer", - 0x0024: "Pause-Printer-After-Current-Job", - 0x0025: "Hold-New-Jobs", - 0x0026: "Release-Held-New-Jobs", - 0x0027: "Deactivate-Printer", - 0x0028: "Activate-Printer", - 0x0029: "Restart-Printer", - 0x002A: "Shutdown-Printer", - 0x002B: "Startup-Printer", - 0x002C: "Reprocess-Job", - 0x002D: "Cancel-Current-Job", - 0x002E: "Suspend-Current-Job", - 0x002F: "Resume-Job", - 0x0030: "Promote-Job", - 0x0031: "Schedule-Job-After", - 0x0033: "Cancel-Document", - 0x0034: "Get-Document-Attributes", - 0x0035: "Get-Documents", - 0x0036: "Delete-Document", - 0x0037: "Set-Document-Attributes", - 0x0038: "Cancel-Jobs", - 0x0039: "Cancel-My-Jobs", - 0x003A: "Resubmit-Job", - 0x003B: "Close-Job", - 0x003C: "Identify-Printer", - 0x003D: "Validate-Document", - 0x003E: "Add-Document-Images", - 0x003F: "Acknowledge-Document", - 0x0040: "Acknowledge-Identify-Printer", - 0x0041: "Acknowledge-Job", - 0x0042: "Fetch-Document", - 0x0043: "Fetch-Job", - 0x0044: "Get-Output-Device-Attributes", - 0x0045: "Update-Active-Jobs", - 0x0046: "Deregister-Output-Device", - 0x0047: "Update-Document-Status", - 0x0048: "Update-Job-Status", - 0x0049: "Update-Output-Device-Attributes", - 0x004A: "Get-Next-Document-Data", - 0x004B: "Allocate-Printer-Resources", - 0x004C: "Create-Printer", - 0x004D: "Deallocate-Printer-Resources", - 0x004E: "Delete-Printer", - 0x004F: "Get-Printers", - 0x0050: "Shutdown-One-Printer", - 0x0051: "Startup-One-Printer", - 0x0052: "Cancel-Resource", - 0x0053: "Create-Resource", - 0x0054: "Install-Resource", - 0x0055: "Send-Resource-Data", - 0x0056: "Set-Resource-Attributes", - 0x0057: "Create-Resource-Subscriptions", - 0x0058: "Create-System-Subscriptions", - 0x0059: "Disable-All-Printers", - 0x005A: "Enable-All-Printers", - 0x005B: "Get-System-Attributes", - 0x005C: "Get-System-Supported-Values", - 0x005D: "Pause-All-Printers", - 0x005E: "Pause-All-Printers-After-Current-Job", - 0x005F: "Register-Output-Device", - 0x0060: "Restart-System", - 0x0061: "Resume-All-Printers", - 0x0062: "Set-System-Attributes", - 0x0063: "Shutdown-All-Printers", - 0x0064: "Startup-All-Printers", - 0x0065: "Get-Printer-Resources", - 0x0066: "Get-User-Printer-Attributes", - 0x0067: "Restart-One-Printer", - } - - attribute_group_tags = { - 0x00: "Reserved", - 0x01: "operation-attributes-tag", - 0x02: "job-attributes-tag", - 0x03: "end-of-attributes-tag", - 0x04: "printer-attributes-tag", - 0x05: "unsupported-attributes-tag", - 0x06: "subscription-attributes-tag", - 0x07: "event-notification-attributes-tag", - 0x08: "resource-attributes-tag", - 0x09: "document-attributes-tag", - 0x0A: "system-attributes-tag", - } - - attribute_syntaxes = { - 0x20: "Unassigned", - 0x21: "integer", - 0x22: "boolean", - 0x23: "enum", - 0x30: "octetString", - 0x31: "dateTime", - 0x32: "resolution", - 0x33: "rangeOfInteger", - 0x34: "begCollection", - 0x35: "textWithLanguage", - 0x36: "nameWithLanguage", - 0x37: "endCollection", - 0x40: "Unassigned", - 0x41: "textWithoutLanguage", - 0x42: "nameWithoutLanguage", - 0x43: "Unassigned", - 0x44: "keyword", - 0x45: "uri", - 0x46: "uriScheme", - 0x47: "charset", - 0x48: "naturalLanguage", - 0x49: "mimeMediaType", - 0x4A: "memberAttrName", - 0x7F: "extension", - } - - def get_uint8_t(self, index, data): - return index + 1, unpack("b", data[index : index + 1])[0] - - def get_uint16_t(self, index, data): - return index + 2, unpack(">H", data[index : index + 2])[0] - - def get_uint32_t(self, index, data): - return index + 4, unpack(">I", data[index : index + 4])[0] - - def get_string(self, index, length, data): - return index + length, data[index : index + length] - - def render_POST(self, request): + isLeaf = True # noqa: N815 + + def render_POST(self, request): # noqa: N802 client_ip, headers = get_headers_and_ip_from_request(request, _q_s.options) - with suppress(Exception): - log_data = { - "action": "connection", - "src_ip": client_ip, - "src_port": request.getClientAddress().port, - } - if "capture_commands" in _q_s.options: - log_data["data"] = headers - _q_s.log(log_data) + log_data = { + "action": "connection", + "src_ip": client_ip, + "src_port": request.getClientAddress().port, + } + if "capture_commands" in _q_s.options: + log_data["data"] = headers + _q_s.log(log_data) data = request.content.read() - response = "" - version = [0, 0] - groups = [] - groups_parsed = "" - status = "success" + response, status = self._build_response(data) - with suppress(Exception): - index, version[0] = self.get_uint8_t(0, data) - index, version[1] = self.get_uint8_t(index, data) - index, uint16_t = self.get_uint16_t(index, data) - operation = self.operations_supported[uint16_t] - index, request_id = self.get_uint32_t(index, data) - index, uint8_t = self.get_uint8_t(index, data) - group = self.attribute_group_tags[uint8_t] - index, uint8_t = self.get_uint8_t(index, data) - to_parse_len = len(data[index:]) - if uint8_t in self.attribute_syntaxes: - while index < to_parse_len: - try: - value = "" - if self.attribute_syntaxes[uint8_t] == "integer": - index, attribute = self.get_uint32_t(index, data) - elif self.attribute_syntaxes[uint8_t] == "boolean": - index, attribute = self.get_uint8_t(index, data) - else: - index, uint16_t = self.get_uint16_t(index, data) - index, attribute = self.get_string(index, uint16_t, data) - index, uint16_t = self.get_uint16_t(index, data) - index, value = self.get_string(index, uint16_t, data) - if attribute == b"": - groups[-1][1].append(check_bytes(value)) - else: - groups.append([check_bytes(attribute), [check_bytes(value)]]) - index, uint8_t = self.get_uint8_t(index, data) - - if uint8_t in self.attribute_group_tags: - break - except BaseException: - status = "failed" - break - - with suppress(Exception): - response += "" - response = f"VERSION {version[0]}.{version[1]}|" - response += f"REQUEST {hex(request_id)}|" - response += f"OPERATION {operation}|" - response += f"GROUP {group}|" - if len(groups) > 0: - for i in groups: - groups_parsed += "ATTR " + i[0] + " " + ",".join(i[1]) + "|" - groups_parsed = groups_parsed.strip() - response += groups_parsed - with suppress(Exception): - if response[-1] == "|": - response = response[0:-1] if len(response) > 0: _q_s.log( { @@ -278,6 +214,79 @@ def render_POST(self, request): ) return self.send_response(data, status != "failed") + def _build_response(self, data: bytes) -> tuple[str, str]: + status = "success" + version = [0, 0] + groups = [] + + try: + index, version[0] = get_uint8_t(0, data) + index, version[1] = get_uint8_t(index, data) + index, uint16_t = get_uint16_t(index, data) + operation = SUPPORTED_OPERATIONS[uint16_t] + index, request_id = get_uint32_t(index, data) + index, uint8_t = get_uint8_t(index, data) + group = ATTRIBUTE_GROUP_TAGS[uint8_t] + index, uint8_t = get_uint8_t(index, data) + if uint8_t in ATTRIBUTE_SYNTAXES: + groups, status = self._parse_attributes(data, index, uint8_t) + + response = ( + f"VERSION {version[0]}.{version[1]}|" + f"REQUEST {hex(request_id)}|" + f"OPERATION {operation}|" + f"GROUP {group}|" + ) + if len(groups) > 0: + for attribute, values in groups: + response += f"ATTR {attribute} {','.join(values)}|" + with suppress(IndexError): + if response[-1] == "|": + response = response[:-1] + except Exception as error: + _q_s.logger.debug( + f"[{_q_s.NAME}]: An error occurred during data parsing: {error}", + exc_info=True, + ) + response = "" + return response, status + + @staticmethod + def _parse_attributes( + data: bytes, index: int, attr_type: int + ) -> tuple[list[tuple[str, list[str]]], str]: + status = "success" + groups = [] + to_parse_len = len(data[index:]) + while index < to_parse_len: + try: + value = "" + if ATTRIBUTE_SYNTAXES[attr_type] == "integer": + index, attribute = get_uint32_t(index, data) + elif ATTRIBUTE_SYNTAXES[attr_type] == "boolean": + index, attribute = get_uint8_t(index, data) + else: + index, uint16_t = get_uint16_t(index, data) + index, attribute = get_string(index, uint16_t, data) + index, uint16_t = get_uint16_t(index, data) + index, value = get_string(index, uint16_t, data) + if attribute == b"": + groups[-1][1].append(check_bytes(value)) + else: + groups.append((check_bytes(attribute), [check_bytes(value)])) + index, attr_type = get_uint8_t(index, data) + + if attr_type in ATTRIBUTE_GROUP_TAGS: + break + except (KeyError, IndexError, struct.error) as error: + _q_s.logger.debug( + f"[{_q_s.NAME}]: Error while parsing attributes: {error}", + exc_info=True, + ) + status = "failed" + break + return groups, status + @staticmethod def send_response(request: bytes, successful: bool) -> bytes: version, request_id = request[0:2], request[3:7] @@ -319,10 +328,20 @@ def test_server(self, ip=None, port=None): s.sendall(headers.encode() + body) -ATTRIBUTE_NAME_TO_VALUE_TAG = { - "attributes-charset": b"\x47", - "attributes-natural-language": b"\x48", -} +def get_uint8_t(index, data): + return index + 1, struct.unpack("b", data[index : index + 1])[0] + + +def get_uint16_t(index, data): + return index + 2, struct.unpack(">H", data[index : index + 2])[0] + + +def get_uint32_t(index, data): + return index + 4, struct.unpack(">I", data[index : index + 4])[0] + + +def get_string(index, length, data): + return index + length, data[index : index + length] def attributes_dict_to_bytes(attributes: dict[str, str]) -> bytes: From 600cca239cd7248b8de73144ac5f5af39f1f181b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Tue, 13 Feb 2024 11:37:32 +0100 Subject: [PATCH 19/35] ruff: ignore print --- honeypots/__main__.py | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/honeypots/__main__.py b/honeypots/__main__.py index e7e9d17..7f79672 100755 --- a/honeypots/__main__.py +++ b/honeypots/__main__.py @@ -168,7 +168,7 @@ def main(self): if self.options.list: for service in all_servers: - print(service) # noqa: T201 + print(service) elif self.options.kill: clean_all() elif self.options.chameleon and self.config_data is not None: diff --git a/pyproject.toml b/pyproject.toml index 968d5ba..c08897a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,6 +100,7 @@ ignore = [ "RUF002", "RUF003", "RUF015", + "T201", # pydantic only supports these from python>=3.9 "UP006", "UP007", From 0bd7b7639b0a19412a966751ee3765a59b837c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Tue, 13 Feb 2024 17:15:47 +0100 Subject: [PATCH 20/35] elastic server: extend test --- tests/test_elastic_server.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_elastic_server.py b/tests/test_elastic_server.py index 5d358e9..951f135 100644 --- a/tests/test_elastic_server.py +++ b/tests/test_elastic_server.py @@ -35,7 +35,10 @@ def test_elastic_server(server_logs): logs = load_logs_from_file(server_logs) - assert len(logs) == 2 - connect, login = logs + assert len(logs) == 3 + connect, login, dump = logs assert_connect_is_logged(connect, PORT) assert_login_is_logged(login) + + assert "headers" in dump + assert dump["data"] == "POST /test/_search HTTP/1.1\r\n" From 131250eee4d4c6ec3cc9da46d90fd553cf33e44b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Tue, 13 Feb 2024 17:24:32 +0100 Subject: [PATCH 21/35] helper: refactoring + ruff fixes --- honeypots/__init__.py | 10 +- honeypots/base_server.py | 24 +- honeypots/helper.py | 505 +++++++++++++++++---------------------- 3 files changed, 243 insertions(+), 296 deletions(-) diff --git a/honeypots/__init__.py b/honeypots/__init__.py index df9d946..be50362 100644 --- a/honeypots/__init__.py +++ b/honeypots/__init__.py @@ -5,12 +5,9 @@ from .helper import ( is_privileged, clean_all, - close_port_wrapper, get_free_port, - get_running_servers, - kill_server_wrapper, kill_servers, - postgres_class, + PostgresClass, server_arguments, set_local_vars, setup_logger, @@ -76,12 +73,9 @@ "QVNCServer", "is_privileged", "clean_all", - "close_port_wrapper", "get_free_port", - "get_running_servers", - "kill_server_wrapper", "kill_servers", - "postgres_class", + "PostgresClass", "server_arguments", "set_local_vars", "setup_logger", diff --git a/honeypots/base_server.py b/honeypots/base_server.py index 623b2a5..c751283 100644 --- a/honeypots/base_server.py +++ b/honeypots/base_server.py @@ -1,13 +1,16 @@ from __future__ import annotations from abc import ABC, abstractmethod +from contextlib import suppress from multiprocessing import Process from os import getenv +from socket import AF_INET, SOCK_STREAM, socket from typing import Any from uuid import uuid4 +from psutil import process_iter, TimeoutExpired + from honeypots.helper import ( - close_port_wrapper, get_free_port, service_has_started, set_local_vars, @@ -58,7 +61,24 @@ def __init__(self, **kwargs): self._server_process: Process | None = None def close_port(self): - return close_port_wrapper(self.NAME, self.ip, self.port, self.logs) + sock = socket(AF_INET, SOCK_STREAM) + sock.settimeout(2) + if sock.connect_ex((self.ip, self.port)) == 0: + for process in process_iter(): + with suppress(Exception): + for conn in process.connections(kind="inet"): + if self.port == conn.laddr.port: + process.terminate() + try: + process.wait(timeout=5) + except TimeoutExpired: + process.kill() + with suppress(OSError): + sock.bind((self.ip, self.port)) + if sock.connect_ex((self.ip, self.port)) != 0: + return True + self.logger.error(f"[{self.NAME}]: Could not close port {self.port}") + return False def kill_server(self): if self._server_process: diff --git a/honeypots/helper.py b/honeypots/helper.py index f7bc2c4..cea01fd 100644 --- a/honeypots/helper.py +++ b/honeypots/helper.py @@ -11,17 +11,18 @@ """ from __future__ import annotations +import json import logging import os import sys from argparse import ArgumentParser from collections.abc import Mapping -from contextlib import suppress, contextmanager +from contextlib import contextmanager, suppress from datetime import datetime -from json import dumps, JSONEncoder, load -from logging import DEBUG, Formatter, getLogger, Handler +from json import JSONEncoder +from logging import DEBUG, Formatter, getLogger, Handler, LogRecord from logging.handlers import RotatingFileHandler, SysLogHandler -from os import getuid, makedirs, path, scandir +from os import getuid, scandir from pathlib import Path from signal import SIGTERM from socket import AF_INET, SOCK_STREAM, socket @@ -29,7 +30,7 @@ from sys import stdout from tempfile import _get_candidate_names, gettempdir, NamedTemporaryFile from time import sleep, time -from typing import Any, Iterator +from typing import Any, Iterator, MutableMapping from urllib.parse import urlparse import psutil @@ -38,16 +39,6 @@ from psycopg2 import connect as psycopg2_connect, sql -def is_privileged(): - with suppress(Exception): - return getuid() == 0 - with suppress(Exception): - import ctypes - - return ctypes.windll.shell32.IsUserAnAdmin() != 0 - return False - - def set_up_error_logging(): _logger = logging.getLogger("honeypots.error") if not _logger.handlers: @@ -60,128 +51,104 @@ def set_up_error_logging(): return _logger -def set_local_vars(self, config): +logger = set_up_error_logging() + + +def is_privileged(): + with suppress(Exception): + return getuid() == 0 + with suppress(Exception): + import ctypes + + return ctypes.windll.shell32.IsUserAnAdmin() != 0 + return False + + +def set_local_vars(self, config: str | None = None): + if not config: + return try: - if config: - with open(config) as f: - config_data = load(f) - honeypots = config_data.get("honeypots", []) - honeypot = self.__class__.__name__[1:-6].lower() - if honeypot and honeypot in honeypots: - for attr, value in honeypots[honeypot].items(): - setattr(self, attr, value) - if attr == "port": - self.auto_disabled = True + config_data = json.loads(Path(config).read_text()) + honeypots = config_data.get("honeypots", []) + honeypot = self.__class__.__name__[1:-6].lower() + if honeypot and honeypot in honeypots: + for attr, value in honeypots[honeypot].items(): + setattr(self, attr, value) + if attr == "port": + self.auto_disabled = True except Exception as error: - logging.exception(f"Setting local variables failed: {error}") + logger.debug(f"Setting local variables failed: {error}", exc_info=True) -def parse_record(record, custom_filter, type_): - timestamp = {"timestamp": datetime.utcnow().isoformat()} +def _serialize_message( # noqa: C901 + record: LogRecord, + custom_filter: dict, +) -> dict | str | None: try: - if custom_filter is not None: - if "remove_errors" in custom_filter["honeypots"]["options"]: - if "error" in record.msg: + if custom_filter: + filters = custom_filter.get("honeypots", {}) + options = filters.get("options", []) + if "remove_errors" in options and "error" in record.msg: + return None + if isinstance(record.msg, MutableMapping): + if "remove_init" in options and record.msg.get("action") == "process": + return None + if "remove_word_server" in options and "server" in record.msg: + record.msg["server"] = record.msg["server"].replace("_server", "") + for old_key, new_key in filters.get("change", {}).items(): + if old_key in record.msg: + record.msg[new_key] = record.msg.pop(old_key) + for key in filters.get("remove", []): + record.msg.pop(key, None) + if "contains" in filters and any(k not in record.msg for k in filters["contains"]): return None - if isinstance(record.msg, Mapping): - if "remove_init" in custom_filter["honeypots"]["options"]: - if record.msg.get("action", None) == "process": - return None - if "remove_word_server" in custom_filter["honeypots"]["options"]: - if "server" in record.msg: - record.msg["server"] = record.msg["server"].replace("_server", "") - if "honeypots" in custom_filter: - for key in record.msg.copy(): - if key in custom_filter["honeypots"]["change"]: - record.msg[custom_filter["honeypots"]["change"][key]] = record.msg.pop( - key - ) - for key in record.msg.copy(): - if key in custom_filter["honeypots"]["remove"]: - del record.msg[key] - if custom_filter["honeypots"]["contains"]: - if not all(k in record.msg for k in custom_filter["honeypots"]["contains"]): - return None if isinstance(record.msg, Mapping): - record.msg = serialize_object({**timestamp, **record.msg}) - else: - record.msg = serialize_object(record.msg) - except Exception as e: - record.msg = serialize_object({"name": record.name, "error": repr(e)}) + return serialize_object({"timestamp": datetime.utcnow().isoformat(), **record.msg}) + return serialize_object(record.msg) + except Exception as error: + return serialize_object({"name": record.name, "error": repr(error)}) + + +def _parse_record(record: LogRecord, custom_filter: dict, type_: str) -> LogRecord | None: + serialized_msg = _serialize_message(record, custom_filter) + if not serialized_msg: + return None with suppress(Exception): if type_ == "file": - if custom_filter is not None: - if "dump_json_to_file" in custom_filter["honeypots"]["options"]: - record.msg = dumps(record.msg, sort_keys=True, cls=ComplexEncoder) + if custom_filter: + options = custom_filter.get("honeypots", {}).get("options", []) + if "dump_json_to_file" in options: + record.msg = json.dumps(serialized_msg, sort_keys=True, cls=ComplexEncoder) elif type_ == "db_postgres": pass elif type_ == "db_sqlite": for item in ["data", "error"]: - if item in record.msg: - if not isinstance(record.msg[item], str): - record.msg[item] = repr(record.msg[item]).replace("\x00", " ") + if item in serialized_msg and not isinstance(serialized_msg[item], str): + serialized_msg[item] = repr(serialized_msg[item]).replace("\x00", " ") else: - record.msg = dumps(record.msg, sort_keys=True, cls=ComplexEncoder) + record.msg = json.dumps(serialized_msg, sort_keys=True, cls=ComplexEncoder) return record -def get_running_servers(): - temp_list = [] - with suppress(Exception): - honeypots = [ - "QDNSServer", - "QFTPServer", - "QHTTPProxyServer", - "QHTTPServer", - "QHTTPSServer", - "QIMAPServer", - "QMysqlServer", - "QPOP3Server", - "QPostgresServer", - "QRedisServer", - "QSMBServer", - "QSMTPServer", - "QSOCKS5Server", - "QSSHServer", - "QTelnetServer", - "QVNCServer", - "QElasticServer", - "QMSSQLServer", - "QLDAPServer", - "QNTPServer", - "QMemcacheServer", - "QOracleServer", - "QSNMPServer", - ] - for process in process_iter(): - cmdline = " ".join(process.cmdline()) - for honeypot in honeypots: - if "--custom" in cmdline and honeypot in cmdline: - temp_list.append(cmdline.split(" --custom ")[1]) - return temp_list - - -def setup_logger(name, temp_name, config, drop=False): +def setup_logger(name: str, temp_name: str, config: str, drop: bool = False): logs = "terminal" logs_location = "" - syslog_address = "" - syslog_facility = "" - config_data = None + config_data = {} custom_filter = None - if config and config != "": - with suppress(Exception): - with open(config) as f: - config_data = load(f) - logs = config_data.get("logs", logs) - logs_location = config_data.get("logs_location", logs_location) - syslog_address = config_data.get("syslog_address", syslog_address) - syslog_facility = config_data.get("syslog_facility", syslog_facility) - custom_filter = config_data.get("custom_filter", custom_filter) - if logs_location == "" or logs_location is None: - logs_location = path.join(gettempdir(), "logs") - if not path.exists(logs_location): - makedirs(logs_location) - file_handler = None + if config: + try: + config_data = json.loads(Path(config).read_text()) + logs = config_data.get("logs", logs) + logs_location = config_data.get("logs_location", logs_location) + custom_filter = config_data.get("custom_filter", custom_filter) + except json.JSONDecodeError as error: + logger.error(f"Could not parse config '{config}' as JSON: {error}") + except OSError as error: + logger.error(f"Could not read config file '{config}': {error}") + + logs_path = Path(logs_location) if logs_location else Path(gettempdir()) / "logs" + logs_path.mkdir(parents=True, exist_ok=True) + ret_logs_obj = getLogger(temp_name) ret_logs_obj.setLevel(DEBUG) if "db_postgres" in logs or "db_sqlite" in logs: @@ -189,45 +156,41 @@ def setup_logger(name, temp_name, config, drop=False): elif "terminal" in logs: ret_logs_obj.addHandler(CustomHandler(temp_name, logs, custom_filter)) if "file" in logs: - max_bytes = 10000 - backup_count = 10 - with suppress(Exception): - if config_data is not None: - if "honeypots" in config_data: - temp_server_name = name[1:].lower().replace("server", "") - if temp_server_name in config_data["honeypots"]: - if "log_file_name" in config_data["honeypots"][temp_server_name]: - temp_name = config_data["honeypots"][temp_server_name]["log_file_name"] - if "max_bytes" in config_data["honeypots"][temp_server_name]: - max_bytes = config_data["honeypots"][temp_server_name]["max_bytes"] - if "backup_count" in config_data["honeypots"][temp_server_name]: - backup_count = config_data["honeypots"][temp_server_name][ - "backup_count" - ] + server = name[1:].lower().replace("server", "") + server_config = config_data.get("honeypots", {}).get(server, {}) file_handler = CustomHandlerFileRotate( - temp_name, - logs, - custom_filter, - path.join(logs_location, temp_name), - maxBytes=max_bytes, - backupCount=backup_count, + str(logs_path / server_config.get("log_file_name", temp_name)), + logs=logs, + custom_filter=custom_filter, + maxBytes=server_config.get("max_bytes", 10000), + backupCount=server_config.get("backup_count", 10), ) ret_logs_obj.addHandler(file_handler) if "syslog" in logs: - if syslog_address == "": - address = ("localhost", 514) - else: - address = ( - syslog_address.split("//")[1].split(":")[0], - int(syslog_address.split("//")[1].split(":")[1]), - ) - syslog = SysLogHandler(address=address, facility=syslog_facility) - formatter = Formatter("[%(name)s] [%(levelname)s] - %(message)s") - syslog.setFormatter(formatter) - ret_logs_obj.addHandler(syslog) + syslog_handler = _set_up_syslog_handler( + config_data.get("syslog_address"), + config_data.get("syslog_facility"), + ) + if syslog_handler: + ret_logs_obj.addHandler(syslog_handler) return ret_logs_obj +def _set_up_syslog_handler(address: str | None, facility: int | None) -> Handler | None: + if not address: + address = ("localhost", 514) + else: + url = urlparse(address) + if not url.hostname or not url.port: + logger.error(f"Could not parse syslog address '{address}': host or port not found") + return None + address = (url.hostname, url.port) + handler = SysLogHandler(address=address, facility=facility) + formatter = Formatter("[%(name)s] [%(levelname)s] - %(message)s") + handler.setFormatter(formatter) + return handler + + def clean_all(): for entry in scandir("."): if entry.is_file() and entry.name.endswith("_server.py"): @@ -243,19 +206,6 @@ def kill_servers(name): process.kill() -def kill_server_wrapper(server_name, name, process): - with suppress(Exception): - if process is not None: - process.kill() - for process in process_iter(): - cmdline = " ".join(process.cmdline()) - if "--custom" in cmdline and name in cmdline: - process.send_signal(SIGTERM) - process.kill() - return True - return False - - def get_free_port(): port = 0 with suppress(Exception): @@ -266,81 +216,53 @@ def get_free_port(): return port -def close_port_wrapper(server_name, ip, port, logs): - ret = False - sock = socket(AF_INET, SOCK_STREAM) - sock.settimeout(2) - if sock.connect_ex((ip, port)) == 0: - for process in process_iter(): - with suppress(Exception): - for conn in process.connections(kind="inet"): - if port == conn.laddr.port: - process.send_signal(SIGTERM) - process.kill() - with suppress(Exception): - sock.bind((ip, port)) - ret = True - - if sock.connect_ex((ip, port)) != 0 and ret: - return True - else: - logs.error({"server": server_name, "error": f"port_open.. {ip} still open.."}) - return False - - class ComplexEncoder(JSONEncoder): def default(self, obj): return repr(obj).replace("\x00", " ") -def serialize_object(_dict): - if isinstance(_dict, Mapping): - return dict((k, serialize_object(v)) for k, v in _dict.items()) - elif isinstance(_dict, list): - return list(serialize_object(v) for v in _dict) - elif isinstance(_dict, (int, float)): - return str(_dict) - elif isinstance(_dict, str): - return _dict.replace("\x00", " ") - elif isinstance(_dict, bytes): - return _dict.decode("utf-8", "ignore").replace("\x00", " ") - else: - return repr(_dict).replace("\x00", " ") +def serialize_object(obj: Any) -> dict | list | str: + if isinstance(obj, Mapping): + return {k: serialize_object(v) for k, v in obj.items()} + if isinstance(obj, list): + return [serialize_object(v) for v in obj] + if isinstance(obj, (int, float)): + return str(obj) + if isinstance(obj, bytes): + obj = obj.decode("utf-8", "ignore") + elif not isinstance(obj, str): + obj = repr(obj) + return obj.replace("\x00", " ") class CustomHandlerFileRotate(RotatingFileHandler): - def __init__( - self, - uuid="", - logs="", - custom_filter=None, - filename="", - mode="a", - maxBytes=0, - backupCount=0, - encoding=None, - delay=False, - errors=None, - ): + def __init__(self, *args, logs="", custom_filter=None, **kwargs): self.logs = logs self.custom_filter = custom_filter - RotatingFileHandler.__init__(self, filename, mode, maxBytes, backupCount, encoding, delay) + super().__init__(*args, **kwargs) def emit(self, record): - _record = parse_record(record, self.custom_filter, "file") - if _record is not None: + _record = _parse_record(record, self.custom_filter, "file") + if _record: super().emit(_record) class CustomHandler(Handler): - def __init__(self, uuid="", logs="", custom_filter=None, config=None, drop=False): + def __init__( # noqa: PLR0913 + self, + uuid: str = "", + logs: str = "", + custom_filter: dict | None = None, + config: dict | None = None, + drop: bool = False, + ): self.db = {"db_postgres": None, "db_sqlite": None} self.logs = logs self.uuid = uuid self.custom_filter = custom_filter - if config and config != "" and "db_postgres" in self.logs: + if config and "db_postgres" in self.logs: parsed = urlparse(config["postgres"]) - self.db["db_postgres"] = postgres_class( + self.db["db_postgres"] = PostgresClass( host=parsed.hostname, port=parsed.port, username=parsed.username, @@ -349,56 +271,60 @@ def __init__(self, uuid="", logs="", custom_filter=None, config=None, drop=False uuid=self.uuid, drop=drop, ) - if config and config != "" and "db_sqlite" in self.logs: - self.db["db_sqlite"] = sqlite_class( + if config and "db_sqlite" in self.logs: + self.db["db_sqlite"] = SqliteClass( file=config["sqlite_file"], drop=drop, uuid=self.uuid ) - Handler.__init__(self) + super().__init__() - def emit(self, record): + def emit(self, record: LogRecord): # noqa: C901,PLR0912 try: - if "db_postgres" in self.logs: - if self.db["db_postgres"]: - if isinstance(record.msg, list): - if record.msg[0] == "sniffer" or record.msg[0] == "errors": - self.db["db_postgres"].insert_into_data_safe( - record.msg[0], - dumps(serialize_object(record.msg[1]), cls=ComplexEncoder), - ) - elif isinstance(record.msg, Mapping): - if "server" in record.msg: - self.db["db_postgres"].insert_into_data_safe( - "servers", dumps(serialize_object(record.msg), cls=ComplexEncoder) - ) + if "db_postgres" in self.logs and self.db["db_postgres"]: + if isinstance(record.msg, list): + if record.msg[0] in {"sniffer", "errors"}: + self.db["db_postgres"].insert_into_data_safe( + record.msg[0], + json.dumps(serialize_object(record.msg[1]), cls=ComplexEncoder), + ) + elif isinstance(record.msg, Mapping) and "server" in record.msg: + self.db["db_postgres"].insert_into_data_safe( + "servers", + json.dumps(serialize_object(record.msg), cls=ComplexEncoder), + ) if "db_sqlite" in self.logs: - _record = parse_record(record, self.custom_filter, "db_sqlite") + _record = _parse_record(record, self.custom_filter, "db_sqlite") if _record: self.db["db_sqlite"].insert_into_data_safe(_record.msg) if "terminal" in self.logs: - _record = parse_record(record, self.custom_filter, "terminal") + _record = _parse_record(record, self.custom_filter, "terminal") if _record: stdout.write(_record.msg + "\n") if "syslog" in self.logs: - _record = parse_record(record, self.custom_filter, "terminal") + _record = _parse_record(record, self.custom_filter, "terminal") if _record: stdout.write(_record.msg + "\n") - except Exception as e: - if self.custom_filter is not None: - if "honeypots" in self.custom_filter: - if "remove_errors" in self.custom_filter["honeypots"]["options"]: - return - stdout.write( - dumps( - {"error": repr(e), "logger": repr(record)}, sort_keys=True, cls=ComplexEncoder - ) - + "\n" - ) + except Exception as error: + if ( + self.custom_filter is not None + and "honeypots" in self.custom_filter + and "remove_errors" in self.custom_filter["honeypots"].get("options", []) + ): + return + log_entry = {"error": repr(error), "logger": repr(record)} + stdout.write(f"{json.dumps(log_entry, sort_keys=True, cls=ComplexEncoder)}\n") stdout.flush() -class postgres_class: - def __init__( - self, host=None, port=None, username=None, password=None, db=None, drop=False, uuid=None +class PostgresClass: + def __init__( # noqa: PLR0913 + self, + host=None, + port=None, + username=None, + password=None, + db=None, + drop=False, + uuid=None, ): self.host = host self.port = port @@ -410,7 +336,10 @@ def __init__( self.wait_until_up() if drop: self.con = psycopg2_connect( - host=self.host, port=self.port, user=self.username, password=self.password + host=self.host, + port=self.port, + user=self.username, + password=self.password, ) self.con.set_isolation_level(0) self.cur = self.con.cursor() @@ -420,7 +349,10 @@ def __init__( self.con.close() else: self.con = psycopg2_connect( - host=self.host, port=self.port, user=self.username, password=self.password + host=self.host, + port=self.port, + user=self.username, + password=self.password, ) self.con.set_isolation_level(0) self.cur = self.con.cursor() @@ -443,7 +375,7 @@ def wait_until_up(self): test = True while test: with suppress(Exception): - print(f"{self.uuid} - Waiting on postgres connection") + logger.info(f"{self.uuid} - Waiting on postgres connection") stdout.flush() conn = psycopg2_connect( host=self.host, @@ -455,7 +387,7 @@ def wait_until_up(self): conn.close() test = False sleep(1) - print(f"{self.uuid} - postgres connection is good") + logger.info(f"{self.uuid} - postgres connection is good") def addattr(self, x, val): self.__dict__[x] = val @@ -473,7 +405,7 @@ def check_db_if_exists(self): def drop_db(self): with suppress(Exception): - print(f"[x] Dropping {self.db} db") + logger.warning(f"Dropping {self.db} db") if self.check_db_if_exists(): self.cur.execute( sql.SQL("drop DATABASE IF EXISTS {}").format(sql.Identifier(self.db)) @@ -482,7 +414,7 @@ def drop_db(self): self.cur.execute(sql.SQL("CREATE DATABASE {}").format(sql.Identifier(self.db))) def create_db(self): - print("create") + logger.info("Creating PostgreSQL database") self.cur.execute(sql.SQL("CREATE DATABASE {}").format(sql.Identifier(self.db))) def drop_tables( @@ -494,11 +426,12 @@ def drop_tables( ) def create_tables(self): - for x in self.mapped_tables: + for table in self.mapped_tables: self.cur.execute( sql.SQL( - "CREATE TABLE IF NOT EXISTS {} (id SERIAL NOT NULL,date timestamp with time zone DEFAULT now(),data json)" - ).format(sql.Identifier(x + "_table")) + "CREATE TABLE IF NOT EXISTS {} " + "(id SERIAL NOT NULL,date timestamp with time zone DEFAULT now(),data json)" + ).format(sql.Identifier(table + "_table")) ) def insert_into_data_safe(self, table, obj): @@ -511,7 +444,7 @@ def insert_into_data_safe(self, table, obj): ) -class sqlite_class: +class SqliteClass: def __init__(self, file=None, drop=False, uuid=None): self.file = file self.uuid = uuid @@ -548,43 +481,30 @@ def wait_until_up(self): test = True while test: with suppress(Exception): - print(f"{self.uuid} - Waiting on sqlite connection") + logger.info(f"{self.uuid} - Waiting on sqlite connection") conn = sqlite3_connect(self.file, timeout=1, check_same_thread=False) conn.close() test = False sleep(1) - print(f"{self.uuid} - sqlite connection is good") - - def drop_db_test(self): - with suppress(Exception): - file_exists = False - sql_file = False - with open(self.file, "rb") as f: - file_exists = True - header = f.read(100) - if header[:16] == b"SQLite format 3\x00": - sql_file = True - if sql_file: - print("yes") + logger.info(f"{self.uuid} - sqlite connection is good") def drop_db(self): with suppress(Exception): file = Path(self.file) file.unlink(missing_ok=False) - def drop_tables( - self, - ): + def drop_tables(self): with suppress(Exception): - for x in self.mapped_tables: - self.cur.execute(f"DROP TABLE IF EXISTS '{x:s}'") + for table in self.mapped_tables: + self.cur.execute(f"DROP TABLE IF EXISTS '{table:s}'") def create_tables(self): with suppress(Exception): self.cur.execute( - "CREATE TABLE IF NOT EXISTS '{:s}' (id INTEGER PRIMARY KEY,date DATETIME DEFAULT CURRENT_TIMESTAMP,server text, action text, status text, src_ip text, src_port text,dest_ip text, dest_port text, username text, password text, data text, error text)".format( - "servers_table" - ) + "CREATE TABLE IF NOT EXISTS 'servers_table' (id INTEGER PRIMARY KEY," + "date DATETIME DEFAULT CURRENT_TIMESTAMP,server text, action text, " + "status text, src_ip text, src_port text,dest_ip text, dest_port text, " + "username text, password text, data text, error text)" ) def insert_into_data_safe(self, obj): @@ -592,7 +512,9 @@ def insert_into_data_safe(self, obj): parsed = {k: v for k, v in obj.items() if v is not None} dict_ = {**self.servers_table_template, **parsed} self.cur.execute( - "INSERT INTO servers_table (server, action, status, src_ip, src_port, dest_ip, dest_port, username, password, data, error) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "INSERT INTO servers_table (" + "server, action, status, src_ip, src_port, dest_ip, dest_port, username, " + "password, data, error) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", ( dict_["server"], dict_["action"], @@ -613,7 +535,11 @@ def server_arguments(): _server_parser = ArgumentParser(prog="Server") _server_parsergroupdeq = _server_parser.add_argument_group("Initialize Server") _server_parsergroupdeq.add_argument( - "--ip", type=str, help="Change server ip, current is 0.0.0.0", required=False, metavar="" + "--ip", + type=str, + help="Change server ip, current is 0.0.0.0", + required=False, + metavar="", ) _server_parsergroupdeq.add_argument( "--port", type=int, help="Change port", required=False, metavar="" @@ -653,7 +579,11 @@ def server_arguments(): ) _server_parsergroupdef = _server_parser.add_argument_group("Initialize Loging") _server_parsergroupdef.add_argument( - "--config", type=str, help="config file for logs and database", required=False, default="" + "--config", + type=str, + help="config file for logs and database", + required=False, + default="", ) _server_parsergroupdea = _server_parser.add_argument_group("Auto Configuration") _server_parsergroupdea.add_argument( @@ -669,7 +599,10 @@ def server_arguments(): "--custom", action="store_true", help="Run custom server", required=False ) _server_parsergroupdea.add_argument( - "--auto", action="store_true", help="Run auto configured with random port", required=False + "--auto", + action="store_true", + help="Run auto configured with random port", + required=False, ) _server_parsergroupdef.add_argument("--uuid", type=str, help="unique id", required=False) return _server_parser.parse_args() From facb6734212129ed41c3881fd988c616cfc2a621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 14 Feb 2024 10:39:07 +0100 Subject: [PATCH 22/35] irc server: ruff fixes --- honeypots/irc_server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/honeypots/irc_server.py b/honeypots/irc_server.py index 8dee8b6..8241d24 100644 --- a/honeypots/irc_server.py +++ b/honeypots/irc_server.py @@ -31,7 +31,7 @@ def server_main(self): _q_s = self class CustomIRCProtocol(service.IRCUser): - def connectionMade(self): + def connectionMade(self): # noqa: N802 _q_s.log( { "action": "connection", @@ -40,7 +40,7 @@ def connectionMade(self): } ) - def handleCommand(self, command, prefix, params): + def handleCommand(self, command, prefix, params): # noqa: N802 if "capture_commands" in _q_s.options: _q_s.log( { @@ -56,7 +56,7 @@ def handleCommand(self, command, prefix, params): ) service.IRCUser.handleCommand(self, command, prefix, params) - def dataReceived(self, data: bytes): + def dataReceived(self, data: bytes): # noqa: N802 try: service.IRCUser.dataReceived(self, data) except UnicodeDecodeError: @@ -67,7 +67,7 @@ def dataReceived(self, data: bytes): def irc_unknown(self, prefix, command, params): pass - def irc_NICK(self, prefix, params): + def irc_NICK(self, prefix, params): # noqa: N802,ARG002 username = check_bytes("".join(params)) password = check_bytes(self.password) peer = self.transport.getPeer() From fd090cd4c2820c89043de3e3b38a40a568743738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 14 Feb 2024 14:25:39 +0100 Subject: [PATCH 23/35] ldap server: ruff fixes + refactoring --- honeypots/ldap_server.py | 56 ++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/honeypots/ldap_server.py b/honeypots/ldap_server.py index 26db9d8..0ba2cc9 100644 --- a/honeypots/ldap_server.py +++ b/honeypots/ldap_server.py @@ -11,6 +11,7 @@ """ from __future__ import annotations +import struct from binascii import unhexlify from contextlib import suppress from struct import unpack @@ -24,18 +25,21 @@ check_bytes, ) +# authentication with clear text password +AUTH_SIMPLE_BIND = 0x80 + class QLDAPServer(BaseServer): NAME = "ldap_server" DEFAULT_PORT = 389 - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self class CustomLDAProtocol(Protocol): _state = None - def connectionMade(self): + def connectionMade(self): # noqa: N802 self._state = 1 _q_s.log( { @@ -48,44 +52,40 @@ def connectionMade(self): @staticmethod def parse_ldap_packet(data: bytes) -> tuple[str, str]: # V - # 30[20] 0201[02] 60[1b] 0201[03] 04[0a] 7379736261636b757031 [80][0a] 7379736261636b757032 + # 30[20] 0201[02] 60[1b] 0201[03] 04[0a] 7379736261636b757031 + # [80][0a] 7379736261636b757032 username = "" password = "" - with suppress(Exception): - version = data.find(b"\x02\x01\x03") - if version > 0: - username_start = version + 5 - username_end = ( - unpack("b", data[version + 4 : username_start])[0] + username_start - ) - username = data[username_start:username_end] + with suppress(IndexError, struct.error): + version_offset = data.find(b"\x02\x01\x03") + if version_offset > 0: + username_start = version_offset + 5 + username_len = data[username_start - 1] + username_end = username_start + username_len + username = check_bytes(data[username_start:username_end]) auth_type = data[username_end] - if auth_type == 0x80: - if data[username_end + 1] == 0x82: + if auth_type == AUTH_SIMPLE_BIND: + if data[username_end + 1] == 0x82: # noqa: PLR2004 + # the length of the password needs more than one byte password_start = username_end + 4 - password_end = ( - unpack(">H", data[username_end + 2 : username_end + 4])[0] - + username_end - + 4 - ) + password_len = unpack( + ">H", data[password_start - 2 : password_start] + )[0] else: password_start = username_end + 2 - password_end = ( - unpack("b", data[username_end + 2 : username_end + 3])[0] - + username_start - + 2 - ) - password = data[password_start:password_end] + password_len = data[password_start - 1] + password_end = password_start + password_len + password = check_bytes(data[password_start:password_end]) - return check_bytes(username), check_bytes(password) + return username, password - def dataReceived(self, data): + def dataReceived(self, data): # noqa: N802 if self._state == 1: self._state = 2 self._check_login(data) self.transport.write(unhexlify(b"300c02010165070a013204000400")) - elif self._state == 2: + elif self._state == 2: # noqa: PLR2004 self._state = 3 self._check_login(data) self.transport.write(unhexlify(b"300c02010265070a013204000400")) @@ -98,7 +98,7 @@ def _check_login(self, data): peer = self.transport.getPeer() _q_s.check_login(username, password, ip=peer.host, port=peer.port) - def connectionLost(self, reason): + def connectionLost(self, reason): # noqa: N802,ARG002 self._state = None factory = Factory() From ab18e5f820a87e5b74d4677a9c722e50a47665ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 14 Feb 2024 15:46:39 +0100 Subject: [PATCH 24/35] memcache server: ruff fixes + refactoring --- honeypots/memcache_server.py | 193 +++++++++++++++++------------------ 1 file changed, 93 insertions(+), 100 deletions(-) diff --git a/honeypots/memcache_server.py b/honeypots/memcache_server.py index 1aa48cf..503e984 100644 --- a/honeypots/memcache_server.py +++ b/honeypots/memcache_server.py @@ -27,115 +27,111 @@ class QMemcacheServer(BaseServer): NAME = "memcache_server" DEFAULT_PORT = 11211 - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self class CustomRedisProtocol(Protocol): _state = None - def get_stats(self): + def get_stats(self) -> bytes: items = randint(80000000, 90000000) ret = "" temp = { - b"pid": randint(5, 400), - b"uptime": randint(1000, 2000), - b"time": int(time()), - b"version": b"1.5.6", - b"libevent": b"2.1.8-stable", - b"pointer_size": 64, - b"rusage_user": round(uniform(0.1, 0.9), 4), - b"rusage_system": round(uniform(0.1, 0.9), 6), - b"max_connections": 1024, - b"curr_connections": randint(1, 1024), - b"total_connections": 5, - b"rejected_connections": 0, - b"connection_structures": 2, - b"reserved_fds": 20, - b"cmd_get": 0, - b"cmd_set": 40, - b"cmd_flush": 0, - b"cmd_touch": 0, - b"get_hits": 0, - b"get_misses": 0, - b"get_expired": 0, - b"get_flushed": 0, - b"delete_misses": 0, - b"delete_hits": 0, - b"incr_misses": 0, - b"incr_hits": 0, - b"decr_misses": 0, - b"decr_hits": 0, - b"cas_misses": 0, - b"cas_hits": 0, - b"cas_badval": 0, - b"touch_hits": 0, - b"touch_misses": 0, - b"auth_cmds": 0, - b"auth_errors": 0, - b"bytes_read": randint(7000000, 8000000), - b"bytes_written": randint(500000, 1000000), - b"limit_maxbytes": 33554432, - b"accepting_conns": 1, - b"listen_disabled_num": 0, - b"time_in_listen_disabled_us": 0, - b"threads": randint(4, 9000), - b"conn_yields": 0, - b"hash_power_level": 16, - b"hash_bytes": 524288, - b"hash_is_expanding": False, - b"slab_reassign_rescues": 0, - b"slab_reassign_chunk_rescues": 0, - b"slab_reassign_evictions_nomem": 0, - b"slab_reassign_inline_reclaim": 0, - b"slab_reassign_busy_items": 0, - b"slab_reassign_busy_deletes": 0, - b"slab_reassign_running": False, - b"slabs_moved": 0, - b"lru_crawler_running": 0, - b"lru_crawler_starts": randint(500000, 700000), - b"lru_maintainer_juggles": randint(400000, 500000), - b"malloc_fails": 0, - b"log_worker_dropped": 0, - b"log_worker_written": 0, - b"log_watcher_skipped": 0, - b"log_watcher_sent": 0, - b"bytes": randint(13554432, 33554432), - b"curr_items": items, - b"total_items": items, - b"slab_global_page_pool": 0, - b"expired_unfetched": 0, - b"evicted_unfetched": 0, - b"evicted_active": 0, - b"evictions": 0, - b"reclaimed": 0, - b"crawler_reclaimed": 0, - b"crawler_items_checked": randint(5000, 6000), - b"lrutail_reflocked": 0, - b"moves_to_cold": randint(5000, 6000), - b"moves_to_warm": randint(5000, 6000), - b"moves_within_lru": 0, - b"direct_reclaims": 0, - b"lru_bumps_dropped": 0, + "pid": randint(5, 400), + "uptime": randint(1000, 2000), + "time": int(time()), + "version": "1.5.6", + "libevent": "2.1.8-stable", + "pointer_size": 64, + "rusage_user": round(uniform(0.1, 0.9), 4), + "rusage_system": round(uniform(0.1, 0.9), 6), + "max_connections": 1024, + "curr_connections": randint(1, 1024), + "total_connections": 5, + "rejected_connections": 0, + "connection_structures": 2, + "reserved_fds": 20, + "cmd_get": 0, + "cmd_set": 40, + "cmd_flush": 0, + "cmd_touch": 0, + "get_hits": 0, + "get_misses": 0, + "get_expired": 0, + "get_flushed": 0, + "delete_misses": 0, + "delete_hits": 0, + "incr_misses": 0, + "incr_hits": 0, + "decr_misses": 0, + "decr_hits": 0, + "cas_misses": 0, + "cas_hits": 0, + "cas_badval": 0, + "touch_hits": 0, + "touch_misses": 0, + "auth_cmds": 0, + "auth_errors": 0, + "bytes_read": randint(7000000, 8000000), + "bytes_written": randint(500000, 1000000), + "limit_maxbytes": 33554432, + "accepting_conns": 1, + "listen_disabled_num": 0, + "time_in_listen_disabled_us": 0, + "threads": randint(4, 9000), + "conn_yields": 0, + "hash_power_level": 16, + "hash_bytes": 524288, + "hash_is_expanding": False, + "slab_reassign_rescues": 0, + "slab_reassign_chunk_rescues": 0, + "slab_reassign_evictions_nomem": 0, + "slab_reassign_inline_reclaim": 0, + "slab_reassign_busy_items": 0, + "slab_reassign_busy_deletes": 0, + "slab_reassign_running": False, + "slabs_moved": 0, + "lru_crawler_running": 0, + "lru_crawler_starts": randint(500000, 700000), + "lru_maintainer_juggles": randint(400000, 500000), + "malloc_fails": 0, + "log_worker_dropped": 0, + "log_worker_written": 0, + "log_watcher_skipped": 0, + "log_watcher_sent": 0, + "bytes": randint(13554432, 33554432), + "curr_items": items, + "total_items": items, + "slab_global_page_pool": 0, + "expired_unfetched": 0, + "evicted_unfetched": 0, + "evicted_active": 0, + "evictions": 0, + "reclaimed": 0, + "crawler_reclaimed": 0, + "crawler_items_checked": randint(5000, 6000), + "lrutail_reflocked": 0, + "moves_to_cold": randint(5000, 6000), + "moves_to_warm": randint(5000, 6000), + "moves_within_lru": 0, + "direct_reclaims": 0, + "lru_bumps_dropped": 0, } for key, value in temp.items(): - key = key.decode() - if isinstance(value, bytes): - value = value.decode() ret += f"STAT {key} {value}\r\n" + ret += "END\r\n" + return ret.encode() - ret = ret.encode() + b"END\r\n" - return ret - - def get_key(self, key): - ret = b"" - with suppress(Exception): + def get_key(self, key: bytes) -> bytes: + try: random = randint(80000000, 90000000) temp = f"VALUE {key.decode()} 0 {len(str(random))}\r\n{random}\r\nEND\r\n" - ret = temp.encode() - return ret + return temp.encode() + except UnicodeDecodeError: + return b"" - def connectionMade(self): + def connectionMade(self): # noqa: N802 _q_s.log( { "action": "connection", @@ -144,17 +140,14 @@ def connectionMade(self): } ) - def dataReceived(self, data): + def dataReceived(self, data: bytes): # noqa: N802 with suppress(Exception): _data = data.split(b"\r\n")[0].split(b" ") if _data[0] == b"stats": self.transport.write(self.get_stats()) elif _data[0] == b"get": self.transport.write(self.get_key(_data[1])) - elif _data[0] == b"set": - name = _data[1] - size = _data[4] - value = data.split(b"\r\n")[1] + elif _data[0] == b"set" and len(_data) > 4: # noqa: PLR2004 self.transport.write(b"STORED\r\n") else: self.transport.write(b"ERROR\r\n") @@ -173,7 +166,7 @@ def dataReceived(self, data): reactor.listenTCP(port=self.port, factory=factory, interface=self.ip) reactor.run() - def test_server(self, ip=None, port=None, username=None, password=None): + def test_server(self, ip=None, port=None, username=None, password=None): # noqa: ARG002 with suppress(Exception): from warnings import filterwarnings @@ -185,7 +178,7 @@ def test_server(self, ip=None, port=None, username=None, password=None): c = socket(AF_INET, SOCK_STREAM) c.connect((_ip, _port)) c.send(b"stats\r\n") - data, address = c.recvfrom(10000) + c.recvfrom(10000) c.close() From 0973f4e7a33fc466e2bae74352729082648eca36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 14 Feb 2024 16:37:06 +0100 Subject: [PATCH 25/35] mssql server: ruff fixes + refactoring --- honeypots/mssql_server.py | 105 ++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 56 deletions(-) diff --git a/honeypots/mssql_server.py b/honeypots/mssql_server.py index 486ec7d..bed2a46 100644 --- a/honeypots/mssql_server.py +++ b/honeypots/mssql_server.py @@ -24,6 +24,11 @@ ) +class PacketType: + login = 0x10 + pre_login = 0x12 + + class QMSSQLServer(BaseServer): NAME = "mssql_server" DEFAULT_PORT = 1433 @@ -32,36 +37,32 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.file_name = None - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self class CustomMSSQLProtocol(Protocol): _state = None - def create_payload(self, server_name=b"", token_error_msg=b"", error_code=2): + def create_payload( + self, server_name: str = "", token_error_msg: str = "", error_code=2 + ) -> bytes: with suppress(Exception): - if server_name == b"": - server_name = b"R&Dbackup" - if token_error_msg == b"": + if server_name == "": + server_name = "R&Dbackup" + if token_error_msg == "": token_error_msg = ( - b"An error has occurred while establishing a connection to the server." + "An error has occurred while establishing a connection to the server." ) - server_name_hex = ("00".join(hex(c)[2:] for c in server_name)).encode( - "utf-8" - ) + b"00" server_name_hex_len = hexlify(pack("b", len(server_name))) - token_error_msg_hex = ("00".join(hex(c)[2:] for c in token_error_msg)).encode( - "utf-8" - ) + b"00" token_error_msg_hex_len = hexlify(pack("H", len(unhexlify(data_stream)))) - + data_stream[8:] - ) + data_len = hexlify(pack(">H", len(unhexlify(data_stream)))) + return unhexlify(data_stream[0:4] + data_len + data_stream[8:]) - def connectionMade(self): + def connectionMade(self): # noqa: N802 self._state = 1 _q_s.log( { @@ -85,48 +83,43 @@ def connectionMade(self): } ) - def dataReceived(self, data): + def dataReceived(self, data: bytes): # noqa: N802 if self._state == 1: - version = b"11000000" - if data[0] == 0x12: - self.transport.write( - unhexlify( - b"0401002500000100000015000601001b000102001c000103001d0000ff" - + version - + b"00000200" - ) - ) - elif data[0] == 0x10: - value_start, value_length = unpack("=HH", data[48:52]) - username = ( - data[8 + value_start : 8 + value_start + (value_length * 2)] - .replace(b"\x00", b"") - .decode("utf-8") - ) - value_start, value_length = unpack("=HH", data[52:56]) - password = data[8 + value_start : 8 + value_start + (value_length * 2)] - password = password.replace(b"\x00", b"").replace(b"\xa5", b"") - password_decrypted = "" - for x in password: - password_decrypted += chr( - ((x ^ 0xA5) & 0x0F) << 4 | ((x ^ 0xA5) & 0xF0) >> 4 - ) - username = check_bytes(username) - password = check_bytes(password_decrypted) - peer = self.transport.getPeer() - _q_s.check_login(username, password, ip=peer.host, port=peer.port) - + version = "11000000" + packet_type = data[0] + if packet_type == PacketType.pre_login: self.transport.write( - unhexlify( - self.create_payload( - token_error_msg=b"Login Failed", error_code=18456 - ) + bytes.fromhex( + "0401002500000100000015000601001b000102" + f"001c000103001d0000ff{version}00000200" ) ) + elif packet_type == PacketType.login: + self._handle_login(data) else: self.transport.loseConnection() - def connectionLost(self, reason): + def _handle_login(self, data: bytes): + value_start, value_length = unpack("=HH", data[48:52]) + username = ( + data[8 + value_start : 8 + value_start + (value_length * 2)] + .replace(b"\x00", b"") + .decode("utf-8") + ) + value_start, value_length = unpack("=HH", data[52:56]) + password = data[8 + value_start : 8 + value_start + (value_length * 2)] + password = password.replace(b"\x00", b"").replace(b"\xa5", b"") + password_decrypted = "" + for x in password: + password_decrypted += chr(((x ^ 0xA5) & 0x0F) << 4 | ((x ^ 0xA5) & 0xF0) >> 4) + peer = self.transport.getPeer() + _q_s.check_login( + check_bytes(username), password_decrypted, ip=peer.host, port=peer.port + ) + payload = self.create_payload(token_error_msg="Login Failed", error_code=18456) + self.transport.write(payload) + + def connectionLost(self, reason): # noqa: N802,ARG002 self._state = None factory = Factory() @@ -145,7 +138,7 @@ def test_server(self, ip=None, port=None, username=None, password=None): conn = pconnect( host=_ip, port=str(_port), user=_username, password=_password, database="dbname" ) - cursor = conn.cursor() + conn.cursor() if __name__ == "__main__": From 6dc02e045a1440e27dab52284448a7065ef9a8ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 14 Feb 2024 17:41:07 +0100 Subject: [PATCH 26/35] mysql server: ruff fixes + refactoring --- honeypots/mysql_server.py | 125 ++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 73 deletions(-) diff --git a/honeypots/mysql_server.py b/honeypots/mysql_server.py index 0abeef1..a3aa539 100644 --- a/honeypots/mysql_server.py +++ b/honeypots/mysql_server.py @@ -10,8 +10,11 @@ // ------------------------------------------------------------- """ +from __future__ import annotations + from contextlib import suppress from hashlib import sha1 +from pathlib import Path from struct import pack from twisted.internet import reactor @@ -30,92 +33,68 @@ class QMysqlServer(BaseServer): def __init__(self, **kwargs): super().__init__(**kwargs) - self.words = [self.password.encode()] - - def load_words( - self, - ): - with open(self.file_name, encoding="utf-8") as file: - self.words = file.read().splitlines() + if hasattr(self, "file_name"): + self.words = Path(self.file_name).read_text("utf-8").splitlines() + else: + self.words = [self.password.encode()] def greeting(self): base = [ - "\x0a", - "5.7.00" + "\0", - "\x36\x00\x00\x00", - "12345678" + "\0", - "\xff\xf7", - "\x21", - "\x02\x00", - "\x0f\x81", - "\x15", - "\0" * 10, - "123456789012" + "\0", - "mysql_native_password" + "\0", + b"\x0a", + b"5.7.00" + b"\0", + b"\x36\x00\x00\x00", + b"12345678" + b"\0", + b"\xff\xf7", + b"\x21", + b"\x02\x00", + b"\x0f\x81", + b"\x15", + b"\0" * 10, + b"123456789012" + b"\0", + b"mysql_native_password" + b"\0", ] - payload_len = list(pack(" bytes: + # MySQL Packets structure: 3 byte payload_len, 1 byte sequence_id, payload + payload_len = pack(" tuple[bytes, bytes, bool]: + username, password = "", "" + username_offset = 36 + username_len = data[username_offset:].find(b"\x00") + if username_len != -1: + with suppress(IndexError): + username = data[username_offset : username_offset + username_len] + password_offset = username_offset + username_len + 2 + password_len = data[password_offset - 1] + password = data[password_offset : password_offset + password_len] + if password_len == 20: # noqa: PLR2004 + return username, password, True return username, password, False - def decode(self, hash): + def decode(self, hash_: bytes): with suppress(Exception): for word in self.words: - temp = word - word = word.strip(b"\n") - hash1 = sha1(word).digest() + hash1 = sha1(word.strip(b"\n")).digest() hash2 = sha1(hash1).digest() encrypted = [ (a ^ b) for a, b in zip(hash1, sha1(b"12345678123456789012" + hash2).digest()) ] - if encrypted == list([(i) for i in hash]): - return temp + if encrypted == list(hash_): + return word return None def server_main(self): @@ -124,7 +103,7 @@ def server_main(self): class CustomMysqlProtocol(Protocol): _state = None - def connectionMade(self): + def connectionMade(self): # noqa: N802 self._state = 1 self.transport.write(_q_s.greeting()) _q_s.log( @@ -135,7 +114,7 @@ def connectionMade(self): } ) - def dataReceived(self, data): + def dataReceived(self, data): # noqa: N802 try: if self._state == 1: ret_access_denied = False @@ -175,7 +154,7 @@ def dataReceived(self, data): self.transport.write(_q_s.too_many()) self.transport.loseConnection() - def connectionLost(self, reason): + def connectionLost(self, reason): # noqa: N802,ARG002 self._state = None factory = Factory() From 924585502178b0a32e8d3678c213fb6850b88dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 14 Feb 2024 17:42:48 +0100 Subject: [PATCH 27/35] ntp server: ruff fixes --- honeypots/ntp_server.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/honeypots/ntp_server.py b/honeypots/ntp_server.py index e164e8f..9299af0 100644 --- a/honeypots/ntp_server.py +++ b/honeypots/ntp_server.py @@ -11,7 +11,7 @@ """ import struct from contextlib import suppress -from struct import pack, unpack +from struct import pack from time import time from twisted.internet import reactor @@ -41,7 +41,7 @@ def ntp_to_system_time(self, time_): f = float(int(i) & 0xFFFFFFFF) / 4294967296 return i, f - def datagramReceived(self, data, addr): + def datagramReceived(self, data, addr): # noqa: N802 version = "UnKnown" mode = "UnKnown" _q_s.log( @@ -89,7 +89,7 @@ def datagramReceived(self, data, addr): ) reactor.run() - def test_server(self, ip=None, port=None, username=None, password=None): + def test_server(self, ip=None, port=None, username=None, password=None): # noqa: ARG002 with suppress(Exception): from warnings import filterwarnings @@ -100,8 +100,7 @@ def test_server(self, ip=None, port=None, username=None, password=None): _port = port or self.port c = socket(AF_INET, SOCK_DGRAM) c.sendto(b"\x1b" + 47 * b"\0", (_ip, _port)) - data, address = c.recvfrom(256) - ret_time = unpack("!12I", data)[10] - 2208988800 + c.recvfrom(256) c.close() From 76f0934af5da45b63faa7bf9ca0100c23afb6eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Wed, 14 Feb 2024 17:59:00 +0100 Subject: [PATCH 28/35] oracle server: ruff fixes --- honeypots/oracle_server.py | 87 +++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 34 deletions(-) diff --git a/honeypots/oracle_server.py b/honeypots/oracle_server.py index 3e93da7..ead3db6 100644 --- a/honeypots/oracle_server.py +++ b/honeypots/oracle_server.py @@ -10,9 +10,10 @@ // ------------------------------------------------------------- """ +from __future__ import annotations + from contextlib import suppress from re import findall -from struct import unpack from twisted.internet import reactor from twisted.internet.protocol import Factory, Protocol @@ -27,49 +28,56 @@ class QOracleServer(BaseServer): NAME = "oracle_server" DEFAULT_PORT = 1521 - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self class CustomRedisProtocol(Protocol): _state = None - def wrong_password(self): - payload = b"\x02B\xc5\xbb\xe7\x7f\x02B\xac\x11\x00\x02\x08\x00E\x00\x01\x02Md@\x00@\x06\x94l\xac\x11\x00\x02\xac\x11\x00\x01\x05\xf1\xa5\xa8\xab\xf5\xff\x94\x98\xdf\xd5\xa1\x80\x18\x01\xf5Y\x1a\x00\x00\x01\x01\x08\nJ\xe7\xf0,\xb2,\xfe\x08\x00\x00\x00\xce\x06\x00\x00\x00\x00\x00\x04\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\xf9\x03\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x006\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x82\x1c\x86u\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf9\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003ORA-01017: invalid username/password; logon denied\n" - return payload + @staticmethod + def wrong_password() -> bytes: + return ( + b"\x02B\xc5\xbb\xe7\x7f\x02B\xac\x11\x00\x02\x08\x00E\x00\x01\x02Md@\x00@\x06" + b"\x94l\xac\x11\x00\x02\xac\x11\x00\x01\x05\xf1\xa5\xa8\xab\xf5\xff\x94\x98" + b"\xdf\xd5\xa1\x80\x18\x01\xf5Y\x1a\x00\x00\x01\x01\x08\nJ\xe7\xf0,\xb2,\xfe" + b"\x08\x00\x00\x00\xce\x06\x00\x00\x00\x00\x00\x04\x01\x00\x00\x00\x00\x00" + b"\x01\x00\x00\x00\x00\xf9\x03\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x006\x01\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x82\x1c\x86u\x7f\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf9\x03\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x003ORA-01017: " + b"invalid username/password; logon denied\n" + ) - def refuse_payload(self): - payload = b"\x00\x08\x00\x00\x04\x00\x00\x00" - return payload + @staticmethod + def refuse_payload() -> bytes: + return b"\x00\x08\x00\x00\x04\x00\x00\x00" - def parse_payload(self, data): + @staticmethod + def parse_payload(data: bytes) -> tuple[str | None, str | None, str | None]: service_name = None program = None local_user = None with suppress(Exception): - ( - packet_len, - packet_checksum, - packet_type, - packet_reserved_bytes, - packet_header_checksum, - ) = unpack(">hhbbh", data[0:8]) - if b"(DESCRIPTION=" in data: - connect = data[data.index(b"(DESCRIPTION=") :].split(b"\0")[0] - found_temp = findall(rb"[^\(\)]+", connect) - if len(found_temp) > 0: - found_fixed = [item for item in found_temp if not item.endswith(b"=")] - if len(found_fixed) > 0: - for item in found_fixed: - name, value = item.split(b"=") - if name.startswith(b"SERVICE_NAME"): - service_name = value.decode() - elif name.startswith(b"PROGRAM"): - program = value.decode() - elif name.startswith(b"USER"): - local_user = value.decode() + offset = data.index(b"(DESCRIPTION=") + connect = data[offset:].split(b"\0")[0] + attributes = [ + item for item in findall(rb"[^\(\)]+", connect) if not item.endswith(b"=") + ] + for item in attributes: + name, value = item.split(b"=", maxsplit=1) + if name.startswith(b"SERVICE_NAME"): + service_name = value.decode() + elif name.startswith(b"PROGRAM"): + program = value.decode() + elif name.startswith(b"USER"): + local_user = value.decode() return service_name, program, local_user - def connectionMade(self): + def connectionMade(self): # noqa: N802 _q_s.log( { "action": "connection", @@ -78,7 +86,7 @@ def connectionMade(self): } ) - def dataReceived(self, data): + def dataReceived(self, data: bytes): # noqa: N802 service_name, program, local_user = self.parse_payload(data) if service_name or program or local_user: _q_s.log( @@ -101,14 +109,25 @@ def dataReceived(self, data): reactor.listenTCP(port=self.port, factory=factory, interface=self.ip) reactor.run() - def test_server(self, ip=None, port=None, username=None, password=None): + def test_server(self, ip=None, port=None, username=None, password=None): # noqa: ARG002 with suppress(Exception): from warnings import filterwarnings filterwarnings(action="ignore", module=".*socket.*") from socket import socket, AF_INET, SOCK_STREAM - payload = b"\x00\x00\x03\x04\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x01F\xb9\xd9@\x00@\x06\x81\xd6\x7f\x00\x00\x01\x7f\x00\x00\x01\xbf\xce\x06\x13\xacW\xde\xc0Z\xb5\x0cI\x80\x18\x02\x00\xff:\x00\x00\x01\x01\x08\n\x1bdZ^\x1bdZ^\x01\x12\x00\x00\x01\x00\x00\x00\x01>\x01,\x0cA \x00\xff\xff\x7f\x08\x00\x00\x01\x00\x00\xc8\x00J\x00\x00\x14\x00AA\xa7C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x01(DESCRIPTION=(CONNECT_DATA=(SERVICE_NAME=xe)(CID=(PROGRAM=linux_1)(HOST=xxxxxxxxxxxxxx)(USER=xxxxxxxxxxxxxx))(CONNECTION_ID=xxxxxxxxxxxxxxxxxxxxxxxx))(ADDRESS=(PROTOCOL=tcp)(HOST=xxxxxxx)(PORT=xxxx)))" + payload = ( + b"\x00\x00\x03\x04\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x01F\xb9" + b"\xd9@\x00@\x06\x81\xd6\x7f\x00\x00\x01\x7f\x00\x00\x01\xbf\xce\x06\x13\xacW\xde" + b"\xc0Z\xb5\x0cI\x80\x18\x02\x00\xff:\x00\x00\x01\x01\x08\n\x1bdZ^\x1bdZ^\x01\x12" + b"\x00\x00\x01\x00\x00\x00\x01>\x01,\x0cA \x00\xff\xff\x7f\x08\x00\x00\x01\x00" + b"\x00\xc8\x00J\x00\x00\x14\x00AA\xa7C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00 \x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x01(DESCRIPTION=(CONNECT_DATA=(SERVICE_NAME=xe)" + b"(CID=(PROGRAM=linux_1)(HOST=xxxxxxxxxxxxxx)(USER=xxxxxxxxxxxxxx))" + b"(CONNECTION_ID=xxxxxxxxxxxxxxxxxxxxxxxx))(ADDRESS=(PROTOCOL=tcp)" + b"(HOST=xxxxxxx)(PORT=xxxx)))" + ) _ip = ip or self.ip _port = port or self.port c = socket(AF_INET, SOCK_STREAM) From 80c34773414daab9b7a7e66789ec6af4bf76bb6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 15 Feb 2024 09:30:22 +0100 Subject: [PATCH 29/35] pjl server: ruff fixes --- honeypots/pjl_server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/honeypots/pjl_server.py b/honeypots/pjl_server.py index 588a7dc..c45c38a 100644 --- a/honeypots/pjl_server.py +++ b/honeypots/pjl_server.py @@ -54,7 +54,7 @@ def server_main(self): class Custompjlrotocol(Protocol): _state = None - def connectionMade(self): + def connectionMade(self): # noqa: N802 self._state = 1 _q_s.log( { @@ -64,7 +64,7 @@ def connectionMade(self): } ) - def dataReceived(self, data): + def dataReceived(self, data): # noqa: N802 # Control to PJL (Removed) data = data.replace(b"\x1b%-12345X", b"") if data.lower().startswith(b"@pjl echo"): @@ -85,7 +85,7 @@ def dataReceived(self, data): ) self.transport.loseConnection() - def connectionLost(self, reason): + def connectionLost(self, reason): # noqa: N802,ARG002 self._state = None factory = Factory() @@ -93,7 +93,7 @@ def connectionLost(self, reason): reactor.listenTCP(port=self.port, factory=factory, interface=self.ip) reactor.run() - def test_server(self, ip=None, port=None, username=None, password=None): + def test_server(self, ip=None, port=None, username=None, password=None): # noqa: ARG002 with suppress(Exception): from warnings import filterwarnings From ed717d5be73b123a3b435e89901106e9ec6765b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 15 Feb 2024 09:35:46 +0100 Subject: [PATCH 30/35] pop3 server: ruff fixes --- honeypots/pop3_server.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/honeypots/pop3_server.py b/honeypots/pop3_server.py index 06d07ec..b5b7cdd 100644 --- a/honeypots/pop3_server.py +++ b/honeypots/pop3_server.py @@ -9,18 +9,17 @@ // contributors list qeeqbox/honeypots/graphs/contributors // ------------------------------------------------------------- """ + +from __future__ import annotations + from contextlib import suppress -from typing import Tuple from twisted.internet import reactor from twisted.internet.protocol import Factory from twisted.mail.pop3 import POP3, POP3Error from honeypots.base_server import BaseServer -from honeypots.helper import ( - server_arguments, - check_bytes, -) +from honeypots.helper import check_bytes, server_arguments class QPOP3Server(BaseServer): @@ -31,13 +30,13 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.mocking_server = "Microsoft Exchange POP3 service is ready" - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self class CustomPOP3Protocol(POP3): self._user = None - def connectionMade(self): + def connectionMade(self): # noqa: N802 _q_s.log( { "action": "connection", @@ -48,7 +47,7 @@ def connectionMade(self): self._user = None self.successResponse(_q_s.mocking_server) - def processCommand(self, command: bytes, *args): + def processCommand(self, command: bytes, *args): # noqa: N802 with suppress(Exception): if "capture_commands" in _q_s.options: _q_s.log( @@ -74,19 +73,18 @@ def processCommand(self, command: bytes, *args): return None command = command.upper() - authCmd = command in self.AUTH_CMDS - if not self.mbox and not authCmd: + if not self.mbox and command not in self.AUTH_CMDS: raise POP3Error(b"not authenticated yet: cannot do " + command) f = getattr(self, f"do_{check_bytes(command)}", None) if f: return f(*args) raise POP3Error(b"Unknown protocol command: " + command) - def do_USER(self, user): + def do_USER(self, user): # noqa: N802 self._user = user self.successResponse("USER Ok") - def do_PASS(self, password: bytes, *words: Tuple[bytes]): + def do_PASS(self, password: bytes, *words: tuple[bytes]): # noqa: N802 if self._user: username = check_bytes(self._user) password = check_bytes(b" ".join((password, *words))) @@ -102,7 +100,7 @@ class CustomPOP3Factory(Factory): protocol = CustomPOP3Protocol portal = None - def buildProtocol(self, address): + def buildProtocol(self, address): # noqa: N802,ARG002 p = self.protocol() p.portal = self.portal p.factory = self @@ -114,14 +112,13 @@ def buildProtocol(self, address): def test_server(self, ip=None, port=None, username=None, password=None): with suppress(Exception): - from poplib import POP3 as poplibPOP3 + from poplib import POP3 as POP3Client # noqa: N811 _ip = ip or self.ip _port = port or self.port _username = username or self.username _password = password or self.password - pp = poplibPOP3(_ip, _port) - # pp.getwelcome() + pp = POP3Client(_ip, _port) pp.user(_username) pp.pass_(_password) From 2973807ccd46b0215ed8559807ac98b0db213a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 15 Feb 2024 09:49:16 +0100 Subject: [PATCH 31/35] postgres server: ruff fixes --- honeypots/postgres_server.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/honeypots/postgres_server.py b/honeypots/postgres_server.py index 8152056..0a40969 100644 --- a/honeypots/postgres_server.py +++ b/honeypots/postgres_server.py @@ -11,40 +11,39 @@ """ from contextlib import suppress -from struct import unpack from twisted.internet import reactor from twisted.internet.protocol import Factory, Protocol from honeypots.base_server import BaseServer -from honeypots.helper import ( - server_arguments, - check_bytes, -) +from honeypots.helper import check_bytes, server_arguments + +GSSResponse = 112 # password response message class QPostgresServer(BaseServer): NAME = "postgres_server" DEFAULT_PORT = 5432 - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self class CustomPostgresProtocol(Protocol): - _state = None - _variables = {} + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._variables = {} + self._state = None - def read_data_custom(self, data): + def read_data_custom(self, data: bytes): _data = data.decode("utf-8") - length = unpack("!I", data[0:4]) encoded_list = _data[8:-1].split("\x00") self._variables = dict(zip(*([iter(encoded_list)] * 2))) - def read_password_custom(self, data): + def read_password_custom(self, data: bytes): data = data.decode("utf-8") self._variables["password"] = data[5:].split("\x00")[0] - def connectionMade(self): + def connectionMade(self): # noqa: N802 self._state = 1 self._variables = {} _q_s.log( @@ -55,16 +54,17 @@ def connectionMade(self): } ) - def dataReceived(self, data): + def dataReceived(self, data: bytes): # noqa: N802 if self._state == 1: self._state = 2 self.transport.write(b"N") - elif self._state == 2: + elif self._state == 2: # noqa: PLR2004 self.read_data_custom(data) self._state = 3 self.transport.write(b"R\x00\x00\x00\x08\x00\x00\x00\x03") - elif self._state == 3: - if data[0] == 112 and "user" in self._variables: + elif self._state == 3: # noqa: PLR2004 + message_type = data[0] + if message_type == GSSResponse and "user" in self._variables: self.read_password_custom(data) username = check_bytes(self._variables["user"]) password = check_bytes(self._variables["password"]) @@ -74,7 +74,7 @@ def dataReceived(self, data): else: self.transport.loseConnection() - def connectionLost(self, reason): + def connectionLost(self, reason): # noqa: N802,ARG002 self._state = 1 self._variables = {} @@ -91,7 +91,7 @@ def test_server(self, ip=None, port=None, username=None, password=None): _port = port or self.port _username = username or self.username _password = password or self.password - x = connect(host=_ip, port=_port, user=_username, password=_password) + connect(host=_ip, port=_port, user=_username, password=_password) if __name__ == "__main__": From 1c187ef9d528e89a39360e031d54e3f91e78bce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 15 Feb 2024 10:52:56 +0100 Subject: [PATCH 32/35] sniffer: ruff fixes --- honeypots/__main__.py | 2 +- honeypots/qbsniffer.py | 311 ++++++++++++++++++----------------------- 2 files changed, 135 insertions(+), 178 deletions(-) diff --git a/honeypots/__main__.py b/honeypots/__main__.py index 7f79672..b873504 100755 --- a/honeypots/__main__.py +++ b/honeypots/__main__.py @@ -350,7 +350,7 @@ def _setup_logging(self) -> logging.Logger: def _start_sniffer(self, sniffer_filter, sniffer_interface): logger.info("[x] Starting sniffer") sniffer = QBSniffer( - filter=sniffer_filter, + filter_=sniffer_filter, interface=sniffer_interface, config=self.options.config, ) diff --git a/honeypots/qbsniffer.py b/honeypots/qbsniffer.py index 54649af..14ad1cb 100644 --- a/honeypots/qbsniffer.py +++ b/honeypots/qbsniffer.py @@ -10,10 +10,13 @@ // ------------------------------------------------------------- """ +from __future__ import annotations + from binascii import hexlify from multiprocessing import Process -from re import compile as rcompile, search as rsearch +import re from sys import stdout +from typing import Iterable, TYPE_CHECKING from uuid import uuid4 from netifaces import AF_INET, AF_LINK, ifaddresses @@ -22,12 +25,17 @@ from honeypots.helper import server_arguments, setup_logger +TCP_SYN_FLAG = 0b10 + +if TYPE_CHECKING: + from scapy.packet import Packet + class QBSniffer: - def __init__(self, filter=None, interface=None, config=""): + def __init__(self, filter_=None, interface=None, config=""): self.current_ip = ifaddresses(interface)[AF_INET][0]["addr"].encode("utf-8") self.current_mac = ifaddresses(interface)[AF_LINK][0]["addr"].encode("utf-8") - self.filter = filter + self.filter = filter_ self.interface = interface self.method = "TCPUDP" self.ICMP_codes = [ @@ -71,207 +79,156 @@ def __init__(self, filter=None, interface=None, config=""): ] self.allowed_ports = [] self.allowed_ips = [] - self.common = rcompile(rb"pass|user|login") - self.uuid = "honeypotslogger" + "_" + __class__.__name__ + "_" + str(uuid4())[:8] + self.common = re.compile(rb"pass|user|login") + self.uuid = f"honeypotslogger_{__class__.__name__}_{str(uuid4())[:8]}" self.config = config if config: self.logs = setup_logger(__class__.__name__, self.uuid, config) else: self.logs = setup_logger(__class__.__name__, self.uuid, None) - def find_ICMP(self, x1, x2): + def find_icmp(self, x1, x2): for _ in self.ICMP_codes: if x1 == _[0] and x2 == _[1]: return _[2] return "None" - def get_layers(self, packet): + @staticmethod + def get_layers(packet: Packet) -> Iterable[str]: try: yield packet.name while packet.payload: packet = packet.payload yield packet.name - except BaseException: + except AttributeError: pass def scapy_sniffer_main(self): - _q_s = self - - def capture_logic(packet): - _layers, hex_payloads, raw_payloads, _fields, _raw, _hex = ( - [], - {}, - {}, - {}, - "None", - "None", - ) - _layers = list(self.get_layers(packet)) - for layer in _layers: - try: - _fields[layer] = packet[layer].fields - if "load" in _fields[layer]: - raw_payloads[layer] = _fields[layer]["load"] - hex_payloads[layer] = hexlify(_fields[layer]["load"]) - if rsearch(self.common, raw_payloads[layer]): - _q_s.logs.info( - [ - "sniffer", - {"action": "creds_check", "payload": raw_payloads[layer]}, - ] - ) - except Exception as e: - _q_s.logs.error( - ["errors", {"error": "capture_logic_1", "type": "error -> " + repr(e)}] - ) + sniff(filter=self.filter, iface=self.interface, prn=self.capture_logic) + def _get_payloads(self, layers: list[str], packet: Packet): + hex_payloads, raw_payloads, _fields = {}, {}, {} + for layer in layers: try: - if _q_s.method == "ALL": - try: - _q_s.logs.info( - [ - "sniffer", - { - "action": "all", - "ip": _q_s.current_ip, - "mac": _q_s.current_mac, - "layers": _layers, - "fields": _fields, - "payload": hex_payloads, - }, - ] - ) - except Exception as e: - _q_s.logs.error( - ["errors", {"error": "capture_logic_2", "type": "error -> " + repr(e)}] - ) - elif _q_s.method == "TCPUDP": - if ( - packet.haslayer("IP") - and len(hex_payloads) > 0 - and packet["IP"].src != _q_s.current_ip - ): - if packet.haslayer("TCP"): - try: - _q_s.logs.info( - [ - "sniffer", - { - "action": "tcppayload", - "ip": _q_s.current_ip, - "mac": _q_s.current_mac, - "dest_ip": packet["IP"].src, - "dest_port": packet["TCP"].sport, - "dst_ip": packet["IP"].dst, - "dst_port": packet["TCP"].dport, - "raw_payload": raw_payloads, - "payload": hex_payloads, - }, - ] - ) - except Exception as e: - _q_s.logs.error( - [ - "errors", - { - "error": "capture_logic_3", - "type": "error -> " + repr(e), - }, - ] - ) - elif packet.haslayer("UDP"): - try: - _q_s.logs.info( - [ - "sniffer", - { - "action": "udppayload", - "ip": _q_s.current_ip, - "mac": _q_s.current_mac, - "dest_ip": packet["IP"].src, - "dest_port": packet["UDP"].sport, - "dst_ip": packet["IP"].dst, - "dst_port": packet["UDP"].dport, - "raw_payload": raw_payloads, - "payload": hex_payloads, - }, - ] - ) - except Exception as e: - _q_s.logs.error( - [ - "errors", - { - "error": "capture_logic_4", - "type": "error -> " + repr(e), - }, - ] - ) - - if ( - packet.haslayer("IP") - and packet.haslayer("ICMP") - and packet["IP"].src != _q_s.current_ip - ): - _q_s.logs.info( - [ - "sniffer", + _fields[layer] = packet[layer].fields + if "load" in _fields[layer]: + raw_payloads[layer] = _fields[layer]["load"] + hex_payloads[layer] = hexlify(_fields[layer]["load"]) + if re.search(self.common, raw_payloads[layer]): + self._log( { - "action": "icmp", - "ip": _q_s.current_ip, - "mac": _q_s.current_mac, - "dest_ip": packet["IP"].src, - "dst_ip": packet["IP"].dst, - "ICMP_Code": packet["ICMP"].code, - "ICMP_Type": packet["ICMP"].type, - "ICMP_MSG": self.find_ICMP( - packet["ICMP"].type, packet["ICMP"].code - ), + "action": "creds_check", + "payload": raw_payloads[layer], }, - ] - ) - - if ( - packet.haslayer("IP") - and packet.haslayer("TCP") - and packet["IP"].src != _q_s.current_ip - ): - if packet["TCP"].flags == 2: - _q_s.logs.info( - [ - "sniffer", - { - "action": "tcpscan", - "ip": _q_s.current_ip, - "mac": _q_s.current_mac, - "dest_ip": packet["IP"].src, - "dest_port": packet["TCP"].sport, - "dst_ip": packet["IP"].dst, - "dst_port": packet["TCP"].dport, - "raw_payload": raw_payloads, - "payload": hex_payloads, - }, - ] - ) - send( - IP(dst=packet["IP"].src, src=packet["IP"].dst) - / TCP( - dport=packet["TCP"].sport, - sport=packet["TCP"].dport, - ack=(packet["TCP"].seq + 1), - flags="SA", - ), - verbose=False, ) + except Exception as error: + self._log_error(error, 1) + return _fields, hex_payloads, raw_payloads + + def capture_logic(self, packet: Packet): + _layers: list[str] = list(self.get_layers(packet)) + _fields, hex_payloads, raw_payloads = self._get_payloads(_layers, packet) - except Exception as e: - _q_s.logs.error( - ["errors", {"error": "capture_logic_5", "type": "error -> " + repr(e)}] + try: + if self.method == "ALL": + try: + self._log( + { + "action": "all", + "layers": _layers, + "fields": _fields, + "payload": hex_payloads, + }, + ) + except Exception as error: + self._log_error(error, 2) + elif ( + self.method == "TCPUDP" + and packet.haslayer("IP") + and len(hex_payloads) > 0 + and packet["IP"].src != self.current_ip + ): + self._log_tcp_udp(packet, hex_payloads, raw_payloads) + + if ( + packet.haslayer("IP") + and packet.haslayer("ICMP") + and packet["IP"].src != self.current_ip + ): + self._log( + { + "action": "icmp", + "dest_ip": packet["IP"].src, + "dst_ip": packet["IP"].dst, + "ICMP_Code": packet["ICMP"].code, + "ICMP_Type": packet["ICMP"].type, + "ICMP_MSG": self.find_icmp(packet["ICMP"].type, packet["ICMP"].code), + }, ) - stdout.flush() + if ( + packet.haslayer("IP") + and packet.haslayer("TCP") + and packet["IP"].src != self.current_ip + and packet["TCP"].flags == TCP_SYN_FLAG + ): + self._handle_tcp_scan(packet, hex_payloads, raw_payloads) + + except Exception as error: + self._log_error(error, 5) + + stdout.flush() + + def _handle_tcp_scan(self, packet: Packet, hex_payloads: dict, raw_payloads: dict): + self._log( + { + "action": "tcpscan", + "dest_ip": packet["IP"].src, + "dest_port": packet["TCP"].sport, + "dst_ip": packet["IP"].dst, + "dst_port": packet["TCP"].dport, + "raw_payload": raw_payloads, + "payload": hex_payloads, + }, + ) + ip_pkg = IP(dst=packet["IP"].src, src=packet["IP"].dst) + tcp_pkg = TCP( + dport=packet["TCP"].sport, + sport=packet["TCP"].dport, + ack=(packet["TCP"].seq + 1), + flags="SA", + ) + send(ip_pkg / tcp_pkg, verbose=False) - sniff(filter=self.filter, iface=self.interface, prn=capture_logic) + def _log_tcp_udp(self, packet: Packet, hex_payloads: dict, raw_payloads: dict): + for layer in ["TCP", "UDP"]: + if packet.haslayer(layer): + try: + self._log( + { + "action": f"{layer.lower()}payload", + "dest_ip": packet["IP"].src, + "dest_port": packet[layer].sport, + "dst_ip": packet["IP"].dst, + "dst_port": packet[layer].dport, + "raw_payload": raw_payloads, + "payload": hex_payloads, + }, + ) + except Exception as error: + self._log_error(error, 3) + + def _log(self, log_data: dict): + log_data.update({"ip": self.current_ip, "mac": self.current_mac}) + self.logs.info(["sniffer", log_data]) + + def _log_error(self, error: Exception, _id: int): + self.logs.error( + [ + "errors", + {"error": f"capture_logic_{_id}", "type": f"error -> {error!r}"}, + ] + ) def run_sniffer(self, process=None): if process: @@ -289,6 +246,6 @@ def kill_sniffer(self): parsed = server_arguments() if parsed.docker or parsed.aws or parsed.custom: qsniffer = QBSniffer( - filter=parsed.filter, interface=parsed.interface, config=parsed.config + filter_=parsed.filter, interface=parsed.interface, config=parsed.config ) qsniffer.run_sniffer() From 8e3478a87e9b69fa1ac2baa5c28c920dcd766307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 15 Feb 2024 10:55:59 +0100 Subject: [PATCH 33/35] testing module: ruff fix --- honeypots/testing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/honeypots/testing.py b/honeypots/testing.py index ac362ac..d17a3c2 100644 --- a/honeypots/testing.py +++ b/honeypots/testing.py @@ -1,3 +1,5 @@ +import sys + import honeypots from time import sleep from pkg_resources import get_distribution @@ -13,4 +15,4 @@ sleep(2) temp_server.kill_server() honeypots.clean_all() -exit() +sys.exit() From c8d906e8db96ce259cc809e392a07d97a5ecc80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 15 Feb 2024 10:58:39 +0100 Subject: [PATCH 34/35] speed up tests by running them in parallel --- .github/workflows/tests.yml | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cc726ab..b7219ca 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,4 +26,4 @@ jobs: - name: Installation run: python -m pip install ".[dev]" - name: Unit Tests - run: pytest -v ./tests + run: pytest -v -n 3 ./tests diff --git a/pyproject.toml b/pyproject.toml index c08897a..ba5ce36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ dev = [ "pymssql", "pysnmplib", "pytest", + "pytest-xdist==3.5.0", "redis", "redis", "vncdotool", From ab9965aff9cb84a1d480e57dd8c3417f4164257a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Stucke?= Date: Thu, 21 Mar 2024 16:00:03 +0100 Subject: [PATCH 35/35] only run the pre-commit action for PRs and not on push --- .github/workflows/pre-commit.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 505623a..9a0a630 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -2,8 +2,6 @@ name: pre-commit on: pull_request: - push: - branches: [main] jobs: lint: