☰
Linux安全加固:如何彻底禁用SCP、SFTP与WinSCP连接
2026/10/5 6:16:10 网站建设 项目流程

1. 先说清楚:scp、sftp、WinSCP的关系,为什么禁一个等于禁三个

不少朋友一听到“禁用scp、sftp和WinSCP”,下意识会以为要装什么安全软件、改防火墙,甚至有人直接去把OpenSSH卸载了。这完全是想偏了。这三样东西本质上是同一个链路的不同环节:scp和sftp都是基于SSH协议的文件传输方式,WinSCP则是Windows下用来连接这两种协议的图形客户端。

先捋一下它们各自的位置:

  • scp:全称Secure Copy,基于SSH协议,把远程文件复制到本地,或者把本地文件推到远程。它在服务端实际是调用了远程主机的scp命令,通过SSH通道执行。底层实现早期是走RCP协议,后来新版本OpenSSH默认走SFTP协议的内部模式,但对外表现仍然是scp。
  • sftp:Secure File Transfer Protocol,它不是简单的“加密版FTP”,而是SSH协议的一个子系统。当客户端发起SFTP连接时,SSH服务端会启动/usr/lib/openssh/sftp-server这个子系统程序来处理文件传输。所以SFTP和SSH共用22端口,不需要单独开端口。
  • WinSCP:一个Windows平台的开源图形化SFTP/SCP客户端。它支持SFTP协议和SCP协议两种模式,连接Linux服务器时要么走SFTP子系统,要么走SCP命令通道,二者必居其一。

搞清楚这个关系,你就明白禁用思路了:只要在服务端把SSH协议里的SFTP子系统和SCP执行通道关掉,WinSCP自然也就废了,因为它不是独立的服务器组件,只是一个客户端工具。真正要动刀的地方只有一个文件:/etc/ssh/sshd_config。

这篇文章默认环境是Ubuntu 16.04 + OpenSSH 7.2p2,操作思路对其他版本也通用,只是个别配置项在旧版本上可能不存在,我会在对应位置特别说明。

2. 动手前准备:确认环境、备份配置、留好逃生通道

老司机都知道,改SSH配置最怕的不是配置不对,而是改完连不上。Ubuntu 16.04的SSH服务是sshd,配置文件在/etc/ssh/sshd_config,修改之前有几个准备工作必须先做。

2.1 确认当前SSH版本和子系统配置

先登录服务器,跑几条命令确认状态:

# 查看OpenSSH版本 ssh -V # 查看sshd当前生效的配置 sudo sshd -T | grep -E 'subsystem|sftp|permitrootlogin' # 查看sshd_config里现有的Subsystem配置 grep -i subsystem /etc/ssh/sshd_config

正常情况下你会看到类似这样的输出:

subsystem sftp /usr/lib/openssh/sftp-server

或者在新一点的版本上可能是:

Subsystem sftp internal-sftp

这两个的区别后面会详细讲,先记下来你机器上是哪一种。sshd -T这个命令很重要,它不会改动任何东西,只是把实际生效的配置打出来,不用猜配置有没有被include文件覆盖。

2.2 备份配置文件

这个步骤看起来废话,但每次改完SSH出问题的人,十有八九没备份。一个cp命令的事,别省:

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F)

我建议备份带上日期,以后翻记录能知道是哪天改的。

2.3 留好两条以上的连接通道

改SSH配置之前,务必保证当前已经有一个已建立的SSH连接窗口,或者配置了密钥登录且测试无误,又或者你有机房带外管理、云控制台的VNC访问通道。为什么?因为改完重启sshd之后,如果配置有误,新的连接会被拒绝,而原来已建立的连接不受影响——但是一旦你不小心把这个连接也关了,又没有带外通道,那就只能提交工单让机房帮你重启了。

我自己的习惯是:先开两个SSH窗口都连着服务器,改完配置后先不要断线,用第二个窗口测试新连接能正常建立,再关第一个窗口。这条习惯帮我避免过至少三次把自己关在门外的丢人事故。

2.4 检查配置语法

Ubuntu上提供了一个非常实用的检查命令:

sudo sshd -t

这个命令只检查配置语法,不实际生效。如果有错误会提示具体行号和原因,没输出就是配置没问题。每次改完sshd_config,先跑这命令,再重启服务。

3. 核心实操:全局禁用SFTP子系统和SCP命令

准备工作做完了,进入正题。先讲全局禁用,也就是服务器上所有用户都不能走SFTP和SCP。适合安全要求严格的场景,比如跳板机、生产环境堡垒机,或者不想让开发人员把代码拉出去的封闭环境。

