协议欺骗工程实践:HTTP/FTP/Telnet/SSH 的伪装与实现要点

导语

蜜罐成败在于"骗真"与"安全"。这篇是实操清单,直接映射到你现有实现的每个 handler。


目录

  1. HTTPHandler 实战细节
  2. FTPHandler 实战细节
  3. TelnetHandler 实战细节
  4. SSHHandler(asyncssh)实战细节
  5. 伪装通用策略与反识别建议

1. HTTPHandler 实战细节(基于你代码)

现状要点

  • 手工解析 request head,随机 nginx 版本,动态 .php 文件,返回 admin/phpmyadmin 模板,记录 attack_logger.log_*

缺陷与改进

  • POST body 未读 :需根据 Content-Length 读取并脱敏后记录。

    python 复制代码
    if 'content-length' in headers:
        body_len = int(headers['content-length'])
        body = await reader.read(body_len)
  • 增加 keep-alive 限制支持:支持短期 keep-alive 增加真实度。

  • 提高头部多样性 :偶尔返回 Apache/IIS、不同 Date 格式、chunked encoding。

  • 模板外置 :用 templates/ 存放 HTML,便于快速替换和本地化。

日志样例

json 复制代码
{"session_id":"...","protocol":"http","inputs":["GET /phpmyadmin HTTP/1.1"],"events":[{"type":"auth_attempt","username":"php","password":"<captured>"}]}

2. FTPHandler 实战细节

现状要点

  • 实现 USER/PASS/SYST/PWD/CWD/LIST/RETR/STOR/QUIT,记录 auth 与 file_access。

改进

  • 实现 PASV/PORT:伪造 PASV 返回并监听随机端口以记录是否有数据连接尝试。
  • 概率性失败/延迟:不要总是返回 230。部分返回 530/421 增加真实感。
  • 限制并发与速率:每 IP 每秒尝试次数限制,避免日志污染。

日志样例

json 复制代码
{"session_id":"...","protocol":"ftp","username":"admin","authenticated":true,"events":[{"type":"file_access","file_path":"/etc/passwd","access_type":"read"}]}

3. TelnetHandler 实战细节

现状要点

  • Cisco-like CLI,支持 show versionshow interfacespingtraceroutereload,记录输入并保存原始 bytes。

改进

  • 命令 registry:将命令->处理函数映射化,便于扩展与单元测试。
  • 实现 Telnet options negotiation(IAC):高级侦测器会检查 IAC 交互;做最小支持避免被识别为简单实现。
  • 状态化 prompt:将 hostname、uptime 等做为可配置模板,增强仿真。

日志样例

json 复制代码
{"session_id":"...","protocol":"telnet","login":"admin","inputs":[{"ts":"...","input":"show running-config"}],"raw_base64":"..."}

4. SSHHandler(asyncssh)实战细节

现状要点

  • HoneySSHServer 实现 validate_password/validate_public_keyHoneySSHSession 处理 shell/exec,fake_exec_response 提供假输出。

改进

  • session meta 完善:包含 client_version、host_key_fingerprint。
  • 丰富 fake 输出/etc/passwduname -awhoami 返回更真实内容。
  • SFTP/ SCP 风险 :禁用或高度受限(当前实现 allow_scp=False 是正确选择)。
  • Host key 管理:支持配置 host_key_path 或生成临时 host key(当前有此逻辑,建议日志记录 fingerprint)。

日志样例

json 复制代码
{"event":"auth_attempt","time":"...","method":"password","username":"root","remote_ip":"x.x.x.x"}

5. 伪装通用策略与反识别建议

  • 概率性行为:不要让所有请求都产生相同成功/失败模式。
  • 随机延迟:根据 body 大小模拟延迟,避免"瞬秒"响应。
  • 头部与模板多样化:Server header、Date 格式、ETag 随机化。
  • 限制暴露能力:所有可能导致真实资源写入/执行的命令统一返回安全拒绝(如 550 或 403),并记录尝试。

代码

python 复制代码
#!/usr/bin/env python3
"""
FTP协议处理器
"""
import asyncio
import base64
import uuid
from datetime import datetime, timezone

