APT404-不作恶

在路上,一直在路上!

OpenVpn 安全部署实战指南 [一]



0x01 关于openvpn

1
2
一种基于OpenSSL加密库的开源VPN实现,此处的主要目的还是旨在说明如何快速安全部署openvpn
关于其内部的加解密细节及封装与解封装过程,相信大家都已经比较熟练了,此处不做过多涉及,暂以实战上手应用为主

0x02 openvpn 可工作于两种不同的模式,使用 TUN / TAP 作为接口建立隧道,但需要相应内核支持

1
2
3
TUN 接口创建的是三层路由隧道,实际中用的较多,TAP 是二层网卡桥接隧道,即创建一个以太网桥接,相对复杂
TAP 接口的好处在于,客户端可以获得 VPN 服务器所处子网的 IP [即,忽略物理上的区别,可以完全将客户端看做是与VPN服务器处于同一子网的另一台机器]
而TUN 接口下所有的客户端则处于一个完全独立的子网内,与 VPN 服务器所在的子网没有关系,待到后面实际部署时,会深有体会的

0x03 下面则是 openvpn 提供的两种安全模式

1
2
Static Key
X509 PKI [Public Key Infrastructure]

此次演示环境

1
2
3
4
OpenVpnServer  	  ip: 192.168.3.71 [假设为公网ip]   ip: 192.168.6.39 [假设为其所在的内网ip] ip: 192.168.7.39 [假设为其所在的另一个内网ip]
OpenVpnClient ip: 192.168.6.41 [内网ip] ip: 192.168.7.41 [另一个内网ip段] 为OpenVpnServer所在内网段的一台linux机器
win7cn ip: 192.168.32.253 [本地机器的内网ip] 作为连接OpenVpnServer的windows访问客户端
CentOS6.8_x86_64 ip: 192.168.32.136 [本地机器的内网ip] 作为连接OpenVpnServer的linux访问客户端

我们要实现的最终目的

1
2
通过拨入 OpenVpnServer 可在本地直接访问远程内网,即`192.168.32.x`和`192.168.6.x`,本身是两个完全独立相互不同的内网段
但现在要实现的效果就是,可以直接在win7cn或者CentOS6.8上ping通OpenVpnServer所在内网中的OpenVpnClient机器

0x04 Ok,明确了最终目的之后,我们就开始来实战部署OpenVpn

首先,进行一些前期的必要环境准备,务必严格保证所有机器时间完全一致,此项非常重要,否则后期OpenVpn客户端在连服务端时会出现证书无法被验证的情况

1
2
3
4
# /usr/sbin/ntpdate time.nist.gov  先手工在所有机器上同步一次,之后再加到计划任务中自动同步即可
# crontab -e
*/2 * * * * /usr/sbin/ntpdate time.nist.gov > /dev/null 2>&1
# crontab -l

安装C编译器及openssl加密库,众所周知,openvpn依赖openssl对隧道数据进行加密

1
# yum install -y  gcc gcc-c++ openssl openssl-devel

禁用selinux,之后一定要记得重启下系统,才可生效

1
2
3
# setenforce 0
# sed -i '/^SELINUX=/c\SELINUX=disabled' /etc/selinux/config
# shutdown -r now

准备好环境所需的源码包,此,分别为数据压缩库和openvpn自身的源码包openvpn的下载地址可能已经被墙掉了,大家暂时挂ss下一下就好了

1
2
# wget http://www.oberhumer.com/opensource/lzo/download/lzo-2.06.tar.gz
# wget https://build.openvpn.net/downloads/releases/openvpn-2.2.2.tar.gz

编译安装LZO,主要用它来压缩通信数据加快传输速度

1
2
3
4
# tar xf lzo-2.06.tar.gz
# cd lzo-2.06
# ./configure && make && make install
# echo $?

0x05 编译安装OpenVPN,记得要载入lzo一起编译

1
2
3
4
5
6
# tar -zxf openvpn-2.2.2.tar.gz
# cd openvpn-2.2.2
# ./configure --with-lzo-headers=/usr/local/lzo/include --with-lzo-lib=/usr/local/lzo/lib
# make && make install
# echo $?
# which openvpn 如果能看到路径,说明openvpn已经基本安装成功,默认是在/usr/local/sbin/openvpn目录下