3.1 禁用SFTP子系统

SFTP依赖的是sshd_config里这一行:

Subsystem sftp /usr/lib/openssh/sftp-server

这行的意思是:当客户端请求SFTP子系统时,sshd启动/usr/lib/openssh/sftp-server程序来处理。只要注释掉这一行,服务端就根本不提供SFTP子系统,客户端发起SFTP连接时会直接收到“subsystem request failed on channel 0”的报错。

操作方式:

sudo vim /etc/ssh/sshd_config

找到Subsystem那一行,行首加#注释掉,保存退出:

#Subsystem sftp /usr/lib/openssh/sftp-server

然后检查语法并重启:

sudo sshd -t sudo systemctl restart sshd

Ubuntu 16.04上是systemctl没问题的,如果是老版本SysVinit,用sudo service ssh restart。

重启完本地试一下:

sftp root@你的IP

输出应该类似:

root@你的IP's password: subsystem request failed on channel 0 Connection closed

能出现这行报错,说明SFTP子系统已经被切掉了。

3.2 禁用SCP命令传输

SCP和SFTP是两条不同的通道,只注释Subsystem那行,SCP依然能用,这是最多人踩的坑。原因很简单:老版本的OpenSSH(包括Ubuntu 16.04自带的OpenSSH 7.2)里,scp命令使用的是传统的SCP协议,它在服务端通过执行远程scp命令来完成文件复制,走的不是SFTP子系统。你注释了Subsystem,相当于堵死了SFTP这条路,但SCP走了另一条路,自然不受影响。

怎么验证?注释完Subsystem后,从你的电脑执行:

scp user@你的IP:/etc/hostname /tmp/

你会发现文件还是能拉下来,一点毛病没有。

那怎么禁SCP?方案有几种,我按推荐程度从高到低讲:

方案A:把用户的shell改成nologin

SCP协议要求在服务端能执行scp命令,而执行命令的前提是用户有一个能登录的shell。如果用户shell被改成了/sbin/nologin或/bin/false,SSH连接时不分配shell,SCP命令也就没法执行。

sudo usermod -s /sbin/nologin username

改完再试scp,会报错:

subsystem request failed on channel 0

不对,这是SFTP的报错。SCP的报错一般是这样:

user@你的IP: Permission denied (publickey,password).

或者干脆直接显示:

/bin/bash: scp: No such file or directory

原理上,用户的登录shell被改成nologin之后,SSH服务器在收到SCP请求时会尝试以该用户身份执行远程命令,但由于shell是nologin,命令执行直接被拒绝。

这个方案的好处是一并禁止了该用户的交互式shell登录——如果这个账号本来就是专门做文件传输的,那就完全合理;坏处是如果你还想让这个用户能正常ssh登录执行命令,那这个方案就不适用了,得看方案B。

方案B:在sshd_config里用ForceCommand限制

在sshd配置中,你可以在Match User块或全局配置中强制指定某个用户执行特定命令。如果把ForceCommand设为/bin/false,那么该用户通过SSH发起的任何命令执行请求都会被替换成执行/bin/false,SCP自然就废了。

但这里有个关键点:ForceCommand要么放在全局段,要么放在Match块里。如果你想针对特定用户,需要这样写:

Match User username ForceCommand /bin/false

不过这么写有个副作用:该用户的所有SSH命令都会被拒绝,包括正常的ssh username@host登录。如果只是想禁SCP,不想禁SSH登录,那就不能用/bin/false,得换一个思路。

方案C:在ssh会话中禁用scp命令(适合还想保留shell的用户)

这个方案有点绕,但确实是可以实现只禁SCP不禁登录:原理是利用ForceCommand与Match结合,将所有SSH命令请求强制交给一个包装脚本,脚本里判断如果是SCP命令就拒绝,如果是交互式shell就放行。

不过说实话,这个方案在实际生产中用得少,因为维护成本高,而且很容易误伤。生产环境里SCP禁用的需求要么是全部禁,要么是针对专用传输账号禁,很少会出现“要保留shell但不能scp”这种需求。真有这种需求,我建议直接考虑上一个方案B的变体——把用户的SSH登录方式改成仅允许密钥登录且限制在特定目录。

实践下来,最稳妥的办法是:全局注释Subsystem(禁SFTP)+ 针对需要限制的用户改shell为nologin(禁SCP和交互登录)。如果需求是“所有用户都不能传文件”,这两个操作做完就齐活了。

3.3 重启服务并验证全部禁用效果

配置改完后,完整验证顺序我是这么做的:

