前些天接了个安全加固的活儿,目标是一台跑了多年的Ubuntu 16.04服务器,业务还在上面跑着。领导一句话:把scp、sftp和winscp都禁掉。听起来就是“禁个协议”的事儿,真上手才发现,这三个词根本不是同一个层面的东西——scp是一条命令,sftp是一种协议,WinSCP是个Windows客户端。混在一起理解,很容易让人一头扎进sshd_config里盲改,结果要么没禁住,要么把自己锁在门外。
这篇文章把项目里的完整思路和落地操作梳理一遍,包括我在现场踩过的坑、验证过的方案,以及改完之后的排查和加固组合。适合正在做Unix/Linux服务器文件传输通道收敛的运维、安全工程师参考,也适合刚接手老旧Ubuntu 16.04系统的同学避坑。
1. 先搞清楚要禁的目标,才能选对方法
1.1 SCP与SFTP是两套机制,不能混为一谈
很多人一上来就想在sshd_config里加一行配置同时禁掉SCP和SFTP,这基本行不通,因为这两个东西走的根本不是同一条通道。
SFTP是SSH2协议里定义的子系统,客户端通过SSH连接后,额外请求一个名为sftp的subsystem,由服务端的sftp-server进程来服务。它和普通SSH登录是两条路:SSH登录要的是shell或pty,SFTP要的是子系统。所以只要把Subsystem sftp这一行干掉或者替换掉,SFTP就废了,简单直接。
SCP则是完全另一套逻辑。早期SCP继承自BSD的rcp,它通过普通的SSH连接,在远端执行一条类似scp -t /path的命令。看到这个机制就明白:SCP依赖的是用户的shell去执行远端命令,而不是Subsystem。所以你以为禁了SFTP就能禁SCP?那是在给它挠痒痒。
一个小对照表能说明问题:
| 传输方式 | 激活方式 | 服务端依赖 | 禁用切入点 |
|---|---|---|---|
| SFTP | SSH连接后请求sftp子系统 | /usr/lib/openssh/sftp-server | 注释Subsystem配置 |
| SCP | SSH连接后在远端执行scp命令 | 用户shell能找到/usr/bin/scp | 让远端scp命令不可执行 |
| WinSCP | 客户端工具,可走SFTP或SCP协议 | 依赖上述两种服务端能力 | 断掉SFTP和SCP即可 |
顺带说一句,OpenSSH 9.0之后,客户端默认不再使用老的SCP协议,而是改用SFTP协议传输。但Ubuntu 16.04自带的OpenSSH是7.2p2,SCP还是老协议,远端命令执行这一环必须堵死。
1.2 WinSCP为什么必须从服务端下手
WinSCP是Windows上的图形化传输工具,它支持SFTP、SCP、FTP、WebDAV等多种协议。你不可能跑到每台Windows电脑上卸载WinSCP,所以“禁用WinSCP”这个需求,本质上是在服务端把WinSCP能用的通道全断掉。
大多数场景下,WinSCP连接这台服务器只会走两种协议:SFTP或SCP。只要你把服务端的SFTP子系统和SCP执行通道都收掉,WinSCP无论怎么配置协议都连不上。至于它还能用FTP连,那是另一个故事了,如果服务器上根本没跑vsftpd,就不用担心。
1.3 动手前先问自己三个问题
改配置之前,我建议先把需求盘明白,不然改动方向可能完全跑偏。
第一个问题:是否还需要保留SSH登录?如果连SSH登录都禁,那不是“禁用scp/sftp”的范畴,而是直接封22端口、关掉openssh-server就行了,甚至可以考虑物理断网。既然标题说的是禁用scp、sftp和winscp,默认前提一定是保留SSH终端管理。
第二个问题:是否要给特定账号留白名单?比如备份服务器每天要通过scp拉文件,发布平台要推送包,这些业务没有文件传输通道就得停。全局禁用简单,但误伤业务后半夜起来恢复配置的滋味不好受。
第三个问题:要不要顺手防住端口转发?很多人禁了scp和sftp后,忘了SSH还支持-L端口转发。只要还能ssh -L,内网文件照样能通过隧道传出来。所以安全要求高的话,AllowTcpForwarding no等选项要一并加。
2. 动手前的环境检查与安全网
2.1 确认OpenSSH版本和配置细节
Ubuntu 16.04虽然老,但许多生产环境还在用。第一步应该先确认OpenSSH的版本和当前生效的配置,避免凭记忆改文件。
ssh -V # OpenSSH_7.2p2 Ubuntu-4ubuntu2.10, OpenSSL 1.0.2g 1 Mar 2016 dpkg -l | grep openssh-server # ii openssh-server 1:7.2p2-4ubuntu2.10 amd64 ... grep -E '^Subsystem|^PasswordAuthentication|^PermitRootLogin' /etc/ssh/sshd_config这几个命令输出出来后,你就能确认三件事:服务端OpenSSH版本、sftp-server组件路径、当前登录认证方式。Ubuntu 16.04的sftp-server路径默认是/usr/lib/openssh/sftp-server,有的系统在/usr/libexec/openssh/sftp-server,不同发行版不完全一样,别抄错。
2.2 盘点用户和登录方式
禁用文件传输通道会影响所有能登录这台机器的用户。我先列出可登录账号,再确认密钥登录情况,避免某个同事明天早上发现自己的传输工具全废了才来群里艾特你。
grep -E '/(bash|sh|zsh)$' /etc/passwd ls -la /home/*/.ssh/authorized_keys 2>/dev/null这一步很关键,因为SCP执行依赖用户shell,如果某些用户shell已经设置为/bin/false或/usr/sbin/nologin,他们本来就无法scp和sftp,也不需要额外管。真正需要处理的是那些shell正常、能登录SSH的账号。
2.3 备份、恢复和防锁死的准备
改SSH服务配置最怕的就是远程操作时写错一个参数,然后重启失败,自己也被卡在外面。所以我习惯在改动前把配置文件和计划要动的二进制全部备份,并准备好一条恢复命令。
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F) sudo cp /usr/bin/scp /usr/local/bin/scp.bak.$(date +%F) 2>/dev/null || true另外建议开一个tmux或screen会话执行后面的操作。即使网络抖动断了,重连回去还能看到现场。有条件的话,用带外管理或物理终端垫底更好,毕竟谁也不想半夜蹲在机房门口。
3. 核心实操:禁用SFTP、SCP与WinSCP通道
3.1 关闭SFTP子系统
SFTP是最好禁的。打开/etc/ssh/sshd_config,找到类似这样的行:
Subsystem sftp /usr/lib/openssh/sftp-server把它注释掉,或者在行首直接加上#:
#Subsystem sftp /usr/lib/openssh/sftp-server有人喜欢改成Subsystem sftp /bin/false,效果是sftp请求会被引导去执行一个常失败的程序,客户端会报错。这两种方式都能用,我倾向直接注释掉。因为注释后的语义最清楚:这个子系统不存在了。改成/bin/false只是“让它失败”,别人看配置时还得想一下为什么是这个路径。
改完先检查语法,再重启。注意一定要先检查,别直接重启:
sudo sshd -t如果没有任何输出,说明配置语法没问题。然后重启SSH服务。Ubuntu 16.04上服务名通常是ssh,不是sshd:
sudo service ssh restart # 或者 sudo systemctl restart ssh到这里,SFTP通道已经断了。用sftp客户端连接时,会看到类似输出:
sftp user@server subsystem request failed on channel 0 Connection closed这是预期结果,不是配置错误。
3.2 让SCP失效的两种方案
这是整个项目里最容易被忽视的地方。只禁SFTP,SCP照样能用,因为它是通过shell执行远端scp命令。我给出两种实际验证过的方案。
方案A:用组权限控制/usr/bin/scp,推荐。
思路很简单:把/usr/bin/scp的属组改成一个专用组,比如scpallowed,然后权限设为750。这样只有这个组内的用户才有执行权限,其他用户无论本地执行还是远程scp都会因权限不足而失败。
sudo groupadd scpallowed sudo chown root:scpallowed /usr/bin/scp sudo chmod 750 /usr/bin/scp以后真心需要保留scp能力的账号,就加进这个组:
sudo usermod -aG scpallowed backupuser不在组里的用户,远程执行scp时shell会报“Permission denied”,scp客户端显示连接失败或命令被权限拦下。这个方案干净、易维护,而且升级软件包时不会丢权限,很实用。
方案B:替换/usr/bin/scp为拦截脚本,适合彻底封死。
如果确认整个服务器都不需要scp,也不想留白名单,可以把原始scp挪走,换成一个拒绝执行的脚本:
sudo mv /usr/bin/scp /usr/bin/scp.bak sudo tee /usr/bin/scp >/dev/null <<'EOF' #!/bin/bash echo "SCP has been disabled on this server." >&2 exit 1 EOF sudo chmod 755 /usr/bin/scp这样远端scp执行时,会命中拦截脚本,输出提示并退出。优点是一刀切,缺点也明显:apt upgrade升级openssh-client包时,/usr/bin/scp可能被原装程序覆盖回来,必须配合定期巡检或脚本加固,否则安全策略会悄悄失效。
实际生产里,我推荐方案A。运维人员总有一条活路,业务白名单也可以灵活加,不会把自己逼进死胡同。
有一点要特别说明:/usr/bin/scp属于openssh-client包常见内容,不是只在服务器上存在。改了它之后,本机的管理员也不能直接使用scp命令,需要临时用/usr/bin/scp.bak救急。这个“副作用”要在交付时跟客户或团队成员讲清楚。
3.3 对WinSCP不同协议的兜底处理
WinSCP连接时可以在SFTP、SCP、FTP等协议之间切换。当你按上面的步骤完成配置后:
- WinSCP使用SFTP:连接发起时会请求sftp子系统,子系统已经不存在,握手失败。
- WinSCP使用SCP协议:连接后会在远端执行
scp -t,而这个命令不可执行,传输立即失败。
所以无需在WinSCP侧做任何特殊配置,只要服务器端这两条通道收掉,它自己就会掉线。不过要提一句,如果服务器上还装了FTP服务器,比如vsftpd,WinSCP走FTP协议依然能连上,那不是这道题的范围。只针对“Ubuntu 16.04下禁用scp、sftp和winscp”,解决方案到这里其实已经完成了大半。
3.4 重启sshd并整体验证
配置完成后,我习惯做一轮“全通道”验证。先在机器上开一个保底SSH会话,再执行重启:
sudo sshd -t && sudo service ssh restart然后从另一台机器上依次测试:
| 测试动作 | 预期结果 |
|---|---|
ssh user@server | 正常登录,终端可用 |
sftp user@server | 报错subsystem request failed on channel 0 |
scp file user@server:/tmp | 报错Permission denied或自定义拦截提示 |
| WinSCP连接(SFTP) | 连接失败,子系统中协议错误 |
| WinSCP连接(SCP) | 连接失败,远端scp命令无权限 |
如果这些结果都符合预期,说明目标已经达到。验证时千万注意,先在服务器上保留一个已登录的终端窗口,万一配置有问题,还可以手动恢复文件。
4. 常见问题与排查实录
4.1 sftp报Subsystem request failed说明什么
不少同事第一次看到subsystem request failed on channel 0这个报错会慌,以为是自己把SSH整个改坏了。其实这是Subsystem被注释掉后的正常表现。它可以出现在sftp命令行、WinSCP、FileZilla等客户端,本质都是“服务端没有sftp子系统,客户端请求失败”。
如果项目要求“禁用”,看到这个错误就对了。如果你本来没想禁sftp,却看到这个错误,那就去/etc/ssh/sshd_config里检查Subsystem行是否被注释,或者是否有其他配置片段把子系统覆盖掉了。
4.2 禁了sftp后scp为什么还能用
这个问题我见过太多次。不少教程说“注释Subsystem就能禁掉scp、sftp”,这是以讹传讹。SCP和SFTP的通道机制不同,SCP不经过Subsystem。正确理解是:要禁SFTP就断Subsystem,要禁SCP就让远端/usr/bin/scp无法执行,两者缺一不可。
还有另一个迷惑点:某些新版本OpenSSH的scp客户端默认走SFTP协议,但服务端OpenSSH 7.2还是老协议。所以在这台Ubuntu 16.04上面,scp仍是老玩法,必须按老玩法处理。
4.3 Received message too long的误诊
网络上很多关于sftp received message too long 1416128883的讨论,说的是sftp客户端兼容性问题。这个报错通常不是配置错误,而是用户的shell启动时输出了非协议内容。比如.bashrc或/etc/profile里的echo、横幅、提示符美化输出,把SFTP协议头污染了。
有一种常见误判:有人禁了sftp或改了Subsystem路径后,客户端报出类似错误,就以为禁用成功了。实际上报错类型不一样。Subsystem被禁时是subsystem request failed,shell输出污染时才是received message too long。排查时先把这两类错误分开,能省很多时间。
如果真的遇到received message too long,排查思路很简单:
# 用sftp手动指定子系统并加-v,观察哪里出了问题 sftp -vvv user@server多数情况下,清理远程用户的.bashrc、.profile、/etc/profile.d/*.sh里的打印输出就能解决。
4.4 apt upgrade后发现scp又活了
这是替换scp二进制方案最大的坑。Ubuntu的openssh-client包里带有/usr/bin/scp,系统执行apt-get upgrade时,如果openssh-client有更新,被移走或被替换的scp很可能被重新装回来。
针对这个问题,我通常会写一个小的cron巡检脚本,每5分钟或每天检查一次权限和属组,把策略固化住:
#!/bin/bash if [ -x /usr/bin/scp ]; then chown root:scpallowed /usr/bin/scp chmod 750 /usr/bin/scp fi如果你是替换脚本方案,巡检脚本还要比对文件哈希,发现被覆盖就重新写拦截脚本,并给出提示。更专业的做法是用dpkg-divert接管文件替换,让包管理器不恢复原文件,但那套机制配置起来更繁琐,对小规模服务器巡检脚本反而更直观。
4.5 想恢复就这么干
变更是可逆的,恢复动作也要提前想好。sftp恢复很简单,把Subsystem那行的注释去掉,重新加载即可。scp恢复要看用的哪个方案:
# 如果是组权限方案 sudo chown root:root /usr/bin/scp sudo chmod 755 /usr/bin/scp # 如果是替换脚本方案 sudo mv /usr/bin/scp.bak /usr/bin/scp # 最后重载SSH服务 sudo service ssh reload恢复后建议再做一次通读检查,确认没有遗漏的AllowTcpForwarding no等限制还在生效,毕竟“恢复部分功能”和“恢复全部原始状态”是两码事。
5. 进阶加固与更精细的管控
5.1 用AllowTcpForwarding等堵住替代通道
禁scp和sftp,目的是不让文件通过这两个常规协议外流,但SSH还有一个隐含能力:端口转发。攻击者或者有权限的内部人员,完全可以ssh -L建立一个本地转发通道,再通过HTTP或其他工具把文件传出去。所以安全加固时,我会顺手把这些选项一起加上:
AllowTcpForwarding no PermitTunnel no X11Forwarding no GatewayPorts no这几条不会影响正常SSH登录,但能显著减少“禁了scp又出现新老鼠洞”的尴尬。如果你想同时减少密码暴力破解的风险,还可以把PasswordAuthentication改成no,强制使用密钥登录:
PasswordAuthentication no不过改动认证方式前,一定要确认所有管理员账号的密钥都在,否则你也会被拒之门外。
5.2 只留SSH登录、不放文件传输的策略组合
把这篇文章提到的内容归拢一下,一套“只留终端、不放文件”的推荐配置长这样:
# /etc/ssh/sshd_config 关键行 #Subsystem sftp /usr/lib/openssh/sftp-server PasswordAuthentication no PermitRootLogin prohibit-password AllowTcpForwarding no PermitTunnel no X11Forwarding no GatewayPorts no配合组权限限制:
sudo groupadd scpallowed sudo chown root:scpallowed /usr/bin/scp sudo chmod 750 /usr/bin/scp这套组合下,SSH终端正常用,SFTP通道消失,SCP只有白名单组能执行,端口转发被封堵。对大部分“保留远程管理、收紧文件外带”的需求都很适用。
5.3 如果要给特定用户开sftp/scp怎么办
有时候业务确实离不开文件传输,但又不希望所有人都用。scp的白名单组方案已经够用,sftp则稍微麻烦一点。因为sshd_config原生的Subsystem指令是全局的,不支持按用户单独启用或禁用。想在全局禁sftp的同时给某些用户开放,常见做法有这么几种:
第一种是部署受限sftp环境,把Subsystem设为internal-sftp,再用Match把指定用户强制进chroot目录:
Subsystem sftp internal-sftp Match Group sftpusers ChrootDirectory /home/%u ForceCommand internal-sftp X11Forwarding no AllowTcpForwarding no这个配置的意义是:对sftpusers组用户,登录后只能进自己的home目录,只能使用sftp命令,无法获取shell。适合需要“给外部用户开一个受限文件交换区”的场景。注意这种需求和“全局禁sftp”是相反的,二选一,别混在一起写。
第二种是另开一个SSH端口,比如22022,专门承载允许scp/sftp的流量,22端口则严格禁用。这种做法配置更重,但隔离最彻底,适合安全要求严格的网络环境。
无论选哪种,事先都要把账号角色、可访问目录、是否需要chroot这些业务细节定清楚,否则技术方案写得再漂亮,上线也会被业务方反复找麻烦。
这两天折腾下来,我最深的体会就是:安全加固最怕的不是技术难,而是没把“禁什么、留什么”想清楚就开始改。scp、sftp、WinSCP这三个名词背后是协议、命令、客户端三个不同层次的能力,逐层拆开再动手,错误率会低很多。
实际操作层面,建议大家先按“全局禁sftp + 白名单限制scp + 禁用端口转发”三件套落地,观察几天日志,确认没有业务投诉后再把配置固化到自动化脚本或配置管理工具里。千万别为了追求“禁得彻底”把运维后路也断了,毕竟哪天你同事要用scp拉一份紧急日志,你总得留个白名单的入口。