动手向——搭建安全、快速的家庭网络

设备

之前在淘宝 3000 块买了个 R720XD,体验不错。于是在这个机器上做了个虚拟路由,以单臂路由的形式接入路由器,提供科学的路由和 DNS 服务。

安全的局域网

一旦得到内网权限,对于普通的家庭网络,我们可以通过 ARP 劫持拿到流量并可以 MITM;对于部分 HTTPS 流量可以通过 sslstrip 等工具降级至 HTTP 继而拿到明文;利用 SNI 扩展和 DNS 流量也可以轻松地拿到用户的访问记录。如果内网部署的服务、或者路由器本身存在漏洞,则影响可以进一步扩大。

这里我们不考虑物理 Hack,那基本上需要重点关注的就是守好 WIFI 这道门。

无线网络的脆弱性

很久以前研究过 Wi-Fi 密码破解,那时候光驱还是电脑标配,刻了几张 Backtrack3 的 live cd,还蛮好使的,特别是对 WEP 加密的邻居们,这个加密简直不存在。如果是 WPA,拿到握手包就可以离线破解,只要资源足够跑出来也不是什么难事。所以如果想搭建安全的无线网络,WEP 和 WPA 都是不可靠的加密方式。

进了大学以后发现还有 EAP 这种认证方式,校园网或者是 eduroam 都是走这类认证。设计上有对认证服务器证书的验证,但是不像针对域名的证书一样,可以方便准确地确认域名所有权下发证书,SSID 是不存在绝对的拥有权的,所以这块的认证只能靠一些预共享的信息,比如连接时去人肉验证证书,或者是在连接时预先导入 CA 证书或信任 Server 证书。这种预共享信息的分发往往很难:在我设备(特别是 PC、Mac 这种无法扫码的设备)还没有网络可用的情况下,可能还真的只有键盘输入方便一些。

于是往往学校或公司的这类网络会提供给你用户名和密码,在连接时你会选择忽略证书(Windows 配这个很麻烦)或者信任单个证书。可能由于这种操作太过普遍,旧版本的安卓甚至直接忽略了证书(如果你没有明确指定一个预先导入的 CA 的话),这样做的问题就是单向认证容易被钓鱼。本科时尝试过用 hostapd 搭建伪造的 EAP 热点骗取用户 token,借用实验室的卡用 hashcat 跑出来过同学的 UIS 密码,虽然跑密码花了点时间,但是是可行的,和 WPA 的问题一样,拥有足够多资源就可以足够快。

要解决信任问题,那双向认证基本是不可避免的。

配置 EAP-TLS

这里环境是 ESXi + Debian + Docker,路由器 ASUS-AX88U。

配置并启动 freeradius:

1
2
3
4
5
6
7
8
9
10
version: '2.4'
services:
radius:
image: freeradius/freeradius-server
container_name: radius
volumes:
- "./freeradius:/etc/freeradius:ro"
restart: always
ports:
- "1812:1812/udp"

同目录需要从原镜像中拷贝出一份 /etc/freeradius ,并修改部分配置。

修改 certs/ca.cnf, certs/server.cnf, certs/client.cnf 后,生成一套证书:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
openssl dhparam -out dh 2048
openssl req -new -out server.csr -keyout server.key -config ./server.cnf
openssl req -new -x509 -keyout ca.key -out ca.pem -days 3650 -config ./ca.cnf
touch index.txt
echo '01' > serial
openssl ca -batch -keyfile ca.key -cert ca.pem -in server.csr -key whatever -out server.crt -config ./server.cnf
openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12 -passin pass:whatever -passout pass:whatever
openssl pkcs12 -in server.p12 -out server.pem -passin pass:whatever -passout pass:whatever
openssl verify -CAfile ca.pem server.pem
openssl x509 -inform PEM -outform DER -in ca.pem -out ca.der

openssl req -new -out client.csr -keyout client.key -config ./client.cnf
openssl ca -batch -keyfile ca.key -cert ca.pem -in client.csr -key whatever -out client.crt -config ./client.cnf
openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -passin pass:whatever -passout pass:whatever

之后在路由器上修改认证方式为 EAP,Radius 服务器配好 IP、Port 和 Secret 即可。

生成配置文件

对于 Windows,我们直接导入已生成的 CA 和 client.p12 ,然后在连接时选择 CA 证书和用户证书即可。但是对于 macOS 和 iOS 用户就很麻烦了。iOS 还可以手动信任并选择证书连接, macOS 连配置的地方都没有。

研究了一下发现对于苹果的设备需要下发一个 mobileconfig 文件,这个文件需要专门的工具来制作。