# 1. 检查配置语法 sudo sshd -t # 2. 重启sshd sudo systemctl restart sshd # 3. 从本机测试SFTP sftp user@你的IP # 4. 从本机测试SCP scp user@你的IP:/etc/hostname /tmp/

预期结果:

命令预期输出
sftp user@IPsubsystem request failed on channel 0,连接关闭
scp user@IP:file localPermission denied或类似报错,文件无法传输
WinSCP连接选择SFTP协议报错,选择SCP协议也报错

到这里,你对“禁用”这个概念应该有个清晰的认知了:禁用的本质不是在客户端删除软件,而是在服务端断掉协议通道。WinSCP只是一辆车,路没了,车再好也开不过去。

4. 更精细的控制:用Match User做定向禁用,只禁特定账号

全局禁用适合安全要求高、业务简单的场景。但现实往往很骨感:服务器上十几个账号,运维要传文件、开发不能传,或者只有某几个外包账号不能往出拉数据。这种时候,Match User和Match Group就能派上用场了。

4.1 Match User的基本语法和限制

Match是sshd_config的一个配置指令,用来对特定条件应用额外的配置段。基本语法是:

Match User 用户名1,用户名2 配置项1 配置项2 Match Group 组名 配置项1

要注意几个硬性规则:

  • Match必须是配置文件的最后一个有效配置段。在第一个Match之后,所有后续配置都必须属于某个Match块,不能再写全局配置。
  • Match内的配置项会覆盖全局配置。
  • 不是所有全局配置项都能在Match里用。比如Subsystem就是全局专属,不能放进Match块里。

这意味着你没办法用Match针对某个用户单独禁用SFTP子系统——因为子系统是全局加载的。但你可以换个思路:允许全局SFTP但让特定用户的SFTP不可用。

4.2 针对特定用户禁用SFTP和SCP

实际配置可以这样写:

Subsystem sftp internal-sftp Match User developer,testuser ForceCommand /bin/false

这段配置的效果是:

  • 全局正常提供SFTP子系统,其他用户一切照旧。
  • developer和testuser这两个用户在执行任何SSH命令时,都会被强制替换成执行/bin/false命令,因此SFTP子系统的请求一样会被拒绝。

实测效果:

sftp developer@你的IP

会直接报:

subsystem request failed on channel 0

因为客户端请求SFTP子系统也是一种“命令执行”,而ForceCommand /bin/false把这个命令换掉了。

scp同样废掉,因为SCP在服务端本质也是执行远程命令。而ssh developer@IP登录进去也只会执行/bin/false然后立即退出。所以这个方案副作用很大:目标用户的完整SSH登录功能也会被禁。

如果你希望这个用户还能正常SSH登录执行命令,只是不能传文件,那用ForceCommand就不太合适。这时候可以考虑用Match User配合ChrootDirectory,把用户限制在一个隔离目录里,再配合ForceCommand internal-sftp实现一个只能SFTP不能SCP也不能shell的环境。但这个属于“受限SFTP”的正向配置,思路跟本文的反向禁用不完全一样,这里按下不表。

4.3 只禁SCP不禁SFTP怎么做

有些时候你的需求可能是“禁止scp,但保留sftp”,比如统一要求所有文件传输必须走SFTP以获得审计日志。这种场景配置思路是反过来:

既然scp在服务端需要执行远程命令,那只要让SSH收到的非交互式命令被拒绝,而交互式会话正常,理论上就能做到。但实际配置中很难光靠sshd_config一行实现。我的做法是配合ForceCommand加一个包装脚本:

新建/usr/local/bin/allow-shell-only.sh:

#!/bin/bash # 只允许交互式shell登录和SFTP子系统,其余命令(包括scp)一律拒绝 if [ -n "$SSH_ORIGINAL_COMMAND" ]; then case "$SSH_ORIGINAL_COMMAND" in internal-sftp) exec /usr/lib/openssh/sftp-server ;; *) echo "scp is disabled on this server." exit 1 ;; esac else exec /bin/bash fi

对应配置:

Match User targetuser ForceCommand /usr/local/bin/allow-shell-only.sh

这个脚本做的事情是:如果这个SSH连接带了原始命令(即非交互式请求),就判断是不是SFTP子系统的调用——是就放行,不是就拒绝;如果没有原始命令(即正常的交互式登录),就正常进bash。

这个方案不完美,因为case "$SSH_ORIGINAL_COMMAND"里匹配internal-sftp这一项,在实际环境中可能因为客户端请求内容不同而匹配不上,需要现场调试。所以我一般不会在博客里把这种配置吹成标准答案,但思路值得参考——面向特殊场景,用包装脚本做命令级控制,是sshd_config静态配置搞不定时的常见补充手段。