from .base_handler import ProtocolHandler
from .logger import op_logger
from multihoneypot.attacklogger import attack_logger, AttackType, ProtocolType
class FTPHandler(ProtocolHandler):
    """
    FTP协议处理器,模拟FTP控制通道并记录客户端交互
    """

    def __init__(self, host, port, log_file):
        """
        初始化FTP处理器

        Args:
            host (str): 监听主机地址
            port (int): 监听端口
            log_file (str): 日志文件路径
        """
        super().__init__("ftp", host, port, log_file)

    async def start(self):
        """
        启动FTP服务器
        """
        # 创建异步服务器实例来处理客户端连接
        self._server = await asyncio.start_server(self._handle_client, host=self.host, port=self.port)
        addrs = ", ".join(str(sock.getsockname()) for sock in self._server.sockets)
        op_logger.info("FTP监听于 %s", addrs)
        # 启动服务器任务并返回任务对象
        return asyncio.create_task(self._server.serve_forever())

    async def _send_line(self, writer: asyncio.StreamWriter, line: str):
        """
        向客户端发送一行FTP响应

        Args:
            writer (asyncio.StreamWriter): 用于向连接写入数据的流写入器
            line (str): 要发送的响应行
        """
        writer.write((line + "\r\n").encode())
        await writer.drain()

    async def _handle_client(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
        """
        处理FTP客户端连接

        Args:
            reader (asyncio.StreamReader): 用于从连接中读取数据的流读取器
            writer (asyncio.StreamWriter): 用于向连接写入数据的流写入器
        """
        # 初始化会话相关信息
        peer = writer.get_extra_info("peername") or ("unknown", 0)
        session_id = str(uuid.uuid4())
        start_ts = datetime.now(timezone.utc)
        raw_buf = bytearray()
        inputs = []
        username = None
        authenticated = False

        op_logger.info("FTP新连接 %s 会话=%s", peer, session_id)
        try:
            # 发送FTP欢迎消息
            await self._send_line(writer, "220 Microsoft FTP Service")
            while True:
                # 读取客户端输入行
                line = await reader.readline()
                if not line:
                    break
                raw_buf.extend(line)
                try:
                    text = line.decode(errors="ignore").rstrip("\r\n")
                except Exception:
                    text = "<无法解码>"
                inputs.append({"ts": datetime.now(timezone.utc).isoformat(), "input": text})
                op_logger.debug("FTP %s: %s", session_id, text)

                # 解析FTP命令和参数
                parts = text.split(" ", 1)
                cmd = parts[0].upper()
                arg = parts[1] if len(parts) > 1 else ""

                # 处理FTP命令
                if cmd == "USER":
                    username = arg
                    await self._send_line(writer, "331 Password required for " + username)
                elif cmd == "PASS":
                    # 记录认证尝试到攻击日志
                    attack_logger.log_auth_attempt(
                        source_ip=peer[0],
                        destination_port=self.port,
                        protocol=ProtocolType.FTP,
                        username=username,
                        password=arg
                    )

                    await self._send_line(writer, "230 User " + username + " logged in")
                    authenticated = True
                elif cmd == "SYST":
                    await self._send_line(writer, "215 UNIX Type: L8")
                elif cmd == "PWD":
                    await self._send_line(writer, '257 "/" is current directory')
                elif cmd == "CWD":
                    await self._send_line(writer, "250 CWD command successful")
                elif cmd == "LIST":
                    # 无数据连接:发送预设的成功消息
                    await self._send_line(writer, "150 Opening ASCII mode data connection for file list")
                    await self._send_line(writer, "226 Transfer complete")
                elif cmd in ("RETR", "STOR"):
                    # 记录文件访问到攻击日志
                    attack_logger.log_file_access(
                        source_ip=peer[0],
                        destination_port=self.port,
                        protocol=ProtocolType.FTP,
                        file_path=arg,
                        access_type="read" if cmd == "RETR" else "write"
                    )

                    # 出于安全原因拒绝实际传输
                    await self._send_line(writer, "550 Action not taken (simulated)")
                elif cmd == "QUIT":
                    await self._send_line(writer, "221 Goodbye.")
                    break
                else:
                    await self._send_line(writer, "502 Command not implemented")
        except asyncio.CancelledError:
            op_logger.info("FTP会话已取消 %s", session_id)
        except Exception as e:
            op_logger.exception("FTP处理器错误 %s: %s", session_id, e)
        finally:
            # 构建并保存会话日志条目
            entry = {
                "session_id": session_id,
                "start_time": start_ts.isoformat(),
                "end_time": datetime.now(timezone.utc).isoformat(),
                "duration_seconds": (datetime.now(timezone.utc) - start_ts).total_seconds(),
                "remote_ip": peer[0],
                "remote_port": peer[1],
                "username": username,
                "authenticated": authenticated,
                "inputs": inputs,
                "raw_base64": base64.b64encode(bytes(raw_buf)).decode("ascii"),
            }
            await self.persist(entry)
            try:
                writer.close()
                await writer.wait_closed()
            except Exception:
                pass
            op_logger.info("FTP关闭会话 %s 来自 %s", session_id, peer)
python 复制代码
#!/usr/bin/env python3
"""
HTTP协议处理器
"""
import asyncio
import base64
import random
import uuid
from datetime import datetime, timezone
from typing import Optional

from .base_handler import ProtocolHandler
from .logger import op_logger
from multihoneypot.attacklogger import attack_logger, AttackType, ProtocolType


class HTTPHandler(ProtocolHandler):
    """
    HTTP协议处理器,模拟简单的HTTP服务端
    """

    # 多个响应页面内容,增加随机性
    RESPONSE_BODIES = [
        b"<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\n    body {\n        width: 35em;\n        margin: 0 auto;\n        font-family: Tahoma, Verdana, Arial, sans-serif;\n    }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href="http://nginx.org/">nginx.org</a>.<br/>\nCommercial support is available at\n<a href="http://nginx.com/">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
        b"<!DOCTYPE html>\n<html>\n<head>\n<title>Test Page for nginx</title>\n<style>\n    body {\n        width: 35em;\n        margin: 0 auto;\n        font-family: Tahoma, Verdana, Arial, sans-serif;\n    }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>This is a test page used to test the correct operation of the nginx.</p>\n</body>\n</html>\n",
        b"<!DOCTYPE html>\n<html>\n<head>\n<title>Default Web Page</title>\n<style>\n    body {\n        width: 35em;\n        margin: 0 auto;\n        font-family: Tahoma, Verdana, Arial, sans-serif;\n    }\n</style>\n</head>\n<body>\n<h1>Default Web Page</h1>\n<p>This is the default web page for this server.</p>\n<p>The web server software is running but no content has been added, yet.</p>\n</body>\n</html>\n"
    ]

    # 真实的nginx版本号
    NGINX_VERSIONS = [
        "nginx/1.18.0",
        "nginx/1.20.1",
        "nginx/1.21.6",
        "nginx/1.22.1",
        "nginx/1.23.3",
        "nginx/1.24.0"
    ]

    # 登录页面模板
    LOGIN_PAGE = b"""<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
    <style>
        body { font-family: Arial, sans-serif; background-color: #f5f5f5; }
        .login-container { width: 300px; margin: 100px auto; padding: 20px; background-color: white; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
        h2 { text-align: center; color: #333; }
        input { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 3px; box-sizing: border-box; }
        button { width: 100%; padding: 10px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer; }
        button:hover { background-color: #005a87; }
    </style>
</head>
<body>
    <div class="login-container">
        <h2>Admin Login</h2>
        <form method="POST">
            <input type="text" name="username" placeholder="Username" required>
            <input type="password" name="password" placeholder="Password" required>
            <button type="submit">Login</button>
        </form>
    </div>
</body>
</html>"""

    # 登录API的JSON响应
    LOGIN_JSON_RESPONSES = [
        b'{"success": false, "message": "Invalid credentials"}',
        b'{"success": false, "message": "Authentication failed"}',
        b'{"error": {"code": "INVALID_CREDENTIALS", "message": "The provided credentials are incorrect"}}',
        b'{"error": "Unauthorized", "message": "Login failed. Please check your username and password."}'
    ]

    # phpMyAdmin页面模板
    PHPMYADMIN_PAGE = b"""<!DOCTYPE html>
<html lang="en">
<head>
    <title>phpMyAdmin</title>
    <meta charset="utf-8">
    <style>
        body { font-family: sans-serif; background-color: #f5f5f5; margin: 0; padding: 0; }
        .container { max-width: 500px; margin: 100px auto; background: white; padding: 30px; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
        h1 { text-align: center; color: #666; }
        .form-group { margin-bottom: 20px; }
        label { display: block; margin-bottom: 5px; }
        input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 3px; box-sizing: border-box; }
        button { width: 100%; padding: 10px; background-color: #ffd600; color: black; border: none; border-radius: 3px; cursor: pointer; font-weight: bold; }
        button:hover { background-color: #e6c000; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Welcome to phpMyAdmin</h1>
        <form method="post" action="index.php">
            <div class="form-group">
                <label for="server">Server:</label>
                <input type="text" id="server" name="server" value="localhost" required>
            </div>
            <div class="form-group">
                <label for="username">Username:</label>
                <input type="text" id="username" name="username" required>
            </div>
            <div class="form-group">
                <label for="password">Password:</label>
                <input type="password" id="password" name="password" required>
            </div>
            <button type="submit">Go</button>
        </form>
    </div>
</body>
</html>"""

    # Admin面板页面模板
    ADMIN_PAGE = b"""<!DOCTYPE html>
<html>
<head>
    <title>Admin Panel</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f1f1f1; }
        .header { background-color: #333; color: white; padding: 15px; }
        .login-form { width: 300px; margin: 100px auto; background: white; padding: 30px; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
        .form-group { margin-bottom: 20px; }
        label { display: block; margin-bottom: 5px; }
        input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 3px; box-sizing: border-box; }
        button { width: 100%; padding: 10px; background-color: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; }
        button:hover { background-color: #45a049; }
    </style>
</head>
<body>
    <div class="header">
        <h2>Admin Panel</h2>
    </div>
    <div class="login-form">
        <h3>Login</h3>
        <form method="POST">
            <div class="form-group">
                <label>Username:</label>
                <input type="text" name="username" required>
            </div>
            <div class="form-group">
                <label>Password:</label>
                <input type="password" name="password" required>
            </div>
            <button type="submit">Login</button>
        </form>
    </div>
</body>
</html>"""

    # API数据示例
    API_DATA = [
        b'{"users": [{"id": 1, "name": "John Doe", "email": "john@example.com"}, {"id": 2, "name": "Jane Smith", "email": "jane@example.com"}], "total": 2}',
        b'{"products": [{"id": 1, "name": "Product 1", "price": 29.99}, {"id": 2, "name": "Product 2", "price": 39.99}], "total": 2}',
        b'{"orders": [{"id": 1001, "customer": "John Doe", "amount": 99.99, "status": "shipped"}, {"id": 1002, "customer": "Jane Smith", "amount": 149.99, "status": "pending"}], "total": 2}'
    ]

    # 静态资源内容
    STATIC_CONTENTS = {
        "/favicon.ico": (b"\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00", "image/x-icon"),
        "/robots.txt": (b"User-agent: *\nDisallow: /admin/\nDisallow: /private/\n", "text/plain"),
        "/sitemap.xml": (
            b"<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n<url>\n<loc>http://localhost/</loc>\n</url>\n</urlset>",
            "application/xml"),
    }

    def __init__(self, host, port, log_file):
        """
        初始化HTTP处理器

        Args:
            host (str): 监听主机地址
            port (int): 监听端口
            log_file (str): 日志文件路径
        """
        super().__init__("http", host, port, log_file)
        # 生成带hash的动态路径
        self.dynamic_paths = self._generate_dynamic_paths()

    def _generate_dynamic_paths(self):
        """生成动态路径"""
        paths = {}
        for i in range(10):
            path = f"/{uuid.uuid4().hex}.php"
            content = b"<?php\n// This is a simulated PHP file\nphpinfo();\n?>"
            paths[path] = (content, "text/plain")
        return paths

    async def start(self):
        """
        启动HTTP服务器
        """
        self._server = await asyncio.start_server(self._handle, host=self.host, port=self.port)
        addrs = ", ".join(str(sock.getsockname()) for sock in self._server.sockets)
        op_logger.info("HTTP监听于 %s", addrs)
        return asyncio.create_task(self._server.serve_forever())

    def _generate_session_id(self):
        """生成随机会话ID"""
        return ''.join(random.choices('0123456789abcdef', k=32))

    def _generate_etag(self):
        """生成ETag"""
        return '"' + ''.join(random.choices('0123456789abcdef', k=16)) + '"'

    async def _simulate_network_delay(self, writer: asyncio.StreamWriter, content_length: int):
        """
        模拟网络延迟

        Args:
            writer (asyncio.StreamWriter): 流写入器
            content_length (int): 内容长度
        """
        # 根据内容长度模拟网络延迟
        delay = min(content_length / 10000, 1.0)  # 最大1秒延迟
        if delay > 0.01:
            await asyncio.sleep(delay * 0.1)

    async def _handle(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
        """
        处理HTTP客户端请求

        Args:
            reader (asyncio.StreamReader): 用于从连接中读取数据的流读取器
            writer (asyncio.StreamWriter): 用于向连接写入数据的流写入器
        """
        peer = writer.get_extra_info("peername") or ("unknown", 0)
        session_id = str(uuid.uuid4())
        start_ts = datetime.now(timezone.utc)
        try:
            raw = await reader.read(64 * 1024)  # 读取最多64KB的头部和内容
            text = raw.decode(errors="ignore")

            # 解析请求
            request_lines = text.split('\n')
            if not request_lines:
                writer.close()
                return

            request_line = request_lines[0].strip()
            headers = {}
            for line in request_lines[1:]:
                if ':' in line:
                    key, value = line.split(':', 1)
                    headers[key.strip().lower()] = value.strip()

            parts = request_line.split()
            if len(parts) < 3:
                writer.close()
                return

            method, path, version = parts[0], parts[1], parts[2]

            # 记录HTTP请求到攻击日志
            attack_logger.log_command_execution(
                source_ip=peer[0],
                destination_port=self.port,
                protocol=ProtocolType.HTTP,
                command=f"{method} {path} {version}",
                command_type="http_request"
            )

            # 检查是否为可疑路径
            if any(pattern in path.lower() for pattern in ['manager', 'python', '.env', 'flask', 'django']):
                body = b'<!DOCTYPE html>\n<html>\n<head>\n<title>404 Not Found</title>\n</head>\n<body>\n<center><h1>404 Not Found</h1></center>\n<hr><center>nginx</center>\n</body>\n</html>\n'
                server_header = random.choice(self.NGINX_VERSIONS)
                resp = b"HTTP/1.1 404 Not Found\r\nServer: %s\r\nDate: %s\r\nContent-Type: text/html\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s" % (
                    server_header.encode(),
                    datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT').encode(),
                    len(body),
                    body
                )
                writer.write(resp)
                await writer.drain()
                return

            # 处理phpMyAdmin页面请求
            if path.startswith("/phpmyadmin"):
                if path == "/phpmyadmin" or path == "/phpmyadmin/":
                    if method == "GET":
                        body = self.PHPMYADMIN_PAGE
                        content_type = "text/html"
                    elif method == "POST":
                        # 记录登录尝试
                        post_data = f"<PHPMyAdmin login attempt with {headers.get('content-length', 'unknown')} bytes of data>"
                        op_logger.info("phpMyAdmin login attempt from %s: %s", peer[0], post_data)

                        # 记录认证尝试到攻击日志
                        attack_logger.log_auth_attempt(
                            source_ip=peer[0],
                            destination_port=self.port,
                            protocol=ProtocolType.HTTP,
                            username="phpmyadmin_user",
                            password="<captured>"
                        )

                        # 返回登录失败页面
                        body = b"""<!DOCTYPE html>
<html>
<head>
    <title>phpMyAdmin</title>
    <style>
        body { font-family: sans-serif; background-color: #f5f5f5; }
        .error { width: 500px; margin: 100px auto; background: white; padding: 30px; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.1); border-left: 5px solid #f44336; }
        .error h2 { color: #f44336; }
        .back-link { display: block; text-align: center; margin-top: 20px; color: #2196F3; text-decoration: none; }
    </style>
</head>
<body>
    <div class="error">
        <h2>Login failed</h2>
        <p>Invalid credentials. Please try again.</p>
        <a href="index.php" class="back-link">Back to login</a>
    </div>
</body>
</html>"""
                        content_type = "text/html"
                    else:
                        body = b'{"error": "Method not allowed"}'
                        content_type = "application/json"

                    server_header = random.choice(self.NGINX_VERSIONS)
                    resp = b"HTTP/1.1 200 OK\r\nServer: %s\r\nDate: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s" % (
                        server_header.encode(),
                        datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT').encode(),
                        content_type.encode(),
                        len(body),
                        body
                    )
                    await self._simulate_network_delay(writer, len(body))
                    writer.write(resp)
                    await writer.drain()
                    return
                else:
                    # phpMyAdmin子路径返回404
                    body = b'<!DOCTYPE html>\n<html>\n<head>\n<title>404 Not Found</title>\n</head>\n<body>\n<center><h1>404 Not Found</h1></center>\n<hr><center>nginx</center>\n</body>\n</html>\n'
                    server_header = random.choice(self.NGINX_VERSIONS)
                    resp = b"HTTP/1.1 404 Not Found\r\nServer: %s\r\nDate: %s\r\nContent-Type: text/html\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s" % (
                        server_header.encode(),
                        datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT').encode(),
                        len(body),
                        body
                    )
                    writer.write(resp)
                    await writer.drain()
                    return

            # 处理Admin面板请求
            if path.startswith("/admin"):
                if path == "/admin" or path == "/admin/":
                    if method == "GET":
                        body = self.ADMIN_PAGE
                        content_type = "text/html"
                    elif method == "POST":
                        # 记录登录尝试
                        post_data = f"<Admin panel login attempt with {headers.get('content-length', 'unknown')} bytes of data>"
                        op_logger.info("Admin panel login attempt from %s: %s", peer[0], post_data)

                        # 记录认证尝试到攻击日志
                        attack_logger.log_auth_attempt(
                            source_ip=peer[0],
                            destination_port=self.port,
                            protocol=ProtocolType.HTTP,
                            username="admin_user",
                            password="<captured>"
                        )

                        # 返回登录失败页面
                        body = b"""<!DOCTYPE html>
<html>
<head>
    <title>Admin Panel</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f1f1f1; }
        .header { background-color: #333; color: white; padding: 15px; }
        .error { width: 300px; margin: 100px auto; background: white; padding: 30px; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.1); border-left: 5px solid #f44336; }
        .error h3 { color: #f44336; margin-top: 0; }
        .back-link { display: block; text-align: center; margin-top: 20px; color: #2196F3; text-decoration: none; }
    </style>
</head>
<body>
    <div class="header">
        <h2>Admin Panel</h2>
    </div>
    <div class="error">
        <h3>Login Failed</h3>
        <p>Invalid username or password.</p>
        <a href="/" class="back-link">Back to login</a>
    </div>
</body>
</html>"""
                        content_type = "text/html"
                    else:
                        body = b'{"error": "Method not allowed"}'
                        content_type = "application/json"

                    server_header = random.choice(self.NGINX_VERSIONS)
                    resp = b"HTTP/1.1 200 OK\r\nServer: %s\r\nDate: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s" % (
                        server_header.encode(),
                        datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT').encode(),
                        content_type.encode(),
                        len(body),
                        body
                    )
                    await self._simulate_network_delay(writer, len(body))
                    writer.write(resp)
                    await writer.drain()
                    return
                else:
                    # Admin子路径返回404
                    body = b'<!DOCTYPE html>\n<html>\n<head>\n<title>404 Not Found</title>\n</head>\n<body>\n<center><h1>404 Not Found</h1></center>\n<hr><center>nginx</center>\n</body>\n</html>\n'
                    server_header = random.choice(self.NGINX_VERSIONS)
                    resp = b"HTTP/1.1 404 Not Found\r\nServer: %s\r\nDate: %s\r\nContent-Type: text/html\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s" % (
                        server_header.encode(),
                        datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT').encode(),
                        len(body),
                        body
                    )
                    writer.write(resp)
                    await writer.drain()
                    return

            # 处理API请求
            if path.startswith("/api/"):
                if method == "GET":
                    body = random.choice(self.API_DATA)
                    content_type = "application/json"
                else:
                    body = b'{"error": "Method not allowed"}'
                    content_type = "application/json"

                server_header = random.choice(self.NGINX_VERSIONS)
                resp = b"HTTP/1.1 200 OK\r\nServer: %s\r\nDate: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s" % (
                    server_header.encode(),
                    datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT').encode(),
                    content_type.encode(),
                    len(body),
                    body
                )
                await self._simulate_network_delay(writer, len(body))
                writer.write(resp)
                await writer.drain()
                return

            # 处理登录页面请求
            if path == "/login" and method == "GET":
                # 检查Accept头以决定返回HTML还是JSON
                accept_header = headers.get('accept', '')
                if 'application/json' in accept_header and 'text/html' not in accept_header:
                    # 返回JSON格式的登录页面信息
                    body = b'{"login_url": "/login", "method": "POST", "fields": ["username", "password"]}'
                    content_type = "application/json"
                else:
                    # 返回HTML登录页面
                    body = self.LOGIN_PAGE
                    content_type = "text/html"

                server_header = random.choice(self.NGINX_VERSIONS)
                resp = b"HTTP/1.1 200 OK\r\nServer: %s\r\nDate: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s" % (
                    server_header.encode(),
                    datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT').encode(),
                    content_type.encode(),
                    len(body),
                    body
                )
                await self._simulate_network_delay(writer, len(body))
                writer.write(resp)
                await writer.drain()
                return

            # 处理登录表单提交
            if path == "/login" and method == "POST":
                # 检查Content-Type以决定如何处理请求
                content_type_header = headers.get('content-type', '').lower()

                # 记录提交的登录凭据
                post_data = b""
                content_length = 0
                if "content-length" in headers:
                    try:
                        content_length = int(headers["content-length"])
                    except ValueError:
                        pass

                # 简化处理,实际应该读取body内容
                post_data = f"<{content_length} bytes of POST data>".encode()

                # 记录认证尝试到攻击日志
                attack_logger.log_auth_attempt(
                    source_ip=peer[0],
                    destination_port=self.port,
                    protocol=ProtocolType.HTTP,
                    username="web_user",
                    password="<captured>"
                )

                # 根据请求的Accept头决定返回什么类型的数据
                accept_header = headers.get('accept', '')
                if 'application/json' in accept_header and 'text/html' not in accept_header:
                    # 返回JSON格式的错误响应
                    body = random.choice(self.LOGIN_JSON_RESPONSES)
                    content_type = "application/json"
                    status_line = "HTTP/1.1 401 Unauthorized"
                else:
                    # 返回HTML格式的错误响应
                    body = b'<!DOCTYPE html>\n<html>\n<head>\n<title>Login Failed</title>\n</head>\n<body>\n<center><h1>Login Failed</h1></center>\n<hr><center>nginx</center>\n</body>\n</html>\n'
                    content_type = "text/html"
                    status_line = "HTTP/1.1 401 Unauthorized"

                server_header = random.choice(self.NGINX_VERSIONS)
                resp = b"%s\r\nServer: %s\r\nDate: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s" % (
                    status_line.encode(),
                    server_header.encode(),
                    datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT').encode(),
                    content_type.encode(),
                    len(body),
                    body
                )
                writer.write(resp)
                await writer.drain()

                # 记录登录尝试
                op_logger.info("Login attempt from %s: %s", peer[0], post_data.decode(errors="ignore"))
                return

            # 处理静态资源请求
            all_static_contents = {**self.STATIC_CONTENTS, **self.dynamic_paths}
            if path in all_static_contents:
                content, content_type = all_static_contents[path]

                # 记录文件访问到攻击日志
                attack_logger.log_file_access(
                    source_ip=peer[0],
                    destination_port=self.port,
                    protocol=ProtocolType.HTTP,
                    file_path=path,
                    access_type="read"
                )

                server_header = random.choice(self.NGINX_VERSIONS)
                resp = b"HTTP/1.1 200 OK\r\nServer: %s\r\nDate: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nETag: %s\r\nCache-Control: max-age=3600\r\nConnection: close\r\n\r\n%s" % (
                    server_header.encode(),
                    datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT').encode(),
                    content_type.encode(),
                    len(content),
                    self._generate_etag().encode(),
                    content
                )
                await self._simulate_network_delay(writer, len(content))
                writer.write(resp)
                await writer.drain()
                return

            # 处理/favicon.ico特殊请求
            if path == "/favicon.ico":
                content = b"\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00"

                # 记录文件访问到攻击日志
                attack_logger.log_file_access(
                    source_ip=peer[0],
                    destination_port=self.port,
                    protocol=ProtocolType.HTTP,
                    file_path=path,
                    access_type="read"
                )

                server_header = random.choice(self.NGINX_VERSIONS)
                resp = b"HTTP/1.1 200 OK\r\nServer: %s\r\nDate: %s\r\nContent-Type: image/x-icon\r\nContent-Length: %d\r\nETag: %s\r\nCache-Control: max-age=86400\r\nConnection: close\r\n\r\n%s" % (
                    server_header.encode(),
                    datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT').encode(),
                    len(content),
                    self._generate_etag().encode(),
                    content
                )
                await self._simulate_network_delay(writer, len(content))
                writer.write(resp)
                await writer.drain()
                return

            # 检查是否需要返回304
            if 'if-none-match' in headers:
                server_header = random.choice(self.NGINX_VERSIONS)
                resp = b"HTTP/1.1 304 Not Modified\r\nServer: %s\r\nDate: %s\r\nETag: %s\r\nConnection: close\r\n\r\n" % (
                    server_header.encode(),
                    datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT').encode(),
                    self._generate_etag().encode()
                )
                writer.write(resp)
                await writer.drain()
                return

            # 生成Cookie
            has_cookie = 'cookie' in headers
            session_cookie = ""
            if not has_cookie:
                session_cookie = "Set-Cookie: sessionid=%s; Path=/; HttpOnly\r\n" % self._generate_session_id()

            # 随机选择响应页面
            body = random.choice(self.RESPONSE_BODIES)

            # 随机选择nginx版本
            server_header = random.choice(self.NGINX_VERSIONS)

            # 构建响应
            resp_headers = [
                "HTTP/1.1 200 OK",
                "Server: %s" % server_header,
                "Date: %s" % datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT'),
                "Content-Type: text/html",
                "Content-Length: %d" % len(body),
                "ETag: %s" % self._generate_etag(),
                "Cache-Control: no-cache",
                "Connection: close"
            ]

            if not has_cookie:
                resp_headers.append(session_cookie.rstrip())

            resp = ("\r\n".join(resp_headers) + "\r\n\r\n").encode() + body
            await self._simulate_network_delay(writer, len(body))
            writer.write(resp)
            await writer.drain()

            entry = {
                "session_id": session_id,
                "start_time": start_ts.isoformat(),
                "end_time": datetime.now(timezone.utc).isoformat(),
                "duration_seconds": (datetime.now(timezone.utc) - start_ts).total_seconds(),
                "remote_ip": peer[0],
                "remote_port": peer[1],
                "inputs": [text],
                "raw_base64": base64.b64encode(raw).decode("ascii"),
            }
            await self.persist(entry)
        except Exception as e:
            op_logger.exception("HTTP处理器错误 %s: %s", session_id, e)
        finally:
            try:
                writer.close()
                await writer.wait_closed()
            except Exception:
                pass
            op_logger.info("HTTP关闭会话 %s 来自 %s", session_id, peer)
python 复制代码
#!/usr/bin/env python3
"""
HTTPS协议处理器
"""
import asyncio
import base64
import random
import ssl
import uuid
from datetime import datetime, timezone
from typing import Optional

from .http_handler import HTTPHandler
from .logger import op_logger

class HTTPSHandler(HTTPHandler):
    """
    HTTPS协议处理器,通过SSL包装HTTP处理器实现HTTPS服务
    """

    def __init__(self, host, port, log_file, certfile: str, keyfile: str):
        """
        初始化HTTPS处理器

        Args:
            host (str): 监听主机地址
            port (int): 监听端口
            log_file (str): 日志文件路径
            certfile (str): SSL证书文件路径
            keyfile (str): SSL密钥文件路径
        """
        super().__init__(host, port, log_file)
        self.name = "https"
        self.certfile = certfile
        self.keyfile = keyfile
        # 生成带hash的动态路径
        self.dynamic_paths = self._generate_dynamic_paths()

    async def start(self):
        """
        启动HTTPS服务器
        """
        try:
            # 创建SSL上下文
            ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
            ctx.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1  # 禁用旧版本
            ctx.load_cert_chain(certfile=self.certfile, keyfile=self.keyfile)
            # 添加更强的SSL配置
            ctx.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS')
            self._server = await asyncio.start_server(self._handle, host=self.host, port=self.port, ssl=ctx)
            addrs = ", ".join(str(sock.getsockname()) for sock in self._server.sockets)
            op_logger.info("HTTPS监听于 %s", addrs)
            asyncio.create_task(self._server.serve_forever())
        except FileNotFoundError:
            op_logger.error("找不到SSL证书文件: certfile=%s keyfile=%s", self.certfile, self.keyfile)
            op_logger.error("请使用--generate-cert参数自动生成证书,或手动提供证书文件")
        except Exception as e:
            op_logger.exception("HTTPS服务器启动失败: %s", e)
python 复制代码
#!/usr/bin/env python3
"""
HTTPS协议处理器
"""
import asyncio
import base64
import random
import ssl
import uuid
from datetime import datetime, timezone
from typing import Optional

from .http_handler import HTTPHandler
from .logger import op_logger

class HTTPSHandler(HTTPHandler):
    """
    HTTPS协议处理器,通过SSL包装HTTP处理器实现HTTPS服务
    """

    def __init__(self, host, port, log_file, certfile: str, keyfile: str):
        """
        初始化HTTPS处理器

        Args:
            host (str): 监听主机地址
            port (int): 监听端口
            log_file (str): 日志文件路径
            certfile (str): SSL证书文件路径
            keyfile (str): SSL密钥文件路径
        """
        super().__init__(host, port, log_file)
        self.name = "https"
        self.certfile = certfile
        self.keyfile = keyfile
        # 生成带hash的动态路径
        self.dynamic_paths = self._generate_dynamic_paths()

    async def start(self):
        """
        启动HTTPS服务器
        """
        try:
            # 创建SSL上下文
            ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
            ctx.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1  # 禁用旧版本
            ctx.load_cert_chain(certfile=self.certfile, keyfile=self.keyfile)
            # 添加更强的SSL配置
            ctx.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS')
            self._server = await asyncio.start_server(self._handle, host=self.host, port=self.port, ssl=ctx)
            addrs = ", ".join(str(sock.getsockname()) for sock in self._server.sockets)
            op_logger.info("HTTPS监听于 %s", addrs)
            asyncio.create_task(self._server.serve_forever())
        except FileNotFoundError:
            op_logger.error("找不到SSL证书文件: certfile=%s keyfile=%s", self.certfile, self.keyfile)
            op_logger.error("请使用--generate-cert参数自动生成证书,或手动提供证书文件")
        except Exception as e:
            op_logger.exception("HTTPS服务器启动失败: %s", e)
python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
增强版 SSH 蜜罐处理器(基于 asyncssh)
与现有 ProtocolHandler 集成:继承自 ProtocolHandler,并调用 self.persist(entry) 保存事件。
功能:
 - 完整 SSH 握手(KEX、host key)
 - 认证点位(password / publickey):记录凭证尝试
 - 交互 shell(PTY)与 exec:记录命令与响应
 - 会话元数据(session_id, client_version, remote ip/port, start/end/duration)
 - 可配置:是否接受密码、公钥;欢迎词、prompt、伪文件内容等
注意:不要在生产网络直接暴露未隔离的蜜罐实例,务必放在隔离网络。
"""
from __future__ import annotations

import asyncio
import asyncssh
import base64
import uuid
import logging
from datetime import datetime, timezone
from typing import Dict, Any, Optional

from .base_handler import ProtocolHandler
from .config import get_ssh_config
from .logger import op_logger
from multihoneypot.attacklogger import attack_logger, AttackType, ProtocolType

def now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()

class HoneySSHServer(asyncssh.SSHServer):
    """
    asyncssh 的 server-side 回调:处理连接级事件与认证回调委托给 handler。
    """

    def __init__(self, handler: "SSHHandler", peername):
        self.handler = handler
        self.peername = peername  # (ip, port)

    def connection_made(self, conn):
        # conn: asyncssh.SSHServerConnection
        client_version = getattr(conn, "client_version", None)
        op_logger.info("connection_made peer=%s client_version=%s", self.peername, client_version)

    def connection_lost(self, exc):
        op_logger.info("connection_lost peer=%s exc=%s", self.peername, exc)

    def begin_auth(self, username: str) -> bool:
        # True 表示需要认证(将触发 validate_password / validate_public_key)
        return True

    async def validate_password(self, username: str, password: str) -> bool:
        # 记录认证尝试到攻击日志
        attack_logger.log_auth_attempt(
            source_ip=self.peername[0],
            destination_port=self.handler.port,
            protocol=ProtocolType.SSH,
            username=username,
            password=password
        )

        # 记录尝试(避免在日志中直接打印明文密码,请注意权限)
        await self.handler._record_auth_attempt({
            "method": "password",
            "username": username,
            "password": password,  # 如果合规/敏感,后续可替换为 hash
            "remote_ip": self.peername[0],
            "remote_port": self.peername[1],
            "time": now_iso(),
        })
        # 根据配置决定是否允许(蜜罐策略)
        # return bool(self.handler.config.get("accept_password", False))
        allowed: Dict[str, str] = self.handler.config.get("allowed_credentials", {})
        if username in allowed and allowed[username] == password:
            return True
        return False

    async def validate_public_key(self, username: str, key: asyncssh.PKey) -> bool:
        """
        验证SSH公钥认证请求

        Args:
            username (str): 尝试认证的用户名
            key (asyncssh.PKey): 客户端提供的公钥对象

        Returns:
            bool: 如果接受公钥认证则返回True,否则返回False
        """
        fp = key.get_fingerprint()

        # 记录认证尝试到攻击日志
        attack_logger.log_auth_attempt(
            source_ip=self.peername[0],
            destination_port=self.handler.port,
            protocol=ProtocolType.SSH,
            username=username,
            pubkey_fingerprint=fp
        )

        await self.handler._record_auth_attempt({
            "method": "publickey",
            "username": username,
            "pubkey_fingerprint": fp,
            "pubkey_type": getattr(key, "get_type", lambda: "unknown")(),
            "remote_ip": self.peername[0],
            "remote_port": self.peername[1],
            "time": now_iso(),
        })
        return bool(self.handler.config.get("accept_publickey", False))

class HoneySSHSession(asyncssh.SSHServerSession):
    """
    处理单个 session(shell/exec/pty)
    """

    def __init__(self, handler: "SSHHandler", session_meta: Dict[str, Any]):
        self.handler = handler
        self.session_meta = session_meta
        self._chan = None
        self._buffer = ""
        self.pty_info = None
        self._closed = False

    def connection_made(self, chan):
        self._chan = chan
        op_logger.debug("session connection_made session_id=%s", self.session_meta.get("session_id"))

    def connection_lost(self, exc):
        op_logger.debug("session connection_lost session_id=%s exc=%s", self.session_meta.get("session_id"), exc)

    def session_started(self):
        # callback when session established
        pass

    def pty_requested(self, term, width, height, pxwidth, pxheight, modes):
        self.pty_info = {"term": term, "width": width, "height": height}
        return True

    def shell_requested(self):
        # 启动交互 shell
        asyncio.create_task(self._run_shell())
        return True

    def exec_requested(self, command):
        # 记录命令执行到攻击日志
        if self.session_meta.get("remote_ip"):
            attack_logger.log_command_execution(
                source_ip=self.session_meta["remote_ip"],
                destination_port=self.session_meta.get("destination_port", 22),
                protocol=ProtocolType.SSH,
                command=command,
                command_type="exec"
            )

        # 记录 exec 命令并立即返回伪结果后退出
        asyncio.create_task(self.handler._record_command(self.session_meta, command, is_exec=True))
        # 使用 handler 的 fake_exec_response 生成结果
        out = self.handler.fake_exec_response(command)
        try:
            # write 可能在不同线程上下文,使用 chan.write
            if self._chan:
                self._chan.write(out + "\n")
                self._chan.exit(0)
        except Exception:
            op_logger.exception("exec response error")
        return True

    async def _run_shell(self):
        """
        读取 channel 数据(byte),按行解析命令并响应。
        同时记录原始字节(base64)与解析后的命令事件。
        """
        prompt = self.handler.shell_prompt()
        welcome = self.handler.welcome_message()
        try:
            if self._chan is None:
                return
            # 写欢迎词和 prompt
            if welcome:
                self._chan.write(welcome + "\r\n")
            if prompt:
                self._chan.write(prompt)
            # 读取循环
            while True:
                data = await self._chan.read(1024)
                if data is None:
                    break
                # data 是 bytes
                try:
                    decoded = data.decode(errors="ignore")
                except Exception:
                    decoded = str(data)
                # 记录原始交互片段
                await self.handler._record_interaction(self.session_meta, data)
                # 拼接缓冲并按换行拆分以得到完整命令
                self._buffer += decoded
                if "\n" in self._buffer or "\r" in self._buffer:
                    # 取第一条命令(保守)
                    line, _, rest = self._buffer.partition("\n")
                    line = line.rstrip("\r")
                    self._buffer = rest
                    cmd = line.strip()

                    # 记录命令执行到攻击日志
                    if self.session_meta.get("remote_ip") and cmd:
                        attack_logger.log_command_execution(
                            source_ip=self.session_meta["remote_ip"],
                            destination_port=self.session_meta.get("destination_port", 22),
                            protocol=ProtocolType.SSH,
                            command=cmd,
                            command_type="shell"
                        )

                    # 记录命令事件
                    await self.handler._record_command(self.session_meta, cmd, is_exec=False)
                    # 特殊命令处理
                    if cmd in ("exit", "logout", "quit"):
                        self._chan.write("logout\r\n")
                        self._chan.exit(0)
                        break
                    # 生成伪输出并写回
                    resp = self.handler.fake_exec_response(cmd)
                    if resp:
                        self._chan.write(resp + "\r\n")
                    # 再写 prompt
                    if prompt:
                        self._chan.write(prompt)
                else:
                    # 未到换行,继续读取
                    pass
        except Exception as e:
            op_logger.exception("shell loop exception session=%s: %s", self.session_meta.get("session_id"), e)
        finally:
            self._closed = True

class SSHHandler(ProtocolHandler):
    """
    可直接替换你现有的 SSH Handler:
      - 保持与 ProtocolHandler.persist(entry) 的兼容(调用 await self.persist(entry))
      - 配置源自 get_ssh_config(config)
    """

    def __init__(self, host: str, port: int, log_file: str, config=None):
        super().__init__("ssh", host, port, log_file)
        self.config: Dict[str, Any] = {}
        if config:
            self.config.update(get_ssh_config(config))
        # 默认值
        self.config.setdefault("banner", "SSH-2.0-OpenSSH_7.9")
        self.config.setdefault("accept_password", False)
        self.config.setdefault("accept_publickey", False)
        self.config.setdefault("shell_prompt", "[root@server ~]$ ")
        self.config.setdefault("welcome_message", "Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 4.15.0-xyz)")
        self.config.setdefault("host_key_path", None)  # 若配置了 path 则读取私钥文件
        self.config.setdefault("allowed_credentials", {"root": "123456", "admin": "adminpwd"})

        self._server = None
        self._active_sessions = {}  # session_id -> meta

    def shell_prompt(self) -> str:
        return str(self.config.get("shell_prompt", ""))

    def welcome_message(self) -> str:
        return str(self.config.get("welcome_message", ""))

    async def _record_auth_attempt(self, info: Dict[str, Any]):
        # 标准化保存
        entry = {
            "event": "auth_attempt",
            "time": now_iso(),
            **info
        }
        try:
            await self.persist(entry)
        except Exception:
            op_logger.exception("persist auth attempt failed")

    async def _record_command(self, session_meta: Dict[str, Any], command: str, is_exec: bool = False):
        entry = {
            "event": "command",
            "time": now_iso(),
            "session_id": session_meta.get("session_id"),
            "username": session_meta.get("username"),
            "command": command,
            "is_exec": bool(is_exec),
            "remote_ip": session_meta.get("remote_ip"),
            "remote_port": session_meta.get("remote_port"),
        }
        try:
            await self.persist(entry)
        except Exception:
            op_logger.exception("persist command failed")

    async def _record_interaction(self, session_meta: Dict[str, Any], raw_bytes: bytes):
        entry = {
            "event": "interaction",
            "time": now_iso(),
            "session_id": session_meta.get("session_id"),
            "username": session_meta.get("username"),
            "raw_base64": base64.b64encode(raw_bytes).decode("ascii"),
            "remote_ip": session_meta.get("remote_ip"),
            "remote_port": session_meta.get("remote_port"),
        }
        try:
            await self.persist(entry)
        except Exception:
            op_logger.exception("persist interaction failed")

    def fake_exec_response(self, cmd: str) -> str:
        # 伪造命令输出(可扩展成模板或脚本)
        cmd = (cmd or "").strip()
        if cmd in ("ls", "ls -la"):
            return "total 8\ndrwxr-xr-x 2 root root 4096 Sep 20 12:00 .\n-rw-r--r-- 1 root root   23 Sep 20 12:00 README"
        if cmd.startswith("cat "):
            return "This is a fake file content"
        if cmd in ("whoami",):
            return "root"
        if cmd in ("pwd",):
            return "/root"
        if cmd.startswith("uname") or cmd.startswith("uname -a"):
            return "Linux server 4.15.0-xyz #1 SMP Thu Sep 10 12:00:00 UTC 2020 x86_64 GNU/Linux"
        if cmd.startswith("wget ") or cmd.startswith("curl "):
            return "Downloading... saved to /tmp/fakefile"
        if cmd == "":
            return ""
        return f"bash: {cmd}: command not found"

    async def start(self):
        """
        启动 asyncssh 服务。会生成临时 host key(除非配置 host_key_path)。
        """
        # 读取配置 host key 或生成临时 host key
        host_key_path = self.config.get("host_key_path")
        server_host_keys = None
        if host_key_path:
            try:
                server_host_keys = [host_key_path]
            except Exception:
                op_logger.exception("failed to use host_key_path %s", host_key_path)
                server_host_keys = None

        if not server_host_keys:
            # 生成临时 host key(RSA)
            try:
                # asyncssh.generate_private_key 返回 PKey,create_server 接受 PKey 对象
                temp_key = asyncssh.generate_private_key("ssh-rsa")
                server_host_keys = [temp_key]
                op_logger.info("generated ephemeral host key")
            except Exception:
                op_logger.exception("failed to generate ephemeral host key")
                server_host_keys = None  # let asyncssh decide (rare)

        # 启动 asyncssh server
        op_logger.info("starting asyncssh server on %s:%s", self.host, self.port)
        try:
            server = await asyncssh.create_server(
                lambda: HoneySSHServer(self, None),
                host=self.host,
                port=self.port,
                server_host_keys=server_host_keys,
                # 下面参数可以根据需要调整
                allow_scp=False,
                # 如果要支持 SFTP,你需要传入 sftp_factory
            )
            self._server = server
            op_logger.info("asyncssh server started listen=%s:%s", self.host, self.port)
            # asyncssh.create_server 返回一个 Server 对象;保持运行由外部 event loop 管理
        except Exception as e:
            op_logger.exception("failed to start asyncssh server: %s", e)
            raise

    async def stop(self):
        if self._server:
            try:
                self._server.close()
                await self._server.wait_closed()
                op_logger.info("ssh server closed")
            except Exception:
                op_logger.exception("error while stopping ssh server")
相关推荐
软件供应链安全指南2 小时前
“基于‘多模态SCA+全周期协同’的中间件开源风险治理实践”荣获OSCAR开源+安全及风险治理案例
安全·中间件·开源
KKKlucifer3 小时前
技术漏洞被钻营!Agent 感知伪装借 ChatGPT Atlas 批量输出虚假数据,AI 安全防线面临新挑战
人工智能·安全·chatgpt
wanhengidc4 小时前
物理服务器都有哪些作用?
运维·服务器·安全·智能手机·云计算
九章-4 小时前
中旅国际数据库国产化升级:以金仓KES打造安全可控的旅游服务底座
数据库·安全·旅游
Mxsoft6196 小时前
电力系统智能运维网络安全威胁检测与防御策略
运维·安全·web安全
jenchoi4139 小时前
【2025-11-07】软件供应链安全日报:最新漏洞预警与投毒预警情报汇总
网络·安全·web安全·网络安全
云动雨颤14 小时前
访问宝塔面板安全入口404?SSH命令轻松解决
linux·运维·安全
东方隐侠安全团队-千里14 小时前
第4节 ARPANet 第一次意识到“密码不能明着传”
网络·安全·web安全