劫持DNS是個(gè)很簡單的工作,家用路由器基本都自帶dnsmasq,直接加解析就行。
之前某次嘗試劫持某視頻App的廣告接口解析到一個(gè)空的本地服務(wù)器上,發(fā)現(xiàn)該App使用了DnsPod的HttpDNS服務(wù),所以傳統(tǒng)的DNS劫持方案不好用。而EdgeRouter的DPI功能也沒有對(duì)外開放墻一般的高級(jí)接口,所以這次用NAT來實(shí)現(xiàn)。
首先,原有的App廣告流程如下:
圖中的流程很傳統(tǒng),12為HttpDNS請(qǐng)求過程,34為廣告資源拉取。沒有畫路由器,因?yàn)檫@里沒有用到路由器的DNS轉(zhuǎn)發(fā)功能,所以路由器不重要。
針對(duì)這個(gè)場(chǎng)景,如果想劫持HttpDNS Server的解析,就需要在終端和HttpDNS Server之間加上一個(gè)代理,圖就變成了:
這里Hijack Server存在的前提是使用了非ssl/tls的傳輸協(xié)議。在HttpDNS的常規(guī)邏輯上,也不太好加常用的SSL,因?yàn)椴粫?huì)有CA給IP頒發(fā)證書。DnsPod的HttpDNS的一個(gè)參考地址是 http://119.29.29.29/d?dn= 。當(dāng)然企業(yè)級(jí)自己預(yù)置證書這種玩法我就管不了了。應(yīng)該不會(huì)有人問為什么HttpDNS不用域名來設(shè)置目標(biāo)服務(wù)器地址吧。
從實(shí)際角度來說,圖應(yīng)該變成下邊這個(gè)樣子,我沒有刻意地畫路由、防火墻設(shè)備,只是拿個(gè)方框區(qū)分出所謂的LAN和WAN。
又回到常識(shí)性的邏輯上,1/4這個(gè)鏈路其實(shí)是不可能成立的,因?yàn)锳pp這邊的IP是寫死的。在不修改App的前提下,圖其實(shí)應(yīng)該是這樣的:
從路由/防火墻上配置規(guī)則,將App的HttpDNS Server的請(qǐng)求,轉(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。我定義了兩個(gè)組,一個(gè)是DnsPod HttpDNS的所有IP,一個(gè)是需要劫持的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
不出意料的,掛了。請(qǐng)求發(fā)出去一致沒有響應(yīng),開日志發(fā)現(xiàn),只有SYN包到了192.168.1.13上。原因是:在LAN里直接做DNAT,只是修改了目的地址,源地址沒改,導(dǎo)致在同一個(gè)局域網(wǎng)下,1.13這臺(tái)機(jī)器看到源IP是同網(wǎng)段,所以把ACK直接發(fā)給了源IP的機(jī)器,于是出現(xiàn)了下圖:
解決方案就是再開一個(gè)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寫了一個(gè)簡單的HttpDNS查詢接口,預(yù)留了一個(gè)讀配置文件的地方:
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;
}
}