5. 验证与效果检查:从客户端实测禁用后的表现

配完之后,验证这一环不能省。我见过有人改完配置以为生效了,结果过了俩星期发现用户还在用scp拉文件,一查才发现注释Subsystem那行根本没保存。所以这篇文章专门把验证环节独立出来。

5.1 服务端自查

先确认sshd当前生效的配置:

sudo sshd -T | grep -i subsystem

如果输出为空,说明SFTP子系统确实没加载。如果还有输出,检查你是不是注释错行、或者有include文件二次加载了配置。

再确认对应用户的shell状态:

grep username /etc/passwd

如果最后一列是/usr/sbin/nologin或/bin/false,说明SCP通道被断掉了。

5.2 客户端实测

从你自己的工作电脑上分别测试:

# 测试SFTP sftp username@IP # 测试SCP scp username@IP:/etc/hostname /tmp/ # 测试SSH登录是否正常 ssh username@IP

我这里放一个实际测试的输出节选:

$ sftp testuser@192.168.1.100 The authenticity of host '192.168.1.100 (192.168.1.100)' can't be established. ... testuser@192.168.1.100's password: subsystem request failed on channel 0 Connection closed

SFTP被正确禁用。再看SCP:

$ scp testuser@192.168.1.100:/etc/hostname /tmp/ testuser@192.168.1.100's password:

然后卡住几秒,最终报错或者被拒绝。因为服务端没有可用的shell来执行scp命令,连接会很快关闭。

5.3 WinSCP客户端的表现

WinSCP需要写清楚,因为它报错的方式挺有迷惑性。

启动WinSCP,填好主机名、用户名、密码,协议那里如果选的是SFTP,连接时会卡在“初始化SFTP协议”阶段,然后弹窗提示:

连接被异常关闭。服务器发送的消息包含无效数据。The server sent an unexpected message.

或者更直白一点:

Cannot initialize SFTP protocol. Is the host running a SFTP server?

如果你把协议切到SCP,报错会变成:

Connection has been unexpectedly closed. Server sent command line.

或者:

Error: Connection refused

两种报错看着吓人,但其实都是好事——说明服务端的协议通道确实已经关闭了。WinSCP不是独立的服务器端组件,它是客户端工具。只要服务端的SFTP子系统和SCP命令通道关了,WinSCP换什么设置都连不上。

注意:网上有些教程会让你在WinSCP里改“高级→SSH→认证→尝试‘键盘交互’认证”之类的选项,这些对协议禁用没有任何帮助。WinSCP的协议栈是完整实现了SFTP或SCP的标准流程的,服务端不响应对应协议,客户端怎么配置都白搭。

5.4 验证时的几条经验

  1. 刚重启完sshd,建议等2-3秒再测试,因为sshd重启后可能需要短暂时间监听端口。用systemctl is-active ssh确认服务是active状态再测。

  2. 测试期间保持一个已登录的SSH窗口别关。所有验证做完、确认没问题了,再关闭旧窗口。

  3. 如果验证发现问题需要回滚,直接恢复备份:

sudo cp /etc/ssh/sshd_config.bak.$(date +%F) /etc/ssh/sshd_config sudo systemctl restart sshd

实测恢复后立即生效,不需要重启机器。

6. 典型问题与排查技巧实录

改成套配置遇到问题太正常了,我把这几年群里和博客里问得最多的情况整理出来,按场景分类说一下排查思路。

6.1 注释了Subsystem sftp,但scp还是能用

这是最常见的误解。前面已经解释过,老版OpenSSH的SCP不走SFTP子系统,走的是传统SCP协议,在服务端调用远程scp命令完成。所以注释Subsystem只影响sftp命令和WinSCP的SFTP模式,不影响scp命令和WinSCP的SCP模式。

解决办法就是按第3节的方案,把目标用户的shell改成nologin,或者用Match+ForceCommand限制。

6.2 sftp报错“received message too long 1416128883”

这个报错本身不是“禁用”引起的,但它经常出现在禁用/限制配置后的排查过程中。原因是:当SSH连接时,远程服务器在SSH会话初始化阶段输出了不属于SFTP协议内容的多余文本,导致sftp客户端解析时把这段文本当成了SFTP协议包的长度字段,数值异常巨大(1416128883)。

