Skip to content

服务器上到底发生了什么?SSH 会话审计与操作回放怎么做

磁盘被误删、配置被改错、数据库被 drop、一台机器突然多了个陌生进程——事故发生后,第一个问题永远是同一句:刚才到底是谁、在什么时候、敲了什么命令、看到了什么输出。SSH 会话审计与操作回放要解决的正是这个问题:把一次远程操作从「结果」还原成「过程」。

难点在于 SSH 本身就是端到端加密的。它承诺的是传输保密和完整性,而不是「操作可追溯」。服务器默认只留下命令执行的结果——一堆零散的日志——不会留下操作者当时看到的画面和上下文。所以当你想回答「发生了什么」时,才发现手里根本没有一条完整的记录。这篇文章讲清楚四件事:单机上的 history 和 auditd 为什么不够、录像录的到底是什么、在哪一层录才可信、以及怎么把录像变成不可篡改的证据。

为什么事后查不出真相:历史记录和日志都只记了结果

一台没有做过任何审计加固的服务器,事后能翻的东西大致只有下面几样,而每一样都缺了关键一块:

手段记录了什么缺了什么
shell history交互式 shell 里敲过的命令默认无时间戳、可一键清空、不记录输出
auditd内核层的 execve/open 等系统调用是调用流不是会话,难以还原上下文
last / wtmp登录与登出事件只有「谁来过」,没有「做了什么」

~/.bash_history 是最常被寄予厚望的那个,也是最先让人失望的那个。它默认不记时间戳,要显式配置 HISTTIMEFORMAT 才有;更关键的是它只记录交互式 shell,用 ssh host 'command' 这种非交互方式执行的命令根本不会写进去。最致命的一点是——它是可被彻底抹掉的:

bash
# 默认 history 不带时间戳,加上这一行才有
export HISTTIMEFORMAT="%F %T "
# 但下面任意一条都能让历史记录"消失"
history -c
> ~/.bash_history
unset HISTFILE

对一个有 shell 权限的用户来说,清掉自己的痕迹只需要一条命令,而且 bash 的历史文件按用户隔离,root 想查看别人在做什么,还得去翻对方的家目录。用 history 做审计,等于把证据交给当事人自己保管。

auditd 走的是另一条路:它在内核层记录系统调用,能看到类似 execve("/bin/rm", ...) 这样的记录,理论上连非交互命令都能抓到。但它的产物是海量的 syscall 流,一次普通的 ls 就能产生几百条记录,要还原「某人在某次会话里做了什么」,得按 sessionid、父进程 id 一路关联拼凑,噪声极大。而且 auditd 的规则本身由 root 管理——当调查对象恰恰是 root 时,这套记录的可信度就存疑了。

lastwtmp 只登记登录登出事件:谁、何时、从哪个 IP 来过。它能证明「这个人来过」,不能证明「他干了什么」。事故复盘时,这三样东西拼在一起,往往只能得出「有人登录过,然后系统就坏了」这种判断。

这几种手段还有一个共同的天花板:它们都躺在被管理的服务器本地,而 root 能改能删。所谓「服务器侧自审计」,本质上是让被审计的对象自己给自己记日记——排查普通操作失误时勉强够用,一旦涉及恶意破坏或入侵后的痕迹清理,可信度就归零。要突破这个天花板,记录就必须从「被管理的机器」上挪出去。

录像录的到底是什么:PTY 字节流,不是命令

要理解会话审计,得先搞清楚「会话」在技术上的本质。一次 SSH 登录在服务器侧的结构大致是:sshd 为这次连接分配一个 PTY,启动登录 shell;操作者的每个按键经 sshd 写进 PTY 主端,shell 和它派生的程序从 PTY 从端读输入、把输出写回。终端会话的核心就是这条 PTY(伪终端)字节流——操作者的按键作为输入、程序的打印作为输出,交错成一条连续的流。会话录像录的就是这条流——所以回放时你看到的不仅有命令本身,还有输出、vim 里的编辑过程、top 的刷新画面,也就是操作者当时眼睛看到的一切。

这里有一个经常被混淆的区分:命令日志会话录像是两回事。命令日志是从输入流里解析出「敲了什么命令」,把输出和交互上下文全部丢掉,好处是体积小、好检索;会话录像则保留完整字节流,是证据级的记录。一个像样的审计体系通常两者都要:录像用来做证据和回放,命令解析用来做快速检索和高危拦截。

会话录像之所以不可替代,是因为它能还原命令日志永远还原不了的东西:一句敲错又删掉的命令、cat 出来的敏感输出、交互式提示符下的决策过程。命令日志记下的是「最终按回车的那条」,录像记下的是「整个过程」。

录像还有「在线」与「离线」两种形态:在线会话指管理员实时旁观正在进行的连接,适合有人值班时的即时干预;离线录像是落盘后的回放,是事后取证的主体。多数场景真正依赖的是后者——实时旁观能拦下正在发生的误操作,但能留存、能作为证据的,永远是那份离线录像。

