远程开发SSH隧道如何区分恶意连接?
在开发机与开发容器中核查 IDE 扩展篡改风险:从版本来源校验到 SSH 后门痕迹与凭据轮换
远程开发最让我心里发毛的一刻,是某天在服务器上敲命令,发现后台跑着三四条 ssh,而我只记得自己开了一条。那一刻我甚至开始回忆:是我昨天忘了关,还是有人替我开了一条?
后来我才明白,光看“有没有 ssh”,这个问题根本回答不了。因为 VS Code 的远程连接、各种跳板、端口转发,本来就会拉起 ssh 子进程。我自己一边跑着合法的隧道,一边怀疑那条隧道——纯粹是吓自己。
真正的分界线不在进程名,而在是谁把它拉起来的、带什么参数、连去哪。
我现在的顺序是:先看进程树,再看启动项,最后对网络。用 ps 加上父子关系扫一遍,重点看 IDE 进程下面挂的子进程。语言服务器、调试器拉起 node 是常态,SSH 远程开发拉起 ssh 也是常态。但如果有条 ssh 的父进程是扩展宿主,它就不是“我在终端里敲出来的”那条,得单独解释。
更值得停下来的是 autossh。开发机上它不算常见依赖,如果它由扩展宿主拉起,参数里还带着 -N 这类不执行远端命令、只维持通道的选项,我会先把机器隔离再琢磨。Coralogix 那份通告里描述过一条完整链条:扩展把内嵌私钥解码到临时目录,拉起 autossh 并回退到 ssh,连接远端隧道地址,禁用 host-key 校验,每隔一段时间自动重连。这里面真正稳的信号不是某个地址,而是“隐藏私钥文件 + 关掉 host-key 校验 + 定时重连”这一组行为的组合。
host-key 那部分我觉得特别好认。正常连一台机器,第一次会问你指纹;脚本化场景用 accept-new 也能理解。但 StrictHostKeyChecking=no 配上把 known_hosts 丢到 /dev/null,基本等于宣告“我不在乎对面是谁”。这种参数如果出现在扩展拉起的命令行里,我不会有耐心去核实。
启动项是另一条我差点忽略的线。用户级 LaunchAgents、用户级 systemd 服务、crontab,都不需要 root,却能每次登录把它唤回来。我吃过一次教训:只盯着 plist 的文件名看,觉得叫什么 sysmond 挺像系统组件就放过去了——其实要看的是里面 ProgramArguments 指向哪个可执行文件、RunAtLoad 是不是 true。
最后一步是网络。把 ss 和 lsof 的输出分成三类:市场与更新域名、代码托管与包仓库、剩下的未知远端。前两类我不追究,第三类必须和进程、启动项落在同一个时间窗口里对得上,否则一条 grep 结果什么也证明不了。
我不追求零误报,那是不可能的。我追求的是每条误报都能解释清楚、能回退。毕竟排查一次隧道,比事后轮换一堆凭据便宜太多了。

参与讨论
我也被后台多出的 ssh 吓过,结果是 IDE 自己拉的