最常见触发场景是:用户登录shell被改成了某个自定义脚本,或者/etc/motd、/etc/issue、.bashrc里写了echo内容。当sftp客户端发起连接时,sshd会先给用户分配shell执行相关启动脚本,而shell启动脚本里的echo输出混入了SSH传输流中,sftp客户端一解析,发现数据长度异常,直接报错。

排查步骤:

# 1. 检查用户shell getent passwd username # 2. 检查是否修改了全局motd或issue cat /etc/motd cat /etc/issue # 3. 检查该用户家目录下的.bashrc和.profile cat ~username/.bashrc | tail -20

如果发现shell是/bin/bash而且.bashrc里有echo输出,注释掉echo即可。如果是自定义shell脚本,把非协议内容清掉。这个报错的定位思路同样适用于配置ForceCommand后黑屏卡住、连接立即断开等其他SSH疑难杂症。

6.3 重启sshd失败,连接全部断开

改配置时语法写错,重启sshd失败会造成已经建立的连接不受影响,但新连接无法登录。如果当前已建立的窗口还在,可以马上用sudo sshd -t检查错误并修复。常见错误:

  • Match块后写了全局配置
  • ForceCommand路径写错
  • 权限不对导致/bin/false无法执行

修复后重启:

sudo systemctl restart sshd

如果当前窗口也断了,就只能靠带外管理。所以备份和保持多窗口这两件事,真不是废话。

6.4 用Match User限制了用户,但用户还能用密钥scp

很多人忽略的一点:SSH公钥认证如果配置了authorized_keys里的command=前缀,或者ForceCommand被全局授权覆盖,配置可能不按你设想的执行。

排查这类问题,先做一个调试:

# 在服务器上查看sshd实际拿到的请求 sudo journalctl -u ssh -f

然后在客户端重新发起scp请求,观察服务端日志里显示的命令是什么。如果看到的是/usr/lib/openssh/sftp-server或scp -t,说明你的ForceCommand没生效或者被覆盖了。此时检查sshd_config里是否还有其他的Match块把该用户匹配进去了——多个Match块,后面的会覆盖前面的配置。

6.5 禁用后rsync还能走吗

rsync有自己的一套传输协议,默认不走SFTP子系统。如果用rsync over ssh,走的是SSH的远程命令通道,和scp同理——如果你只注释了Subsystem,rsync完全不受影响;如果你把用户的shell改成了nologin,那rsync over ssh也会被禁,因为服务端无法执行rsync命令。

6.6 这个方案是否影响FTP

不影响。服务器上如果还跑着vsftpd、proftpd这类传统FTP服务,它们和SSH是完全独立的进程和端口(默认21端口)。本文讲的禁用只针对SSH协议链路内的文件传输能力。如果你需要连FTP一起禁,那是防火墙和FTP服务配置的事,别混在一起。

6.7 配完以后用户反馈“连接被拒绝”

优先检查sshd是否还在跑:

sudo systemctl status ssh

如果sshd正常,用ss -lntp | grep :22确认端口监听正常。再检查是不是防火墙规则动了。多数时候是改配置时手滑动了全局配置,导致原本能连的IP被拒绝。回滚配置是最快的解法,不要想着现场一点点改回来。

7. 关于禁用方式的一些个人经验补充

最后聊点实际运维的体会,这些是文档里不会写的。

我在生产环境里做这类操作,比较推荐“先定向、后全局”的顺序。也就是先在测试机或者非核心业务机上,用Match User先把一两个账号禁掉试水,观察几天,确认没有误伤业务,再决定是否推广到全局。直接一上来就全局禁用,如果有些老系统刚好在用scp做备份,可能第二天就有同事找你喝茶了。

另外,建议改完配置之后在服务器上留一个配置文件注释说明,比如:

# 2024-05-20 by xxx: 禁用了SFTP/SCP,原因:安全加固要求,操作单号XXX

多人维护的服务器最怕这种“无声修改”,后面接手的人看着配置一脸懵,排查问题时浪费大量时间。这行注释的成本几乎为零,价值却很实在。

还有一点,Ubuntu 16.04已经比较老,如果你是在较新版本的Ubuntu(20.04/22.04)上操作,scp命令本身已经默认改用SFTP协议传输了,那么你只需要禁用SFTP子系统就会连带影响scp和WinSCP的SFTP模式,不需要再改shell——但WinSCP如果选SCP协议,老版本行为依旧,所以还是要区分版本来看待。

整个操作如果拆成一句话总结,那就是:scp和sftp是服务端的两个门,WinSCP是一把钥匙,把两个门都锁上,钥匙自然就没有用武之地了。希望这篇梳理能帮你少走一些弯路,改配置前记得备份,改完后记得验证,剩下的交给时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询