在单机层面,最朴素的录像工具是 util-linux 自带的 script

bash
script -q --timing=session.timing session.log
# ... 执行你的操作 ...
exit
# 事后带时序回放
scriptreplay -t session.timing session.log

script 的原理是在操作者和 shell 之间插入一个 PTY,把双向流量都拷贝一份存盘。asciinemattyrec 是同一思路的不同实现。但它们的共同短板也很明显:这些工具都要「操作者自己主动去跑」,是客户端侧、手工、单次的,攻击者只要不跑就什么都没有,天然无法作为不可抵赖的证据。

在哪一层录:服务器、客户端,还是中间层

录像这件事有三个可选的位置,三者的可信度完全不同。

客户端侧是最弱的。它只记录操作者自己工具里看到的那份,不录就没有,对取证毫无意义。

服务器侧在每台机器上做。可以通过 authorized_keys 里的 command= 选项,或者 sshd 的 ForceCommand,把登录后的命令强制包进一个录像 wrapper,对客户端透明。问题在于:要在每台机器、每个 key 上分别配置,机器一多就失控;更致命的是录像文件就躺在服务器本地,root 能改能删——这恰恰摧毁了「证据」的意义。你想用录像指证的事故,往往正是拥有最高权限的人干的。还有一个隐蔽的盲区:登录 shell 的录像抓不到不走 shell 的流量,scp 传文件、sftp 会话、ssh -N 端口转发都不会经过登录 shell,只包一层 login shell wrapper,这几类操作就成了审计盲区。

中间层——代理或网关——是唯一能规模化的位置。让所有 SSH 访问都经过一个中间节点,这个节点在中间能看到明文的字节流,于是集中录像、统一存储都变得自然,而且它不必在每个程序里埋点,只需在流经过的地方整体拷贝一份。它的代价有两个:一是这个中间层必须成为所有访问的唯一入口,否则它只录到一部分流量,审计就漏了;二是必须正视它带来的信任点——既然中间层能录到所有明文,「谁在运营这个中间层」本身就变成了一个极高权限的角色,操作者到中间层这一段仍要叠加双因子认证,别让「为了审计引入的入口」自己先变成新的短板。

录像不是终点:防篡改与留存才让它成为证据

很多人把「能录」当成「有审计」的终点,其实录像落盘那一刻才是开始。录像文件如果放在操作者能碰到的机器上,等于没有——取证的价值来自它无法被当事人事后改写。真正能站得住的会话审计要解决三件事:

  • 录像存到操作者够不着的地方:对象存储加版本控制或对象锁,而不是留在被管理的服务器上;
  • 有明确的留存周期,例如不少于 180 天,按制度或合规要求定;
  • 「删除录像」本身是独立于「操作服务器」的一种权限,普通运维即使有 root 也删不掉。

命令日志和录像的配合关系也在这里体现:录像负责「证据与回放」,命令解析负责「快速检索与高危拦截」。只做命令日志,事故复盘时你查得到 rm -rf 这条命令,却看不到它执行前操作者 cd 到了哪个目录、看到了什么提示;只做录像不做解析,几百台机器的录像堆成山,出了事半天翻不到关键帧。

还有两个容易被忽略的现实问题。一是存储成本:完整的字节流录像在规模化后非常可观,留存周期直接决定账单,压缩和转码(把原始流转成可回放的紧凑格式)从第一天就要考虑;二是图形会话(RDP 这类)的录像比纯文本 SSH 重得多,回放和存储的代价不是一个量级,留存策略往往要对不同协议分别对待。

落地到唯一入口:堡垒机就是那个中间层

当所有 SSH 访问被收敛到一个统一入口时,会话录像就从「每台机器各自配置」变成了「入口处天然获得」。开源堡垒机 Next Terminal 做的正是这件事:会话经它接入时完成录像与回放,配合命令过滤拦截高危命令、数据库审计记录 SQL 与文件操作、资产管理收敛「谁能碰哪台」,录像可留存到本地或对象存储——前提仍然是,让所有访问都从它这里走,它才录得全。从合规视角看,等保、ISO 27001、SOC 2 这类框架对「操作可追溯」的要求,通常都指向「能定位到人 + 有留存证据」两层,命令级日志在取证时往往不够,这也是会话录像成为审计刚需的原因。这些能力与控制项的对应关系可对照合规与审计

选型时真正要权衡的其实只有两件事:你只需要「谁敲了什么命令」(命令日志就够,便宜、好检索),还是需要「他当时看到了什么」(要完整会话录像);以及录像放在哪里、谁能删。只有把「录像不可被操作者篡改」这一步想清楚,会话审计才有意义——否则它只是一堆迟早会被清理掉的大文件。