Add per-service customization, stealth hardening, and BYOS support
- HTTP: configurable server_header, response_code, fake_app presets (apache/nginx/wordpress/phpmyadmin/iis), extra_headers, custom_body, static files directory mount - SSH/Cowrie: configurable kernel_version, hardware_platform, ssh_banner, and users/passwords via COWRIE_USERDB_ENTRIES; switched to build mode so cowrie.cfg.j2 persona fields and userdb.txt generation work - SMTP: configurable banner and MTA hostname - MySQL: configurable version string in protocol greeting - Redis: configurable redis_version and os string in INFO response - BYOS: [custom-*] INI sections define bring-your-own Docker services - Stealth: rename all *_honeypot.py → server.py; replace HONEYPOT_NAME env var with NODE_NAME across all 22+ service templates and plugins; strip "honeypot" from all in-container file content - Config: DeckyConfig.service_config dict; INI [decky-N.svc] subsections; composer passes service_cfg to compose_fragment - 350 tests passing (100%) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
[honeypot]
|
||||
hostname = {{ COWRIE_HOSTNAME | default('svr01') }}
|
||||
listen_endpoints = tcp:2222:interface=0.0.0.0
|
||||
kernel_version = {{ COWRIE_HONEYPOT_KERNEL_VERSION | default('5.15.0-76-generic') }}
|
||||
kernel_build_string = {{ COWRIE_HONEYPOT_KERNEL_BUILD_STRING | default('#83-Ubuntu SMP Thu Jun 15 19:16:32 UTC 2023') }}
|
||||
hardware_platform = {{ COWRIE_HONEYPOT_HARDWARE_PLATFORM | default('x86_64') }}
|
||||
|
||||
[ssh]
|
||||
enabled = true
|
||||
listen_endpoints = tcp:2222:interface=0.0.0.0
|
||||
version = {{ COWRIE_SSH_VERSION | default('SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.5') }}
|
||||
|
||||
{% if COWRIE_LOG_HOST is defined and COWRIE_LOG_HOST %}
|
||||
[output_jsonlog]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Render Jinja2 template using the venv's python (has jinja2)
|
||||
# Render Jinja2 config template
|
||||
/home/cowrie/cowrie-env/bin/python3 - <<'EOF'
|
||||
import os
|
||||
from jinja2 import Template
|
||||
@@ -15,4 +15,19 @@ with open("/home/cowrie/cowrie-env/etc/cowrie.cfg", "w") as f:
|
||||
f.write(rendered)
|
||||
EOF
|
||||
|
||||
# Write userdb.txt if custom users were provided
|
||||
# Format: COWRIE_USERDB_ENTRIES=root:toor,admin:admin123
|
||||
if [ -n "${COWRIE_USERDB_ENTRIES}" ]; then
|
||||
USERDB="/home/cowrie/cowrie-env/etc/userdb.txt"
|
||||
: > "$USERDB"
|
||||
IFS=',' read -ra PAIRS <<< "${COWRIE_USERDB_ENTRIES}"
|
||||
for pair in "${PAIRS[@]}"; do
|
||||
user="${pair%%:*}"
|
||||
pass="${pair#*:}"
|
||||
uid=1000
|
||||
[ "$user" = "root" ] && uid=0
|
||||
echo "${user}:${uid}:${pass}" >> "$USERDB"
|
||||
done
|
||||
fi
|
||||
|
||||
exec authbind --deep /home/cowrie/cowrie-env/bin/twistd -n --pidfile= cowrie
|
||||
|
||||
@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ENV PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
RUN pip3 install --no-cache-dir flask
|
||||
|
||||
COPY docker_api_honeypot.py /opt/docker_api_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/docker_api_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Docker API honeypot.
|
||||
Docker APIserver.
|
||||
Serves a fake Docker REST API on port 2375. Responds to common recon
|
||||
endpoints (/version, /info, /containers/json, /images/json) with plausible
|
||||
but fake data. Logs all requests as JSON.
|
||||
@@ -13,7 +13,7 @@ from datetime import datetime, timezone
|
||||
|
||||
from flask import Flask, request
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "docker-host")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "docker-host")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
app = Flask(__name__)
|
||||
@@ -38,7 +38,7 @@ _INFO = {
|
||||
"MemoryLimit": True,
|
||||
"SwapLimit": True,
|
||||
"KernelMemory": False,
|
||||
"Name": HONEYPOT_NAME,
|
||||
"Name": NODE_NAME,
|
||||
"DockerRootDir": "/var/lib/docker",
|
||||
"HttpProxy": "",
|
||||
"HttpsProxy": "",
|
||||
@@ -73,7 +73,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "docker_api",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -127,5 +127,5 @@ def catch_all(path):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_log("startup", msg=f"Docker API honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"Docker API server starting as {NODE_NAME}")
|
||||
app.run(host="0.0.0.0", port=2375, debug=False)
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY elasticsearch_honeypot.py /opt/elasticsearch_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/elasticsearch_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Elasticsearch honeypot — presents a convincing ES 7.x HTTP API on port 9200.
|
||||
Elasticsearch server — presents a convincing ES 7.x HTTP API on port 9200.
|
||||
Logs all requests (especially recon probes like /_cat/, /_cluster/, /_nodes/)
|
||||
as JSON. Designed to attract automated scanners and credential stuffers.
|
||||
"""
|
||||
@@ -11,14 +11,14 @@ import socket
|
||||
from datetime import datetime, timezone
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "esserver")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "esserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
_CLUSTER_UUID = "xC3Pr9abTq2mNkOeLvXwYA"
|
||||
_NODE_UUID = "dJH7Lm2sRqWvPn0kFiEtBo"
|
||||
|
||||
_ROOT_RESPONSE = {
|
||||
"name": HONEYPOT_NAME,
|
||||
"name": NODE_NAME,
|
||||
"cluster_name": "elasticsearch",
|
||||
"cluster_uuid": _CLUSTER_UUID,
|
||||
"version": {
|
||||
@@ -51,7 +51,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "elasticsearch",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -110,7 +110,7 @@ class ESHandler(BaseHTTPRequestHandler):
|
||||
if "_search" in path or "_bulk" in path:
|
||||
self._send_json(200, {"took": 1, "timed_out": False, "hits": {"total": {"value": 0}, "hits": []}})
|
||||
else:
|
||||
self._send_json(200, {"result": "created", "_id": "1", "_index": "honeypot"})
|
||||
self._send_json(200, {"result": "created", "_id": "1", "_index": "server"})
|
||||
|
||||
def do_PUT(self):
|
||||
src = self.client_address[0]
|
||||
@@ -133,6 +133,6 @@ class ESHandler(BaseHTTPRequestHandler):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_log("startup", msg=f"Elasticsearch honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"Elasticsearch server starting as {NODE_NAME}")
|
||||
server = HTTPServer(("0.0.0.0", 9200), ESHandler)
|
||||
server.serve_forever()
|
||||
@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ENV PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
RUN pip3 install --no-cache-dir twisted jinja2
|
||||
|
||||
COPY ftp_honeypot.py /opt/ftp_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/ftp_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
FTP honeypot using Twisted's FTP server infrastructure.
|
||||
FTP server using Twisted's FTP server infrastructure.
|
||||
Accepts any credentials, logs all commands and file requests,
|
||||
forwards events as JSON to LOG_TARGET if set.
|
||||
"""
|
||||
@@ -15,7 +15,7 @@ from twisted.internet import defer, protocol, reactor
|
||||
from twisted.protocols.ftp import FTP, FTPFactory
|
||||
from twisted.python import log as twisted_log
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "ftpserver")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "ftpserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "ftp",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -42,22 +42,22 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
_forward(event)
|
||||
|
||||
|
||||
class HoneypotFTP(FTP):
|
||||
class ServerFTP(FTP):
|
||||
def connectionMade(self):
|
||||
peer = self.transport.getPeer()
|
||||
_log("connection", src_ip=peer.host, src_port=peer.port)
|
||||
super().connectionMade()
|
||||
|
||||
def ftp_USER(self, username):
|
||||
self._honeypot_user = username
|
||||
self._server_user = username
|
||||
_log("user", username=username)
|
||||
return super().ftp_USER(username)
|
||||
|
||||
def ftp_PASS(self, password):
|
||||
_log("auth_attempt", username=getattr(self, "_honeypot_user", "?"), password=password)
|
||||
# Accept everything — we're a honeypot
|
||||
_log("auth_attempt", username=getattr(self, "_server_user", "?"), password=password)
|
||||
# Accept everything — we're a server
|
||||
self.state = self.AUTHED
|
||||
self._user = getattr(self, "_honeypot_user", "anonymous")
|
||||
self._user = getattr(self, "_server_user", "anonymous")
|
||||
return defer.succeed((230, "Login successful."))
|
||||
|
||||
def ftp_RETR(self, path):
|
||||
@@ -71,12 +71,12 @@ class HoneypotFTP(FTP):
|
||||
super().connectionLost(reason)
|
||||
|
||||
|
||||
class HoneypotFTPFactory(FTPFactory):
|
||||
protocol = HoneypotFTP
|
||||
class ServerFTPFactory(FTPFactory):
|
||||
protocol = ServerFTP
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
twisted_log.startLogging(sys.stdout)
|
||||
_log("startup", msg=f"FTP honeypot starting as {HONEYPOT_NAME} on port 21")
|
||||
reactor.listenTCP(21, HoneypotFTPFactory())
|
||||
_log("startup", msg=f"FTP server starting as {NODE_NAME} on port 21")
|
||||
reactor.listenTCP(21, ServerFTPFactory())
|
||||
reactor.run()
|
||||
@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ENV PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
RUN pip3 install --no-cache-dir flask jinja2
|
||||
|
||||
COPY http_honeypot.py /opt/http_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/http_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
HTTP honeypot using Flask.
|
||||
Accepts all requests, logs every detail (method, path, headers, body),
|
||||
and responds with convincing but empty pages. Forwards events as JSON
|
||||
to LOG_TARGET if set.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from flask import Flask, request
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "webserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
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": "http",
|
||||
"host": HONEYPOT_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
print(json.dumps(event), flush=True)
|
||||
_forward(event)
|
||||
|
||||
|
||||
@app.before_request
|
||||
def log_request():
|
||||
_log(
|
||||
"request",
|
||||
method=request.method,
|
||||
path=request.path,
|
||||
remote_addr=request.remote_addr,
|
||||
headers=dict(request.headers),
|
||||
body=request.get_data(as_text=True)[:512],
|
||||
)
|
||||
|
||||
|
||||
@app.route("/", defaults={"path": ""})
|
||||
@app.route("/<path:path>", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
||||
def catch_all(path):
|
||||
return (
|
||||
"<html><body><h1>403 Forbidden</h1></body></html>",
|
||||
403,
|
||||
{"Server": "Apache/2.4.54 (Debian)", "Content-Type": "text/html"},
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_log("startup", msg=f"HTTP honeypot starting as {HONEYPOT_NAME}")
|
||||
app.run(host="0.0.0.0", port=80, debug=False)
|
||||
118
templates/http/server.py
Normal file
118
templates/http/server.py
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
HTTP service emulator using Flask.
|
||||
Accepts all requests, logs every detail (method, path, headers, body),
|
||||
and responds with configurable pages. Forwards events as JSON to LOG_TARGET if set.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from flask import Flask, request, send_from_directory
|
||||
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "webserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
SERVER_HEADER = os.environ.get("SERVER_HEADER", "Apache/2.4.54 (Debian)")
|
||||
RESPONSE_CODE = int(os.environ.get("RESPONSE_CODE", "403"))
|
||||
FAKE_APP = os.environ.get("FAKE_APP", "")
|
||||
EXTRA_HEADERS = json.loads(os.environ.get("EXTRA_HEADERS", "{}"))
|
||||
CUSTOM_BODY = os.environ.get("CUSTOM_BODY", "")
|
||||
FILES_DIR = os.environ.get("FILES_DIR", "")
|
||||
|
||||
_FAKE_APP_BODIES: dict[str, str] = {
|
||||
"apache_default": (
|
||||
"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
|
||||
"<html><head><title>Apache2 Debian Default Page</title></head>\n"
|
||||
"<body><h1>Apache2 Debian Default Page</h1>\n"
|
||||
"<p>It works!</p></body></html>"
|
||||
),
|
||||
"nginx_default": (
|
||||
"<!DOCTYPE html><html><head><title>Welcome to nginx!</title></head>\n"
|
||||
"<body><h1>Welcome to nginx!</h1>\n"
|
||||
"<p>If you see this page, the nginx web server is successfully installed.</p>\n"
|
||||
"</body></html>"
|
||||
),
|
||||
"wordpress": (
|
||||
"<!DOCTYPE html><html><head><title>WordPress › Error</title></head>\n"
|
||||
"<body id=\"error-page\"><div class=\"wp-die-message\">\n"
|
||||
"<h1>Error establishing a database connection</h1></div></body></html>"
|
||||
),
|
||||
"phpmyadmin": (
|
||||
"<!DOCTYPE html><html><head><title>phpMyAdmin</title></head>\n"
|
||||
"<body><form method=\"post\" action=\"index.php\">\n"
|
||||
"<input type=\"text\" name=\"pma_username\" />\n"
|
||||
"<input type=\"password\" name=\"pma_password\" />\n"
|
||||
"<input type=\"submit\" value=\"Go\" /></form></body></html>"
|
||||
),
|
||||
"iis_default": (
|
||||
"<!DOCTYPE html><html><head><title>IIS Windows Server</title></head>\n"
|
||||
"<body><h1>IIS Windows Server</h1>\n"
|
||||
"<p>Welcome to Internet Information Services</p></body></html>"
|
||||
),
|
||||
}
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
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": "http",
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
print(json.dumps(event), flush=True)
|
||||
_forward(event)
|
||||
|
||||
|
||||
@app.before_request
|
||||
def log_request():
|
||||
_log(
|
||||
"request",
|
||||
method=request.method,
|
||||
path=request.path,
|
||||
remote_addr=request.remote_addr,
|
||||
headers=dict(request.headers),
|
||||
body=request.get_data(as_text=True)[:512],
|
||||
)
|
||||
|
||||
|
||||
@app.route("/", defaults={"path": ""})
|
||||
@app.route("/<path:path>", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
||||
def catch_all(path):
|
||||
# Serve static files directory if configured
|
||||
if FILES_DIR and path:
|
||||
files_path = Path(FILES_DIR) / path
|
||||
if files_path.is_file():
|
||||
return send_from_directory(FILES_DIR, path)
|
||||
|
||||
# Select response body: custom > fake_app preset > default 403
|
||||
if CUSTOM_BODY:
|
||||
body = CUSTOM_BODY
|
||||
elif FAKE_APP and FAKE_APP in _FAKE_APP_BODIES:
|
||||
body = _FAKE_APP_BODIES[FAKE_APP]
|
||||
else:
|
||||
body = "<html><body><h1>403 Forbidden</h1></body></html>"
|
||||
|
||||
headers = {"Server": SERVER_HEADER, "Content-Type": "text/html", **EXTRA_HEADERS}
|
||||
return body, RESPONSE_CODE, headers
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_log("startup", msg=f"HTTP server starting as {NODE_NAME}")
|
||||
app.run(host="0.0.0.0", port=80, debug=False)
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY imap_honeypot.py /opt/imap_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/imap_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
IMAP honeypot.
|
||||
IMAPserver.
|
||||
Presents an IMAP4rev1 banner, captures LOGIN credentials (plaintext and
|
||||
AUTHENTICATE), then returns a NO response. Logs all commands as JSON.
|
||||
"""
|
||||
@@ -11,9 +11,9 @@ import os
|
||||
import socket
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "mailserver")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "mailserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
BANNER = f"* OK [{HONEYPOT_NAME}] IMAP4rev1 Service Ready\r\n"
|
||||
BANNER = f"* OK [{NODE_NAME}] IMAP4rev1 Service Ready\r\n"
|
||||
|
||||
|
||||
def _forward(event: dict) -> None:
|
||||
@@ -31,7 +31,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "imap",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -87,7 +87,7 @@ class IMAPProtocol(asyncio.Protocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"IMAP honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"IMAP server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
server = await loop.create_server(IMAPProtocol, "0.0.0.0", 143)
|
||||
async with server:
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Kubernetes API honeypot.
|
||||
Kubernetes APIserver.
|
||||
Serves a fake K8s REST API on port 6443 (HTTPS-ish, plain HTTP) and 8080.
|
||||
Responds to recon endpoints (/version, /api, /apis, /api/v1/namespaces,
|
||||
/api/v1/pods) with plausible but fake data. Logs all requests as JSON.
|
||||
@@ -13,7 +13,7 @@ from datetime import datetime, timezone
|
||||
|
||||
from flask import Flask, request
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "k8s-master")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "k8s-master")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
app = Flask(__name__)
|
||||
@@ -33,7 +33,7 @@ _VERSION = {
|
||||
_API_VERSIONS = {
|
||||
"kind": "APIVersions",
|
||||
"versions": ["v1"],
|
||||
"serverAddressByClientCIDRs": [{"clientCIDR": "0.0.0.0/0", "serverAddress": f"{HONEYPOT_NAME}:6443"}],
|
||||
"serverAddressByClientCIDRs": [{"clientCIDR": "0.0.0.0/0", "serverAddress": f"{NODE_NAME}:6443"}],
|
||||
}
|
||||
|
||||
_NAMESPACES = {
|
||||
@@ -80,7 +80,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "k8s",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -138,5 +138,5 @@ def catch_all(path):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_log("startup", msg=f"Kubernetes API honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"Kubernetes API server starting as {NODE_NAME}")
|
||||
app.run(host="0.0.0.0", port=6443, debug=False)
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY ldap_honeypot.py /opt/ldap_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/ldap_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
LDAP honeypot.
|
||||
LDAPserver.
|
||||
Parses BER-encoded BindRequest messages, logs DN and password, returns an
|
||||
invalidCredentials error. Logs all interactions as JSON.
|
||||
"""
|
||||
@@ -11,7 +11,7 @@ import os
|
||||
import socket
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "ldapserver")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "ldapserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "ldap",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -154,7 +154,7 @@ class LDAPProtocol(asyncio.Protocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"LDAP honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"LDAP server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
server = await loop.create_server(LDAPProtocol, "0.0.0.0", 389)
|
||||
async with server:
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY llmnr_honeypot.py /opt/llmnr_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/llmnr_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -13,7 +13,7 @@ import socket
|
||||
import struct
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "lan-host")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "lan-host")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "llmnr",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -104,7 +104,7 @@ class LLMNRProtocol(asyncio.DatagramProtocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"LLMNR/mDNS honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"LLMNR/mDNS server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
# LLMNR: UDP 5355
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY mongodb_honeypot.py /opt/mongodb_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/mongodb_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
MongoDB honeypot.
|
||||
MongoDBserver.
|
||||
Implements the MongoDB wire protocol OP_MSG/OP_QUERY handshake. Responds
|
||||
to isMaster/hello, listDatabases, and authenticate commands. Logs all
|
||||
received messages as JSON.
|
||||
@@ -13,7 +13,7 @@ import socket
|
||||
import struct
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "mongodb")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "mongodb")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
# Minimal BSON helpers
|
||||
@@ -64,7 +64,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "mongodb",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -115,7 +115,7 @@ class MongoDBProtocol(asyncio.Protocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"MongoDB honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"MongoDB server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
server = await loop.create_server(MongoDBProtocol, "0.0.0.0", 27017)
|
||||
async with server:
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY mqtt_honeypot.py /opt/mqtt_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/mqtt_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
MQTT honeypot (port 1883).
|
||||
MQTT server (port 1883).
|
||||
Parses MQTT CONNECT packets, extracts client_id, username, and password,
|
||||
then returns CONNACK with return code 5 (not authorized). Logs all
|
||||
interactions as JSON.
|
||||
@@ -13,7 +13,7 @@ import socket
|
||||
import struct
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "mqtt-broker")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "mqtt-broker")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
# CONNACK: packet type 0x20, remaining length 2, session_present=0, return_code=5
|
||||
@@ -35,7 +35,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "mqtt",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -137,7 +137,7 @@ class MQTTProtocol(asyncio.Protocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"MQTT honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"MQTT server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
server = await loop.create_server(MQTTProtocol, "0.0.0.0", 1883)
|
||||
async with server:
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY mssql_honeypot.py /opt/mssql_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/mssql_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
MSSQL (TDS) honeypot.
|
||||
MSSQL (TDS)server.
|
||||
Reads TDS pre-login and login7 packets, extracts username, responds with
|
||||
a login failed error. Logs auth attempts as JSON.
|
||||
"""
|
||||
@@ -12,7 +12,7 @@ import socket
|
||||
import struct
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "dbserver")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "dbserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
# Minimal TDS pre-login response
|
||||
@@ -54,7 +54,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "mssql",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -137,7 +137,7 @@ class MSSQLProtocol(asyncio.Protocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"MSSQL honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"MSSQL server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
server = await loop.create_server(MSSQLProtocol, "0.0.0.0", 1433)
|
||||
async with server:
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY mysql_honeypot.py /opt/mysql_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/mysql_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
MySQL honeypot.
|
||||
MySQLserver.
|
||||
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.
|
||||
@@ -13,24 +13,25 @@ import socket
|
||||
import struct
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "dbserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "dbserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
_MYSQL_VER = os.environ.get("MYSQL_VERSION", "5.7.38-log")
|
||||
|
||||
# Minimal MySQL 5.7 server greeting (protocol v10)
|
||||
# Minimal MySQL server greeting (protocol v10) — version string is configurable
|
||||
_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
|
||||
+ _MYSQL_VER.encode() + b"\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
|
||||
)
|
||||
|
||||
|
||||
@@ -54,7 +55,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "mysql",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -110,7 +111,7 @@ class MySQLProtocol(asyncio.Protocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"MySQL honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"MySQL server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
server = await loop.create_server(MySQLProtocol, "0.0.0.0", 3306)
|
||||
async with server:
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
POP3 honeypot.
|
||||
POP3server.
|
||||
Presents a convincing POP3 banner, collects USER/PASS credentials, then
|
||||
stalls with a generic error. Logs every interaction as JSON and forwards
|
||||
to LOG_TARGET if set.
|
||||
@@ -12,9 +12,9 @@ import os
|
||||
import socket
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "mailserver")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "mailserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
BANNER = f"+OK {HONEYPOT_NAME} POP3 server ready\r\n"
|
||||
BANNER = f"+OK {NODE_NAME} POP3 server ready\r\n"
|
||||
|
||||
|
||||
def _forward(event: dict) -> None:
|
||||
@@ -32,7 +32,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "pop3",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -83,7 +83,7 @@ class POP3Protocol(asyncio.Protocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"POP3 honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"POP3 server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
server = await loop.create_server(POP3Protocol, "0.0.0.0", 110)
|
||||
async with server:
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY postgres_honeypot.py /opt/postgres_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/postgres_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
PostgreSQL honeypot.
|
||||
PostgreSQLserver.
|
||||
Reads the startup message, extracts username and database, responds with
|
||||
an AuthenticationMD5Password challenge, logs the hash sent back, then
|
||||
returns an error. Logs all interactions as JSON.
|
||||
@@ -13,7 +13,7 @@ import socket
|
||||
import struct
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "pgserver")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "pgserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
SALT = b"\xde\xad\xbe\xef"
|
||||
|
||||
@@ -40,7 +40,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "postgres",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -118,7 +118,7 @@ class PostgresProtocol(asyncio.Protocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"PostgreSQL honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"PostgreSQL server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
server = await loop.create_server(PostgresProtocol, "0.0.0.0", 5432)
|
||||
async with server:
|
||||
@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ENV PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
RUN pip3 install --no-cache-dir twisted jinja2
|
||||
|
||||
COPY rdp_honeypot.py /opt/rdp_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/rdp_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Minimal RDP honeypot using Twisted.
|
||||
Minimal RDP server using Twisted.
|
||||
Listens on port 3389, logs connection attempts and any credentials sent
|
||||
in the initial RDP negotiation request. Forwards events as JSON to
|
||||
LOG_TARGET if set.
|
||||
@@ -15,7 +15,7 @@ from datetime import datetime, timezone
|
||||
from twisted.internet import protocol, reactor
|
||||
from twisted.python import log as twisted_log
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "WORKSTATION")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "WORKSTATION")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "rdp",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -42,7 +42,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
_forward(event)
|
||||
|
||||
|
||||
class RDPHoneypotProtocol(protocol.Protocol):
|
||||
class RDPServerProtocol(protocol.Protocol):
|
||||
def connectionMade(self):
|
||||
peer = self.transport.getPeer()
|
||||
_log("connection", src_ip=peer.host, src_port=peer.port)
|
||||
@@ -61,12 +61,12 @@ class RDPHoneypotProtocol(protocol.Protocol):
|
||||
_log("disconnect", src_ip=peer.host, src_port=peer.port)
|
||||
|
||||
|
||||
class RDPHoneypotFactory(protocol.ServerFactory):
|
||||
protocol = RDPHoneypotProtocol
|
||||
class RDPServerFactory(protocol.ServerFactory):
|
||||
protocol = RDPServerProtocol
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
twisted_log.startLogging(sys.stdout)
|
||||
_log("startup", msg=f"RDP honeypot starting as {HONEYPOT_NAME} on port 3389")
|
||||
reactor.listenTCP(3389, RDPHoneypotFactory())
|
||||
_log("startup", msg=f"RDP server starting as {NODE_NAME} on port 3389")
|
||||
reactor.listenTCP(3389, RDPServerFactory())
|
||||
reactor.run()
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY redis_honeypot.py /opt/redis_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/redis_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Redis honeypot.
|
||||
Redisserver.
|
||||
Implements enough of the RESP protocol to respond to AUTH, INFO, CONFIG GET,
|
||||
KEYS, and arbitrary commands. Logs every command and argument as JSON.
|
||||
"""
|
||||
@@ -11,19 +11,22 @@ import os
|
||||
import socket
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "cache-server")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "cache-server")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
_REDIS_VER = os.environ.get("REDIS_VERSION", "7.0.12")
|
||||
_REDIS_OS = os.environ.get("REDIS_OS", "Linux 5.15.0")
|
||||
|
||||
_INFO = f"""# Server
|
||||
redis_version:7.0.12
|
||||
redis_mode:standalone
|
||||
os:Linux 5.15.0
|
||||
arch_bits:64
|
||||
tcp_port:6379
|
||||
uptime_in_seconds:864000
|
||||
connected_clients:1
|
||||
# Keyspace
|
||||
""".encode()
|
||||
_INFO = (
|
||||
f"# Server\n"
|
||||
f"redis_version:{_REDIS_VER}\n"
|
||||
f"redis_mode:standalone\n"
|
||||
f"os:{_REDIS_OS}\n"
|
||||
f"arch_bits:64\n"
|
||||
f"tcp_port:6379\n"
|
||||
f"uptime_in_seconds:864000\n"
|
||||
f"connected_clients:1\n"
|
||||
f"# Keyspace\n"
|
||||
).encode()
|
||||
|
||||
|
||||
def _forward(event: dict) -> None:
|
||||
@@ -41,7 +44,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "redis",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -158,7 +161,7 @@ class RedisProtocol(asyncio.Protocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"Redis honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"Redis server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
server = await loop.create_server(RedisProtocol, "0.0.0.0", 6379)
|
||||
async with server:
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY sip_honeypot.py /opt/sip_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/sip_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SIP honeypot (UDP + TCP port 5060).
|
||||
SIP server (UDP + TCP port 5060).
|
||||
Parses SIP REGISTER and INVITE messages, logs credentials from the
|
||||
Authorization header and call metadata, then responds with 401 Unauthorized.
|
||||
"""
|
||||
@@ -12,7 +12,7 @@ import re
|
||||
import socket
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "pbx")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "pbx")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
_401 = (
|
||||
@@ -42,7 +42,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "sip",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -92,7 +92,7 @@ def _handle_message(data: bytes, src_addr) -> bytes | None:
|
||||
to=headers.get("to", ""),
|
||||
call_id=headers.get("call-id", ""),
|
||||
cseq=headers.get("cseq", ""),
|
||||
host=HONEYPOT_NAME,
|
||||
host=NODE_NAME,
|
||||
)
|
||||
return response.encode()
|
||||
return None
|
||||
@@ -134,7 +134,7 @@ class SIPTCPProtocol(asyncio.Protocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"SIP honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"SIP server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
udp_transport, _ = await loop.create_datagram_endpoint(
|
||||
SIPUDPProtocol, local_addr=("0.0.0.0", 5060)
|
||||
@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ENV PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
RUN pip3 install --no-cache-dir impacket jinja2
|
||||
|
||||
COPY smb_honeypot.py /opt/smb_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
mkdir -p /tmp/smb_share
|
||||
exec python3 /opt/smb_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Minimal SMB honeypot using Impacket's SimpleSMBServer.
|
||||
Minimal SMB server using Impacket's SimpleSMBServer.
|
||||
Logs all connection attempts, optionally forwarding them as JSON to LOG_TARGET.
|
||||
"""
|
||||
|
||||
@@ -11,7 +11,7 @@ from datetime import datetime, timezone
|
||||
|
||||
from impacket import smbserver
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "WORKSTATION")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "WORKSTATION")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "smb",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -39,7 +39,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_log("startup", msg=f"SMB honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"SMB server starting as {NODE_NAME}")
|
||||
os.makedirs("/tmp/smb_share", exist_ok=True)
|
||||
|
||||
server = smbserver.SimpleSMBServer(listenAddress="0.0.0.0", listenPort=445)
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY smtp_honeypot.py /opt/smtp_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/smtp_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SMTP honeypot — emulates a realistic ESMTP server (Postfix-style).
|
||||
SMTP server — emulates a realistic ESMTP server (Postfix-style).
|
||||
Logs EHLO/AUTH/MAIL FROM/RCPT TO attempts as JSON, then denies auth.
|
||||
"""
|
||||
|
||||
@@ -10,8 +10,10 @@ import os
|
||||
import socket
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "mailserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "mailserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
_SMTP_BANNER = os.environ.get("SMTP_BANNER", f"220 {NODE_NAME} ESMTP Postfix (Debian/GNU)")
|
||||
_SMTP_MTA = os.environ.get("SMTP_MTA", NODE_NAME)
|
||||
|
||||
|
||||
def _forward(event: dict) -> None:
|
||||
@@ -29,7 +31,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "smtp",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -47,7 +49,7 @@ class SMTPProtocol(asyncio.Protocol):
|
||||
self._transport = transport
|
||||
self._peer = transport.get_extra_info("peername", ("?", 0))
|
||||
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
||||
transport.write(f"220 {HONEYPOT_NAME} ESMTP Postfix (Debian/GNU)\r\n".encode())
|
||||
transport.write(f"{_SMTP_BANNER}\r\n".encode())
|
||||
|
||||
def data_received(self, data):
|
||||
self._buf += data
|
||||
@@ -62,7 +64,7 @@ class SMTPProtocol(asyncio.Protocol):
|
||||
domain = line.split(None, 1)[1] if " " in line else ""
|
||||
_log("ehlo", src=self._peer[0], domain=domain)
|
||||
self._transport.write(
|
||||
f"250-{HONEYPOT_NAME}\r\n"
|
||||
f"250-{_SMTP_MTA}\r\n"
|
||||
f"250-PIPELINING\r\n"
|
||||
f"250-SIZE 10240000\r\n"
|
||||
f"250-VRFY\r\n"
|
||||
@@ -106,7 +108,7 @@ class SMTPProtocol(asyncio.Protocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"SMTP honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"SMTP server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
server = await loop.create_server(SMTPProtocol, "0.0.0.0", 25)
|
||||
async with server:
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY snmp_honeypot.py /opt/snmp_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/snmp_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SNMP honeypot (UDP 161).
|
||||
SNMP server (UDP 161).
|
||||
Parses SNMPv1/v2c GetRequest PDUs, logs the community string and OID list,
|
||||
then responds with a GetResponse containing plausible system OID values.
|
||||
Logs all requests as JSON.
|
||||
@@ -13,16 +13,16 @@ import socket
|
||||
import struct
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "switch")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "switch")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
# OID value map — fake but plausible
|
||||
_OID_VALUES = {
|
||||
"1.3.6.1.2.1.1.1.0": f"Linux {HONEYPOT_NAME} 5.15.0-76-generic #83-Ubuntu SMP x86_64",
|
||||
"1.3.6.1.2.1.1.1.0": f"Linux {NODE_NAME} 5.15.0-76-generic #83-Ubuntu SMP x86_64",
|
||||
"1.3.6.1.2.1.1.2.0": "1.3.6.1.4.1.8072.3.2.10",
|
||||
"1.3.6.1.2.1.1.3.0": "12345678", # sysUpTime
|
||||
"1.3.6.1.2.1.1.4.0": "admin@localhost",
|
||||
"1.3.6.1.2.1.1.5.0": HONEYPOT_NAME,
|
||||
"1.3.6.1.2.1.1.5.0": NODE_NAME,
|
||||
"1.3.6.1.2.1.1.6.0": "Server Room",
|
||||
"1.3.6.1.2.1.1.7.0": "72",
|
||||
}
|
||||
@@ -43,7 +43,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "snmp",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -180,7 +180,7 @@ class SNMPProtocol(asyncio.DatagramProtocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"SNMP honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"SNMP server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
transport, _ = await loop.create_datagram_endpoint(
|
||||
SNMPProtocol, local_addr=("0.0.0.0", 161)
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY tftp_honeypot.py /opt/tftp_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/tftp_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TFTP honeypot (UDP 69).
|
||||
TFTP server (UDP 69).
|
||||
Parses RRQ (read) and WRQ (write) requests, logs filename and transfer mode,
|
||||
then responds with an error packet. Logs all requests as JSON.
|
||||
"""
|
||||
@@ -12,7 +12,7 @@ import socket
|
||||
import struct
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "tftpserver")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "tftpserver")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
# TFTP opcodes
|
||||
@@ -40,7 +40,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "tftp",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -81,7 +81,7 @@ class TFTPProtocol(asyncio.DatagramProtocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"TFTP honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"TFTP server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
transport, _ = await loop.create_datagram_endpoint(
|
||||
TFTPProtocol, local_addr=("0.0.0.0", 69)
|
||||
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY vnc_honeypot.py /opt/vnc_honeypot.py
|
||||
COPY server.py /opt/server.py
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
exec python3 /opt/vnc_honeypot.py
|
||||
exec python3 /opt/server.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
VNC (RFB) honeypot.
|
||||
VNC (RFB)server.
|
||||
Performs the RFB 3.8 handshake, offers VNC authentication, captures the
|
||||
24-byte DES-encrypted challenge response, then rejects with "Authentication
|
||||
failed". Logs the raw response for offline cracking.
|
||||
@@ -12,7 +12,7 @@ import os
|
||||
import socket
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HONEYPOT_NAME = os.environ.get("HONEYPOT_NAME", "desktop")
|
||||
NODE_NAME = os.environ.get("NODE_NAME", "desktop")
|
||||
LOG_TARGET = os.environ.get("LOG_TARGET", "")
|
||||
|
||||
# RFB challenge — fixed so captured responses are reproducible
|
||||
@@ -34,7 +34,7 @@ def _log(event_type: str, **kwargs) -> None:
|
||||
event = {
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
"service": "vnc",
|
||||
"host": HONEYPOT_NAME,
|
||||
"host": NODE_NAME,
|
||||
"event": event_type,
|
||||
**kwargs,
|
||||
}
|
||||
@@ -100,7 +100,7 @@ class VNCProtocol(asyncio.Protocol):
|
||||
|
||||
|
||||
async def main():
|
||||
_log("startup", msg=f"VNC honeypot starting as {HONEYPOT_NAME}")
|
||||
_log("startup", msg=f"VNC server starting as {NODE_NAME}")
|
||||
loop = asyncio.get_running_loop()
|
||||
server = await loop.create_server(VNCProtocol, "0.0.0.0", 5900)
|
||||
async with server:
|
||||
Reference in New Issue
Block a user