如何为端口转发创建一个受限制的SSH用户?

为了与他人建立简便的SSH连接(进行远程帮助),请使用ændrük建议的反向连接。为使其正常工作,需要创建一个额外的用户来接受连接。该用户需要能够通过服务器转发端口(服务器充当代理)。

如何创建一个只能执行上述操作而无法进行其他操作的受限用户?

新用户不能具备以下能力:

  • 执行Shell命令
  • 访问文件或将文件上传到服务器
  • 使用服务器作为代理(例如Web代理)
  • 访问由于防火墙而无法公开访问的本地服务
  • 关闭服务器

总结一下,如何创建一个受限制的SSH用户,该用户只能以无特权方式连接到SSH服务器,以便可以通过该连接与他的计算机进行连接?


https://unix.stackexchange.com/questions/44248/is-it-safe-to-use-bin-cat-as-shell-for-a-restricted-user - Keith
点击此链接,它总结了我的经验。 - ShaileshKumarMPatel
3个回答

TL;DR - 去回答底部,"应用限制"

添加受限用户包括两个部分: 1. 创建用户 2. 配置SSH守护进程(sshd)

配置sshd

了解SSH的可能性最好的方法是阅读相关的手册页面:

SSH客户端可以在哪里执行操作?

在你可以限制某些东西之前,你需要了解SSH的功能。浏览手册页面得到以下信息:

  • Shell命令执行
  • 通过sftp上传文件
  • 端口转发
    • 客户端将(未)使用的端口转发到服务器
    • 服务器将他的端口转发给客户端
    • 服务器将另一主机的端口转发给客户端(类似代理)
  • X11转发(显示转发)
  • 身份验证代理转发
  • 隧道设备的转发

来自 sshd(8)手册页面上的身份验证部分。

如果客户端成功进行身份验证,将进入准备会话的对话框。此时,客户端可以请求诸如分配伪终端、转发X11连接、转发TCP连接或转发身份验证代理连接等内容,通过安全通道进行传输。
之后,客户端要么请求一个shell,要么执行一个命令。然后双方进入会话模式。在这种模式下,任何一方都可以随时发送数据,并且这些数据将被转发到服务器端的shell或命令以及客户端的用户终端之间。

限制SSH功能的选项

影响行为的文件及其选项包括:

  • ~/.ssh/authorized_keys - 包含允许连接的密钥,可以给出选项:
    • command="command" - 忽略用户提供的命令(如果有)。请注意,客户端可以指定TCP和/或X11转发,除非明确禁止。请注意,此选项适用于shell、命令或子系统执行。
    • no-agent-forwarding - 当使用此密钥进行身份验证时,禁止身份验证代理转发。
    • no-port-forwarding - 当使用此密钥进行身份验证时,禁止TCP转发。
    • no-X11-forwarding - 当使用此密钥进行身份验证时,禁止X11转发。
    • permitopen="host:port" - 限制本地'ssh -L'端口转发,只能连接到指定的主机和端口。
  • ~/.ssh/environment - 如果存在,则在登录时将此文件读入环境中。默认情况下,禁用环境处理,并通过PermitUserEnvironment选项进行控制。
  • ~/.ssh/rc - 包含在用户的主目录变得可访问之前要运行的初始化例程。
  • /etc/ssh/sshd_config - 系统范围的配置文件
    • AllowAgentForwarding - 指定是否允许ssh-agent(1)转发。
    • AllowTcpForwarding
    • ForceCommand - "强制执行由ForceCommand指定的命令,忽略客户端提供的任何命令和~/.ssh/rc(如果存在)。使用用户的登录shell以-c选项调用该命令。"
    • GatewayPorts - "指定远程主机是否允许连接到为客户端转发的端口。默认情况下,sshd(8)将远程端口转发绑定到回环地址。这样可以防止其他远程主机连接到转发的端口。可以使用GatewayPorts来指定sshd应允许远程端口转发绑定到非回环地址,从而允许其他主机连接。"
    • PermitOpen:

指定允许TCP端口转发的目标。转发规范必须是以下形式之一: PermitOpen 主机:端口 PermitOpen IPv4地址:端口 PermitOpen [IPv6地址]:端口
可以通过使用空格分隔它们来指定多个转发。可以使用'any'参数来移除所有限制并允许任何转发请求。默认情况下,允许所有端口转发请求。
PermitTunnel - 指定是否允许tun(4)设备转发。默认值为'no' X11Forwarding - 指定是否允许X11转发。默认值为'no'
应用限制规则。
修改系统范围的配置文件/etc/ssh/sshd_config可以使配置即使在应用基于密码的身份验证或者在~/.ssh/authorized_keys中的限制被意外删除时也能生效。如果您已经修改了全局默认值,您应该相应地取消注释选项。
Match User limited-user
   #AllowTcpForwarding yes
   #X11Forwarding no
   #PermitTunnel no
   #GatewayPorts no
   AllowAgentForwarding no
   PermitOpen localhost:62222
   ForceCommand echo 'This account can only be used for [reason]'

现在添加一个用户:
sudo useradd -m limited-user