0x06 安装成功后,我们就开始来实际部署配置OpenVpn

首先,修改默认证书信息,而后,以此来创建各种证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# cd /root/openvpn-2.2.2/easy-rsa/2.0/ && ll
# cp vars vars.bak
# vi vars
export KEY_COUNTRY="CN"
export KEY_PROVINCE="BJ"
export KEY_CITY="BeiJing"
export KEY_ORG="klionsec"
export KEY_EMAIL="klion@rootkit.org"
export KEY_EMAIL=klion@rootkit.org
export KEY_CN=CN
export KEY_NAME=kliosec
export KEY_OU=klionsec
export PKCS11_MODULE_PATH=changeme
export PKCS11_PIN=1234
# tail -n 11 vars
# source vars 重新载入,让变量生效
# ./clean-all 把之前已有的证书全部先删掉
# ./build-ca 创建ca证书ca.crt 及根密钥ca.key [基本上一路按回车就好],因为在vars文件中已经定义好了默认的变量值,生成时会自动逐个读取
# ll keys/ 看下刚刚生成的ca证书在keys目录里有没有

创建服务端证书及密钥 [ 中间会有一处让你输入密码,注意,后续创建证书时的密码最好都跟此密码保持一致,防止认证失败,之后再按照提示输入两次y即可]

1
2
# ./build-key-server server
# ll keys/

创建完服务端证书和秘钥后,我们还需要为每一个登陆到OpenVpn服务器的客户端创建一个证书,注意直接用build-key创建的客户端证书默认是没密码的,也就是说,别人只要拿到你的这套客户端的证书就可以直接连上vpn服务器,有一定风险,当然,前提也得服务端配置中允许同一用户多点登陆才行,后续如果还要新增客户端用户,只需要再用build-key生成即可

1
2
# ./build-key client  注意,这里也会提示输入密码,为了避免出问题,和上面创建服务端证书时设置的密码保持一致即可
# ll keys/


1
2
# ./build-key-pass clientsec 如果你想创建带密码的客户端证书,直接用 build-key-pass 来创建即可
# ll keys/


创建秘钥交换协议文件

1
2
3
# ./build-dh    即迪菲·赫尔曼密钥,会生成dh1024.pem文件,生成过程可能会比较慢,在此期间不要去中断它
# ll keys/
# openvpn --genkey --secret keys/ta.key 生成ta.key文件,防DDos攻击,UDP淹没等恶意攻击,说实话,效果貌似并不太好

0x07 创建并修改openvpn服务器配置文件,将需要用到的openvpn证书和密钥复制一份到创建好的keys目录中

1
2
3
4
5
6
7
8
9
10
# mkdir -pv /etc/openvpn
# cd /root/openvpn-2.2.2/easy-rsa/2.0
# cp -ap keys/ /etc/openvpn/
# cd ../../
# cp sample-config-files/{server.conf,client.conf} /etc/openvpn/
# tree /etc/openvpn/
# cd /etc/openvpn/
# cp -a server.conf server.conf.bak
# grep '^[^#;]' /etc/openvpn/server.conf > tmp.txt
# cat tmp.txt > server.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 下面是openvpn服务端的完整配置
# vi server.conf
local 192.168.3.71 # 要绑定到的公网ip
port 11941 # openvpn服务的默认端口,最好改掉,其实,别人如果全端口扫描还是能扫到
proto tcp # 使用tcp协议,相对稳定
dev tun # 此处使用tun接口
ca /etc/openvpn/keys/ca.crt # ca证书路径
cert /etc/openvpn/keys/server.crt
key /etc/openvpn/keys/server.key # openvpn服务端证书路径
dh /etc/openvpn/keys/dh1024.pem
server 10.18.0.0 255.255.255.0 # openvpn内网ip地址池
push "route 192.168.6.0 255.255.255.0" # 此项非常重要,即vpn服务器所在的内网段
push "route 192.168.7.0 255.255.255.0" # 如果想让别人拨入成功后能直接跟vpn所在的内网段的机器进行通信,就需要用此方式把配置推到每个客户端
client-to-client # 让openvpn客户端和客户端能相互通信
duplicate-cn # 同一个vpn账号允许同时多点登陆,在上面已经提到过
ifconfig-pool-persist ipp.txt
keepalive 10 120 # 发心跳包,每10一次,如果超过120秒都没动静,就自动断开
comp-lzo # 启用传输数据压缩
persist-key
persist-tun
status openvpn-status.log # 开启OpenVpn服务端日志
log /var/log/openvpn.log # 指定日志存放位置
verb 3 # 指定记录的日志级别

