Files
DECNET/templates/mysql/mysql_honeypot.py
anti c9f9e1000e Fix SyntaxError in mysql_honeypot.py: replace b"\x00" * 10 with literal bytes
b"\x00" * 10 inside an implicit bytes-literal concatenation block is a
multiplication expression, not a literal. Python's greedy concatenation
absorbs the b"\x00" into the preceding chain, applies * 10 to the whole
thing, then finds the following b"\x21..." literal stranded after an
expression without a comma — SyntaxError.

Fix: inline the 10 null bytes as a literal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:51:14 -03:00

122 lines
4.1 KiB
Python

#!/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("<I", length)[:3] + bytes([seq]) + payload
def _forward(event: dict) -> 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("<I", self._buf[:3] + b"\x00")[0]
if len(self._buf) < 4 + length:
break
payload = self._buf[4:4 + length]
self._buf = self._buf[4 + length:]
self._handle_packet(payload)
def _handle_packet(self, payload: bytes):
if not payload:
return
# Login packet: capability flags (4), max_packet (4), charset (1), reserved (23), username (NUL-terminated)
if len(payload) > 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 = "<parse_error>"
_log("auth", src=self._peer[0], username=username)
# Send Access Denied error
err = b"\xff" + struct.pack("<H", 1045) + b"#28000Access denied for user\x00"
self._transport.write(_make_packet(err, seq=2))
self._transport.close()
def connection_lost(self, exc):
_log("disconnect", src=self._peer[0] if self._peer else "?")
async def main():
_log("startup", msg=f"MySQL honeypot starting as {HONEYPOT_NAME}")
loop = asyncio.get_running_loop()
server = await loop.create_server(MySQLProtocol, "0.0.0.0", 3306)
async with server:
await server.serve_forever()
if __name__ == "__main__":
asyncio.run(main())