劫持DNS是個很簡單的工作,家用路由器基本都自帶dnsmasq,直接加解析就行。
之前某次嘗試劫持某視頻App的廣告接口解析到一個空的本地服務(wù)器上,發(fā)現(xiàn)該App使用了DnsPod的HttpDNS服務(wù),所以傳統(tǒng)的DNS劫持方案不好用。而EdgeRouter的DPI功能也沒有對外開放墻一般的高級接口,所以這次用NAT來實現(xiàn)。
首先,原有的App廣告流程如下:
圖中的流程很傳統(tǒng),12為HttpDNS請求過程,34為廣告資源拉取。沒有畫路由器,因為這里沒有用到路由器的DNS轉(zhuǎn)發(fā)功能,所以路由器不重要。
針對這個場景,如果想劫持HttpDNS Server的解析,就需要在終端和HttpDNS Server之間加上一個代理,圖就變成了:
這里Hijack Server存在的前提是使用了非ssl/tls的傳輸協(xié)議。在HttpDNS的常規(guī)邏輯上,也不太好加常用的SSL,因為不會有CA給IP頒發(fā)證書。DnsPod的HttpDNS的一個參考地址是 http://119.29.29.29/d?dn= 。當(dāng)然企業(yè)級自己預(yù)置證書這種玩法我就管不了了。應(yīng)該不會有人問為什么HttpDNS不用域名來設(shè)置目標(biāo)服務(wù)器地址吧。
從實際角度來說,圖應(yīng)該變成下邊這個樣子,我沒有刻意地畫路由、防火墻設(shè)備,只是拿個方框區(qū)分出所謂的LAN和WAN。
又回到常識性的邏輯上,1/4這個鏈路其實是不可能成立的,因為App這邊的IP是寫死的。在不修改App的前提下,圖其實應(yīng)該是這樣的:
從路由/防火墻上配置規(guī)則,將App的HttpDNS Server的請求,轉(zhuǎn)發(fā)到Hijack Server上。
所以在EdgeRouter上,我們要做的第一件事情是,DNAT。我的Hijack Server IP是192.168.1.13,路由器IP是192.168.1.1,待劫持的設(shè)備IP是192.168.1.151。我定義了兩個組,一個是DnsPod HttpDNS的所有IP,一個是需要劫持的LAN IP。所以參考的配置如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[edit]
root@ubnt# show firewall group address-group DNSPOD_HTTPDNS_SERVER
address 182.254.118.118
address 182.254.116.116
address 119.28.28.28
address 119.29.29.29
[edit]
root@ubnt# show firewall group address-group SRC_HIJACK_DNSPOD_HTTPDNS
address 192.168.1.151
[edit]
root@ubnt# show service nat rule 4108
description "hijack dnspod http dns"
destination {
group {
address-group DNSPOD_HTTPDNS_SERVER
}
port 80
}
inbound-interface eth0
inside-address {
address 192.168.1.1
port 80
}
log disable
protocol tcp
source {
group {
address-group SRC_HIJACK_DNSPOD_HTTPDNS
}
}
type destination
不出意料的,掛了。請求發(fā)出去一致沒有響應(yīng),開日志發(fā)現(xiàn),只有SYN包到了192.168.1.13上。原因是:在LAN里直接做DNAT,只是修改了目的地址,源地址沒改,導(dǎo)致在同一個局域網(wǎng)下,1.13這臺機器看到源IP是同網(wǎng)段,所以把ACK直接發(fā)給了源IP的機器,于是出現(xiàn)了下圖:
解決方案就是再開一個MASQUERADE。配置如下:
root@ubnt# show service nat rule 5108
description "hijack dnspod http dns"
destination {
address 192.168.1.13
port 80
}
log disable
outbound-interface eth0
protocol tcp
source {
group {
address-group SRC_HIJACK_DNSPOD_HTTPDNS
}
}
type masquerade
1.13上,我用php寫了一個簡單的HttpDNS查詢接口,預(yù)留了一個讀配置文件的地方:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
$upstream = [
'182.254.118.118',
'182.254.116.116',
'119.28.28.28',
'119.29.29.29',
];
$hijack = include(__DIR__ . '/hijack_config.php');
if (isset($hijack[$_GET['dn']])) {
$result = $hijack[$_GET['dn']];
if (!empty($_GET['ttl'])) {
$result .= ',3600';
}
echo $result;
exit;
}
$url = 'http://' . $upstream[array_rand($upstream)] . '/d?dn=' . $_GET['dn'];
if (!empty($_GET['ttl'])) {
$url .= '&ttl=' . $_GET['ttl'];
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
echo curl_exec($ch);
curl_close($ch);
exit;
配置文件就是同目錄下的 hijack_config.php,內(nèi)容如下
<?php
return [
'aaa.bbb.com' => '192.168.1.13',
];
nginx配置也很簡單
server {
listen 80 default_server;
root /opt/deploy/dnspod-httpdns;
rewrite ^/d$ /proxy.php break;
location ~ \.php$ {
include fastcgi-php.conf;
fastcgi_pass unix:/tmp/php-fpm.sock;
}
}