标签归档:ssh

SSH反向代理内网穿透

SSH可以用来做正向Socks代理外,还可以用来做反向代理。典型的应用场景是:让内网的机器对外网可见,常见手段是在路由器上做端口映射,但是由于路由器的IP地址会变化(除非你有固定不变的IP),所以还需要在内网或路由器上运行一个动态DNS的工具(这种工具实际就是往外网发送IP地址),然后通过域名获取到最新的IP,而SSH做反向代理就不需要这些步骤,当然,需要外网有一台机器作为接入点。

简单来说,就是内网机器和外网的机器建立一条TCP隧道,外网预期分配的端口通过SSHD服务转发到内网来,内网的数据出去也一样。TCP隧道,头尾都对应端口号,比如把外网的2222端口,通过TCP隧道跟内网的22端口接起来。

ssh -CNR 2222:127.0.0.1:22 -b 0.0.0.0 www@外网IP

然后输入外网IP的www用户的密码就可以建立一条TCP隧道。可以看到,这个时候外网IP的2222端口开放了,它是由SSHD服务开启的。这里的-b参数说明希望外网IP的监听地址是0.0.0.0,就是0.0.0.0:2222,不过可能看到的是127.0.0.1:2222,这个不是我们所期望的。为了让反向代理真正可靠的使用,还需要解决如下几个问题:

0 自动登录问题
这个问题最常见的是放置公钥,内网ssh通过-i来指定私钥文件。如果希望使用密码来登录,那么需要借助sshpass这个包装器,之所以需要它,那是因为ssh不支持通过参数指定密码:

yum install sshpass

#格式
sshpass -p "密码" ssh -CNR 3080:127.0.0.1:80 www@xx.xx.xx.xx

1 在外网监听任意地址
首先,内网ssh命令需要使用-b指定监听地址为0.0.0.0,然后外网的sshd服务的配置,需要修改GatewayPorts为yes:

vi /etc/ssh/sshd_config
GatewayPorts yes

可能还需要重启一下sshd服务。

2 TCP隧道长时保持连接
在内网上运行ssh命令时,加上-o TCPKeepAlive=yes来让TCP保持长时连接。

3 防止TCP被中断
发送大量的数据或长时没有数据发送时,都可能导致TCP连接被卡住或被中间的路由器杀掉。所以需要建立一个心跳检查,在内网运行ssh时添加-o ServerAliveInterval=10 -o ServerAliveCountMax=6来维持心跳,这里的意思是每10秒检测一次,如果连续6次都无法获取响应,那么进程将退出。

4 防止进程异常退出
通过设置TCP长链接和定时发送心跳数据来保存通道畅通,但是从实际使用来看,这些还是远远不够的。进程异常退出监控起来很简单,但是实际遇到过这种情况:通道实际已经无法通信,但是本地进程并未退出,这个情况可能是远程进程异常退出,但是本地进程并没有收到通知,或者中间路由的问题,所以为了解决这个问题,我们可以定时发送数据,根据响应来判断是否要重启本地进程,于是有了以下脚本:

#!/bin/bash

cmm="/usr/bin/kill"
if [ ! -f "$cmm" ]; then
    cmm="/bin/kill"
fi

#不等于空说明通道通畅
ping=`curl -s http://x.x.x.x:2222`
if [ "$ping" = "" ]; then
	ps aux | grep 'x.x.x.x' | grep -v grep | awk '{print $2"|"$6}' | cat | while read line
	do
    		pid=`echo $line | cut -d "|" -f 1`
    		rss=`echo $line | cut -d "|" -f 2`

        	kill=`$cmm -9 $pid`
	        date=`date "+%Y-%m-%d %H:%M:%S"`

	        echo $date' -- 'PID:$pid' - Was Killed.'
	done
fi

#反代
live=`ps -efH | grep 'ssh -CN -R 2222:127.0.0.1:22' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    ssh -CN -R 2222:127.0.0.1:22 -b 0.0.0.0 -o TCPKeepAlive=yes -o ServerAliveInterval=10 -o ServerAliveCountMax=6 sshproxy@x.x.x.x -p 22 > /dev/null 2>&1 &
fi

live=`ps -efH | grep 'ssh -CN -R 8000:127.0.0.1:8000' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    ssh -CN -R 8000:127.0.0.1:8000 -b 0.0.0.0 -o TCPKeepAlive=yes -o ServerAliveInterval=10 -o ServerAliveCountMax=6 sshproxy@x.x.x.x -p 22 > /dev/null 2>&1 &
fi

使用curl来发送数据,如果通道畅通就会有响应,否则就无响应,无响应则强制杀掉本地进程。紧接着重启进程。

这个方法虽然很粗暴,但是很实用。从运行来看,还是相当稳定的(实际我仅仅是把内网的一个服务映射出去,偶尔用一用,但是用的时候要保证能用,比如内网的Gitlab)。

Sockets代理 之 SSH Sockets

使用SSH来开启Socks代理:

ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22
root@x.x.x.x's password:

这样就可以启动一个Socks,C表示压缩数据传输,N表示不进入Bash。(比如浏览器)设置Socket代理IP为127.0.0.1端口为7070,然后就可以通过x.x.x.x(x.x.x.x可以是本机地址)代理上网了。

