VPN中劫持HTTP流量

背景

最近有个需求,隐去背景,可以抽象成用户拨VPN后流量全都经过VPN中转,希望在这台VPN Server上劫持HTTP(HTTPS)流量以进行分析。拆分一下:

  • HTTP劫持 charles, fiddler 等软件都可以完成,基本原理其实就是一个HTTP Proxy,劫持并转发中间流量,类似于MITM(Man in the Middle)中的那个中间人;
  • 但是这类HTTP Proxy都是正向代理软件,需要用户手动配置浏览器、系统代理等方式才可以,简单来说就是无法做到无感。用户不会也不现实拨VPN之后手动配置HTTP代理使流量都经过你的HTTP嗅探器;
  • 这就自然的让我们想到了透明代理,从shadowsocks-libev中学到的经验,iptables+redsocks方案。

VPN选型及配置

常见移动端的内置VPN包括PPTP、IPSEC、L2TP等VPN,其中由于iOS 10开始已经不支持PPTP VPN了,故第一轮筛去。我的第一反应是使用IPSec VPN,因为之前已经研究过StrongSwan这个现成的Linux IPSec VPN解决方案。所以在当我测试的时候却意外的发现,iptables+redsocks的方案并不支持IPSec VPN,因为当vpn client拨入后,系统并不会产生一个虚拟的vpn网卡,这也就意味着所有的vpn流量被发往应用层,由ipsec vpn的进程来处理,而这些流量是未解密的,也就是说iptables并不能区分和捕获加密的IPSec VPN的流量。

故,IPSec VPN对此方案并不支持。最终选择l2tp/ipsec VPN。这里假设vpn网关是192.168.9.1/24,vpn网段是192.168.9.0/24。

Redsocks

Redsocks 可以将tcp连接重定向到后端的socks代理、http代理程序中,重要的是它支持使用iptables这样的fireware来作为redirector,这意味着redsocks可以全局性的、透明的重定向tcp链接,正符合我们的需求。

这里我们的配置如下:

1
2
3
4
5
6
7
8
9
10
11
redsocks {
local_ip = 0.0.0.0;
local_port = 12345;
ip = 127.0.0.1;
port = 8080;
// known types: socks4, socks5, http-connect, http-relay
type = http-relay;
}

这意味着:将去往本地12345端口的流量转发到后端127.0.0.1:8080上,类型是http-relay,而charles正运行在8080端口。需要注意的是:local_ip 需要配置为0.0.0.0而非127.0.0.1,这是因为如果配置成后者,那么vpn client过来的流量目的IP都是192.168.9.1,源IP都是192.168.9.0/24网段的,监听在127.0.0.1地址上的redsocks会拒绝这样的链接。我在测试中所有的TCP链接都被RST就是这个原因。

此外,类型需要选择http-relay,经测试http-connect不能正常中转。

iptables配置

卡在这一步最久。
我的第一反应是使用redsocks官方给出的配置

1
2
3
4
5
# Anything else should be redirected to port 12345
root# iptables -t nat -A REDSOCKS -p tcp -j REDIRECT --to-ports 12345
# Any tcp connection made by `luser' should be redirected.
root# iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner luser -j REDSOCKS

nat表的output的链做REDIRECT,然而发现这并没有生效,倒是vpn server本地产生的http流量确实有被重定向。

仔细看一下下图回顾一下iptables的NAT表的细节我们就能明白了。

iptables

对于去往本地的流量:经过的链如下 PREROUTING->INPUT->OUTPUT->POSTROUTING
其中INPUT和OUTPUT都处于用户空间,由本地进程来处理。所以对于本地产生的HTTP流量,在OUTPUT链进行REDIRECT就已经可以生效了。

但是对于非去往本地的流量:经过的链如下 PREROUTING->FORWARD->POSTROUTING
由于非去往本地,流量并不会到INPUT/OUTPUT链,故配置在OUTPUT链并没有生效。

所以对于VPN来的流量我们应当配置在PREROUTING,在进行路由选择前进行REDIRECTREDIRECT 其实是DNAT的一种,将流量转发到本地,也即指向本地某端口的DNAT。不过接下来的一段是猜测,并没有证实:

对于dnat或者pnat这样的需求,除了在PREROUTING链配置dnat之外,还需要在POSTROUTING配置SNAT,将nat的流量原地址改写为自己,这样回程流量才会经过nat网关。故配置dnat一般又要配置snat。但是redirect只需要配置一条,因为重定向到本地端口,故等于dnat到本地ip,snat的源ip也是自己。

所以最后的iptables配置如下:

1
2
sudo iptables -t nat -I PREROUTING -s 192.168.9.0/24 -p tcp --dport 80 -j REDIRECT --to-port 12345
sudo iptables -t nat -I PREROUTING -s 192.168.9.0/24 -p tcp --dport 443 -j REDIRECT --to-port 12345

此外当时还有一点疑惑,REDIRECT进行了目的地址转换,那么redsocks后端的http proxy收到流量之后,如何知道正确的转发流量给谁呢?于是推测也是: 后端的http proxy根据HTTP报文中的GET部分获取请求的URL。

HTTPS

后来发现按照这种方式配置,HTTPS流量无法转发,导致client上无法访问HTTPS网站。大概看了一下

http1.1引入了一个叫做 http tunnel的代理协议,也就是类型中的 connect协议,专门用来处理代理事宜。通过 connect 协议在 客户端和服务器中间按照一个代理人,一般用于ssl(https的一种实现),在发送https请求之前通过connect和代理人建立连接,然后代理人负责转发,但是代理人也不能知道传输的内容,因为是加密的。

大概意味着对于HTTPS,我们需要选择redsocks的类型是http-connect

修改一下配置如下:

redsocks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// FOR HTTPS
redsocks {
local_ip = 0.0.0.0;
local_port = 23456;
ip = 127.0.0.1;
port = 8080;
type = http-connect;
}
// FOR HTTP
redsocks {
local_ip = 0.0.0.0;
local_port = 12345;
ip = 127.0.0.1;
port = 8080;
// known types: socks4, socks5, http-connect, http-relay
type = http-relay;
}

iptables

1
2
sudo iptables -t nat -A PREROUTING -s 192.168.9.0/24 -p tcp --dport 443 -j REDIRECT --to-ports 23456
sudo iptables -t nat -A PREROUTING -s 192.168.9.0/24 -p tcp --dport 80 -j REDIRECT --to-ports 12345

这样就可以配置完成了,不过http-relay和http-connect这两种方式暂时还没有时间仔细研究,更新就到这里~

如果您觉得这篇文章对您有帮助,不妨支持我一下!