SSH埠轉發也被稱為SSH隧道,我們知道SSH是加密通訊的,所以通過SSH建立的隧道傳輸的內容是安全的。它通常被用來繞過防火牆等裝置穿透到內網,或用於保護TCP連線等。
SSH埠轉發分為以下三種:
讓我們思考以下的情況,當我們使用WorkStation SSH連線到SQL Server時,我們通過SSH傳送的資料是加密的,但我們通過Mysql連線到SQL Server時,它的連線則是不會被加密的,如果這是一條跨越公網的連線,這將是十分危險的。
但如果我們時候SSH埠轉發,我們則能夠在過程中通過SSH進行加密,能夠保護我們資料的安全。
那麼我們應該使用那種SSH埠轉發方式呢?
本地轉發——表示本地某個埠的資料通訊會被轉發到目標主機的特定埠,注意,我們把執行本地轉發命令的裝置稱為本地主機。
在上面講的情況,我們就可以使用本地轉發的方式。在設定完成本地轉發的方式之後,我們就能夠通過本地與目標主機建立的SSH隧道進行傳輸資料,這樣可以將我們傳輸的資料進行加密。
讓我們看看下面這個範例:
我們通過curl命令列工具存取Web伺服器。
[root@localhost ~]# curl -I 192.168.222.128
HTTP/1.1 403 Forbidden
Date: Wed, 23 Nov 2022 20:04:36 GMT
Server: Apache/2.4.37 (centos)
Content-Location: index.html.zh-CN
Vary: negotiate,accept-language
TCN: choice
Last-Modified: Fri, 14 Jun 2019 03:37:43 GMT
ETag: "fa6-58b405e7d6fc0;5ee25c70d2749"
Accept-Ranges: bytes
Content-Length: 4006
Content-Type: text/html; charset=UTF-8
Content-Language: zh-cn
通過上述內容,我們可以通過WireShark抓取到明文的HTTP包。
那麼我們接下來設定SSH埠轉發。
[root@localhost ~]# ssh -L 80:192.168.222.128:80 192.168.222.128
The authenticity of host '192.168.222.128 (192.168.222.128)' can't be established.
ECDSA key fingerprint is SHA256:gfElOdquMLiDDsfg0TQG//KU+uahlfzSjb23pQlbSxk.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.222.128' (ECDSA) to the list of known hosts.
[email protected]'s password:
Last login: Thu Nov 24 04:01:51 2022 from 192.168.222.1
值得注意的是,這裡我們設定了SSH埠轉發,輸入完相應的密碼後,我們將會生成一個新的SSH對談連線到遠端主機。
關於設定隧道的具體引數,我們在端末會提到。
[root@localhost ~]# netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:80 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp6 0 0 ::1:80 :::* LISTEN
tcp6 0 0 :::22 :::* LISTEN
從上可見,我們在本地監聽了一個80埠(此時使用的是剛剛執行埠轉發命令的機器)。我們再次嘗試存取Web伺服器。
[root@localhost ~]# curl -I localhost
HTTP/1.1 403 Forbidden
Date: Wed, 23 Nov 2022 20:12:06 GMT
Server: Apache/2.4.37 (centos)
Content-Location: index.html.zh-CN
Vary: negotiate,accept-language
TCN: choice
Last-Modified: Fri, 14 Jun 2019 03:37:43 GMT
ETag: "fa6-58b405e7d6fc0;5ee25c70d2749"
Accept-Ranges: bytes
Content-Length: 4006
Content-Type: text/html; charset=UTF-8
Content-Language: zh-cn
我們再次檢視產生的流量。
那麼此時的流量則是經過SSH加密後的流量了。
那麼如何不產生新的SSH對談,單獨建立SSH隧道呢?我們可以使用 -N 引數,這個選項將會幫助我們僅建立SSH隧道而不會開啟遠端Shell連線到主機。
但是光使用這個 -N 引數也是不夠的(具體為什麼不夠,可以自己嘗試一下),我們還需要使用 -f 引數,讓這個隧道在後臺執行。
那麼我們目前的命令就是 ssh -f -N -L 80:192.168.222.128:80 192.168.222.128
但是這也是有弊端的,這條命令僅僅只會監聽在 127.0.0.1 這個本地環回地址上。如果想要監聽在一個本地地址上我們可以寫成下面這種形式。
ssh -f -N -L 192.168.222.129:80:192.168.222.128:80 192.168.222.128 像這樣我們就能夠將80埠監聽在自己所需要的地址上。
如果我們需要設定埠監聽在所有地址上,我們可以使用 -g 引數——開啟閘道器模式,這樣它就能夠監聽在所有的地址上了。
介紹完本地轉發後,我們再來思考一下遠端轉發,為了方便理解,我們來描述一個場景。
在上述這個場景中,Web伺服器由於防火牆的原因不能正常的通過外部進行存取,我們能夠控制內部Web伺服器與外部伺服器,但卻沒有防火牆的操作許可權。
此時我們就可以使用遠端轉發,來繞過防火牆的限制,穿透到內網存取內網的資源。
我們來通過實際實驗介紹遠端轉發。
我們是用CentOS 8 作為伺服器以及防火牆,通過設定三臺主機構建網路環境,通過模擬防火牆分割網路,具體拓撲如下圖。
首先我們要設定Linux的全域性轉發功能,使得Linux能夠轉發不同網段之間的資料。
[root@localhost ~]# vim /etc/sysctl.conf
在其中新增如下內容。
net.ipv4.ip_forward = 1
然後執行命令。
[root@localhost ~]# sysctl -p
net.ipv4.ip_forward = 1
其次,設定 CentOS 8 自帶的 Firewalld,我們需要將 FW 連線到 OutSide 裝置的網段的網路卡設定到 external 區域( external 區域自動開啟 masquerade 功能 )。
預設網路卡都在 Public 區域中,我們應該先把網路卡從 Public 區域中刪除,然後將網路卡新增到 external 區域中。
[root@localhost ~]# firewall-cmd --remove-interface=ens160 --zone=public
success
[root@localhost ~]# firewall-cmd --add-interface=ens160 --zone=external
success
下述內容均為操作 Inside 主機。
然後在Web伺服器Ping OutSide 主機,理論上是能夠通訊的。
[root@localhost ~]# ping outside
PING outside (192.168.244.130) 56(84) bytes of data.
64 bytes from outside (192.168.244.130): icmp_seq=1 ttl=63 time=2.49 ms
64 bytes from outside (192.168.244.130): icmp_seq=2 ttl=63 time=0.890 ms
64 bytes from outside (192.168.244.130): icmp_seq=3 ttl=63 time=0.854 ms
64 bytes from outside (192.168.244.130): icmp_seq=4 ttl=63 time=1.02 ms
那麼基礎網路已經構建完畢了。我們給 Inside 伺服器安裝 Web 功能,設定防火牆放行指定服務。
[root@localhost ~]# yum install -y httpd
[root@localhost ~]# systemctl start httpd && systemctl enable httpd
[root@localhost ~]# firewall-cmd --add-service=http --per && firewall-cmd --reload
[root@localhost ~]# curl -I localhost
HTTP/1.1 403 Forbidden
Date: Fri, 25 Nov 2022 20:57:20 GMT
Server: Apache/2.4.37 (centos)
Content-Location: index.html.zh-CN
Vary: negotiate,accept-language
TCN: choice
Last-Modified: Fri, 14 Jun 2019 03:37:43 GMT
ETag: "fa6-58b405e7d6fc0;5ee25c70d2749"
Accept-Ranges: bytes
Content-Length: 4006
Content-Type: text/html; charset=UTF-8
Content-Language: zh-cn
# 能有如上顯示則成功設定
以上就是基礎環境的設定,下面開始遠端轉發的設定。
我們可以從拓撲得知,FW 隔斷開了兩個網段,由於 FW 的 Masquerade 功能,Inside 可以存取 OutSide,而 OutSide 不能存取 Inside。如果此時 Inside 想讓 OutSide 能夠存取自己的Web服務就可以通過遠端轉發功能。
[root@localhost ~]# ssh -f -N -R 80:192.168.222.128:80 192.168.244.130
[email protected]'s password:
此時我們可以在OutSide機器上看到相應埠的建立。
[root@OutSide ~]# netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:80 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp6 0 0 ::1:80 :::* LISTEN
tcp6 0 0 :::22 :::* LISTEN
我們在OutSide機器上存取原生的80埠。
[root@OutSide ~]# curl -I localhost
HTTP/1.1 403 Forbidden
Date: Fri, 25 Nov 2022 22:01:18 GMT
Server: Apache/2.4.37 (centos)
Content-Location: index.html.zh-CN
Vary: negotiate,accept-language
TCN: choice
Last-Modified: Fri, 14 Jun 2019 03:37:43 GMT
ETag: "fa6-58b405e7d6fc0;5ee25c70d2749"
Accept-Ranges: bytes
Content-Length: 4006
Content-Type: text/html; charset=UTF-8
Content-Language: zh-cn
至此,我們的遠端埠轉發也搭建成功。
具體的流量還是會經過 FW ,但是 FW 放行 SSH 埠,資料經過SSH的加密將會穿過FW到達內網,從而能夠存取Inside。
那麼我們都有了遠端轉發和本地轉發,為什麼還需要動態轉發呢?
那麼我們思考這樣一個問題,如果我們本地轉發的目的埠為443,對端是一個HTTPS服務,在我們通過瀏覽器存取時,由於本地監聽的地址為localhost,則會出現證書無法被驗證的問題。還有假設這個Web伺服器將我們重定向至另一個URL,很有可能將連線失敗,例如在使用單點登入時(SSO),這種情況很可能出現問題。
使用動態轉發則能夠解決這個問題,動態轉發的實現是SSH通過在本地建立Socks代理,然後通過SSH轉發到遠端主機,然後遠端主機再將SSH內的封包轉發至內網主機。
在如上的拓撲環境中,OutSide會首先監聽本地埠用作Socks代理,對於傳輸到Socks代理的資料,將會經過SSH的加密傳輸到FW,然後FW再次轉發至內部Web伺服器。
我們在OutSide主機上進行操作。
[root@Outside ~]# ssh -f -N -D 1080 192.168.244.129
[email protected]'s password:
[root@Outside ~]# netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:1080 0.0.0.0:* LISTEN
tcp6 0 0 :::22 :::* LISTEN
tcp6 0 0 ::1:1080 :::* LISTEN
一般Socks代理都會監聽在 TCP/1080 上,不過在上述命令列命令中,你可以任意指定,但是使用的埠要和設定的相匹配。
我們在輸入正確的遠端主機SSH密碼後,則能夠建立SSH隧道。然後我們使用Curl的命令列工具進行測試。
[root@Outside ~]# curl -I --socks5 127.0.0.1:1080 192.168.222.128
HTTP/1.1 403 Forbidden
Date: Sat, 26 Nov 2022 00:08:51 GMT
Server: Apache/2.4.37 (centos)
Content-Location: index.html.zh-CN
Vary: negotiate,accept-language
TCN: choice
Last-Modified: Fri, 14 Jun 2019 03:37:43 GMT
ETag: "fa6-58b405e7d6fc0;5ee25c70d2749"
Accept-Ranges: bytes
Content-Length: 4006
Content-Type: text/html; charset=UTF-8
Content-Language: zh-cn
可以看到,我們雖然指定的是內網地址,但仍能通過Socks代理成功存取到了Web伺服器。
接下來我們利用WireShark檢視實際的情況。首先我們來分析建立隧道時候的封包。
這裡與SSH開啟Shell所建立的過程類似。接下來看看通過Socks代理轉發HTTP請求的情況。
從上圖可以得知,由於Socks地址監聽在本地並不會在虛擬網路卡上產生資料,我們只能抓到通過隧道傳輸過的HTTP請求資料。那麼我們如何抓取本地網路卡上的Socks包呢?
我們通過tcpdump工具監聽資料。
[root@Outside ~]# tcpdump -i lo dst port 1080 -vv > test.pcap
我們將 test.pcap 檔案傳入主機,用WireShark開啟分析。我們可以看到,這裡顯示出的內容證明了SSH動態轉發是通過Socks實現的,而且封包為明文。
接下來關注 FW 到 Inside 伺服器的傳輸情況。
可以看到,FW承擔了一個代理的作用,在內網與外網間轉發流量,而且僅僅使用了SSH所需要的埠。
只要應用程式能夠通過Socks代理,動態轉發理論上可以轉發所有的流量,就不用設定轉發目的埠(本地)這一麻煩的步驟了。