#!/usr/bin/env python3 """ MySQL honeypot. Sends a realistic MySQL 5.7 server handshake, reads the client login packet, extracts username, then closes with Access Denied. Logs auth attempts as JSON. """ import asyncio import json import os import socket import struct from datetime import datetime, timezone HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "dbserver") LOG_TARGET = os.environ.get("LOG_TARGET", "") # Minimal MySQL 5.7 server greeting (protocol v10) _GREETING = ( b"\x0a" # protocol version 10 b"5.7.38-honeypot\x00" # server version + NUL b"\x01\x00\x00\x00" # connection id = 1 b"\x70\x76\x21\x6d\x61\x67\x69\x63" # auth-plugin-data part 1 b"\x00" # filler b"\xff\xf7" # capability flags low b"\x21" # charset utf8 b"\x02\x00" # status flags b"\xff\x81" # capability flags high b"\x15" # auth plugin data length b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # reserved (10 bytes) b"\x21\x4f\x7d\x25\x3e\x55\x4d\x7c\x67\x75\x5e\x31\x00" # auth part 2 b"mysql_native_password\x00" # auth plugin name ) def _make_packet(payload: bytes, seq: int = 0) -> bytes: length = len(payload) return struct.pack(" None: if not LOG_TARGET: return try: host, port = LOG_TARGET.rsplit(":", 1) with socket.create_connection((host, int(port)), timeout=3) as s: s.sendall((json.dumps(event) + "\n").encode()) except Exception: pass def _log(event_type: str, **kwargs) -> None: event = { "ts": datetime.now(timezone.utc).isoformat(), "service": "mysql", "host": HONEYPOT_NAME, "event": event_type, **kwargs, } print(json.dumps(event), flush=True) _forward(event) class MySQLProtocol(asyncio.Protocol): def __init__(self): self._transport = None self._peer = None self._buf = b"" self._greeted = False def connection_made(self, transport): self._transport = transport self._peer = transport.get_extra_info("peername", ("?", 0)) _log("connect", src=self._peer[0], src_port=self._peer[1]) transport.write(_make_packet(_GREETING, seq=0)) self._greeted = True def data_received(self, data): self._buf += data # MySQL packets: 3-byte length + 1-byte seq + payload while len(self._buf) >= 4: length = struct.unpack(" 32: try: # skip capability(4) + max_pkt(4) + charset(1) + reserved(23) = 32 bytes username_start = 32 nul = payload.index(b"\x00", username_start) username = payload[username_start:nul].decode(errors="replace") except (ValueError, IndexError): username = "" _log("auth", src=self._peer[0], username=username) # Send Access Denied error err = b"\xff" + struct.pack("