SSH上的端口转发是一个有争议的话题。
SSH可以作为任意TCP流量的包装器。你可以将未加密的服务(如telnet、POP3、IMAP、HTTP)隐藏在SSH中。安全地传输这些本机不安全的协议。
SSH会话可以携带任何TCP/IP协议,包括你的本地IT安全团队在组织网络上禁止的协议。因此,许多具有高安全要求的组织不允许SSH穿越和/或离开其网络。要求不太严格的组织使用此功能来保护其网络
你也可以使用SSH创建VPN来携带所有IP协议,第十三章中会介绍。
例如,我用WordPress管理我的网站和博客。它为网站管理和设计提供了一个友好的点击式界面,给了我一个外观不错的页面,而我实际上不需要学习任何关于网页设计的知识。曾经,在Let’s Encrypt之前的那些黑暗日子里,我的网站使用了纯HTTP。我使用SSH端口转发在Web服务器和桌面之间建立HTTP隧道。这保护了我在传输过程中的凭据,并消除了我的密码在传输中被盗的风险。这是对SSH端口转发的合理和合法使用。
然而,假设我的桌面位于一个高度安全的网络中。防火墙严格限制网络浏览,并阻止所有文件传输。如果我可以使用SSH连接到网络外部的服务器,我可以将桌面的流量转发到该外部服务器,以获得不受限制的互联网访问。我可以通过SSH上传机密文档,防火墙日志只会显示我建立了SSH连接。你的网络管理员会反对的,这是有充分理由的。
如果你是一个组织的安全官员,端口转发可能会让你考虑完全阻止SSH。我理解。我做过你的工作。您还应该知道,不听话的用户可以在DNS、HTTP或几乎任何其他服务或协议(包括原始ICMP)内通过SSH隧道。绝对阻止SSH的唯一方法是拒绝所有TCP、UDP或ICMP连接,使用智能检查流量的web代理,甚至不允许您的客户端机器通过代理访问公共DNS。我见过一家公司实际实施了这种类型的安全边界,他们对明显笨重的业务关键型软件有许多漏洞和例外。如果您无法在环境中实现这一点,但有严格的安全要求,则必须与用户合作以满足这些要求和业务需求。我强烈建议建立一个可靠的网络流量感知程序以及入侵和挤出检测,这样你就可以知道你的网络流量何时偏离正常水平。阅读Richard Bejtlich关于入侵和挤压分析的书籍,以及我自己的《Network Flow Analysis》(No Starch Press,2010),并实现所讨论的程序。
作为用户,拥有通过SSH隧道传输任意流量的能力并不意味着你应该这样做。如果你的组织的安全策略禁止端口转发和/或隧道传输,那就不要这么做。如果策略说“使用web代理并远离IRC”,那就听。如果你使用这些技术并受到谴责、终止或消灭,我不承担任何责任。(即使我们这些IT安全人员都是微不足道的暴君,他们不理解你对IRC和MySpace的个人和迫切需求。)
某些应用程序在通过端口转发使用时会出现异常。将应用程序故障与端口转发故障分开非常重要。
如果你转发了一个端口,而你的应用程序无法通过它工作,请使用netcat甚至telnet来确定该端口是否实际打开。服务器应该通过转发端口向netcat请求发送与通过非转发端口相同的反馈。
如果没有收到响应,则可能是端口转发配置错误。仔细检查你的命令行。如果必要,在连接的一侧或两侧进行调试,以查看实际情况。请记住,一次只能有一个进程打开指定的端口。
如果端口转发工作正常,则你的应用程序有问题。也许你需要一个主机条目,这在许多web应用程序中常见。也许这是一个古老而笨拙的协议,它希望打开各种各样的端口。FTP就是一个典型的例子。你需要深入了解应用程序及其协议,以找出它不起作用的原因。
端口转发是一种工具。并非所有协议都适用于此工具。有时,使用端口转发就像试图用锤子敲打螺丝;你得到的任何结果都会让你不高兴。
对于所有这些端口转发示例,我假设SSH客户端位于防火墙后面。这可能是从大型企业代理到家庭路由器的任何东西。此防火墙后面还有其他几个服务器,包括web和电子邮件服务器。客户端无法访问公共互联网;外界无法与之联系。
SSH服务器位于公共互联网上,任何人都可以连接到它,它可以自由访问互联网的其他部分。
端口转发有三种类型:本地、远程、动态。
当底层SSH会话终止时,所有端口都停止转发。第10章提供了保持SSH会话活动的建议。
有了这些可能性,很容易理解为什么系统管理员喜欢SSH,以及为什么许多公司安全部门禁止它。
在类Unix系统上,1024以下的TCP端口保留共系统使用。只有root可以绑定这些端口。
作为无特权用户,你可以将SSH端口转发器的本地端连接到1024以上的任何端口。转发保留端口需要使用SSH作为root。以root身份执行常规任务是一种糟糕的做法,所以如果没有真正的理由,不要这样做。
只有连接到特权端口的一侧需要以root身份运行。如果要在客户端绑定保留端口,请以root身份运行客户端,但要以普通用户身份登录服务器。如果要在服务器上绑定保留端口,则需要以root身份登录服务器。在后一种情况下,最好更改端口,这样你就不必直接以root身份登录。
MS系统不实现特权端口。任何人都可以绑定到系统上的任何开放端口。没有端口限制会产生各种可能有趣的安全问题,但它确实使转发低端口的报告并不比转发任何其他端口更困难。
永远不要以root身份运行PuTTY。
在设置本地端口转发之前,请验证正常的SSH是否正常工作。然后弄清除你想转发什么服务,以及该服务在哪个端口上运行。
一些典型的选择是80(HTTP)、25(SMTP)、110(POP3)。通常在这些端口上运行的服务通常不加密。
现在选择要用于转发的本地端口。有些客户端在任何端口上运行时都能很好地工作。几乎任何邮件客户端都允许你设置TCP端口来检查POP3。其他客户端则不允许。
如果你更改端口号,网站经常会堵塞。如果你不知道协议从一个端口转发到另一个端口时的行为,请在测试服务器上尝试并查看。
对于我们的本地端口转发示例,我们将把客户端上的端口8080转发到服务器sloth上端口80。既然TLS证书是免费的,你为什么需要这样做?一些专有的基于web的应用程序不支持TLS,如果你试图将它们转换为TLS,它们就无法工作。我需要编辑客户端的hosts文件,以告诉我的客户端该网站的IP地址为127.0.0.1。我需要第二个域名,以便通过SSH连接到实际的机器。如果我是唯一一个使用此应用程序的人,一旦我设置了端口转发,我就可以告诉应用程序只监听服务器的本地主机地址。这不仅会像TLS一样保护我传输中的数据,还会为应用程序添加另一层保护。
使用-L标志告诉SSH客户端激活本地转发:
xxxxxxxxxx
$ ssh -L localIP:localport:remoteIP:remoteport hostname
如果不在SSH客户端上指定IP地址,SSH将连接到127.0.0.1。在这种情况下,您可以跳过第一个参数,执行以下命令:
xxxxxxxxxx
$ ssh -L localport:remoteIP:remoteport hostname
目前,只使用IP地址127.0.0.1。这是每台机器上的环回地址,只能在该机器上访问。虽然看起来我们正在将一个地址转发到同一个地址,但客户端上的127.0.0.1与服务器上的127..0.1并不相同。我们将在本章后面的“选择IP地址”中考虑将转发端口绑定到不同的IP地址。
以下是我们如何使用本地端口转发连接到服务器sloth,并将sloth的本地主机地址上的端口80转发到客户端的端口8080。
xxxxxxxxxx
$ ssh -L 8080:127.0.0.1:80 sloth
我正在连接工作站上的8080端口。我没有指定本地IP地址,因此ssh将转发附加到客户端的127.0.0.1。我的SSH会话正常登录,并在服务器上为我提供了一个终端。但是,如果我将web浏览器指向localhost:8080,我将连接到服务器上运行的网站。hosts文件中的别名将使网站更可用。
要在每次连接到服务器时设置本地端口转发,请在ssh_config中使用LocalForward关键字:
xxxxxxxxxx
LocalForward client-IP:client-port server-IP:server-port
这看起来像是命令行上的端口转发,但缺少中间的冒号。在这里,我将工作站上的端口8080转发到服务器上的端口80。我们在客户端和服务器上都连接到127.0.0.1或localhost。我在工作站上使用端口8080,因为使用端口80每次都需要以root身份运行SSH。
xxxxxxxxxx
Host envy.mwl.io
LocalForward localhost:8080 localhost:80
LocalForward关键字最常出现在Host语句中,在连接到特定服务器时启用本地端口转发。为了避免IP和端口冲突,每个服务器通常会分配自己的本地端口。
PuTTY有一个专门用于端口转发的特殊控制面板。在PuTTY配置屏幕的左侧,选择连接->SSH->Tunels。
通过本地端口转发,PuTTY默认连接到客户端的localhost地址。但是,我必须指定要使用的SSH服务器上的地址。要将SSH服务器上的端口80转发到工作站上的端口八十,我将使用服务器的本地主机地址。在Source port中,输入80。在Destination中,输入服务器上的IP地址、冒号和要转发的端口。在这里,我将使用127.0.0.1:80。在底部,选择“Local”。
点击Add,然后连接。您现在拥有端口转发功能。将浏览器指向localhost,看看会发生什么。
要将此转发绑定到客户端的面向网络的IP地址,请选择“Local ports accept connections from other hosts(本地端口接受来自其他主机的连接)”。这将转发端口绑定到客户端上的所有IP地址,以便工作站网络上的其他主机可以访问转发。有关影响的讨论,请参阅本章稍后的“Choosing IP Addresses(选择IP地址)”。
如果您想在每次连接到此主机时使用此转发,请保存此会话。
在配置远程端口转发之前,请验证正常的SSH是否正常工作。确定要转发的客户端和服务器端口。
当本地端口转发通常用于用加密包裹(wrap)服务时,远程端口转发用于访问防火墙后面的服务。对于这个例子,我将把SSH服务器本地主机地址上的端口2222转发到工作站上的端口22。当我连接到SSH服务器上的端口2222,远程转发会将我重定向到工作站的SSH服务。
为什么这么做?请记住,在我们的示例环境中,客户端位于防火墙后面。防火墙可能是我的家庭NAT设备,也可能是我雇主的工业级企业防火墙集群。远程转发允许我使用我的客户端为网络外的SSH服务器提供一种连接到防火墙内主机的方法,尽管防火墙规则与此相反。这可能是我进入自己网络的宝贵紧急后门,也可能违反了我雇主的安全政策。或者,更好的是:两者都有!
请注意,您无法将转发端口绑定到SSH服务器的面向公众的IP地址,除非服务器通过GatewayPorts关键字专门配置为允许这样做。请参阅本章后面的“Restricting Port Forwarding(限制端口转发)”。
使用-R选项配置远程端口转发:
xxxxxxxxxx
$ ssh -R remoteIP:remoteport:localIP:localport hostname
如果不在SSH服务器上指定要连接的IP地址,SSH将连接到127.0.0.1。这种情况下,可以跳过第一个参数,执行以下命令:
xxxxxxxxxx
$ ssh -R remoteport:localIP:localport hostname
我想使用两边的localhost地址将SSH服务器sloth上的端口2222连接到工作站上的端口22:
xxxxxxxxxx
$ ssh -R 2222:localhost:22 sloth
我的客户端连接到服务器并给我一个命令提示符。只要该SSH会话保持打开状态,sloth上的另一个用户就可以通过连接到端口2222通过SSH连接到我的工作站:
xxxxxxxxxx
sloth$ ssh -p 2222 localhost
一个新的SSH连接到我的工作站,在我现有的SSH会话内通过隧道传输。此新会话将在客户端日志中显示为来自localhost的新连接。在设置远程端口转发时,你确实需要信任在你的系统上拥有账户的人。任何可以访问系统本地主机地址的人都可以使用端口转发。
永远不要在你不完全信任的SSH服务器上使用远程端口转发。
如果要在每次连接到服务器时建立远程端口转发,请在ssh_config中使用RemoteForward关键字:
xxxxxxxxxx
RemoteForward server-IP:server-port client-IP:client-port
这再次类似于命令行上的端口转发,但缺少中间的冒号。在这里,我在配置文件中设置了相同的端口转发:
xxxxxxxxxx
Host sloth.mwl.io
RemoteForward localhost:2222 localhost:22
RemoteForward关键字最常出现在Host语句中,除非你想在连接的每个主机上执行远程转发。
要配置远程转发,请转到PuTTY配置屏幕的左侧,选择Connection-->SSH-->Tunnels。当我们从服务器转发到客户端时,源端口字段是指将转发到工作站的服务器上的端口。在这种情况下,源端口是2222。Destination 是 localhost:22,因为工作站的SSH服务器在端口22上运行。选择 Remote 进行远程端口转发。
点击 Add ,然后连接。端口转发应该工作。
要将此转发绑定到服务器的面向网络的IP地址,请选择 Remote ports do the same (SSH-2 only)。这将转发端口绑定到SSH服务器上的所有IP地址,以便其他主机可以访问转发。本章稍后将讨论"Choosing IP Addresses"的含义。
要使此服务器的远程转发永久化,请保存会话。
动态端口转发将SSH客户端转换为SOCKS(版本5)代理。发送代理的任何流量都将通过隧道传输到SSH服务器,SSH服务器在自身访问许可的情况下转发该流量。你必须有一个支持SOCKS的应用程序才能访问代理,但大多数web浏览器都支持SOCKS。
在这个例子中,我将把工作站上的端口9999配置为SOCKS代理,并将所有流量动态转发到公共互联网上的服务器。
使用SOCKS时,你的客户端可能需要将所有DNS请求转发到SOCKS服务器。并非所有客户都支持这一点。
使用-D选项告诉OpenSSH使用动态端口转发:
xxxxxxxxxx
$ ssh -D localaddress:localport hostname
如果你没有指定IP地址,ssh自动绑定到127.0.0.1。
此处,我在我的工作站的端口9999创建了我的代理(proxy)。发送到代理的所有流量都会转发到SSH服务器 sloth上,然后会将其中继到目的地。
xxxxxxxxxx
$ ssh -D 999 sloth
与端口转发一样,你将登录到服务器并收到命令提示符。动态转发在后台进行。将工作站上的web浏览器配置为在127.0.0.1:9999处使用SOCKS代理。它应该通过SSH连接将你的所有浏览内容发送到你的服务器。
如果希望每次连接到主机时都配置远程端口转发,请在ssh_config中使用DynamicForward关键字:
xxxxxxxxxx
DynamicForward host:port
与其他转发语句一样,出于同样的原因,DynamicForward关键字最常出现在Host语句中。
转到 Tunnels 屏幕,在 Source port 字段中,输入你希望SOCKS代理使用的端口。将 Destination 留空。选择 Dynamic ,然后点击 Add。你将在 Forwarded ports 列表中看到端口转发。打开连接。你的浏览器现在应该能够通过SOCKS代理进行连接。
在我的示例中,我在 Source port 字段输入9999,选择 Dynamic,点击 Add ,然后连接。就是这样。
要将此转发绑定的客户端的面向网络IP地址,请选择 Local ports accept connections from other hosts。这将代理绑定到工作站上的所有IP地址,以便其他主机可以访问转发。记住,当你这样做时,你是在向所有可以访问你的客户的人提供隧道。
如果你希望每次打开此连接时自动启动此转发,请保存会话。
你可以使用任何支持SOCKS代理的程序验证动态转发。这种类型最常见的程序是web浏览器。
配置防火墙以阻止来自工作站的所有端口80流量。确认你无法再流量网页。如果你要浏览,你需要通过代理完成。
启动动态端口转发SSH会话。配置web浏览器以访问该代理。如果你能看到互联网,动态转发正在工作。
有时你想使用OpenSSH转发连接,但不需要SSH服务器上的终端会话。使用-N标志告诉ssh不要在服务器上运行任何东西,包括终端,使用-f标志告诉ssh在客户端进入后台。此处我为服务器pride提供了一个本地后台转发会话:
xxxxxxxxxx
$ ssh -fNL 2222:localhost:22 pride &
此命令设置为后台运行,即可恢复原始终端。当你在SSH服务器上没有shell访问权限,但允许你进行身份验证并创建隧道时,后台转发非常有用。
这是创建基于SSH的VPN的一种方法,第13章将讨论更好的方法。
端口转发时,你必须选择希望转发端口侦听的IP地址,以及希望将转发端口连接到IP。选择IP有助于控制谁可以连接到转发端口。
最常见的选择是在隧道的一端或两端绑定到localhost地址,127.0.0.1。具有功能性TCP/IP栈的每台机器都使用127.0.01作为自己的地址,只有本地机器可以连接到它。如果我将工作站本地主机地址上的端口80转发到服务器本地主机地址的端口80,则其他主机无法通过我的隧道连接到该转发端口。服务器上的大多数守护进程都会监听localhost地址以及一个或多个面向网络的IP地址,因此使用localhost地址是转发端口的合理方式。
如果你希望客户端接受来自其他计算机的请求,并使用本地端口转发将其发送到SSH服务器,请将端口转发附加到客户端的面向网络的IP地址。如果我将计算机面向网络的IP地址上的端口80转发到SSH服务器上的端口80,则所有可以连接到客户端端口80的主机都可以使用此转发。使用PuTTY,你必须选择本地端口接受来自其他主机的连接。使用OpenSSH时,你必须在ssh_config中设置GatewayPorts关键字(参阅本章后面的 Gateway Ports)。
如果你希望SSH服务器使用远程端口转发将来自其他机器的请求转发到你的客户端,请将端口转发附加到服务器的面向网络的IP地址。你必须在sshd_config中调整网关端口,如本章后面的 Gateway Ports 所示。例如,我们使用远程报告转发服务器上端口连接到客户端的sshd。你可以将此远程转发附加到服务器的面向公众的IP地址,这样互联网上的任何主机都可以连接到客户端的SSH服务,即使它在防火墙后面。记住,虽然在专用网络中创建一个反向通道可能很有用,但向整个互联网开放这个反向通道时非常粗鲁的(gauche)。
如果你希望SSH客户端通过动态端口转发充当其他机器的SOCKS代理,请将端口转发附加到客户端的面向网络的IP地址。
始终记住,运行任何现代操作系统的主机都可以有多个IP地址。选择一个特定的地址而不是允许所有面向网络的地址可能是有意义的。
假设我的工作站的IP地址192.0.2.18,并且与一大堆其他客户端在网络上。我们必须访问一个关键的基于网络的应用程序,该应用程序不会加密传输中的数据。我可以通过本地端口转发提供从我的工作站到服务器的机密隧道。如果我想单独将此隧道提供给我的桌面,我会将隧道的客户端连接到127.0.0.1。如果我想向网络上的每个人提供此隧道,我会将客户连接到192.0.2.18。
或者,我可能负责运行公司的内容过滤网络代理,并试图调试某个网站无法通过代理运行的问题。我想看看这个网站在我的网络之外是什么样子的。我可以设置一个私有的SOCKS代理来绕过组织的代理,让我从外部服务器浏览。设置一个任何人都可以使用的未经授权的代理服务器是需要新作业的好方法,因此我绝对确保该隧道的本地端使用localhost地址。
你可以使用主机名而不是实际的IP地址,前提是主机名在DNS中正确显示。你也可以使用localhost代替127.0.0.1。
OpenSSH服务器控制用户可以执行的端口转发类型。你可以拒绝端口转发,允许端口转发但只允许绑定到localhost地址,或者只允许特定的地址和端口。
不过,在服务器级别实现这些块并不像人们希望的那样有效。具有shell访问权限的用户可以轻松安装自己的转发器。正确禁用shell用户的转发需要控制哪些二进制文件是可执行的,禁用perl或Python等解释器,并阻止用户安装更多程序。在大多数情况下,除非你真的很专注,否则具有shell访问权限的用户可以找到转发端口的方法。尽管如此,禁用或限制端口转发给你的用户一个非常可靠的提示,即它们不应该转发端口。
sshd_config关键字AllowTcpForwarding告诉sshd它是否应该允许端口转发。此关键字有四个可用值:
GatewayPorts关键字控制客户端是否可以将远程转发端口绑定到本地主机以外的任何IP地址。此关键字出现在ssh_config和sshd_config中。ssh_config选项控制本地端口转发,而sshd_config选项控制远程端口转发。
默认情况下,GatewayPorts设置为no,这意味着客户端无法将任何端口转发连接到任何面向网络的IP地址。这在ssh_config和sshd_config中都是相同的。
在ssh_config中使用时,将GatewayPorts设置为yes,可允许ssh请求监听客户端上的任何IP。
在服务器端,在sshd_config中将GatewayPorts设置为yes意味着无论客户端请求什么,远程转发都会监听主机上的所有地址。我不知道为什么要在所有端口转发上启用全球网络访问,但这是一种选择。
服务器支持sshd_config中的一个附加GatewayPorts选项,即clientspecified,它告诉sshd让客户端绑定到它们请求的任何内容。允许客户端在逐个转发的基础上进行细粒度控制通常是最佳选择。
如果你希望比GatewayPorts支持的更具体,可以在sshd_config中使用PermissionOpen关键字限制可以转发哪些TCP端口和地址。PermitOpen采用空格分隔的端口列表,这些端口可以以hostname:port的形式转发。例如,此处我允许服务器的端口25和110转发回客户端,并且只能从localhost地址转发:
xxxxxxxxxx
PermitOpen localhost:25 localhost:110
任何不允许的事情都是被禁止的。SSH会话将正常打开,但当你尝试通过禁止的转发端口传递流量时,SSH客户端会显示错误。
也许端口转发时此连接存在的唯一原因。如果设置端口转发失败,你设置不希望建立连接。ExitOnForwardFailure ssh_config关键字告诉ssh在转发端口的尝试失败时该怎么办。默认值为no,意味着即使无法建立端口转发,也应建立连接。通过将ExitOnForwardFailure设置为yes,如果端口转发不起作用,你可以告诉SSH立即断开连接。
限制你已经直到如何有选择地转发端口以帮助将网络粘合在一起,让我们能看看如何在没有人为干预的情况下,使SSH会话一次保持数小时或数天地活动状态。