根据官方的一些资料,发现专业的 Server 是有这类管理工具可以制作的;还有一个上古软件 “iPhone 配置实用工具” 可以制作,但是实测 mac 版已不兼容跑不起来,Windows 版却还能正常工作;另外还有一个开源的软件可以做这件事:https://github.com/ProfileCreator/ProfileCreator ,但实际使用体验我感觉还不如上古版本的iPhone 配置实用工具的 Windows 版。

做出这个文件之后就可以导入到设备啦,airdrop 丢过去装一下就好。连接时直接单击即可,并且由于是通过描述文件安装,“忘记网络”的按钮也消失了,手残党的福音。

描述文件 CA 证书
描述文件 CA证书

后续还有很多事情要做,比如 IP-MAC 绑定等,在此不写了。

不可信网络

对于家庭网络,有访客要连接是常有的事情;家庭内的部分智能设备也需要 Wi-Fi 密码;部分个人设备也只需要连接外网。这些设备的共同之处是不需要连接内网,只需要外网访问权限。所以我们需要提供一个只有外网连接的网络。

一个方案是在路由器上再开一个 Wi-Fi,以及 DHCP 服务。

路由器固件是 Merlin,我感觉使用体验不如 OpenWRT。以普通用户的视角,在修改某些根本不需要重启网络的设置时,路由器往往也会重启,这明显是代码写的不太行;并且只能开 2.4G、5G 两个广播,加上内置的访客 Wi-Fi 功能是四个广播。以 root 用户的视角,当我想要修改内部文件时,是只读的,对外只提供了 JFFS 可写,可控制性很差。再开一套广播和 DHCP 的方案不太可行。

另一个方案是在某台设备上插张网卡自己做一个广播出来。折腾了半天发现我的那张网卡不支持 AP 模式,方案 GG。

最后采用的方案是再插一个路由器做广播,并在路由器的启动脚本里添加 iptables -I FORWARD -d 192.168.x.x/24 -j DROP 来丢弃通往内网的流量。

为什么不直接使用主路由器的访客模式?这个模式的确挺好用的,可以直接隔离内网以及访客间的互通;但是由于我在路由器 DHCP 配置里下发的网关和 DNS 路由是虚拟机 IP,因为是个内网 IP,这时会导致访客 Wi-Fi 不通。而绝大多数智能设备是不支持手动配置网关的,所以该方案不可行。

科学的外部网络

啥叫科学的外部网络大家都明白。这里也是通过前文提到的 ESXi 虚拟机做的。

VPN in Docker

【免得被查水表,本部分已删除】

写一写 docker compose 配置就跑起来啦!

踩过的坑

在处理 VPN 与 Docker 的时候经常会踩到一个有关 MTU 的坑。Docker 默认的网桥 mtu 是 1500。

对于在宿主机开启 VPN,在容器内使用的用户,会因为宿主机出口是 1500,减去 VPN 的一些头之后只剩 1400 多,所以 1500 的包就 gg 了。这时一个简单的做法就是把这个网桥 MTU 改小一些。如果使用 docker-compose,则添加类似如下配置:

1
2
3
4
5
6
7
8
9
networks:
ihciah:
driver: bridge
ipam:
config:
- subnet: 192.168.230.1/24
gateway: 192.168.230.1
driver_opts:
com.docker.network.driver.mtu: 1400

对于我这种在容器内起 VPN 容器外使用的情况更蛋疼一些:VPN 容器的出入流量都是走这个,所以怎么限制都没用。这时可以强制 TCP 握手时协商较小的 MSS,如 1300,这样即便是加上 IP 和 TCP 的头(40)也不会超那个 1400 多的值:

1
iptables -A OUTPUT -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1300

分流

不能所有的流量都走 VPN,否则排位是要输的。

针对路由,这里就用了很老的那套策略路由的方法,做了两个 ipset:一个是走国内 A 线路的(连 Google 等已经做了专线出国的),一个是直连国外 B 线路的(没有在白名单内的)。然后分别打标记并走各自的路由表。为了维护方便,做了个小脚本可以把带有语义化的注释的 ipset 列表转换为可以直接导入 ipset 的文件。

针对 DNS,起了 unbound,针对名单中的域转发一下就 ok。

这套算是黑名单机制(政治正确一点应该说屏蔽名单机制?)的路由,还是会有漏网之鱼,见到只能手动搞一下。但是好处是不会错误地把国内 IP 路由出去,之前用 CHNList 做白名单路由踩过这个坑,还是很蛋疼的:打开淘宝有时就会提示你是否跳转国外版本。

比较尴尬的是,这套东西刚配起来没两周,那个直连国外的线路就 GG 了。