0x08 修改完OpenVpn服务端配置后,我们再来稍微调整一些系统配置,如下

开启系统路由转发,因为在拨入成功后还要访问vpn服务器所在内网中的其它机器,所以转发是必须的

1
2
# sed -i '/net.ipv4.ip_forward/s/0/1/' /etc/sysctl.conf
# sysctl -p

如果开了iptables,记得把tcp的11941端口[实际中是你自己设置的openvpn服务端口]和转发都放开,不然客户端可能会连不上,如果压根没开,可暂时不用管

1
2
3
4
5
# vi /etc/sysconfig/iptables
-A INPUT -p tcp --dport 11941 -j ACCEPT
# iptables -A INPUT -p tcp --dport 11941 -j ACCEPT
# iptables -L -n
# /etc/init.d/iptables status

最后,我们来尝试启动OpenVpn服务

1
2
3
4
5
6
# /usr/local/sbin/openvpn --config /etc/openvpn/server.conf &
# netstat -tulnp | grep openvpn
# echo "/usr/local/sbin/openvpn --config /etc/openvpn/server.conf &" >> /etc/rc.local
# tail -n 1 /etc/rc.local
# ifconfig -a openvpn服务一旦成功启动系统中就会多出一个名为tun0的虚拟网卡
# pkill openvpn


当然,除了手工去加载服务端配置文件来启动,你也可以用openvpn自己提供好的服务启动脚本来配置成常规启动,只是自己不太喜欢这样搞,过程非常简单,如下

1
2
3
4
5
6
7
8
# cp /root/openvpn-2.2.2/sample-scripts/openvpn.init /etc/init.d/openvpn
# chmod 700 /etc/init.d/openvpn
# chkconfig openvpn on
# chkconfig --list
# /etc/init.d/openvpn restart 会有报错
# vi /etc/init.d/openvpn 把148行的*.conf改成server.conf,这个目录下不能有多个.conf否则就会被干扰到
# /etc/init.d/openvpn restart 正常启动
# lsof -i :1194


0x09 把服务端完全搞定以后,我们再来看如何在不同系统平台下配置OpenVpn客户端进行连接

先下载并安装好 OpenVpn 的windows版客户端工具,地址已被墙

1
http://swupdate.openvpn.org/community/releases/openvpn-2.2.2-install.exe

此时,再到OpenVpn服务端去把指定客户端的证书及配置文件都下载下来[此处以client用户为例],之后再把这些文件全部复制到你OpenVpn客户端的config目录中,此处的OpenVpn客户端config目录是在如下的路径上,而你则要根据你自己的OpenVpn客户端安装目录来调整

1
2
# cd /etc/openvpn/keys/
# sz ca.crt client.crt client.key

1
2
ca.crt client.crt client.key  	所有要复制的文件
C:\Program Files\OpenVPN\config 要复制到的目录



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 手工创建好客户端配置文件,文件具体内容如下,注意在windows中客户端配置文件名必须为 client.ovpn 

client # 声明是客户端
dev tun # 和服务端保持一致,使用tun接口
proto tcp # 同样使用tcp协议,保持一致,不然就没法和服务端进行正常通信了
remote 192.168.3.71 11941 # Openvpn 服务端的ip和端口
resolv-retry infinite
nobind
persist-key
persist-tun
ca ca.crt # 客户端证书所在路径
cert client.crt
key client.key
ns-cert-type server
comp-lzo # 压缩传输
verb 3

之后,再右键以管理员权限运行openvpn客户端,点击connect 进行连接即可,如下即表示连接成功,至此,整个OpenVpn的部署工作也就完成了百分之八十,但我们的最终目的是希望能在本地通过拨入vpn直接跟远程内网中的机器进行通信,再次明确目的之后,我们继续


0x10 以上是使用windows客户端来连接OpenVpn服务端,至于如何在linux中使用客户端连接OpenVpn服务端就更简单了,跟OpenVpn服务端一样,先装好OpenVpn,之后只需用openvpn工具加载对应的客户端配置文件即可,注意,是客户端配置文件,另外,此处不需要再配置服务端,单单只是用下openvpn工具而已,具体如下