SSH可以通过-i指定一个私钥来进行认证(这样就不需要输入密码),这种方式意味在需要在服务器端对应的用户里放置一个公钥:

root@xxx:~/.ssh ls
authorized_keys  known_hosts
root@xxx:~/.ssh# pwd
/root/.ssh
root@xxx:~/.ssh:~/.ssh# ls
authorized_keys  known_hosts
root@xxx:~/.ssh:~/.ssh# cat authorized_keys 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCiaMoJRe..........

公钥放进去后,在客户端就需要指定其对应的私钥:

ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22 -i /roo/is_rsa

这样就不需要输入密码了。Linux中SSH不支持直接输入密码,但是可以使用一个包装工具来完成(sshpass):

yum install sshpass

#运行sshpass
sshpass -p "passwd" ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22 

#查看进程可见:sshpass就是一个包装器
sshpass -p zzzzzzzzzz ssh -CN -D 127.0.0.1:7070 root@127.0.0.1 -p 22
ssh -CN -D 127.0.0.1:7070 root@127.0.0.1 -p 22

注:这里是监听本机的127.0.0.1的7070端口,本机通过SSH把数据传输到远程服务器,把远程服务器设置在本机也是可以的,这样本机和远程都一样的IP。

为了防止进程异常退出,可以定时运行一个SHELL脚本:

#1 使用sshpass包装器

#!/bin/bash

live=`ps -efH | grep 'sshpass' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    sshpass -p "passwd" ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22 2>&1 >> /dev/null
fi


#2 不使用sshpass包装器时,需要在远程服务器上设置放置好公钥
#!/bin/bash

live=`ps -efH | grep 'ssh -CN -D' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22 2>&1 >> /dev/null
fi

本地和远程之间传输数据实际是经过加密的(走SSH),就算本机和远程是同一条机器时也是如此。本地和客户端建立的TCP是一条持久链接:

netstat -ntp

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name   
tcp        0      0 127.0.0.1:22            127.0.0.1:39426         ESTABLISHED 20575/sshd: root                    
tcp        0      0 127.0.0.1:39426         127.0.0.1:22            ESTABLISHED 20574/ssh  

客户端ssh随机打开了一个端口(39436)和远程22端口进行链接。所有从7070过来的数据,都会转发到39436端口,由它把数据发送到服务端。不过这个本地转发是透明的。到此,我们有两个结论:
一、所有的数据都是通过一条持久链接发送到远程的
二、由SSH开放的端口(这里是7070),需要HOLD住所有的链接,等到服务端返回后,再转发回去。
所以,SSH开启的代理,不适用于高并发场景。对于高并发的场景,代理需要HOLD住大量链接,然后在和服务端链接时应该一一对应,或者开一个池。但是对于一般的代理上网,是够用的。

在Windows下也有对应的工具,下载地址:http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html。这里有一个叫Plink的工具,它类似sshpass的功能:

plink.exe -C -N -D 127.0.0.1:7070 root@x.x.x.x -pw password -P 22 -v

参数C表示启用压缩,N表示不进入终端,-pw来指定密码,-P指定端口(注意这里是大写的P), -v表示实时的输出日志。使用Plink这个工具(或者Putty)也可以通过-i来指定私钥。

监控脚本,在Windows下:

:1
bin\plink.exe -C -N -D 127.0.0.1:7070 root@x.x.x.x -pw password -P 22 -v
goto 1

保存为一个bat文件,需要代理时,双击它,然后弹出一个命令行窗口,由于加了-v参数,可以实时参考输出日志(证明它是工作的),不需要时直接关闭即可。

最后就是浏览器的代理配置了(以火狐为例子):
工具 – 选项
firefox-setting

firefox-agent

看起来,虽然实现了使用SSH Socks来上网,但是还是稍有麻烦:
1 不使用代理时,需要重新进入 工具 – 选项,找到网络,然后点击不使用代理
2 每次要上网代理时,需要运行一个命令行窗口

为了解决第一个麻烦问题,我们可以在firefox中安装一个叫autoproxy的插件(也有其它类似的),安装之后浏览器上会有一个按钮,点击切换是否使用代理:
autoproxy
可以配置多个代理,方便切换:
autoproxy-switch
Autoproxy这个插件看起来从2013年开始就再没有更新了。

至于第二个问题,我们需要找到一个客户端管理工具。目前在使用的工具:
1 http://nemesis2.qx.net/pages/MyEnTunnel (需要翻墙)
下载的是一个exe安装文件,一路安装下来:
myentunnel
其中lng是语言文件,ini是配置文件,可以发现plink.exe,所以这个工具是plink.exe包装器,让使用plink.exe使用更加方便一点而已。双击myentunnel.exe:
myentunnel-setting
这里的配置跟plink.exe的命令使用是对应的。可以设置多个配置,默认的是叫default的配置,可以点击右下角的红锁(或绿锁),添加配置,这个配置实际是对应命令目录下的ini文件。看来,配置无法删除,只能到文件夹里面删除对应的文件。

可以把这个文件夹拷贝下来。

2 另一个可用的工具是Bitvise Tunnelier,它不是plink.exe的包装器,可以互联网搜索看看。