フルポートスキャンから開放ポートを隠す方法

フルポートスキャンされた場合でも、全ての開放ポートを特定されない方法とその原理のメモです。
※今回はTCPに限定しています。

隠す方法

iptablesのlimitモジュールを使います。(細かな説明などは後述)

iptables -P INPUT DROP
iptables -N LIMIT

iptables -t filter -A INPUT -p tcp -m state --state ESTABLISHED -j ACCEPT
iptables -t filter -A INPUT -p tcp --syn -m limit --limit 10/m --limit-burst 10 -j LIMIT

iptables -t filter -A LIMIT -p tcp --dport  -j ACCEPT

本当に隠せているか試してみる

検証環境(サーバ側)はDebianを用意して22/ssh, 80/http, 443/httpsを動かしてみました。

root@debian:~# lsb_release -a
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 7.8 (wheezy)
Release:	7.8
Codename:	wheezy

root@debian:~# netstat -nltp
稼働中のインターネット接続 (サーバのみ)
Proto 受信-Q 送信-Q 内部アドレス            外部アドレス            状態        PID/Program name
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      32519/apache2   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      32341/sshd      
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      32519/apache2   

フィルタを設定してみます。

root@debian:~# cat hidden.iptables.sh 
#!/bin/sh

iptables -F
iptables -X
iptables -Z

iptables -P INPUT DROP
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP

iptables -N LIMIT

iptables -t filter -A INPUT -p tcp -m state --state ESTABLISHED -j ACCEPT
iptables -t filter -A INPUT -p tcp --syn -m limit --limit 10/m --limit-burst 10 -j LIMIT

iptables -t filter -A LIMIT -p tcp --dport 22 -j ACCEPT
iptables -t filter -A LIMIT -p tcp --dport 80 -j ACCEPT
iptables -t filter -A LIMIT -p tcp --dport 443 -j ACCEPT

iptables -nvL -t filter


root@debian:~# ./hidden.iptables.sh 
Chain INPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            state ESTABLISHED
    0     0 LIMIT      tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcpflags: 0x17/0x02 limit: avg 10/min burst 10

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain LIMIT (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:443

OUTPUTのポリシーがACCEPTだったりするのはオッカムの剃刀でそぎ落とされたためです。
ポートスキャンをかける前に一般ユーザがサービスにアクセスできることを確認しておきます。

% nc -nv 192.168.11.102 22
(UNKNOWN) [192.168.11.102] 22 (ssh) open
SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2
^C
% nc -nv 192.168.11.102 80
(UNKNOWN) [192.168.11.102] 80 (http) open
^C
% nc -nv 192.168.11.102 443
(UNKNOWN) [192.168.11.102] 443 (https) open
^C

では、攻撃者視点からポートスキャンを実施してみます。

root@kali:~# nmap -n -r -sS -p 1-65535 192.168.11.102

Starting Nmap 6.47 ( http://nmap.org ) at 2015-02-14 05:15 CST
Nmap scan report for 192.168.11.102
Host is up (0.00028s latency).
All 65535 scanned ports on 192.168.11.102 are filtered
MAC Address: 00:0C:29:3B:B9:76 (VMware)

Nmap done: 1 IP address (1 host up) scanned in 1371.88 seconds

65535ポートすべてフィルタされていると判定されました。(こうなるように設定したフィルタだから当然なのですが...)

原理

今回、検証用に書いたフィルタはlimitモジュールを使用して1分あたり10個以上のsynパケットを受け取ると11個目以降のパケットを全てDROPします。

iptables -P INPUT DROP
iptables -t filter -A INPUT -p tcp --syn -m limit --limit 10/m --limit-burst 10 -j LIMIT

limitモジュールの説明 Linux 2.4 Packet Filtering HOWTO: Using iptables

簡単な説明
 書式 : -m limit --limit Rate --limit-burst Max
 Maxサイズのバッファからあふれたものは判定しない。
 Rateで指定したサイズ/単位時間ごとにバッファをクリアする。

上記の検証では、わざとnmapに-rオプション*1を使用しました。つまり、1番から10番までのTCPポートは開放確認されますが、その後は1分経過するまで、synパケットがDROPされるので、その間に22や80を過ぎると開放ポートの見逃しが発生します。
当然、値は各サイトやサービスごとに調整が必要です。
また、問題点もあります。このルールを適用してポートスキャンをされている間は正規のユーザがsshで接続しようとしても、synパケットがDROPされて、接続できません。そこで、hashlimitモジュールを使えば、送信元IPアドレスごとにフィルタされるので、正規のユーザのアクセスに影響を与えることがありません。
hashlimitモジュールの説明 Man page of iptables-extensions

どうやって隠されたポートを発見するか(攻撃者視点)

  • well knownでbindしているサービスは見つけられる可能性が高い(nmapの-Fオプション*2 )
root@kali:~# nmap -n -sS -F 192.168.11.102

Starting Nmap 6.47 ( http://nmap.org ) at 2015-02-14 05:56 CST
Nmap scan report for 192.168.11.102
Host is up (0.00024s latency).
Not shown: 98 filtered ports
PORT    STATE SERVICE
80/tcp  open  http
443/tcp open  https
MAC Address: 00:0C:29:3B:B9:76 (VMware)

Nmap done: 1 IP address (1 host up) scanned in 19.01 seconds
  • 時間的な制約がなければ、スキャンタイミングをゆっくり(nmapの-Tオプション)
root@kali:~# nmap -n -sS -F -T1 192.168.11.102

Starting Nmap 6.47 ( http://nmap.org ) at 2015-02-14 06:32 CST
Nmap scan report for 192.168.11.102
Host is up (0.00066s latency).
Not shown: 97 filtered ports
PORT    STATE SERVICE
22/tcp  open  ssh
80/tcp  open  http
443/tcp open  https
MAC Address: 00:0C:29:3B:B9:76 (VMware)

Nmap done: 1 IP address (1 host up) scanned in 3275.71 seconds
およそ目安(initial_rtt_timeoutの値)
T0 : 5min    (serial)
T1 : 15sec   (serial)
T2 : 1sec    (serial)
T3 : 1sec    (parallel)
T4 : 500msec (parallel)
T5 : 250msec (parallel)

まとめ

limitモジュールはDoSの対策に使われるのが一般的だと思いますが、少し変わった用途を考えてみました。
ハイポートで開放しているポートを隠すのには有効だと思います。
「もっとこうした方がいい」、「こんな場合、問題あるよね」などツッコミあれば連絡いただけると幸いです。

蛇足

knockdを使用することで、予め決められた順番でTCPポートをノック(knock)したIPアドレスからのみ特定のサービスポートにアクセスできるようにすることも可能です。

*1:小さな番号から順番にポートスキャンを実施するオプション

*2:Fastオプション。よく使われる100ポートのみスキャンを実施