1
2
3
4
5
6
7
8
9
10
11
# mkdir /etc/openvpn
# cd /etc/openvpn
# rz 可以直接把刚刚client的一套证书丢上去
# tree ./
├── ca.crt
├── client.conf
├── client.crt
└── client.key

# 断开以后,第二次重连可能会有些问题,把客户端配置文件改个名就好了
# /usr/local/sbin/openvpn --config /etc/openvpn/client.conf &

0x11 再回到我们的最终目的上,如何在本地通过vpn直接跟远程内网进行通信,方式大概有三种,如下

第一种,把所有要通信的远程内网中的机器的网关都指向OpenVpn服务器的ip,这样,包在回传的时候就能找到本地机器,为了防止重启即失效,可直接把命令放在rc.local

1
2
# route add default gw 192.168.7.39  在OpenVpnClient上添加如下指向
# route -n


第二种,走网段路由,在所有要通信的内网的机器上添加如下路由,把来自vpn内网段的数据都直接丢到OpenVpn服务器指定的内网ip上,一样也可以达到目的,但两种方式有个共同的确定,机器比较少的情况下也许还能凑活,如果有几千台机器,每台上都要添加一条这样的指向毕竟是很不现实的

1
2
# route add -net 10.18.0.0/24 gw 192.168.7.39  在OpenVpnClient上添加如下指向
# route -n


所以,我们再来看第三种,利用iptables进行NAT转换,即把源地址为10.18.0.0/24都转换到指定的内网卡ip上,这样就不用再逐个指定网关了

1
2
3
4
# /etc/init.d/iptables status  只需在OpenVpnServer服务器上添加如下规则即可
# /sbin/iptables -t nat -A POSTROUTING -s 10.18.0.0/24 -o eth2 -j SNAT --to-source 192.168.7.39 添加规则
# /sbin/iptables -t nat -D POSTROUTING -s 10.18.0.0/24 -o eth2 -j SNAT --to-source 192.168.7.39 删除规则
# /etc/init.d/iptables stop


0x12 实际生产环境中,由于各种各样的原因,有时可能还需要禁止指定的客户端用户登陆,方法非常简单,直接注销指定的客户端证书,万一要连续禁用多个vpn账号,那就直接一次性注销多个客户端证书,执行revoke-full后直接覆盖原来的crl.pem文件即可,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# cd /root/openvpn-2.2.2/easy-rsa/2.0
# source vars
# vi /root/openvpn-2.2.2/easy-rsa/2.0/openssl-1.0.0.cnf
# tail -n 6 /root/openvpn-2.2.2/easy-rsa/2.0/openssl-1.0.0.cnf 先注释如下行
#[ pkcs11_section ]
#engine_id = pkcs11
#dynamic_path = /usr/lib/engines/engine_pkcs11.so
#MODULE_PATH = $ENV::PKCS11_MODULE_PATH
#PIN = $ENV::PKCS11_PIN
#init = 0
# ./revoke-full client 指定要注销的客户端用户执行注销
# cat keys/crl.pem
# cat keys/index.txt 从该文件中可看到客户端用户的状态,R为已注销
# cp keys/crl.pem /etc/openvpn/keys/
# vi /etc/openvpn/server.conf 再到主配置文件中添加该pem文件位置,然后重启openvpn,该用户登陆即会失效
crl-verify /etc/openvpn/keys/crl.pem
# /etc/init.d/openvpn restart

下面是vpn账户被禁用后再登陆的效果,你会发现客户端的连接在一直被reset掉


后话:

1
2
3
4
vpn作为直插目标内部的一个最直接的入口,也颇受入侵者的热爱,尤其是对各种APT团队来讲更是如此,针对此所进行的各种形式的钓鱼也从未休止
其实,关于openvpn自身的漏洞并不多,甚至可以说,几乎没有,有也只是一些第三方库的,另外,基本也不存在什么错误配置能辅助我们渗透的
关于openvpn的内部通信过程,大家可自行拿wireshark&tcpdump仔细好好跟一下基本就看明白了,如果真想深入研究建议直接去阅读2.2的源码
在部署上基本无任何技术含量,只是为了方便大家用,顺便给自己做个备忘,保证后续随时能用即可