SSHintel 是一个使用 Python 和 Paramiko 构建的轻量级 SSH 蜜罐。它模拟一个虚假的 Linux shell,用于记录未经授权的访问尝试、捕获凭据,并在受控环境中分析攻击者行为。
ls、cd、pwd、cat、echo、grep、find、tree、head、tail、wc、stat、ps、df、free、env、id、whoami 等)--tarpit 模式,通过延迟输出拖慢攻击者SSHintel 并非完整的 Bash/Linux shell。它模拟一组可信的常用命令子集,以收集攻击者遥测数据。命令由轻量级注册表分发;添加一个命令意味着添加一个小型处理函数。
pip install -r requirements.txt
注意: SSH 主机密钥在首次运行时自动生成。无需手动生成密钥。
使用指定的端口、用户名和密码运行蜜罐:
python3 run.py serve --port 2222 --username user1 --password pass123
默认端口为
2222,主机为0.0.0.0。
蜜罐在首次运行时,如果 static/server.key 不存在,会自动生成一个 SSH 主机密钥。无需手动生成密钥。
要启用 tarpit 模式:
python3 run.py serve --port 2222 --username user1 --password pass123 --tarpit
要禁用 SQLite 遥测(仅使用 JSONL):
python3 run.py serve --port 2222 --username user1 --password pass123 --no-db
传感器设置采用 CLI > 环境变量 > 配置文件 > 默认值 的优先级。
只有显式提供的 CLI 标志才会覆盖其他来源。这适用于
serve;dashboard/query 命令保留各自的 CLI 设置。
在 Python 3.11+ 上:
cp sshintel.toml.example sshintel.toml
chmod 600 sshintel.toml
python3 run.py serve
在 Python 3.8/3.10 上,请使用无依赖的 JSON 等效方案:
cp sshintel.json.example sshintel.json
chmod 600 sshintel.json
python3 run.py serve
在部署前编辑复制出的文件。TOML 需要 Python 3.11+;较旧版本会失败并给出明确提示,要求改用 JSON。若未显式指定文件,SSHintel 会在当前目录中依次查找 sshintel.toml,然后查找 sshintel.json。使用 serve --config /path/to/settings.json 或 HONEYPOT_CONFIG 选择其他文件(--config 优先)。显式指定的文件缺失、文件格式错误、未知键以及无效值都会在服务器或数据库启动前导致失败。配置中的文件路径相对于进程工作目录,而非配置文件所在目录。
| 文件键 | 环境变量 | 默认值 |
|---|---|---|
host | HONEYPOT_HOST | 0.0.0.0 |
port | HONEYPOT_PORT(旧别名:PORT) | 2222 |
username | HONEYPOT_USERNAME | user1 |
password | HONEYPOT_PASSWORD | pass123 |
tarpit | HONEYPOT_TARPIT | false |
tarpit_max_seconds | HONEYPOT_TARPIT_MAX_SECONDS | 120 |
max_connections | HONEYPOT_MAX_CONNECTIONS | 50 |
auth_timeout | HONEYPOT_AUTH_TIMEOUT | 60 |
session_idle_timeout | HONEYPOT_SESSION_IDLE_TIMEOUT | 300 |
sensor_id | HONEYPOT_SENSOR_ID | 未设置 |
db_path | HONEYPOT_DB_PATH | data/sshintel.db |
no_db | HONEYPOT_NO_DB | false |
fetch_payloads | HONEYPOT_FETCH_PAYLOADS | false(保留) |
环境变量中的布尔值接受 true/false、1/0、yes/no 和 on/off。HONEYPOT_PORT 优先于 PORT。端口必须为 1–65535,限制值和超时值必须为正整数。使用 --no-tarpit、--db-enabled 或 --no-fetch-payloads 可覆盖优先级较低的真值设置。数据库路径使用现有的 --db CLI 标志。载荷获取功能尚未实现:启用 fetch_payloads 会被拒绝,而不是被静默忽略。
本地的 sshintel.toml/sshintel.json 文件已被 Git 忽略,并从 Docker 构建中排除,因为它们可能包含凭据。请改为将配置文件以只读方式挂载到容器中。Docker 直接使用环境变量(不会对凭据进行 shell 插值);其现有的 ENV 默认值优先于挂载的文件。根据需要覆盖这些变量,或使用显式的 CLI 标志。
SSHintel 可防止因大量并发连接或无限期保持的连接而导致资源耗尽。这些可通过 CLI 配置:
| 标志 | 默认值 | 用途 |
|---|---|---|
--max-connections | 50 | 最大同时活跃连接数;多余的连接会被拒绝并记录为 connection_rejected 事件 |
--auth-timeout | 60(秒) | 完成 SSH 握手/认证所允许的时间;停滞的客户端会被断开连接 |
--session-idle-timeout | 300(秒) | 已认证 shell 的空闲超时;空闲会话会被结束,但正在积极输入的攻击者永远不会被终止 |
示例:
python3 run.py serve --port 2222 --username user1 --password pass123 \
--max-connections 25 --auth-timeout 30 --session-idle-timeout 300
当打开的连接过多时,多余的连接会被立即关闭,并向 JSONL 日志写入一个 connection_rejected 安全事件(带有 reason: connection_limit)。停滞的认证会被记录为一次断开连接,带有 reason: auth_timeout;空闲 shell 则以 reason: idle_timeout 结束。
Tarpit 模式会有意缓慢地发送输出,以保持攻击者被牵制,因此 tarpit 横幅循环不受不活动超时限制——但 tarpit 会话确实会计入连接限制。
SSHintel 包含一个本地 Web 仪表盘,用于可视化存储在 SQLite 中的安全遥测数据。
python3 run.py dashboard
然后在浏览器中打开 http://localhost:5000。
仪表盘默认从位于
data/sshintel.db的 SQLite 数据库读取数据。请先启动蜜罐,以便捕获遥测数据,然后再启动仪表盘以观察数据填充情况。
点击会话 ID(或导航至 /session/<session_id>)以打开会话调查视图,该视图按时间顺序重建单次攻击:
$ command 视图展示攻击者输入的所有内容,并附带工作目录# Terminal 1: start the honeypot
python3 run.py serve --port 2222 --username user1 --password pass123
# Terminal 2: start the dashboard
python3 run.py dashboard
# Terminal 3: simulate an attacker
ssh user1@localhost -p 2222
# (run some commands, then exit)
然后打开 http://localhost:5000 查看捕获的活动。
仪表盘支持实时遥测——它每 2 秒轮询一次蜜罐并自动更新。新的会话、命令和事件会实时出现,无需刷新页面。实时指示器(● Live)显示连接状态。
打开第二个终端并尝试连接:
ssh user1@localhost -p 2222
如果凭据匹配,你将被带入模拟 shell。
要移除过期的 SSH 指纹:
notepad "%USERPROFILE%\.ssh\known_hosts"
删除包含
localhost或蜜罐 IP 的相关行。
creds_loggerfunnel_logger 记录log_files/events.jsonl —— 每行一个有效的 JSON 对象每个 JSONL 事件包含 UTC ISO-8601 格式的 timestamp、event_type、唯一的 session_id 以及 source_ip。连接、认证尝试/结果、命令执行、tarpit 激活以及断开连接均作为结构化事件记录。
每个传入的 SSH 连接都作为独立的 session 进行跟踪,拥有自己的 session_id。会话记录源 IP、连接/断开时间、认证结果以及连接持续时间,该连接内生成的每个事件都携带相同的 session_id(因此认证尝试、命令和断开连接都可以追溯到单个连接)。会话按连接隔离 —— 并发客户端之间不共享任何状态。
每个会话还会获得其自己独立的、内存中的伪造文件系统 —— 模拟文件系统为每个连接全新创建,并在连接结束时清理。一个攻击者创建或更改的文件、目录和工作目录永远不会被同时连接的其他攻击者看到。整个文件系统在 Python 内存中模拟,从不触及真实主机文件系统。
当前 event_type 值:connect、auth_attempt、auth_success、auth_failure、command、disconnect、connection_rejected、tarpit。
SSHintel/
├── honeypot/ # Core honeypot logic
│ ├── __init__.py
│ ├── main.py # Accept loop + connection limiting
│ ├── handlers.py # SSH transport setup + emulated shell
│ ├── server.py # Paramiko server interface (auth)
│ ├── session.py # Per-connection session tracking
│ ├── fs.py # In-memory fake filesystem (isolated per session)
│ ├── shell.py # Fake shell: command registry + dispatcher
│ ├── limits.py # Thread-safe concurrent connection limiting
│ ├── logger.py # JSONL event logging + SQLite bridge
│ └── telemetry_store.py # SQLite telemetry store + query layer
│
├── dashboard/ # Local web dashboard
│ ├── app.py # Flask application + API routes
│ ├── templates/
│ │ ├── index.html # Main dashboard template
│ │ └── session.html # Session investigation template
│ └── static/
│ ├── style.css # Dashboard styles
│ ├── dashboard.js # Main dashboard JS (live updates)
│ └── session.js # Session investigation JS
│
├── log_files/ # Runtime logs (git-ignored)
│ ├── creds_audits.log # Credential attempts
│ ├── cmd_audits.log # Command audit trail
│ └── events.jsonl # Structured JSONL security events
│
├── data/ # SQLite database (git-ignored)
│ └── sshintel.db
│
├── static/ # SSH host key (auto-generated)
│ └── server.key
│
├── .github/workflows/ # CI configuration
│ └── tests.yml
│
├── Dockerfile
├── README.md
├── requirements.txt # Runtime dependencies (paramiko, flask)
├── requirements-dev.txt # Test dependencies (pytest, pytest-cov)
└── run.py # CLI entrypoint