选项ForceCommand可以省略,如果shell设置为非shell,例如/bin/false(或/bin/true),因为/bin/false -c [command]不会执行任何操作。

现在客户端只能通过SSH连接到服务器的回环地址上的62222端口(它不会监听公共IP地址)。

禁用AllowTcpForwarding也将禁止使用-R,从而破坏了使用这样一个受限帐户进行单个端口转发的用途。PermitOpen localhost:62222假定服务器上的端口62222从未被使用,因为客户端可以愉快地连接并监听它。

如果系统范围的配置允许TCP转发,并禁用基于密码的身份验证,则也可以使用每个密钥的设置。编辑~/.ssh/authorized_keys并在ssh-之前添加下一个选项(选项和ssh-之间有一个空格):

command="echo 'This account can only be used for [reason]'",no-agent-forwarding,no-X11-forwarding,permitopen="localhost:62222"

验证

为了确保它按预期工作,需要运行一些测试用例。在下面的命令中,如果~/.ssh/config中没有设置登录名,则应替换host为实际的登录名。在命令后面,显示了一个应在客户端或服务器上执行的命令(根据指定)。

# connection closed:
ssh host
# connection closed (/bin/date is not executed):
ssh host /bin/date
# administratively prohibited (2x):
ssh host -N -D 62222 # client: curl -I --socks5 localhost:62222 example.com
ssh host -N -L 8080:example.com:80 # client: curl -I localhost:8080
sftp host
# should be possible because the client should forward his SSH server
ssh host -N -R 8080:example.com:80 # server: curl -I localhost:8080
# This works, it forwards the client SSH to the server
ssh host -N -R 62222:localhost:22
# unfortunately, the client can listen on that port too. Not a big issue
ssh host -N -L 1234:localhost:62222

结论

检查清单:SSH用户不应该能够:

  • 执行shell命令 - 完成
  • 访问文件或将文件上传到服务器 - 完成
  • 使用服务器作为代理(例如Web代理) - 完成
  • 访问由于防火墙而无法公开访问的本地服务 - 部分完成,客户端无法访问除62222端口之外的其他端口,但可以监听并连接到服务器上的62222端口
  • 关闭服务器 - 完成 (请注意,这些检查仅限于SSH服务器。如果您在机器上有其他易受攻击的服务,可能会允许潜在攻击者运行命令、关闭服务器等)

