feat(templates): per-instance stealth via instance_seed in service servers

Every service template now pulls version strings, cluster/node UUIDs, auth
salts, greeting banners, and uptime from the seeded per-instance RNG instead
of hard-coded defaults. Scanners sweeping the fleet now see legitimately
diverging fingerprints per decky while each decky's own responses stay
internally consistent across restarts.

Covers elasticsearch, ftp, http, https, ldap, mongodb, mqtt, mssql, mysql,
postgres, redis, and smtp templates.
This commit is contained in:
2026-04-22 09:24:16 -04:00
parent 51e9e263ca
commit 3fb84ac5d0
12 changed files with 755 additions and 156 deletions

View File

@@ -8,37 +8,73 @@ a login failed error. Logs auth attempts as JSON.
import asyncio
import os
import struct
import instance_seed as _seed
from syslog_bridge import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "dbserver")
SERVICE_NAME = "mssql"
LOG_TARGET = os.environ.get("LOG_TARGET", "")
_PRELOGIN_RESP = bytes([
0x04, 0x01, 0x00, 0x2f, 0x00, 0x00, 0x01, 0x00, # TDS header type=4, status=1, len=47
# 0. VERSION option
0x00, 0x00, 0x1a, 0x00, 0x06,
# 1. ENCRYPTION option
0x01, 0x00, 0x20, 0x00, 0x01,
# 2. INSTOPT
0x02, 0x00, 0x21, 0x00, 0x01,
# 3. THREADID
0x03, 0x00, 0x22, 0x00, 0x04,
# 4. MARS
0x04, 0x00, 0x26, 0x00, 0x01,
# TERMINATOR
0xff,
# version data: 14.0.2000
0x0e, 0x00, 0x07, 0xd0, 0x00, 0x00,
# encryption: NOT_SUP
0x02,
# instopt
0x00,
# thread id
0x00, 0x00, 0x00, 0x00,
# mars
0x00,
])
# Real SQL Server release families. Pairing (major, minor, build) makes a
# subsequent OSQL/sqlcmd version probe line up with what MS published.
# Builds are taken from publicly documented latest-CU numbers.
_MSSQL_RELEASES = [
# (name, major, minor, build, subbuild)
("SQL Server 2016", 13, 0, 6419, 0),
("SQL Server 2017", 14, 0, 2000, 0),
("SQL Server 2017", 14, 0, 3460, 0),
("SQL Server 2019", 15, 0, 2000, 0),
("SQL Server 2019", 15, 0, 4335, 1),
("SQL Server 2022", 16, 0, 1000, 0),
("SQL Server 2022", 16, 0, 4115, 2),
]
_MSSQL_NAME, _VER_MAJ, _VER_MIN, _VER_BUILD, _VER_SUB = _seed.pick(_MSSQL_RELEASES)
def _build_prelogin_response() -> bytes:
"""TDS PRELOGIN response. Version option carries
major(1) minor(1) build(2, network order) subbuild(2, network order)."""
version_data = (
bytes([_VER_MAJ & 0xff, _VER_MIN & 0xff])
+ struct.pack(">H", _VER_BUILD & 0xffff)
+ struct.pack(">H", _VER_SUB & 0xffff)
)
# Option directory + data. Offsets are from start of directory.
# Five options: VERSION, ENCRYPTION, INSTOPT, THREADID, MARS.
# Data fields, in order:
encryption = b"\x02" # NOT_SUP
instopt = b"\x00"
threadid = struct.pack("<I", _seed.rng.randint(100, 9000))
mars = b"\x00"
directory = b""
data = b""
# Directory header is 5 bytes per option + 1 terminator; compute offsets
# from end of terminator.
dir_size = 5 * 5 + 1
running_offset = dir_size
def add_option(token: int, chunk: bytes) -> None:
nonlocal directory, data, running_offset
directory += bytes([token]) + struct.pack(">H", running_offset) + struct.pack(">H", len(chunk))
data += chunk
running_offset += len(chunk)
add_option(0x00, version_data)
add_option(0x01, encryption)
add_option(0x02, instopt)
add_option(0x03, threadid)
add_option(0x04, mars)
directory += b"\xff"
payload = directory + data
total_len = 8 + len(payload)
header = struct.pack(">BBHBBBB", 0x04, 0x01, total_len, 0x00, 0x00, 0x01, 0x00)
return header + payload
_PRELOGIN_RESP = _build_prelogin_response()