用screen和防火墙构建主动防御型日志审计体系
你有没有遇到过这种情况:
某天早上刚到公司,就收到告警——生产服务器上的关键配置被修改了。你翻遍 bash history,发现记录全被清空;查看登录日志,IP 显示是某个合法运维人员的地址;再查进程列表,一切正常……但系统确实已经被动过手脚。
问题出在哪?攻击者用了screen。
他们通过 SSH 登录后启动一个带会话持久化的screen,在其中执行敏感操作,然后 detach 离开。整个过程不留下任何命令历史痕迹,且会话长期驻留,随时可重新接入继续作恶。更糟的是,这类行为很难被传统监控工具捕捉。
这正是我们今天要解决的问题:如何让看似“隐形”的screen操作无处遁形,并实现自动封禁?
为什么选screen?它既是便利工具,也是安全盲区
screen不是个冷门命令。相反,它几乎存在于每一台 Linux 服务器上,而且默认就能用。它的核心价值在于:
- 断线不断任务:SSH 掉了,后台进程照样跑。
- 多窗口并行:不用开多个终端,一个 screen 能干十件事。
- 支持恢复连接:随时 attach 回去看结果。
但对于安全团队来说,这些优点恰恰成了风险点:
✅ 合法用户喜欢它 → ❌ 攻击者也爱用它。
更重要的是,screen的交互式会话完全绕过了.bash_history这类基于 shell 的日志机制。哪怕你把 history size 设为无限大、关闭 history -c 权限,只要操作发生在screen里,就不会出现在 history 文件中。
所以,常规审计手段在这里集体失效。
把screen变成“透明盒子”:从不可见操作到完整日志捕获
好消息是,screen自带日志功能——只要你愿意打开它。
启用会话级日志记录
最简单的办法是在启动时加上-L参数:
screen -L -Logfile /var/log/screen/session-$(date +%F-%H%M).log -S audit-session这样,所有输入输出(包括密码回显!)都会被原样写入日志文件。你可以看到完整的命令执行流,甚至能“回放”整个操作过程。
但这还不够。我们不能指望每个用户都自觉加-L。我们需要的是强制启用。
强制记录:封装screen命令
创建一个 wrapper 脚本,替代原始screen:
#!/bin/bash # /usr/local/bin/screen-wrapper LOG_DIR="/var/log/screen" USER=$(whoami) HOST=$(hostname) TIMESTAMP=$(date +%F-%H%M%S) mkdir -p $LOG_DIR chmod 750 $LOG_DIR LOGFILE="$LOG_DIR/${USER}@${HOST}-${TIMESTAMP}.log" # 设置不可变属性,防止篡改(需 root 权限) exec /usr/bin/screen -L -Logfile "$LOGFILE" "$@"然后替换系统调用路径或设置别名:
alias screen='/usr/local/bin/screen-wrapper'或者直接重命名原 binary 并用新脚本占位:
mv /usr/bin/screen /usr/bin/screen.real cp screen-wrapper /usr/bin/screen这样一来,任何人使用screen都会被自动记录,无法逃避。
日志写下来只是第一步,关键是要“看得懂”还能“动得快”
光有日志没用。等你下班回家翻日志才发现有人删库,黄花菜都凉了。
我们要的是实时感知 + 自动响应。
这就轮到防火墙出场了。
iptables/nftables 是你的策略执行终端
很多人以为防火墙只能拦流量。其实它可以成为整个安全体系的“执行器”。
设想这样一个场景:
用户 A 在
screen中输入了rm -rf /tmp/*——看着像误操作。
但他紧接着又敲了一行:iptables -F。
这就不对劲了。普通运维不会轻易刷防火墙规则。
如果我们能在第二条命令出现的瞬间,立即封掉这个用户的来源 IP,是不是就能阻止他下一步可能的reboot或shutdown now?
这就是联动的意义。
实战:监听日志 → 分析行为 → 动态封禁
下面这段 Python 脚本,就是我们的“中枢神经”。
import re import subprocess import time from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler # 高危命令模式库(可根据业务扩展) DANGEROUS_PATTERNS = [ r'\brm\s+-rf\s+/', # 危险删除 r'\bchmod\s+777\s+', # 宽松权限 r'\biptables\s+-(F|P)\b', # 刷防火墙规则 r'\bsu\s+-\s+root\b', # 切换 root r'\bpkill\s+-9\s+-f\s+ssh', # 终止 SSH 服务 r'\bsystemctl\s+(stop|disable)\s+sshd' # 关闭 SSH ] BANNED_IPS = set() # 缓存已封禁 IP,避免重复操作 def ban_ip(ip): """调用 iptables 封禁指定 IP(30 分钟)""" if ip in BANNED_IPS: return try: # 添加 DROP 规则 subprocess.run([ 'iptables', '-A', 'INPUT', '-s', ip, '-j', 'DROP' ], check=True, capture_output=True) # 记录日志 subprocess.run([ 'logger', f'[AUDIT] Blocked {ip} for dangerous screen activity' ], check=True) print(f"[+] 已封禁 IP: {ip}") BANNED_IPS.add(ip) # 可选:30分钟后自动解封(使用 at 或后台线程) unban_later(ip, delay=1800) except subprocess.CalledProcessError as e: print(f"[-] 封禁失败 {ip}: {e}") def unban_later(ip, delay): """延迟解封(简单实现)""" def task(): time.sleep(delay) try: subprocess.run(['iptables', '-D', 'INPUT', '-s', ip, '-j', 'DROP'], check=True) subprocess.run(['logger', f'[AUDIT] Unblocked {ip} after timeout']) BANNED_IPS.discard(ip) except subprocess.CalledProcessError: pass import threading threading.Thread(target=task, daemon=True).start()接着是文件监控部分:
class ScreenLogHandler(FileSystemEventHandler): def on_modified(self, event): if event.is_directory or "screen" not in event.src_path: return try: with open(event.src_path, 'r') as f: lines = f.readlines()[-20:] # 只读最后几行,提升效率 for line in lines: # 提取 SSH 客户端 IP(常见于环境变量打印) client_match = re.search(r'SSH_CLIENT=(\d+\.\d+\.\d+\.\d+)', line) connection_match = re.search(r'SSH_CONNECTION=(\d+\.\d+\.\d+\.\d+)', line) src_ip = (client_match or connection_match) if src_ip: src_ip = src_ip.group(1) else: src_ip = "unknown" # 匹配高危命令 for pattern in DANGEROUS_PATTERNS: if re.search(pattern, line, re.IGNORECASE): print(f"[!] 检测到危险操作来自 {src_ip}: {line.strip()}") if src_ip != "unknown" and not src_ip.startswith("192.168"): ban_ip(src_ip) except Exception as e: print(f"[!] 处理日志失败: {e}") # 启动监听 observer = Observer() handler = ScreenLogHandler() observer.schedule(handler, path='/var/log/screen/', recursive=False) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join()保存为screen-audit-guard.py,配合 systemd 注册为守护进程即可长期运行。
如何避免误伤?这些细节决定成败
这套机制听起来很猛,但也容易“走火”。以下是几个必须注意的坑点与应对策略:
1.日志权限必须锁死
否则攻击者可以直接删除或清空日志文件。
chown -R root:adm /var/log/screen chmod 750 /var/log/screen find /var/log/screen/*.log -exec chattr +i {} \; # 启用 immutable 标志注意:开启
chattr +i后,连 root 都不能修改文件,除非先chattr -i。适合只读归档场景。
2.敏感信息脱敏处理
密码、密钥、数据库连接串都会被记录下来。建议:
- 使用正则过滤替换部分内容:
python line = re.sub(r'password=\S+', 'password=***', line) - 或将日志加密存储(如使用 GPG),仅授权人员可解密查阅。
3.性能优化:别让审计拖垮系统
高频写入可能导致 I/O 压力上升。解决方案:
- 启用
logrotate定期切割日志; - 使用 ring buffer 临时缓存,异步落盘;
- 监控脚本采用增量读取(记录 offset),而非全量加载。
4.白名单机制:信任的命令不该报警
不是所有iptables -F都是恶意的。可以加入白名单判断上下文:
WHITELIST_USERS = ['netadmin'] WHITELIST_IPS = ['10.0.0.5'] # 在检测到命令后追加判断 if src_ip in WHITELIST_IPS and 'netadmin' in line: continue # 跳过封禁或者结合时间窗统计:短时间内多次触发才视为异常。
更进一步:不只是screen,而是构建统一行为审计平台
这套方案的本质是什么?
把终端操作转化为可编程的安全事件。
一旦你能解析文本日志、提取行为特征、关联上下文信息,就可以把它扩展成一个轻量级 SIEM。
比如:
- 统计某个用户一天内执行了多少次 sudo;
- 发现某 IP 地址频繁切换不同账号登录;
- 检测是否存在“横向移动”迹象(连续访问多台主机);
- 结合时间规律建模,识别非工作时段的异常活跃行为。
未来甚至可以引入机器学习模型,训练用户操作指纹,实现基于基线偏离的智能告警。
写在最后:安全不是“防住”,而是“看见 + 控制”
很多企业花大价钱买 EDR、部署堡垒机,却忘了最基础的一点:只要允许用户登录服务器,就必须确保每一步操作都被记录和可干预。
而screen+ 防火墙联动的方案,提供了一个低成本、高实效的技术路径:
- 不依赖昂贵商业软件;
- 不需要改造现有架构;
- 所用技术全是开源标准组件;
- 实现从“被动取证”到“主动阻断”的跨越。
如果你正在负责跳板机、数据库服务器、云主机等高权限环境的安全建设,不妨试试这个思路。
毕竟,真正的安全感,来自于你知道“谁做了什么”,并且有能力说:“停下。”
如果你在实际部署中遇到权限冲突、日志延迟或误封问题,欢迎留言交流。我可以分享一套经过生产验证的配置模板和告警分级策略。