diff --git a/honeypots/base_http_server.py b/honeypots/base_http_server.py index 9207663..79974e9 100644 --- a/honeypots/base_http_server.py +++ b/honeypots/base_http_server.py @@ -39,11 +39,11 @@ def __init__(self, **kwargs): ) class MainResource(Resource): - isLeaf = True + isLeaf = True # noqa: N815 home_file = load_template("home.html") login_file = load_template("login.html") - def __init__(self, *args, hp_server=None, **kwargs): + def __init__(self, *args, hp_server: BaseHttpServer = None, **kwargs): super().__init__(*args, **kwargs) self.hp_server = hp_server self.headers = {} @@ -53,30 +53,24 @@ def render(self, request): with suppress(Exception): log_data = { - "server": self.hp_server.NAME, "action": "connection", "src_ip": client_ip, "src_port": request.getClientAddress().port, - "dest_ip": self.hp_server.ip, - "dest_port": self.hp_server.port, } if "capture_commands" in self.hp_server.options: log_data["data"] = headers - self.hp_server.logs.info(log_data) + self.hp_server.log(log_data) if self.hp_server.mocking_server != "": request.responseHeaders.removeHeader("Server") request.responseHeaders.addRawHeader("Server", self.hp_server.mocking_server) if request.method in (b"GET", b"POST"): - self.hp_server.logs.info( + self.hp_server.log( { - "server": self.hp_server.NAME, "action": request.method.decode(), "src_ip": client_ip, "src_port": request.getClientAddress().port, - "dest_ip": self.hp_server.ip, - "dest_port": self.hp_server.port, } ) @@ -115,7 +109,7 @@ def render(self, request): if "username" in form and "password" in form: username = check_bytes(form["username"].value) password = check_bytes(form["password"].value) - self.hp_server.log_login( + self.hp_server.check_login( username, password, client_ip, request.getClientAddress().port ) diff --git a/honeypots/base_server.py b/honeypots/base_server.py index 074fcbd..1faea65 100644 --- a/honeypots/base_server.py +++ b/honeypots/base_server.py @@ -5,6 +5,7 @@ from os import getenv from shlex import split from subprocess import Popen +from typing import Any from uuid import uuid4 from honeypots.helper import ( @@ -96,15 +97,12 @@ def run_server(self, process: bool = False, auto: bool = False) -> bool | None: if self.process.poll() is None and check_if_server_is_running(self.uuid): status = "success" - self.logs.info( + self.log( { - "server": self.NAME, "action": "process", "status": status, "src_ip": self.ip, "src_port": self.port, - "dest_ip": self.ip, - "dest_port": self.port, } ) @@ -113,21 +111,29 @@ def run_server(self, process: bool = False, auto: bool = False) -> bool | None: self.kill_server() return False - def log_login(self, username: str, password: str, ip: str, port: int): + def check_login(self, username: str, password: str, ip: str, port: int) -> bool: status = "success" if self._login_is_correct(username, password) else "failed" - self.logs.info( + self.log( { - "server": self.NAME, "action": "login", "status": status, "src_ip": ip, "src_port": port, "username": username, "password": password, - "dest_ip": self.ip, - "dest_port": self.port, } ) + return status == "success" def _login_is_correct(self, username: str, password: str) -> bool: return username == self.username and password == self.password + + def log(self, log_data: dict[str, Any]): + log_data.update( + { + "server": self.NAME, + "dest_ip": self.ip, + "dest_port": self.port, + } + ) + self.logs.info(log_data) diff --git a/honeypots/dhcp_server.py b/honeypots/dhcp_server.py index 8aabb9f..f26b0b6 100644 --- a/honeypots/dhcp_server.py +++ b/honeypots/dhcp_server.py @@ -17,10 +17,7 @@ from twisted.internet.protocol import DatagramProtocol from honeypots.base_server import BaseServer -from honeypots.helper import ( - server_arguments, - check_bytes, -) +from honeypots.helper import check_bytes, server_arguments class QDHCPServer(BaseServer): @@ -96,15 +93,12 @@ def datagramReceived(self, data, addr): mac_address = "None" data = self.parse_options(data[240:]) data.update({"mac_address": mac_address}) - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "query", "status": "success", "src_ip": addr[0], "src_port": addr[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "data": data, } ) diff --git a/honeypots/dns_server.py b/honeypots/dns_server.py index 48aaeb0..894f1de 100644 --- a/honeypots/dns_server.py +++ b/honeypots/dns_server.py @@ -53,14 +53,11 @@ def gotResolverResponse(self, response, protocol, message, address): src_ip, src_port = address for items in response: for item in items: - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "query", "src_ip": src_ip, "src_port": src_port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "data": item.payload, } ) @@ -68,14 +65,11 @@ def gotResolverResponse(self, response, protocol, message, address): class CustomDnsUdpProtocol(dns.DNSDatagramProtocol): def datagramReceived(self, data: bytes, addr: tuple[str, int]): - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": addr[0], "src_port": addr[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "data": data.decode(errors="replace"), } ) diff --git a/honeypots/elastic_server.py b/honeypots/elastic_server.py index d126a58..14efa25 100644 --- a/honeypots/elastic_server.py +++ b/honeypots/elastic_server.py @@ -44,15 +44,12 @@ def _dump_headers(self): for item, value in dict(self.headers).items(): headers.update({check_bytes(item): check_bytes(value)}) - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "dump", "data": check_bytes(self.raw_requestline), "src_ip": self.client_address[0], "src_port": self.client_address[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "headers": headers, } ) @@ -109,15 +106,12 @@ def do_GET(self): key = self.server.get_auth_key() if self.headers.get("Authorization") is None: - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "login", "status": "failed", "src_ip": self.client_address[0], "src_port": self.client_address[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "username": username, "password": password, } @@ -129,18 +123,14 @@ def do_GET(self): "root_cause": [ { "type": "security_exception", - "reason": "unable to authenticate user [{}] for REST request [/]".format( - username - ), + "reason": f"unable to authenticate user [{username}] for REST request [/]", "header": { "WWW-Authenticate": 'Basic realm="security" charset="UTF-8"' }, } ], "type": "security_exception", - "reason": "unable to authenticate user [{}] for REST request [/]".format( - username - ), + "reason": f"unable to authenticate user [{username}] for REST request [/]", "header": { "WWW-Authenticate": 'Basic realm="security" charset="UTF-8"' }, @@ -152,16 +142,12 @@ def do_GET(self): ) self.wfile.write(self._set_response_gzip_auth(auth_paylaod, 401)) elif self.headers.get("Authorization") == "Basic " + str(key): - extracted = "" - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "login", "status": "success", "src_ip": self.client_address[0], "src_port": self.client_address[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "username": _q_s.username, "password": _q_s.password, } @@ -336,15 +322,12 @@ def do_GET(self): authorization_string = self.headers.get("Authorization").split(" ") basic = b64decode(authorization_string[1]).decode("utf-8") username, password = basic.split(":") - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "login", "status": "failed", "src_ip": self.client_address[0], "src_port": self.client_address[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "username": username, "password": password, } @@ -387,14 +370,11 @@ def log_message(self, format, *args): return def handle_one_request(self): - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.client_address[0], "src_port": self.client_address[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) return SimpleHTTPRequestHandler.handle_one_request(self) diff --git a/honeypots/ftp_server.py b/honeypots/ftp_server.py index 826e5d0..af4b2e5 100644 --- a/honeypots/ftp_server.py +++ b/honeypots/ftp_server.py @@ -80,21 +80,16 @@ def requestAvatarId(self, credentials): username = check_bytes(credentials.username) password = check_bytes(credentials.password) if username == _q_s.username and password == _q_s.password: - username = _q_s.username - password = _q_s.password return defer.succeed(credentials.username) return defer.fail(UnauthorizedLogin()) class CustomFTPProtocol(FTP): def connectionMade(self): - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) self.state = self.UNAUTH @@ -104,9 +99,8 @@ def connectionMade(self): def processCommand(self, cmd, *params): with suppress(Exception): if "capture_commands" in _q_s.options: - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "command", "data": { "cmd": check_bytes(cmd.upper()), @@ -114,8 +108,6 @@ def processCommand(self, cmd, *params): }, "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) return super().processCommand(cmd, *params) @@ -123,22 +115,8 @@ def processCommand(self, cmd, *params): def ftp_PASS(self, password): username = check_bytes(self._user) password = check_bytes(password) - status = "failed" - if username == _q_s.username and password == _q_s.password: - status = "success" - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.transport.getPeer().host, - "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "username": username, - "password": password, - } - ) + peer = self.transport.getPeer() + _q_s.check_login(username, password, ip=peer.host, port=peer.port) if self.factory.allowAnonymous and self._user == self.factory.userAnonymous: creds = credentials.Anonymous() diff --git a/honeypots/http_proxy_server.py b/honeypots/http_proxy_server.py index fceca05..cff4f55 100644 --- a/honeypots/http_proxy_server.py +++ b/honeypots/http_proxy_server.py @@ -62,14 +62,11 @@ def resolve_domain(self, request_string): _, parsed_request = request_string.split(b"\r\n", 1) headers = BytesParser().parsebytes(parsed_request) host = headers["host"].split(":") - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "query", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "data": host[0], } ) @@ -77,14 +74,11 @@ def resolve_domain(self, request_string): return None def dataReceived(self, data): # noqa: N802 - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) ip = self.resolve_domain(data) diff --git a/honeypots/imap_server.py b/honeypots/imap_server.py index 623b605..bac350a 100644 --- a/honeypots/imap_server.py +++ b/honeypots/imap_server.py @@ -64,9 +64,8 @@ def parse_command(self, line): with suppress(Exception): if "capture_commands" in _q_s.options: - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "command", "data": { "cmd": check_bytes(cmd), @@ -75,8 +74,6 @@ def parse_command(self, line): }, "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) @@ -90,14 +87,11 @@ def parse_command(self, line): self.sendNegativeResponse(tag, "Illegal mailbox name: " + str(e)) def connectionMade(self): - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) self.sendPositiveResponse(message=_q_s.mocking_server) @@ -105,25 +99,8 @@ def connectionMade(self): def authenticateLogin(self, user, passwd): username = check_bytes(user) password = check_bytes(passwd) - status = "failed" - if username == _q_s.username and password == _q_s.password: - username = _q_s.username - password = _q_s.password - status = "success" - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.transport.getPeer().host, - "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "username": username, - "password": password, - } - ) - + peer = self.transport.getPeer() + _q_s.check_login(username, password, ip=peer.host, port=peer.port) raise cred.error.UnauthorizedLogin() def lineReceived(self, line): diff --git a/honeypots/ipp_server.py b/honeypots/ipp_server.py index 6161c9e..80cc967 100644 --- a/honeypots/ipp_server.py +++ b/honeypots/ipp_server.py @@ -201,16 +201,13 @@ def render_POST(self, request): with suppress(Exception): log_data = { - "server": _q_s.NAME, "action": "connection", "src_ip": client_ip, "src_port": request.getClientAddress().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } if "capture_commands" in _q_s.options: log_data["data"] = headers - _q_s.logs.info(log_data) + _q_s.log(log_data) data = request.content.read() @@ -270,15 +267,12 @@ def render_POST(self, request): if response[-1] == "|": response = response[0:-1] if len(response) > 0: - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "query", "status": status, "src_ip": client_ip, "src_port": request.getClientAddress().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "data": {"request": response}, } ) diff --git a/honeypots/irc_server.py b/honeypots/irc_server.py index a8b9f42..8dee8b6 100644 --- a/honeypots/irc_server.py +++ b/honeypots/irc_server.py @@ -32,22 +32,18 @@ def server_main(self): class CustomIRCProtocol(service.IRCUser): def connectionMade(self): - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) def handleCommand(self, command, prefix, params): if "capture_commands" in _q_s.options: - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "command", "data": { "command": check_bytes(command), @@ -56,8 +52,6 @@ def handleCommand(self, command, prefix, params): }, "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) service.IRCUser.handleCommand(self, command, prefix, params) @@ -74,25 +68,10 @@ def irc_unknown(self, prefix, command, params): pass def irc_NICK(self, prefix, params): - status = False username = check_bytes("".join(params)) password = check_bytes(self.password) - if password == check_bytes(_q_s.password): - if username == _q_s.username: - status = True - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.transport.getPeer().host, - "src_port": self.transport.getPeer().port, - "username": username, - "password": password, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - } - ) + peer = self.transport.getPeer() + _q_s.check_login(username, password, ip=peer.host, port=peer.port) factory = Factory() factory.protocol = CustomIRCProtocol diff --git a/honeypots/ldap_server.py b/honeypots/ldap_server.py index 4a07c36..26db9d8 100644 --- a/honeypots/ldap_server.py +++ b/honeypots/ldap_server.py @@ -37,14 +37,11 @@ class CustomLDAProtocol(Protocol): def connectionMade(self): self._state = 1 - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) @@ -98,23 +95,8 @@ def dataReceived(self, data): def _check_login(self, data): username, password = self.parse_ldap_packet(data) if username != "" or password != "": - if username == _q_s.username and password == _q_s.password: - status = "success" - else: - status = "failed" - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.transport.getPeer().host, - "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "username": username, - "password": password, - } - ) + peer = self.transport.getPeer() + _q_s.check_login(username, password, ip=peer.host, port=peer.port) def connectionLost(self, reason): self._state = None diff --git a/honeypots/memcache_server.py b/honeypots/memcache_server.py index c2f774c..1aa48cf 100644 --- a/honeypots/memcache_server.py +++ b/honeypots/memcache_server.py @@ -136,14 +136,11 @@ def get_key(self, key): return ret def connectionMade(self): - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) @@ -162,14 +159,11 @@ def dataReceived(self, data): else: self.transport.write(b"ERROR\r\n") if _data[0] != b"": - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": _data[0].decode(), "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) self.transport.loseConnection() diff --git a/honeypots/mssql_server.py b/honeypots/mssql_server.py index 8564a4b..486ec7d 100644 --- a/honeypots/mssql_server.py +++ b/honeypots/mssql_server.py @@ -39,7 +39,6 @@ class CustomMSSQLProtocol(Protocol): _state = None def create_payload(self, server_name=b"", token_error_msg=b"", error_code=2): - ret = "040100c000350100aaa80002000000010e440041006e0020006500720072006f007200200068006100730020006f00630063007500720072006500640020007700680069006c0065002000650073007400610062006c0069007300680069006e00670020006100200063006f006e006e0065006300740069006f006e00200074006f00200074006800650020007300650072007600650072002e00095200260044006200610063006b00750070000001000000fd020000000000000000000000" with suppress(Exception): if server_name == b"": server_name = b"R&Dbackup" @@ -70,23 +69,19 @@ def create_payload(self, server_name=b"", token_error_msg=b"", error_code=2): data_stream = ( b"0401007600350100aa" + token_error_len + token_error_hex + token_done_hex ) - ret = ( + return ( data_stream[0:4] + hexlify(pack(">H", len(unhexlify(data_stream)))) + data_stream[8:] ) - return ret def connectionMade(self): self._state = 1 - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) @@ -118,24 +113,8 @@ def dataReceived(self, data): ) username = check_bytes(username) password = check_bytes(password_decrypted) - status = "failed" - if username == _q_s.username and password == _q_s.password: - username = _q_s.username - password = _q_s.password - status = "success" - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.transport.getPeer().host, - "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "username": username, - "password": password, - } - ) + peer = self.transport.getPeer() + _q_s.check_login(username, password, ip=peer.host, port=peer.port) self.transport.write( unhexlify( diff --git a/honeypots/mysql_server.py b/honeypots/mysql_server.py index 006b081..0abeef1 100644 --- a/honeypots/mysql_server.py +++ b/honeypots/mysql_server.py @@ -94,7 +94,6 @@ def access_denied(self): return string_ def parse_data(self, data): - username, password = "", "" with suppress(Exception): username_len = data[36:].find(b"\x00") username = data[36:].split(b"\x00")[0] @@ -113,8 +112,7 @@ def decode(self, hash): hash1 = sha1(word).digest() hash2 = sha1(hash1).digest() encrypted = [ - ((a) ^ (b)) - for a, b in zip(hash1, sha1(b"12345678123456789012" + hash2).digest()) + (a ^ b) for a, b in zip(hash1, sha1(b"12345678123456789012" + hash2).digest()) ] if encrypted == list([(i) for i in hash]): return temp @@ -129,14 +127,11 @@ class CustomMysqlProtocol(Protocol): def connectionMade(self): self._state = 1 self.transport.write(_q_s.greeting()) - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) @@ -159,17 +154,14 @@ def dataReceived(self, data): else: ret_access_denied = True password = ":".join(hex(c)[2:] for c in data) - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "login", "status": status, "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, "username": username, "password": password, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) @@ -199,7 +191,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 - cnx = mysqlconnect( + mysqlconnect( user=_username, password=_password, host=_ip, diff --git a/honeypots/ntp_server.py b/honeypots/ntp_server.py index 0c84352..b110402 100644 --- a/honeypots/ntp_server.py +++ b/honeypots/ntp_server.py @@ -44,9 +44,8 @@ def ntp_to_system_time(self, time_): def datagramReceived(self, data, addr): version = "UnKnown" mode = "UnKnown" - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": addr[0], "src_port": addr[1], @@ -75,15 +74,12 @@ def datagramReceived(self, data, addr): except (struct.error, TypeError, IndexError): status = "failed" - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "query", "status": status, "src_ip": addr[0], "src_port": addr[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "data": {"version": version, "mode": mode}, } ) diff --git a/honeypots/oracle_server.py b/honeypots/oracle_server.py index ed8fff5..3e93da7 100644 --- a/honeypots/oracle_server.py +++ b/honeypots/oracle_server.py @@ -70,28 +70,22 @@ def parse_payload(self, data): return service_name, program, local_user def connectionMade(self): - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) def dataReceived(self, data): service_name, program, local_user = self.parse_payload(data) if service_name or program or local_user: - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "login", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "data": { "local_user": local_user, "program": program, diff --git a/honeypots/pjl_server.py b/honeypots/pjl_server.py index ab9b27c..588a7dc 100644 --- a/honeypots/pjl_server.py +++ b/honeypots/pjl_server.py @@ -56,14 +56,11 @@ class Custompjlrotocol(Protocol): def connectionMade(self): self._state = 1 - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) @@ -77,15 +74,12 @@ def dataReceived(self, data): elif data.lower().startswith(b"@pjl prodinfo"): prodinfo = "\r\n".join([k + " = " + v for k, v in _q_s.template.items()]) self.transport.write(prodinfo.encode("utf-8") + b"\x1b") - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "query", "status": "success", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "data": {"command": check_bytes(data)}, } ) diff --git a/honeypots/pop3_server.py b/honeypots/pop3_server.py index 380a42b..06d07ec 100644 --- a/honeypots/pop3_server.py +++ b/honeypots/pop3_server.py @@ -10,7 +10,6 @@ // ------------------------------------------------------------- """ from contextlib import suppress -from random import choice from typing import Tuple from twisted.internet import reactor @@ -30,7 +29,7 @@ class QPOP3Server(BaseServer): def __init__(self, **kwargs): super().__init__(**kwargs) - self.mocking_server = choice(["Microsoft Exchange POP3 service is ready"]) + self.mocking_server = "Microsoft Exchange POP3 service is ready" def server_main(self): _q_s = self @@ -39,25 +38,21 @@ class CustomPOP3Protocol(POP3): self._user = None def connectionMade(self): - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) self._user = None - self.successResponse(f"{_q_s.mocking_server}") + self.successResponse(_q_s.mocking_server) def processCommand(self, command: bytes, *args): with suppress(Exception): if "capture_commands" in _q_s.options: - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "command", "data": { "cmd": check_bytes(command), @@ -65,8 +60,6 @@ def processCommand(self, command: bytes, *args): }, "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) @@ -96,25 +89,9 @@ def do_USER(self, user): def do_PASS(self, password: bytes, *words: Tuple[bytes]): if self._user: username = check_bytes(self._user) - password = check_bytes(b" ".join((password,) + words)) - status = "failed" - if username == _q_s.username and password == _q_s.password: - username = _q_s.username - password = _q_s.password - status = "success" - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.transport.getPeer().host, - "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "username": username, - "password": password, - } - ) + password = check_bytes(b" ".join((password, *words))) + peer = self.transport.getPeer() + _q_s.check_login(username, password, ip=peer.host, port=peer.port) self.failResponse("Authentication failed") else: self.failResponse("USER first, then PASS") diff --git a/honeypots/postgres_server.py b/honeypots/postgres_server.py index 2b84d25..8152056 100644 --- a/honeypots/postgres_server.py +++ b/honeypots/postgres_server.py @@ -47,14 +47,11 @@ def read_password_custom(self, data): def connectionMade(self): self._state = 1 self._variables = {} - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) @@ -71,25 +68,8 @@ def dataReceived(self, data): self.read_password_custom(data) username = check_bytes(self._variables["user"]) password = check_bytes(self._variables["password"]) - status = "failed" - if username == _q_s.username and password == _q_s.password: - username = _q_s.username - password = _q_s.password - status = "success" - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.transport.getPeer().host, - "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "username": username, - "password": password, - } - ) - + peer = self.transport.getPeer() + _q_s.check_login(username, password, ip=peer.host, port=peer.port) self.transport.loseConnection() else: self.transport.loseConnection() diff --git a/honeypots/rdp_server.py b/honeypots/rdp_server.py index 95920ad..be5698f 100644 --- a/honeypots/rdp_server.py +++ b/honeypots/rdp_server.py @@ -90,14 +90,11 @@ def run(self): rdpsnd = False initiator = b"\x00\x06" with suppress(Exception): - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.sock.getpeername()[0], "src_port": self.sock.getpeername()[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) # Client X.224 Connection Request PDU @@ -105,15 +102,12 @@ def run(self): data = self.sock.recv(1024) if b"Cookie" in data: cookie = self.extract_cookie(data[11:]).decode(errors="replace") - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "stshash", "mstshash": "success", "src_ip": self.sock.getpeername()[0], "src_port": self.sock.getpeername()[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "data": {"stshash": cookie}, } ) @@ -188,24 +182,13 @@ def run(self): data = self.sock.recv(1024) if len(data) > 14: if data[15] == 64: - status = "failed" username, password = self.extract_creds(data) - username = check_bytes(username) - password = check_bytes(password) - if username == _q_s.username and password == _q_s.password: - status = "success" - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.sock.getpeername()[0], - "src_port": self.sock.getpeername()[1], - "username": username, - "password": password, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - } + peer = self.sock.getpeername() + _q_s.check_login( + check_bytes(username), + check_bytes(password), + ip=peer.host, + port=peer.port, ) break else: diff --git a/honeypots/redis_server.py b/honeypots/redis_server.py index ea39566..70c9d8d 100644 --- a/honeypots/redis_server.py +++ b/honeypots/redis_server.py @@ -58,38 +58,19 @@ def parse_data(self, c, data): 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: - username = check_bytes(username) - password = check_bytes(password) - status = "failed" - if username == _q_s.username and password == _q_s.password: - username = _q_s.username - password = _q_s.password - status = "success" - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.transport.getPeer().host, - "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "username": username, - "password": password, - } + peer = self.transport.getPeer() + _q_s.check_login( + check_bytes(username), check_bytes(password), ip=peer.host, port=peer.port ) def connectionMade(self): self._state = 1 self._variables = {} - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) diff --git a/honeypots/sip_server.py b/honeypots/sip_server.py index cbdf9de..87157d5 100644 --- a/honeypots/sip_server.py +++ b/honeypots/sip_server.py @@ -33,9 +33,8 @@ class CustomSIPServer(Base): def handle_request(self, message, addr): headers = {} - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": addr[0], "src_port": addr[1], @@ -45,9 +44,8 @@ def handle_request(self, message, addr): for item, value in message.headers.items(): headers.update({check_bytes(item): ",".join(map(check_bytes, value))}) - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "request", "src_ip": addr[0], "src_port": addr[1], diff --git a/honeypots/smb_server.py b/honeypots/smb_server.py index 92177ec..96b2a06 100644 --- a/honeypots/smb_server.py +++ b/honeypots/smb_server.py @@ -51,30 +51,24 @@ def write(self, message): or "AUTHENTICATE_MESSAGE" in message.strip() or "authenticated successfully" in message.strip() ): - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "data": message.strip(), "src_ip": ip, "src_port": port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) elif ":aaaaaaaaaaaaaaaa:" in message.strip(): parsed = message.strip().split(":") if len(parsed) == 6: username, _, _, _, nt_res_1, nt_res_2 = parsed - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "login", "username": username, "src_ip": ip, "src_port": port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "data": {"nt_data_1": nt_res_1, "nt_data_2": nt_res_2}, } ) diff --git a/honeypots/smtp_server.py b/honeypots/smtp_server.py index c92da1b..aaae143 100644 --- a/honeypots/smtp_server.py +++ b/honeypots/smtp_server.py @@ -43,28 +43,22 @@ def found_terminator(self): if len(line.split(" ")) > 2: data = line.split(" ", 2)[2] if command != "HELO" and command != "EHLO": - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.addr[0], "src_port": self.addr[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "data": {"command": command, "arg": arg, "data": data}, } ) super().found_terminator() def smtp_EHLO(self, arg): - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.addr[0], "src_port": self.addr[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) if not arg: @@ -86,22 +80,7 @@ def smtp_AUTH(self, arg): .decode("utf-8", errors="replace") .split("\0") ) - status = "failed" - if username == _q_s.username and password == _q_s.password: - status = "success" - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.addr[0], - "src_port": self.addr[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "username": username, - "password": password, - } - ) + _q_s.check_login(username, password, *self.addr) self.push("235 Authentication successful") @@ -116,14 +95,11 @@ def process_message( def handle_accept(self): conn, addr = self.accept() - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": addr[0], "src_port": addr[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) CustomSMTPChannel(self, conn, addr) diff --git a/honeypots/snmp_server.py b/honeypots/snmp_server.py index 5d72b3b..0b099ba 100644 --- a/honeypots/snmp_server.py +++ b/honeypots/snmp_server.py @@ -45,28 +45,22 @@ def parse_snmp(self, data): return version, community, oids def datagramReceived(self, data, addr): - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "status": "fail", "src_ip": addr[0], "src_port": addr[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) version, community, oids = self.parse_snmp(data) if version or community or oids: - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "query", "status": "success", "src_ip": addr[0], "src_port": addr[1], - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "data": {"version": version, "community": community, "oids": oids}, } ) diff --git a/honeypots/socks5_server.py b/honeypots/socks5_server.py index ca026e1..fba97e2 100644 --- a/honeypots/socks5_server.py +++ b/honeypots/socks5_server.py @@ -31,14 +31,11 @@ def server_main(self): class CustomStreamRequestHandler(StreamRequestHandler): def handle(self): src_ip, src_port = self.client_address - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": src_ip, "src_port": src_port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) try: @@ -51,22 +48,7 @@ def handle(self): username = check_bytes(self.connection.recv(_len)) _len = ord(self.connection.recv(1)) password = check_bytes(self.connection.recv(_len)) - status = "failed" - if username == _q_s.username and password == _q_s.password: - status = "success" - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": src_ip, - "src_port": src_port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "username": username, - "password": password, - } - ) + _q_s.check_login(username, password, src_ip, src_port) except ConnectionResetError: _q_s.logger.debug( f"[{_q_s.NAME}]: Connection reset error when trying to handle connection" diff --git a/honeypots/ssh_server.py b/honeypots/ssh_server.py index 198aa2a..d685001 100644 --- a/honeypots/ssh_server.py +++ b/honeypots/ssh_server.py @@ -9,25 +9,31 @@ // contributors list qeeqbox/honeypots/graphs/contributors // ------------------------------------------------------------- """ +from __future__ import annotations +import logging from _thread import start_new_thread from binascii import hexlify from contextlib import suppress +from datetime import datetime from io import StringIO from random import choice -from re import compile as rcompile +import re from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR from threading import Event from time import time +from typing import TYPE_CHECKING from paramiko import ( RSAKey, ServerInterface, Transport, +) +from paramiko.common import ( + AUTH_FAILED, AUTH_SUCCESSFUL, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_SUCCEEDED, - AUTH_FAILED, ) from paramiko.ssh_exception import SSHException @@ -37,6 +43,42 @@ check_bytes, ) +if TYPE_CHECKING: + from paramiko.channel import Channel + + +# deactivate logging output of paramiko +logging.getLogger("paramiko").setLevel(logging.CRITICAL) + +CTRL_C = b"\x03" +CTRL_D = b"\x04" +ANSI_SEQUENCE = b"\x1b" +DEL = b"\x7f" +COMMANDS = { + "ls": ( + "bin boot cdrom dev etc home lib lib32 libx32 lib64 lost+found media mnt opt proc root " + "run sbin snap srv sys tmp usr var" + ), + "pwd": "/", + "whoami": "root", + "": "", + "cd": "", + "cd /": "", + "uname": "Linux", + "uname -s": "Linux", + "uname -n": "n1-v26", + "uname -r": "5.4.0-26-generic", + "uname -v": "#26-Ubuntu SMP %TIME", + "uname -m": "x86_64", + "uname -p": "x86_64", + "uname -i": "x86_64", + "uname -o": "GNU/Linux", + "uname -a": ( + "Linux n1-v26 5.4.0-26-generic #26-Ubuntu SMP %TIME x86_64 x86_64 x86_64 GNU/Linux" + ), +} +ANSI_REGEX = re.compile(rb"(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]") + class QSSHServer(BaseServer): NAME = "ssh_server" @@ -47,17 +89,15 @@ def __init__(self, **kwargs): self.mocking_server = choice( ["OpenSSH 7.5", "OpenSSH 7.3", "Serv-U SSH Server 15.1.1.108", "OpenSSH 6.4"] ) - self.ansi = rcompile(r"(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]") - def generate_pub_pri_keys(self): - with suppress(Exception): - key = RSAKey.generate(2048) - string_io = StringIO() - key.write_private_key(string_io) - return key.get_base64(), string_io.getvalue() - return None, None + @staticmethod + def generate_pub_pri_keys() -> str: + key = RSAKey.generate(2048) + string_io = StringIO() + key.write_private_key(string_io) + return string_io.getvalue() - def server_main(self): + def server_main(self): # noqa: C901 _q_s = self class SSHHandle(ServerInterface): @@ -66,7 +106,7 @@ def __init__(self, ip, port): self.port = port self.event = Event() - def check_channel_request(self, kind, chanid): + def check_channel_request(self, kind, *_, **__): if kind == "session": return OPEN_SUCCEEDED return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED @@ -74,175 +114,107 @@ def check_channel_request(self, kind, chanid): def check_auth_password(self, username, password): username = check_bytes(username) password = check_bytes(password) - status = "failed" - if username == _q_s.username and password == _q_s.password: - username = _q_s.username - password = _q_s.password - status = "success" - if status == "success": - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.ip, - "src_port": self.port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "username": username, - "password": password, - } - ) + if _q_s.check_login(username, password, self.ip, self.port): return AUTH_SUCCESSFUL - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.ip, - "src_port": self.port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "username": username, - "password": password, - } - ) return AUTH_FAILED - def check_channel_exec_request(self, channel, command): + def check_channel_exec_request(self, channel, command): # noqa: ARG002 if "capture_commands" in _q_s.options: - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "command", "src_ip": self.ip, "src_port": self.port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "data": {"command": check_bytes(command)}, } ) self.event.set() return True - def get_allowed_auths(self, username): + def get_allowed_auths(self, *_, **__): return "password,publickey" def check_auth_publickey(self, username, key): - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "login", "src_ip": self.ip, "src_port": self.port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, "username": check_bytes(username), "key_fingerprint": check_bytes(hexlify(key.get_fingerprint())), } ) return AUTH_SUCCESSFUL - def check_channel_shell_request(self, channel): + def check_channel_shell_request(self, *_, **__): return True - def check_channel_direct_tcpip_request(self, chanid, origin, destination): + def check_channel_direct_tcpip_request(self, *_, **__): return OPEN_SUCCEEDED - def check_channel_pty_request( - self, channel, term, width, height, pixelwidth, pixelheight, modes - ): + def check_channel_pty_request(self, *_, **__): return True - def ConnectionHandle(client, priv): - with suppress(Exception): - t = Transport(client) + def handle_connection(client, priv): + try: ip, port = client.getpeername() - _q_s.logs.info( + except OSError as err: + _q_s.logger.debug(f"Server error: {err}") + return + _q_s.log( + { + "action": "connection", + "src_ip": ip, + "src_port": port, + } + ) + + with Transport(client) as session: + session.local_version = f"SSH-2.0-{_q_s.mocking_server}" + session.add_server_key(RSAKey(file_obj=StringIO(priv))) + ssh_handle = SSHHandle(ip, port) + try: + session.start_server(server=ssh_handle) + except (SSHException, EOFError, ConnectionResetError) as err: + _q_s.logger.debug(f"Server error: {err}", exc_info=True) + return + + with session.accept(30) as conn: + if "interactive" in _q_s.options and conn is not None: + _handle_interactive_session(conn, ip, port) + with suppress(TimeoutError): + ssh_handle.event.wait(2) + + def _handle_interactive_session(conn: Channel, ip: str, port: int): + conn.send(b"Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-26-generic x86_64)\r\n\r\n") + timeout = time() + 300 + while time() < timeout: + try: + conn.send(b"$ ") + line = _receive_line(conn) + except (TimeoutError, EOFError): + break + _q_s.log( { - "server": _q_s.NAME, - "action": "connection", + "action": "interactive", "src_ip": ip, "src_port": port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, + "data": {"command": line}, } ) - t.local_version = "SSH-2.0-" + _q_s.mocking_server - t.add_server_key(RSAKey(file_obj=StringIO(priv))) - sshhandle = SSHHandle(ip, port) - try: - t.start_server(server=sshhandle) - except SSHException: - return - conn = t.accept(30) - if "interactive" in _q_s.options and conn is not None: - conn.send( - b"Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.10.60.1-microsoft-standard-WSL2 x86_64)\r\n\r\n" - ) - current_time = time() - while True and time() < current_time + 10: - conn.send(b"/$ ") - line = "" - while ( - not line.endswith("\x0d") - and not line.endswith("\x0a") - and time() < current_time + 10 - ): - conn.settimeout(10) - recv = conn.recv(1).decode() - conn.settimeout(None) - if _q_s.ansi.match(recv) is None and recv != "\x7f": - conn.send(recv.encode()) - line += recv - line = line.rstrip() - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "interactive", - "src_ip": ip, - "src_port": port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "data": {"command": line}, - } - ) - if line == "ls": - conn.send( - b"\r\nbin cdrom etc lib lib64 lost+found mnt proc run snap " - b"swapfile tmp var boot dev home lib32 libx32 media opt root " - b"sbin srv sys usr\r\n" - ) - elif line == "pwd": - conn.send(b"\r\n/\r\n") - elif line == "whoami": - conn.send(b"\r\nroot\r\n") - elif line == "exit": - break - else: - conn.send(f"\r\n{line}: command not found\r\n".encode()) - with suppress(Exception): - sshhandle.event.wait(2) - with suppress(Exception): - conn.close() - with suppress(Exception): - t.close() + if line == "exit": + break + _respond(conn, line) sock = socket(AF_INET, SOCK_STREAM) sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind((self.ip, self.port)) sock.listen(1) - pub, priv = self.generate_pub_pri_keys() + private_key = self.generate_pub_pri_keys() while True: with suppress(Exception): - client, addr = sock.accept() - start_new_thread( - ConnectionHandle, - ( - client, - priv, - ), - ) + client, _ = sock.accept() + start_new_thread(handle_connection, (client, private_key)) def test_server(self, ip=None, port=None, username=None, password=None): from paramiko import SSHClient, AutoAddPolicy @@ -258,6 +230,67 @@ def test_server(self, ip=None, port=None, username=None, password=None): ssh.connect(_ip, port=_port, username=_username, password=_password) +def _receive_line(conn: Channel) -> str: + line = b"" + while not any(line.endswith(char) for char in [b"\r", b"\n", CTRL_C]): + # timeout if the user does not send anything for 10 seconds + conn.settimeout(10) + # a button press may equate to multiple bytes (e.g. non-ascii chars, + # ANSI sequences, etc.), so we receive more than one byte here + recv = conn.recv(1024) + if not recv or recv == CTRL_D: # capture ctrl+D + conn.send(b"^D\r\n") + raise EOFError + if recv == CTRL_C: + conn.send(b"^C\r\n") + elif recv == b"\r": + # ssh only sends "\r" on enter press so we also need to send "\n" back + conn.send(b"\n") + elif ANSI_SEQUENCE in recv: + recv = ANSI_REGEX.sub(b"", recv) + if DEL in recv: + recv.replace(DEL, b"") + if recv: + line += recv + conn.send(recv) + return line.strip().decode(errors="replace") + + +def _respond(conn: Channel, line: str): + if line == "" or line.endswith(CTRL_C.decode()): + return + if line in COMMANDS: + response = COMMANDS.get(line) + if "%TIME" in response: + response = response.replace( + "%TIME", datetime.now().strftime("%a %b %d %H:%M:%S UTC %Y") + ) + conn.send(f"{response}\r\n".encode()) + elif line.startswith("cd "): + target = _parse_args(line) + if not target: + conn.send(b"\r\n") + else: + if target.startswith("~"): + target = target.replace("~", "/root") + conn.send(f"sh: 1: cd: can't cd to {target}\r\n".encode()) + elif line.startswith("ls "): + target = _parse_args(line) + if not target: + conn.send(f"{COMMANDS['ls']}\r\n".encode()) + else: + conn.send(f"ls: cannot open directory '{target}': Permission denied\r\n".encode()) + else: + conn.send(f"{line}: command not found\r\n".encode()) + + +def _parse_args(line: str) -> str | None: + args = [i for i in line.split(" ")[1:] if i and not i.startswith("-")] + if args: + return args[0] + return None + + if __name__ == "__main__": parsed = server_arguments() if parsed.docker or parsed.aws or parsed.custom: diff --git a/honeypots/telnet_server.py b/honeypots/telnet_server.py index ac46d1b..61964e8 100644 --- a/honeypots/telnet_server.py +++ b/honeypots/telnet_server.py @@ -49,14 +49,11 @@ def connectionMade(self): self._pass = None self.transport.write(b"PC login: ") self._state = b"Username" - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) @@ -69,23 +66,8 @@ def dataReceived(self, data): elif self._state == b"Password": username = check_bytes(self._user) password = check_bytes(data) - status = "failed" - # may need decode - if username == _q_s.username and password == _q_s.password: - status = "success" - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.transport.getPeer().host, - "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "username": username, - "password": password, - } - ) + peer = self.transport.getPeer() + _q_s.check_login(username, password, ip=peer.host, port=peer.port) self.transport.loseConnection() else: self.transport.loseConnection() diff --git a/honeypots/vnc_server.py b/honeypots/vnc_server.py index 7a80b99..2d59a21 100644 --- a/honeypots/vnc_server.py +++ b/honeypots/vnc_server.py @@ -10,8 +10,10 @@ // ------------------------------------------------------------- """ -from binascii import unhexlify +from __future__ import annotations + from contextlib import suppress +from pathlib import Path from Crypto.Cipher import DES from twisted.internet import reactor @@ -29,86 +31,81 @@ class QVNCServer(BaseServer): DEFAULT_PORT = 5900 def __init__(self, **kwargs): + self.file_name = None super().__init__(**kwargs) - self.challenge = unhexlify("00000000901234567890123456789012") - self.words = ["test"] - - def load_words( - self, - ): - with open(self.file_name) as file: - self.words = file.read().splitlines() - - def decode(self, c, r): - with suppress(Exception): - for word in self.words: - temp = word - word = word.strip("\n").ljust(8, "\00")[:8] - rev_word = [] - for i in range(8): - rev_word.append(chr(int(f"{ord(word[i]):08b}"[::-1], 2))) - output = DES.new("".join(rev_word).encode("utf-8"), DES.MODE_ECB).encrypt(c) - if output == r: - return temp - return None - - def server_main(self): + self.challenge = bytes.fromhex("00000000901234567890123456789012") + self.words = ["test", "admin", "123", "123456"] + if self.file_name: + self.load_words() + self.known_passwords = {self.encode(w): w for w in [*self.words, self.password]} + + def load_words(self): + path = Path(self.file_name) + if not path.is_file(): + self.logger.error(f"[{self.NAME}]: Could not load word file: {path}") + return + self.words = path.read_text().splitlines() + + def encode(self, word: str) -> bytes: + temp = word.strip("\n").ljust(8, "\00")[:8] + rev_word = [] + for i in range(8): + rev_word.append(int(f"{ord(temp[i]):08b}"[::-1], 2)) + return DES.new(bytes(rev_word), DES.MODE_ECB).encrypt(self.challenge) + + def decode(self, encoded_pw: bytes) -> str | None: + return self.known_passwords.get(encoded_pw) + + def server_main(self): # noqa: C901 _q_s = self class CustomVNCProtocol(Protocol): _state = None - def connectionMade(self): + def connectionMade(self): # noqa: N802 self.transport.write(b"RFB 003.008\n") self._state = 1 - _q_s.logs.info( + _q_s.log( { - "server": _q_s.NAME, "action": "connection", "src_ip": self.transport.getPeer().host, "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, } ) - def dataReceived(self, data): + def dataReceived(self, data: bytes): # noqa: N802 if self._state == 1: if data == b"RFB 003.008\n": self._state = 2 - self.transport.write(unhexlify("0102")) - elif self._state == 2: + self.transport.write(bytes.fromhex("0102")) + elif self._state == 2: # noqa: PLR2004 if data == b"\x02": self._state = 3 self.transport.write(_q_s.challenge) - elif self._state == 3: - with suppress(Exception): - username = check_bytes(_q_s.decode(_q_s.challenge, data.hex())) - password = check_bytes(data) - status = "failed" - # may need decode - if username == _q_s.username and password == _q_s.password: - status = "success" - else: - password = data.hex() - _q_s.logs.info( - { - "server": _q_s.NAME, - "action": "login", - "status": status, - "src_ip": self.transport.getPeer().host, - "src_port": self.transport.getPeer().port, - "dest_ip": _q_s.ip, - "dest_port": _q_s.port, - "username": username, - "password": password, - } - ) + elif self._state == 3: # noqa: PLR2004 + self._handle_login(data) self.transport.loseConnection() else: self.transport.loseConnection() - def connectionLost(self, reason): + def _handle_login(self, data: bytes): + username = "" # there is no user auth + password = check_bytes(_q_s.decode(data)) + status = "success" if password == _q_s.password else "failed" + if password is None: + password = f"DES:{data.hex()}" + _q_s.log( + { + "action": "login", + "status": status, + "src_ip": self.transport.getPeer().host, + "src_port": self.transport.getPeer().port, + "username": username, + "password": password, + } + ) + + def connectionLost(self, reason): # noqa: N802,ARG002 self._state = None factory = Factory() @@ -117,14 +114,16 @@ def connectionLost(self, reason): reactor.run() def test_server(self, ip=None, port=None, username=None, password=None): + from vncdotool import api + with suppress(Exception): - ip or self.ip - port or self.port - username or self.username - password or self.password - # client = vncapi.connect('{}::{}'.format(self.ip, self.port), password=password) - # client.captureScreen('screenshot.png') - # client.disconnect() + client = api.connect( + f"{ip or self.ip}::{port or self.port}", + username=username or self.username, + password=password or self.password, + ) + client.captureScreen("screenshot.png") + client.disconnect() if __name__ == "__main__": diff --git a/tests/data/pw_file b/tests/data/pw_file new file mode 100644 index 0000000..3bd1f0e --- /dev/null +++ b/tests/data/pw_file @@ -0,0 +1,2 @@ +foo +bar diff --git a/tests/test_ftp_server.py b/tests/test_ftp_server.py index 2361752..2896852 100644 --- a/tests/test_ftp_server.py +++ b/tests/test_ftp_server.py @@ -45,7 +45,7 @@ def test_ftp_server(server_logs): assert_connect_is_logged(connect, PORT) assert_login_is_logged(login) - assert cmd1["data"] == {"args": "('testing',)", "cmd": "USER"} - assert cmd2["data"] == {"args": "('testing',)", "cmd": "PASS"} + assert cmd1["data"] == {"args": f"('{USERNAME}',)", "cmd": "USER"} + assert cmd2["data"] == {"args": f"('{PASSWORD}',)", "cmd": "PASS"} assert cmd3["data"] == {"args": "()", "cmd": "PWD"} assert cmd4["data"] == {"args": "()", "cmd": "QUIT"} diff --git a/tests/test_imap_server.py b/tests/test_imap_server.py index b3cc410..9b57d51 100644 --- a/tests/test_imap_server.py +++ b/tests/test_imap_server.py @@ -49,4 +49,4 @@ def test_imap_server(server_logs): assert cmd2["action"] == "command" assert cmd2["data"]["cmd"] == "LOGIN" - assert cmd2["data"]["data"] == 'testing "testing"' + assert cmd2["data"]["data"] == f'{USERNAME} "{PASSWORD}"' diff --git a/tests/test_irc_server.py b/tests/test_irc_server.py index 950761f..f417f95 100644 --- a/tests/test_irc_server.py +++ b/tests/test_irc_server.py @@ -39,4 +39,4 @@ def test_irc_server(server_logs): assert_connect_is_logged(connect, PORT) assert command["action"] == "command" - assert command["data"] == {"command": "PASS", "params": "['testing']", "prefix": ""} + assert command["data"] == {"command": "PASS", "params": f"['{PASSWORD}']", "prefix": ""} diff --git a/tests/test_pop3_server.py b/tests/test_pop3_server.py index ba78fa9..15abc64 100644 --- a/tests/test_pop3_server.py +++ b/tests/test_pop3_server.py @@ -45,6 +45,6 @@ def test_pop3_server(server_logs): assert_login_is_logged(login) assert cmd1["action"] == "command" - assert cmd1["data"] == {"args": "testing", "cmd": "USER"} + assert cmd1["data"] == {"args": USERNAME, "cmd": "USER"} assert cmd2["action"] == "command" - assert cmd2["data"] == {"args": "testing", "cmd": "PASS"} + assert cmd2["data"] == {"args": PASSWORD, "cmd": "PASS"} diff --git a/tests/test_vnc_server.py b/tests/test_vnc_server.py index b118a05..37584cd 100644 --- a/tests/test_vnc_server.py +++ b/tests/test_vnc_server.py @@ -1,18 +1,28 @@ from __future__ import annotations from multiprocessing import Process +from pathlib import Path +from time import sleep import pytest from vncdotool import api from honeypots import QVNCServer -from .utils import assert_connect_is_logged, IP, load_logs_from_file, PASSWORD, wait_for_server +from .utils import ( + assert_connect_is_logged, + IP, + load_logs_from_file, + USERNAME, + PASSWORD, + wait_for_server, +) +PW_FILE = Path(__file__).parent / "data" / "pw_file" PORT = "55900" -def _connect_to_vnc(): - client = api.connect(f"{IP}::{PORT}", password=PASSWORD) +def _connect_to_vnc(port: str | None = None, password: str | None = None): + client = api.connect(f"{IP}::{port or PORT}", username=USERNAME, password=password or PASSWORD) client.disconnect() @@ -25,6 +35,7 @@ def test_vnc_server(server_logs): # This VNC API creates a blocking daemon thread that can't be trivially stopped, # so we just run it in a process and terminate that instead with wait_for_server(PORT): + sleep(0.2) # somehow the server isn't ready sometimes even though the port is occupied process = Process(target=_connect_to_vnc) process.start() process.terminate() @@ -36,5 +47,40 @@ def test_vnc_server(server_logs): connection, login = logs assert_connect_is_logged(connection, PORT) assert login["action"] == "login" - assert login["username"] == "None" # FixMe: seems to be "None" even with a user - assert login["password"] # we can't check the PW because it is encrypted + assert login["status"] == "success" + assert login["username"] == "" + assert login["password"] == PASSWORD + + +PORT2 = str(int(PORT) + 1) +SERVER_CONFIG = { + "honeypots": { + "vnc": { + "file_name": str(PW_FILE), + }, + } +} + + +@pytest.mark.parametrize( + "server_logs", + [{"server": QVNCServer, "port": PORT2, "custom_config": SERVER_CONFIG}], + indirect=True, +) +def test_wrong_pw(server_logs): + with wait_for_server(PORT2): + sleep(0.2) # somehow the server isn't ready sometimes even though the port is occupied + process = Process(target=_connect_to_vnc, args=(PORT2, "foo")) + process.start() + process.terminate() + process.join(timeout=5) + + logs = load_logs_from_file(server_logs) + + assert len(logs) == 2 + connection, login = logs + assert_connect_is_logged(connection, PORT2) + assert login["action"] == "login" + assert login["status"] == "failed" + assert login["username"] == "" + assert login["password"] == "foo" diff --git a/tests/utils.py b/tests/utils.py index 6d453ae..e40eab8 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -12,8 +12,8 @@ from pathlib import Path IP = "127.0.0.1" -USERNAME = "testing" -PASSWORD = "testing" +USERNAME = "test_user" +PASSWORD = "test_pw" EXPECTED_KEYS = ("action", "dest_ip", "dest_port", "server", "src_ip", "src_port", "timestamp")