3为了使此功能正常工作,useradd 添加的帐户必须解锁。可以通过在 /etc/shadow 中将密码替换为星号 (*) 或使用 sudo passwd limited-user 设置密码来完成。 - Lekensteyn
它运行得很好,但我不明白哪个配置选项阻止了SFTP访问。你能解释一下吗?:) - gucki
3SFTP之所以能够运行,是因为SSH服务器执行了sftp-server命令。而通过使用ForceCommand,这个命令将不再被执行。 - Lekensteyn
有没有特别的步骤需要在树莓派上运行这个程序? 我似乎无法让它正常工作... - wittrup
@wittrup 这个方法也适用于树莓派(Raspbian / Arch / 其他系统?)。如果不起作用,请尝试在http://raspberrypi.stackexchange.com/上提出新的问题,并提供您尝试过的方法和结果的详细信息。 - Lekensteyn
@wittrup 看起来在PermitOpen中使用localhost:XXX并不总是有效。你可以使用127.0.0.1:XXX代替。另请参考这个答案 - lauhub
5authorized_keys中,from=选项也是非常有用的。它允许你将密钥的使用限制在特定的IP地址或主机名来源上。例如,from="1.1.1.1"from="10.0.0.?,*.example.com"。请参考手册中的"AUTHORIZED_KEYS FILE FORMAT"部分(https://linux.die.net/man/8/sshd)了解所有`authorized_keys`选项。 - Donn Lee
2"AllowTcpForwarding local" 禁止远程转发。(我不知道这个答案写的时候是否存在。) - Simon Sapin
3请注意,持久的SSH连接会导致此功能失效,因此在使用ssh -N连接时,您的~/.ssh/config文件中不应该有类似于Host * ControlMaster auto的设置。如果确实需要此功能,请使用ssh -N -o ControlMaster=no - nh2
如果您正在设置一个隧道服务器,请使用PermitOpen server1.db.example.com:3306 server2.db.example.com:3306(例如)。然后在Windows上,您可以执行plink.exe -N -L 3307:server1.db.example.com:3306 limited-user@mygateway.example.com -i C:\Users\..path..to\my.ppk - PeteW
1如果您有多个要限制的用户,请使用groupadd limited-users命令,并在sshd配置文件中使用Match Group limited-users替代上面的Match User命令。然后,当您添加一个用户时,执行useradd -m limited-user1,然后执行usermod -a -G limited-users limited-user1 - PeteW
如果用户使用动态端口转发(即SOCKS代理),则此方法不适用作代理。 - Zakaria
@Zakaria 你试过了吗?在我使用包含“PermitOpen”的上述配置的Arch Linux上,ssh -N -D 8080 limited-user@localhost -vvvcurl --socks5 localhost:8080 example.com -v明显失败了。 - Lekensteyn
在服务器的authorized_keys中,我最终使用以下内容允许设备进行"拨出转发"的ssh连接:command="echo '该账户仅可用于[原因]'", permitlisten="127.0.0.1:62222", no-agent-forwarding, no-X11-forwarding, no-pty, no-user-rc, permitopen="[100::]:1",然后在设备上执行ssh host -N -R 127.0.0.1:62222:127.0.0.1:22。设备的ssh连接现在位于服务器的端口62222上。设备只能对IPv6丢弃前缀100::进行-L转发。 - Tugzrida
关于~/.ssh/authorized_keys中的no-pty选项和/etc/ssh/sshd_config中的PermitTTY选项怎么样?有必要进行限制吗?我在这里看到了no-pty选项的提及:https://blog.tinned-software.net/restrict-ssh-access-to-port-forwarding-to-one-specific-port/ - baptx
这可能不是OP的问题,但我仍然不清楚这些设置是否限制了通过从本地机器到远程主机创建的反向SSH隧道在远程机器上通过套接字访问本地机器的权限。一旦隧道建立,是否可以通过本地主机上的权限来限制访问?我主要是因为我正在考虑是否需要创建一个新的问题而提出这个问题。 - highsciguy
非常感谢您这篇出色而详细的帖子,我为了更加谨慎起见,添加了ChrootDirectory /var/empty,并且它运行得很好。 - Rafael Kitover

我相信有很多解决方案,而且比我提出的这个更加强大。然而,这可能已经足够满足您的需求了。为了实现这一点,我假设用户能够进行基于ssh密钥的身份验证(putty或任何unix ssh都应该支持此功能)。
  • 像平常一样添加一个用户(使用'adduser'或其他工具)

  • 创建用户的.ssh目录和.ssh/authorized_keys文件

your_user $ sudo -Hu ssh_forwarder /bin/bash

ssh_forwarder $ cd ~
ssh_forwarder $ mkdir .ssh
ssh_forwarder $ ( umask 066 && cat > .ssh/authorized_keys ) <<EOF
no-agent-forwarding,no-X11-forwarding,command="read a; exit" ssh-rsa AAAB3NzaC1y....2cD/VN3NtHw== smoser@brickies
EOF

禁用此账户的密码访问。
your_user $ sudo usermod --lock ssh_forwarder

现在,用户唯一能够进入您的系统的方式是通过访问正确的SSH密钥,而SSH会为他们运行“/bin/bash -c 'read a'”,无论他们尝试运行什么。'read a'将简单地读取直到换行符,然后Shell将退出,所以用户只需按下“回车”键即可终止连接。

'command='中有很多其他操作可以执行。请参阅man authorized_keys并搜索“command”获取更多信息。

如果您不喜欢按下回车键终止连接的事实,可以使用类似以下内容作为“command=”条目:

command="f=./.fifo.$$ && mkfifo $f && trap \"rm -f $f\" EXIT && read a <$f && echo $a; exit;"

这只是在用户的主目录中创建一个临时FIFO,然后尝试从中读取。没有任何东西会写入该文件,所以它将无限期地挂起。此外,如果您想强制终止该连接,可以执行类似以下操作:
 your_user$ echo GOODBYE | sudo tee ~ssh_forwarder/.fifo.*

这个脚本应该使用非常少的资源,并且在脚本中不会发生任何不会导致终止shell的错误。
sleep 1h; echo You have been here too long. Good bye.

我没有立即看到如何允许用户进行远程转发(ssh -R),但限制(ssh -L)。也许可以使用'permitopen'。谷歌搜索没有提供太多帮助。似乎像'no-port-forwarding,permitremoteopen=10001'这样的东西会很有用,以允许ssh -R 6901:localhost:6901

这是一个解决方案。它肯定可以改进,并且应该仔细审查任何打开远程端口的操作。如果我的目标是允许我的祖母连接到我的局域网,这样我就可以使用VNC来查看她的屏幕,并且对这些密钥的访问仅限于她,我个人会感觉相当安全。如果这是为了企业,需要进行更彻底的调查。要注意的一件事是ssh -N根本不请求shell,所以'command='代码不会执行。

其他可能更安全的机制可能包括为用户创建自定义shell,甚至使用apparmour锁定它。


5这看起来非常破解,甚至没有必要使用 shell,因为不需要执行任何命令。请参考ssh的手册页面了解-N选项。一定有更简洁的方法来完成这个任务。 - Lekensteyn
没错。我也希望能看到一个更简洁的解决方案。如果有一个“允许远程打开”的选项,我认为将authorized_keys与no-port-forwarding结合起来会是一个相当不错的解决方案。 - smoser
我进行了一些研究(见上文)。由于ssh -N根本不执行任何命令,所以command="something"关闭会话也没有问题。 - Lekensteyn
我有什么遗漏吗?似乎只需要执行 chsh ssh_forwarder -s /bin/false - daparic

尝试使用command="exit"命令。
这将强制用户使用此命令进行端口转发。 ssh -NfR EXTERNAL_PORT:localhost:INTERNAL_PORT usr@server -i key

也可以用这种方法。但是怎样停止这个特定的SSH连接呢? - burny