本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/11899478.html
通常,我们用于调试的计算机无法远程访问位于局域网中的待调试设备。通过 ssh 的端口转发(又称 ssh 隧道)技术,可以实现这种远程调试功能。
下文中,sshc 指 ssh 客户端,sshd 指 ssh 服务器。
1. ssh 端口转发模式简介ssh 客户端运行于本地机器,它的作用是:登录到目标机器并在目标机器上执行命令。它可以建立一个安全通道,为不安全网络上两个不受信任的主机提供安全的加密通信。X11 连接、任意 TCP 端口和 UNIX 域套接字也可以通过 ssh 安全通道进行转发。
ssh 连接并登录到指定的主机名(用户名可选)。如果指定了命令,命令将在远程主机上执行,而不是在本机 shell 里执行。
1.1 ssh 常用选项简介ssh 端口转发相关的常用选项如下:
-C
请求压缩所有数据(包括 stdin、stdout、stderr 和用于转发的 X11、TCP 和 UNIX 域连接的数据)。压缩算法与 gzip 使用的算法相同,压缩级别由 ssh 协议版本 1 的 CompressionLevel 选项控制。在调制解调器线路和其他慢速连接上采用压缩是可取的,但它会减慢快速网络上的速度。
-f
请求 ssh 在执行命令之前转到后台。如果用户希望 ssh 在后台运行,但 ssh 需要用户提供密码或口令,使用 -f 选项就很有用,在用户输入密码之后,ssh 就会转入后台运行。这个选项隐含了 -n 选项的功能(-n 选项将 stdin 重定向到 /dev/null,从而避免后台进程读 stdin)。在远程站点上启动 X11 程序的推荐方法是使用 "ssh -f host xterm" 。
如果 ExitOnForwardFailure 配置选项设置的是 "yes",则使用 -f 选项启动的 ssh 客户端会等所有的远程端口转发建立成功后才将自己转到后台运行。
-n
将 stdin 重定向到 /dev/null (实际上是为了防止后台进程从stdin读取数据)。当 ssh 在后台运行时必须使用此选项。
一个常见的技巧是使用它在目标机器上运行 X11 程序。例如,ssh -n shadow.cs.hut.fi emacs &
将在 shadows.cs.hut.fi 上启动 emacs 程序。X11 的连接将通过加密通道自动转发。ssh 程序将在后台运行。(如果 ssh 需要请求密码或口令,则此 *** 作无效;参见-f选项。)
-N
不执行远程命令。此选项用于只需要端口转发功能时。
-g
允许远程主机连接到本地转发端口。如果用于多路复用连接,则必须在主进程上指定此选项。
-t
强制分配一个伪终端。在目标机上执行任意的基于屏幕的程序时(例如,实现菜单服务),分配伪终端很有用。使用多个 -t 选项则会强制分配终端,即使 ssh 没有本地终端。
-T
禁止分配伪终端。
-L [bind_address:]port:host:hostport
-L [bind_address:]port:remote_socket
-L local_socket:host:hostport
-L local_socket:remote_socket
数据从本机转发到远程。本机上指定 TCP 端口或 UNIX 套接字的连接将被转发到目标机上指定端口或套接字。
上述参数中,bind_address 指本地地址;port 指本地端口;local_socket 指本地 UNIX 套接字;host 指远程主机地址;hostport 指远程端口;remote_socket 指远程 UNIX 套接字。
本地(ssh 客户端)与远程(ssh 服务端)建立一条连接,此连接的形式有四种:
本地 [bind_address:]port <====> 远程 host:hostport 本地 [bind_address:]port <====> 远程 remote_socket 本地 local_socket <====> 远程 host:hostport 本地 local_socket <====> 远程 remote_socket
位于本机的 ssh 客户端会分配一个套接字来监听本地 TCP 端口(port),此套接字可绑定本机地址(bind_address,可选,本机不同网卡具有不同的 IP 地址)或本地 UNIX 套接字(local_socket)。每当一个连接建立于本地端口或本地套接字时,此连接就会通过安全通道进行转发。
也可在配置文件中设置端口转发功能。只有超级用户可以转发特权端口。
默认情况下,本地端口是根据 GatewayPorts 设置选项绑定的。但是,使用显式的bind_address 可将连接绑定到指定地址。bind_address 值是 “localhost”时,表示仅监听本机内部数据[Todo: 待验证],值为空或“*”时,表示监听本机所有网卡的监听端口。
注意:localhost 是个域名,不是地址,它可以被配置为任意的 IP 地址,不过通常情况下都指向 127.0.0.1(ipv4)和 ::1。127.0.0.1 这个地址通常分配给 loopback 接口。loopback 是一个特殊的网络接口(可理解成虚拟网卡),用于本机中各个应用之间的网络交互。
GatewayPorts 说明 (查阅 man sshd_config
):指定是否允许远程主机(ssh客户端)连接到本机(ssh服务端)转发端口。默认情况下,sshd(8)将远程端口转发绑定到环回地址,这将阻止其他远程主机连接到本机转发端口。GatewayPorts 也可设置为将将远程端口转发绑定到非环回地址,从而允许其他远程主机连接到本机。GatewayPorts 值“no”,表示强制远程端口转发仅对本机可用;值“yes”,表示强制远程端口转发绑定到通配符地址;值“clIEntspecifIEd”,表示允许客户端选择转发绑定到的地址。默认是“no”。
-R [bind_address:]port:host:hostport
-R [bind_address:]port:local_socket
-R remote_socket:host:hostport
-R remote_socket:local_socket
此选项在本地机上执行,目标机上指定 TCP 端口或 UNIX 套接字的连接将被转发到本机上指定端口或套接字。
上述参数中,bind_address 指远程地址;port 指远程端口;remote_socket 指远程 UNIX 套接字;host 指本地地址;hostport 指本地端口;local_socket 指本地 UNIX 套接字。
工作原理:位于远程的 ssh 服务端会分配一个套接字来监听 TCP 端口或 UNIX 套接字。当目标机(服务端)上有新的连接建立时,此连接会通过安全通道进行转发,本地机执行当前命令的进程收到此转发的连接后,会在本机内部新建一条 ssh 连接,连接到当前选项中指定的端口或套接字。参 2.3 节分析。
也可在配置文件中设置端口转发功能。只有超级用户可以转发特权端口。
默认情况下,目标机(服务端)上的 TCP 监听套接字只绑定回环接口。也可将目标机上的监听套接字绑定指定的 bind_address 地址。bind_address 值为空或 “*” 时,表示目标机上的监听套接字会监听目标机上的所有网络接口。仅当目标机上 GatewayPorts 设置选项使能时,通过此选项为目标机指定 bind_address 才能绑定成功(参考 sshd_config(5))。
如果 port 参数是 ‘0’,目标机(服务端)可在运行时动态分配监听端口并通知本地机(客户端),如果同时指定了 “-O forward” 选项,则动态分配的监听端口会被打印在标准输出上。
-D [bind_address:]port
指定本地“动态”应用程序级端口转发。它的工作方式是分配一个套接字来监听本地端口(可选绑定指定的 bind_address)。每当连接到此端口时,连接都通过安全通道进行转发,然后使用应用程序协议确定将远程计算机连接到何处。目前支持 SOCKS4 和 SOCKS5 协议,ssh 将充当 SOCKS 服务器。只有 root 用户可以转发特权端口。还可以在配置文件中指定动态端口转发。
IPv6 地址可以通过将地址括在方括号中来指定。只有超级用户可以转发特权端口。默认情况下,本地端口是根据 GatewayPorts 设置选项进行绑定的。但是,可以使用显式的 bind_address 将连接绑定到特定的地址。bind_address 值为 “localhost” 时表示监听端口仅绑定为本地使用,而空地址或 “*” 表示监听所有网络接口的此端口。
1.2 ssh 端口转发模式ssh 的端口转发有三种模式:
本地:ssh -C -f -N -g -L local_Listen_port:remote_host:remote_port agent_user@agent_host
将本地机监听端口 local_Listen_port 上的数据转发到远程端口 remote_host:remote_port
远程:ssh -C -f -N -g -R agent_Listen_port:local_host:local_port agent_user@agent_host
将代理机监听端口 agent_Listen_port 上的数据转发到本地端口 local_host:local_port
动态:ssh -C -f -N -g -D Listen_port agent_user@agent_host
2. 利用 ssh 隧道建立远程调试环境组网环境下设备角色如下:
代理机:把一个具有公网 IP 的中间服务器用作 ssh 代理,将这台代理机称作代理 A(Agent)。
目标机:把待调试的目标机器称作目标机 T(Target)。目标机通常是待调试的设备,处于局域网内,外网无法直接访问内网中的设备。
本地机:把调试用的本地计算机称作本地机 L(Local)。本地机通常也位于局域网内。
L 和 T 无法互相访问,但 L 和 T 都能访问 A。我们将 T 通过 ssh 连接到A,将 L 也通过 ssh 连接到A,A 用于转发数据,这样就能使用本地计算机 L 来访问远端设备 R。
2.1 目标机 T (sshc)2.1.1 shell 中 T 连接 A目标机 T 上的 sshc 连接代理机 A 上的 sshd:
ssh -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
这条命令的作用:
1. 建立一条 ssh 连接,T 上的 ssh 客户端连接到 A 上的 ssh 服务器,A 的 IP 是 120.198.45.126,端口号是 10022,账号是10022;
2. 如果有其他 ssh 客户端连接到了 A 的 10022 端口上,则 A 会将这条连接转发到 T,T 在内部建立新的连接,连接到本机 22 端口。
这条命令在 T 上执行。在 T 连接 A 这条命令里,T 是本地主机(local),A 是远程主机(remote)。
解释一下此命令各选项:
-T 不分配伪终端;-f 使 ssh 进程在用户输入密码之后转入后台运行;-N 不执行远程指令,即远程主机(代理机A)不需执行指令,只作端口转发;-g 允许远程主机(代理机A)连接到本地转发端口;-R 将远程主机(代理机A)指定端口上的连接转发到本机端口;frank@120.198.45.126表示使用远程主机 120.198.45.126 上的用户 frank 来连接远程主机;:10022:127.0.0.1:22
表示本机回环接口(127.0.0.1,也可使用本机其他网络接口的地址,比如以太网 IP 或 WiFi IP)的 22 端口连接到远程主机的 10022 接口,因远程主机 10022 绑定的地址为空,所以远程主机会监听其所有网络接口的 10022 端口。
在目标机 shell 中查看连接是否建立:
root@localhost:~# ps | grep ssh22850 root 2492 S ssh -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.12622894 root 3500 S grep ssh
在目标机 shell 中关闭 ssh 连接:
kill -9 $(pIDof ssh)
此时在目标机 T 和代理机 A 中查看 ssh 连接信息,两端都可以看到 ssh 连接不存在了。
2.1.2 C 代码中 T 连接 A 的处理C 代码中主要还是调用 2.1.1 节中的命令。但是由 C 代码编译生成的进程无法在命令行和用户进行交互,因此要避免交互问题。
1. 避免首次连接时的 y/n(或yes/no) 询问
如果是首次登录代理机 A,本机(目标机 T)没有 A 的信息,需用用户手动输入 y 之后才能继续。打印如下:
root@localhost:~# ssh -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126Host '120.198.45.126' is not in the trusted hosts file.(ssh-rsa fingerprint md5 86:09:0c:1b:fd:0b:02:8c:29:62:7f:ff:70:1b:64:f5)Do you want to continue connecting? (y/n)
如果 T 上有 A 的信息,可通过执行删除 *** 作:rm ~/.ssh/kNown_hosts
再进行上述测试。
如果是在 C 代码中执行登录命令,进程在后台自动运行,是无法和用户进行交互的。为了避免交互动作,应该禁止 ssh 发出 y/n 的询问。
如果 ssh 客户端是 dropbear ssh,则添加 -y 参数,如下:
ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
如果 ssh 客户端是 openssh,则添加 -o StrictHostKeyChecking=no
选项,如下:
ssh -o "StrictHostKeyChecking no" -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
2. 避免输入登录密码
避免由用户手动输入登录密码有如下方法:
1) 用 ssh-copy-ID 把本地主机的公钥复制到远程主机的authorized_keys文件上,登录不需要输入密码。
2) 用 expect 调用 shell 脚本,向 shell 脚本发送密码。这种方式是模拟键盘输入。
3) 如果是 openssh,则用 sshpass 向 ssh 命令行传递密码。如果是 dropbear,则通过 DROPBEAR_PASSWORD 环境变量向 ssh 命令行传递密码。
我们采用第 3 种方法。
假如代理机 A 上用户 frank 密码是 123456,则最终 C 代码里应执行的指令如下:
# opensshsshpass -p '123456' ssh -o "StrictHostKeyChecking no" -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126# dropbearDROPBEAR_PASSWORD='123456' ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
dropbear 无法接收 DROPBEAR_PASSWORD 变量传递密码的处理方法:
dropbear 包含 ssh 客户端和 ssh 服务器,体积小巧,常用于嵌入式设备。dropbear ssh 无法接收 sshpass 传入的密码信息。但 dropbear ssh 可以通过环境变量 DROPBEAR_PASSWORD 传入密码信息。openwrt 从某一版开始,通过打补丁的方式禁用了 DROPBEAR_PASSWORD 选项,我们可以找到对应的补丁,开启 DROPBEAR_PASSWORD 选项,再重新编译生成 dropbear。如下:
修改 dropbear patch 文件(如下路径位于 openwrt 源码根目录):
vim package/network/services/dropbear/patches/120-openwrt_options.patch
将如下几行删除:
@@ -226,7 +226,7 @@ much traffic. */ * note that it will be provIDed for all "hIDden" clIEnt-interactive * style prompts - if you want something more sophisticated,use * SSH_ASKPASS instead. Comment out this var to remove this functionality.*/-#define DROPBEAR_PASSWORD_ENV "DROPBEAR_PASSWORD"+/*#define DROPBEAR_PASSWORD_ENV "DROPBEAR_PASSWORD"*/ /* define this (as well as ENABLE_Cli_PASSWORD_AUTH) to allow the use of * a helper program for the ssh clIEnt. The helper program should be
重新编译生成 dropbear,并替换设备里已安装的 dropbear。
#define DEFAulT_SSH_AGENT_HOST "120.198.45.126"#define DEFAulT_SSH_AGENT_PORT "10022"#define DEFAulT_SSH_AGENT_USER "ssha_deBUG"#define DEFAulT_SSH_AGENT_PASSWD "220011ssha"int login_to_ssh_agent(const char *host,const char *port,const char *user,const char *passwd){ // openssh clIEnt: // sshpass -p '123456' ssh -o "StrictHostKeyChecking no" -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126 // dropbear ssh clent: // DROPBEAR_PASSWORD='123456' ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126 char cmd[256]; snprintf(cmd,sizeof(cmd),"DROPBEAR_PASSWORD='%s' ssh -y -T -f -N -g -R :%s:127.0.0.1:22 %s@%s",(passwd != NulL) ? passwd : DEFAulT_SSH_AGENT_PASSWD,(port != NulL) ? port : DEFAulT_SSH_AGENT_PORT,(user != NulL) ? user : DEFAulT_SSH_AGENT_USER,(host != NulL) ? host : DEFAulT_SSH_AGENT_HOST); printf("login to ssh agent: \n%s\n",cmd); system(cmd); return 0;}
2.2 代理机 A (sshd)在 /etc/ssh/sshd_config 中添加如下几行后重启 ssh 服务:
GatewayPorts yesUsednS noGSSAPIAuthentication no
目标机 T 发起连接后,在代理机 A 上查询目标机 T 是否连接成功:
sudo netstat -anp | grep 10022
打印形如:
tcp 0 0 0.0.0.0:10022 0.0.0.0:* ListEN 8264/sshd: franktcp6 0 0 :::10022 :::* ListEN 8264/sshd: frank
上述打印中,8264 就是和目标机 T 保持连接的 sshd 进程号,如需关闭当前连接重新建立一个新的连接,则先在代理机 A 上执行:
kill -9 8264
然后再执行 2.1 节的指令,就会建立一次新的代理连接。
为了安全,我们可以专门新建一个用户,仅用于 ssh 端口转发功能,不能在 shell 中使用此用户登录。如下创建一个 ssha_deBUG 的用户,无 shell 登录权限。然后为此用户创建密码。注意系统中 nologin 文件的位置,不同系统可能路径不同。
sudo useradd ssha_deBUG -M -s /usr/sbin/nologinsudo passwd ssha_deBUG
2.3 本地机 L (sshc)2.3.1 本地机 L 登录目标机 T有三种方式:
1. 在本地机 L 上通过 ssh 登录代理机 A,在 A 的 shell 中再登录目标机 T
代理服务器的公网 ip 是 120.198.45.126,内网 ip 是 192.168.1.102。
1) 先使用 ssh(SecureCRT 或 OpenSSH 命令行) 登录上代理服务器的 shell。如果调试机在内网,既可登录代理机的外网 ip,也可登录其内网 ip。
2) 在代理机的 shell 中执行如下命令登录远程设备:
ssh -p 10022 root@127.0.0.1 -vvv
注意,此命令中用户 root 及其密码是远程设备上的账户。
如果提示 Host key 认证失败之类的信息,请按提示执行如下命令:
ssh-keygen -f "/home/frank/.ssh/kNown_hosts" -R [127.0.0.1]:10022
也可直接删除当前用户目录下的 .ssh/kNown_hosts 文件。
然后重新执行登录设备 *** 作。
建议优先使用此方法。
2. 在本地机 L 上使用 ssh 命令登录目标机 T
Win 10 系统默认安装有 OpenSSH 客户端。可以在调试机 windows 命令行中执行:
ssh -p 10022 root@120.198.45.126 -vvv
对于本地计算机来说,待调试的设备 ip 地址不可见。本机登录到代理机 120.198.45.126 的转发端口 10022,通过代理机转发功能,本地机能成功登录到远程设备上。注意,此命令中用户 root 及其密码是设备上的账户,不是 SSH 代理服务器上的账户。
如果出现认证失败之类的信息。可删除 C:/Users/当前用户/.ssh/kNown_hosts 文件,然后再试。
3. 在本地机 L 上使用 SecureCRT 工具登录目标机 T
也可以直接使用 SecureCRT 软件,设置好代理机的 ip(120.198.45.126) 和端口号(10022),填上设备的登录用户和登录密码。
不建议使用此方法。因为连接过程太长或连接失败的话,无法看到错误提示信息。
2.3.2 查看代理机 A 打印信息在 L 执行登录 T 之前查看打印信息:
frank@SERVER:~$ sudo netstat -anp | grep 10022tcp 0 0 0.0.0.0:10022 0.0.0.0:* ListEN 106438/sshd: franktcp6 0 0 :::10022 :::* ListEN 106438/sshd: frank
在 L 执行登录 T 之后查看打印信息:
frank@SERVER:~$ sudo netstat -anp | grep 10022tcp 0 0 0.0.0.0:10022 0.0.0.0:* ListEN 106438/sshd: franktcp 0 0 192.168.1.102:10022 120.229.163.51:27027 ESTABliSHED 106438/sshd: franktcp6 0 0 :::10022 :::* ListEN 106438/sshd: frank
可以看到,上述第二行是 L 执行登录命令后新出现的打印信息。表示新建立了一条 L 到 A 的 ssh 连接。
L 端的外网地址 120.229.163.51:27027 连接到 A 上的 192.168.1.102:10022,L 通常位于局域网内、具有一个内网地址,120.229.163.51 可能是 L 连接的路由器的公网 IP。
这条连接建立后,A 将这条连接转发到 R。
2.3.3 查看目标机 T 打印信息在 L 执行登录 T 之前查看打印信息:
root@localhost:~# netstat -anp | grep 22tcp 0 0 0.0.0.0:22 0.0.0.0:* ListEN 917/sshdtcp 0 0 192.168.202.140:47989 120.198.45.126:22 ESTABliSHED 9452/sshtcp 0 0 192.168.202.140:22 192.168.202.100:64737 ESTABliSHED 2041/sshdtcp 0 0 :::22 :::* ListEN 917/sshd
在 L 执行登录 T 之后查看打印信息:
root@localhost:~# netstat -anp | grep 22tcp 0 0 0.0.0.0:22 0.0.0.0:* ListEN 917/sshdtcp 0 0 192.168.202.140:47989 120.198.45.126:22 ESTABliSHED 9452/sshtcp 0 0 192.168.202.140:51732 192.168.202.140:22 ESTABliSHED 9452/sshtcp 0 0 192.168.202.140:22 192.168.202.140:51732 ESTABliSHED 9579/sshdtcp 0 0 :::22 :::* ListEN 917/sshd
可以看到,上述第 3 行和第 4 行是登录之后新增加的打印信息。
第 2 行,表示 T 上的 ssh 客户端连接到了 A 上的 ssh 服务端,进程号是 9452。第 3 行,表示进程 9452 收到了 A 转发来的 ssh 连接后,在本机内部建立新的 ssh 连接,使用 51732 端口号作为 ssh 客户端,连接到本机 22 端口,22 端口是 sshd 端口。第 4 行,表示本机新启动一个 sshd 进程,来接收 sshc 的连接。
这样,L 到 T 的 ssh 通路彻底打通了。A 将来自 L 的连接转发到 R,R 在内部启动了 sshd 来处理来自 L 的请求,通过 A 的代理作用,实现了 L 上的 sshc 和 T 上的 sshd 的交互。
3. 典型使用场景步骤总结上文已涵盖详细使用方法,但篇幅太长。此处简单总结使用步骤如下:
3.1 在代理机 A 上执行使用 SecureCRT 登录代理机 A。代理机外网 ip 120.198.45.126,内网 ip 192.168.1.102,端口 22。如果本地机与代理机在同一个局域网里,使用代理机的内网 ip 登录即可。
在代理机 shell 中查看是否有未关闭的 ssh 隧道:
sudo netstat -anp | grep 10022
若打印形如:
tcp 0 0 0.0.0.0:10022 0.0.0.0:* ListEN 8264/sshd: franktcp6 0 0 :::10022 :::* ListEN 8264/sshd: frank
则表示有未关闭的 ssh 隧道连接。执行如下命令可关闭连接。
kill -9 8264
3.2 在目标机 T 上执行使用远程应用程序接口或者在远程设备 T 上做一些特殊 *** 作,触发 T 执行如下两条指令之一:
# opensshsshpass -p '123456' ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126# dropbearDROPBEAR_PASSWORD='123456' ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
3.3 在本地机 L 上执行在本地机 L 上执行如下指令,登录远程目标机 T:
ssh -vvv -p 10022 root@120.198.45.126
另外一种变通的方式是,在本地机先通过 ssh 登录上代理机 A 的 shell。然后在 A 的 shell 中执行如下指令:
ssh -vvv -p 10022 root@127.0.0.1
4. 注意事项1. 确保代理机 A 所在的网络防火墙不屏蔽 10022 端口
2. 确保代理机 A 上 /etc/ssh/sshd_config 配置文件设置正确
3. 关闭 ssh 隧道既可在代理机 A 上进行(关闭相应的 sshd 进程),也可在目标机 T 上进行(关闭相应的 ssh 进程)
4. 每次只能访问一台目标机。如果想同时访问多台,可以代理机上设置多个转发端口,每条连接使用一个端口进行转发
5. 为保证安全,打开 ssh 隧道时尽量使用无登录权限的用户,并且此用户的密码建议经常更新
[1] 阮一峰,SSH原理与运用(二):远程 *** 作与端口转发
[2] SSH 代理设置
[3] ssh命令和SSH服务详解
2019-11-20 V1.0 初稿
总结以上是内存溢出为你收集整理的利用SSH隧道技术穿越内网访问远程设备全部内容,希望文章能够帮你解决利用SSH隧道技术穿越内网访问远程设备所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)