ホストレベルセキュリティの総仕上げ

第2回 ホストレベルセキュリティの総仕上げ 

今回は、前回から行っているホストレベルのセキュリティ対策を完成させる。Linuxでファイアウォールを構築するには、まずホストレベルのセキュリティが重要になる。セキュリティホールだらけのファイアウォールなど何の役にも立たないからだ。

サービスのアクセス制御

 前回に続き、ホストレベルのセキュリティ対策を行います。セキュリティ対策に「絶対」というものはありません。可能な限りの対策を施しましょう。

 デフォルトで起動されるすべてのサービスに制限なくアクセスさせるのは危険です。サービスについては、特定のネットワークあるいはホストからのアクセスのみを許可するようにすべきです。例えば、メンテナンス用にtelnetを使うのであれば、パスワードが適切に設定されていたとしてもアクセスは制御する必要があります。

 そこで、アクセス制御の方法を説明します。アクセス制御には、xinetdのみで行う方法と、TCP_Wrapperを併用する方法の2種類があります。それぞれについて見ていきましょう。

xinetdでのアクセス制御

 Red Hat Linux 7.1Jは、xinetdを採用しています。前回、xinetd自身にアクセス制御機能があると説明しました。ここでは、その具体的な設定方法を紹介します。

 Red Hat Linux 7.1Jのxinetdは、/etc/xinetd.d以下にサービスごとに設定ファイルを用意します。アクセス制御を行う際は、制御したいサービスのファイルに制御条件を追加します。

only_from   = 192.168.0.0/24

 only_fromというパラメータは、アクセスを許可するネットワークあるいはホストを指定します。上記の設定は、「192.168.0.0/24からのアクセスのみを許可」することを意味します。「特定のホスト、ネットワークからの接続を拒否」するにはno_accessというパラメータを使います。192.168.1.1というホストからの接続を拒否するなら、

no_access   = 192.168.1.1

とします。

 /etc/xinetd.d/telnetのサンプルファイルを以下に挙げるので、参考にしてください。

service telnet 

    disable        = no 
    protocol        = tcp 
    socket_type     = stream 
    wait            = no 
    user            = telnetd 
    server          = /sbin/in.telnetd 
    only_from       = 192.168.0.0/24 
}

disable サービスを無効化:yes
無効化しない:no
protocol プロトコル:/etc/protocolsのプロトコルを指定
socket_type ソケットタイプ:stream(TCP)、dgram(UDP)、rawなど
wait 単一のスレッドとする:yes
単一のスレッドとはしない:no
user telnetdを起動するユーザー名
server サービス:サービスを実行するプログラム名(絶対パス)
only_from 接続を許可するネットワークアドレス
表1 各パラメータの説明(一部)

コラム:xinetdでアクセスログを取る方法
 不審なアクセスの有無などを監視できるように、アクセスログを取得しておきましょう。そのためには、/etc/xinetd.confを以下のように修正します。

defaults 

    instances       = 15 
    log_type        = FILE /var/log/servicelog 
    log_on_success  = HOST PID EXIT DURATION 
    log_on_failure  = HOST RECORD 
}

 変更後、xinetdを再起動してください。

TCP_Wrapperを用いたアクセス制御

 telnetサービスがxinetdやinetdを介して呼び出されることは前回説明しました。xinetdあるいはinetdから呼び出されたとき、tcpdが介入して接続の許可/不許可を判断し、許可であればtelnetサービスが起動されます。不許可であれば接続を拒否します。

 TCP_Wrapperの設定は、hosts.allowhosts.denyという2つのファイルに記述されています。これらのファイルを編集することで設定を変更します。

 以下では、例として次のような方針で設定を編集します。

 まず、viなどを使って/etc/hosts.allowファイルを編集します。

# vi /etc/hosts.allow

 /etc/hosts.allowはアクセスを許可する条件を指定します。次の2行を追加します。

in.telnetd : 192.168.0.0/255.255.255.0
in.ftpd : 192.168.1.10

 これに伴い、/etc/inetd.confファイル(inetdの場合)、/etc/xinetd.dディレクトリ内のtelnetftpファイル(xinetdの場合)を変更する必要があります。

xinetdの場合

 telnet、ftpファイルそれぞれのserverの行を次のように修正します。

server      = /sbin/tcpd

 さらに、次の2行を追加します。

flags       = REUSE NAMEINARGS
server_args = /sbin/in.telnetd
telnetファイル

flags       = REUSE NAMEINARGS
server_args = /sbin/in.ftpd
ftpファイル

inetdの場合

 inetdの場合は、/etc/inetd.confを以下のように修正します。

telnet     stream  tcp nowait  root    /sbin/in.telnetd  in.telnetd
telnet     stream  tcp nowait  root    /sbin/tcpd  /sbin/in.telnetd

 FTPの行も同様に修正します。

 次に/etc/hosts.denyの設定です。このファイルは、アクセスを拒否する条件を指定します。

# vi /etc/hosts.deny

 /etc/hosts.denyでは、あらゆるネットワーク/ホストからの接続を拒否するため、次の2行を追加します(編注)。

in.telnetd : ALL
in.ftpd : ALL

編注:/etc/hosts.allowと/etc/hosts.denyでは、/etc/hosts.allowの方が優先度は高い。よって、まず/etc/hosts.denyで全サービスへのアクセスをALL(全ホスト/ネットワーク)で拒否し、/etc/hosts.allowで許可するホスト/ネットワークのみを指定するのがセオリーとなる。

コラム:TCP_Wrapperのログ取得法
 TCP_Wrapperでログを取りたい場合は以下の作業を行います。まず、TCP_Wrapperインストール時にMakefileを編集し、FACILITYの行を次のように変更します。

FACILITY=LOG_LOCAL1

 さらに、/etc/syslog.confを編集します。


local1.info             /var/log/tcp_wrapper.log

 touchコマンドを使い、/var/log/tcp_wrapper.logファイルを作成します。


# touch /var/log/tcp_wrapper.log

 最後にsyslogにハングアップシグナルを送ります。

ipchainsによるパケットフィルタリング

 Linuxには、パケットフィルタリング機能としてipchainsという実装があります。これは、kernel 2.2系以降に対応しているものです。Red Hat Linux 7.1Jはkernelは2.4系であり、ipchainsとは別にkernel 2.4系に対応しているiptablesという実装もあるのですが、今回はipchainsを用いたパケットフィルタリングについて説明します。iptablesを使うにはkernelの再構築が必要になりますが、ipchainsはそのまま使えるので採用しました。iptablesを使うためのkernelの再構築については、別の機会に説明させていただきます。

フィルタリングルールの設計

 まず、最初に行うのはルールを考えることです。これは、設定前に十分に考える必要があります。ルールの検討を怠ると思ったよりも甘い設定になってしまったり、逆に制限が強すぎて思いどおりにサービスを提供できなくなってしまいます。

 どこからのアクセスを許可するかなどを決めるとき、まずは必要なものを紙に書き出しましょう。その後、ネットワークフロー図に起こしましょう。図にすると分かりやすくなります。

 まずは、現在のルールを確認しましょう。現在のルールの確認は以下のようにします。

# /sbin/ipchains -L -n

 ipchainsコマンドのオプションについては、man ipchainsを参照してください。

フィルタリングスクリプトの作成

 ipchainsはコマンドベースで設定します。ipchainsの設定方法についてある程度理解できたらスクリプトを作成しましょう。1つ1つのルールを設定するのに毎回コマンドを実行するのは大変ですし、人為的なミスが発生する可能性も高くなります。

 以下に簡単なスクリプト(/usr/local/bin/packetfilter)の例を挙げておくので参考にしてください。例では、対象となるサーバをSMTPサーバとしています。そして、次のような方針で設計されています。

注:第三者による不正中継を禁止する設定などは、アプリケーション(SMTPサーバ)で行ってください。

#!/bin/sh 
 
#IPアドレス、ネットワークアドレスの情報を変数とする 
MYHOST='192.168.0.1'            #自分自身のアドレス 
LOCALNET='192.168.1.0/24'       #ローカルネットワークアドレス 
MAINTHOST='192.168.1.100'       #メンテナンス用のホスト 
ANY='0.0.0.0/0'                 #すべてのホスト 
 
#すべてのルールを削除する 
/sbin/ipchains -F input 
 
#すべてのアクセスを拒否する 
/sbin/ipchains -P input DENY 
/sbin/ipchains -P forward DENY 
/sbin/ipchains -P output DENY 
 
#Loopbackアドレスはすべて許可とする 
/sbin/ipchains -A input -s 127.0.0.1 -d 127.0.0.1 -i lo -j ACCEPT 
/sbin/ipchains -A output -s 127.0.0.1 -d 127.0.0.1 -i lo -j ACCEPT 
 
#ローカルネットワークに対しては相互にecho requestとecho replyを許可する 
/sbin/ipchains -A input -p icmp -s $MYHOST --icmp-type 8 -d $LOCALNET -j ACCEPT 
/sbin/ipchains -A output -p icmp -s $LOCALNET --icmp-type 0 -d $MYHOST -j ACCEPT 
/sbin/ipchains -A output -p icmp -s $LOCALNET --icmp-type 8 -d $MYHOST -j ACCEPT 
/sbin/ipchains -A input -p icmp -s $MYHOST --icmp-type 0 -d $LOCALNET -j ACCEPT 
 
#DNSサーバに対し、名前解決を許可する 
/sbin/ipchains -A output -p udp -s $MYHOST -d $DNS 53 -i eth0 -j ACCEPT 
/sbin/ipchains -A input -p udp -d $DNS 53 -s $MYHOST -i eth0 -j ACCEPT 
 
#メンテナンス用ホストからのsshを許可する 
/sbin/ipchains -A input -p tcp -s $MAINTHOST -d $MYHOST 22 -i eth0 -j ACCEPT 
/sbin/ipchains -A output -p tcp -d $MYHOST 22 -s $MAINTHOST -i eth0 -j ACCEPT 
 
#すべてのホストからのSMTPを許可する 
/sbin/ipchains -A input -p tcp -s $ANY -d $MYHOST 25 -i eth0 -j ACCEPT 
/sbin/ipchains -A output -p tcp -d $MYHOST 25 -s $ANY -i eth0 -j ACCEPT

 このスクリプトを/usr/local/bin/packetfilterとして保存したら、スクリプトに実行権限を与えて実行します。

# chmod +x /usr/local/bin/packetfilter
# /usr/local/bin/packetfilter

 現在のルールを確認し、実際に設計どおりの設定になっているか確認してください。

# /sbin/ipchains -L -n

 ルールがきちんと設定されていたら、設定を保存しましょう。

# /sbin/ipchains-save

 以上でipchainsの設定作業は終わりです。これはあくまでも設定例ですので、環境に合わせて修正してください。

フィルタリングの動作確認

 設定作業が終わったらパケットフィルタリングの確認を行います。nmapnetcatというプログラムを使って確認してみます。

 MAINTHOST(192.168.1.100)からテストを行うと仮定します。このホストからは、22/TCP、25/TCPの2つのポートが空いているように見えるはずです。実際にサービスを提供しているポートが22/TCPと25/TCPしかなければ完全なテストとはいえません。ほかにもサービスを提供しているポートが存在し、かつそのポートがフィルタリングされることを確認したいからです。そこで、netcatを使います。

 netcatでTCPの1000番ポートをLISTENさせます。

# netcat -l -p 1000 &

 netstatコマンドを使って確認します。

# netstat -ant 
 
Active Internet connections (servers and established) 
Proto Recv-Q Send-Q Local Address        Foreign Address      State 
tcp        0      0 0.0.0.0:22           0.0.0.0:*            LISTEN 
tcp        0      0 0.0.0.0:25           0.0.0.0:*            LISTEN 
tcp        0      0 0.0.0.0:1000         0.0.0.0:*            LISTEN

 この状態で、nmapを使ってポートスキャンを実行します。

# nmap -sT 192.168.0.1 
 
Starting nmap V. 2.54BETA28 ( www.insecure.org/nmap/ ) 
Interesting ports on atmarkit (192.168.0.1): 
(The 1541 ports scanned but not shown below are in state: closed) 
Port       State       Service 
22/tcp     open        ssh 
25/tcp     open        smtp

 上記のような結果を得ることができれば、フィルタリングは成功です。

コラム:ipchainsのログを取る方法
 ipchainsでは、-lオプションを付けることでログを取得できます。ipchainsのルールを設定するスクリプトを編集します。ここではsshに関するルールを例にします。

/sbin/ipchains -A input -p tcp -s $MAINTHOST -d $MYHOST 22 -i eth0 -j ACCEPT -l
/sbin/ipchains -A output -p tcp -d $MYHOST 22 -s $MAINTHOST -i eth0 -j ACCEPT -l

 次に、/etc/syslog.confを修正します。


kern.info                       /var/log/ipchains.log

 touchコマンドを使って、ログ用のファイルを作成します。


# touch /var/log/ipchains.log

 最後に、syslogにハングアップシグナルを送ります。

ファイルシステムに関するセキュリティ対策

 ホスト上には、プログラムを実行するファイルや設定ファイルなど、多くのファイルが存在します。これらの中には、第三者に見せてはいけない重要なファイルも多数あります。ファイルのパーミッションが適切に設定されていないと、これらのファイルが読まれてしまったり、書き換えられてしまいます。

 すべてのユーザーに対して、すべてのファイルの読み出しや書き込みを許可する必要はありません。環境に合わせて適切なパーミッションを設定しましょう。

ファイルのパーミッションとは

 最初に、ファイルのパーミッションとは何かについて簡単に説明します。試しに、次のコマンドを実行してみてください。

# ls -l test
-rwxrw-r--  root  root   0   Aug  31  19:00  test
注:testはファイル名

 先頭の1文字を除いたrwxrw-r--がこのファイルのパーミッションを示しています。パーミッションとは、ファイルに対するアクセスの種類を示すビット列です。

 最初の3文字は所有者のアクセス権、次の3文字が所有グループのアクセス権、最後の3文字がそのほかのユーザーのアクセス権を示しています。つまり、合計9文字でファイルのアクセス権が表現されているのです。

 r、w、x、-には次のような意味があります。

  r 読み取り権
  w 書き込み権
  x 実行権
  - 権限なし
  表2 パーミッションの文字の意味

 rwxrw-r--では、次のようなアクセス権があることを意味します。

 ログファイルや各種アプリケーションの設定ファイルに、一般ユーザーの書き込み権限を与える必要はありません。もし書き込み権があるなら、パーミッションを変更してください。この作業には、chmodコマンドを使います。

 chmodコマンドを使うためには、アクセス権の指定方法を知る必要があります。アクセス権を設定するには、演算子を使う方法と8進数を用いる方法があります。

 
演算子
  + アクセス権を追加
  - アクセス権を削除
  = アクセス権を割り当てる
 
文字
  u ファイルの所有者
  g ファイルの所有グループ
  o そのほかのユーザー
 
8進数
  0 アクセス権なし
  1 実行
  2 書き込み
  3 実行、書き込み
  4 読み取り
  5 実行、読み取り
  6 書き込み、読み取り
  7 実行、書き込み、読み取り
  表3 chmodコマンドの引数

 ファイルtestの所有グループから書き込み権を削除するようにパーミッションを変更するには、以下のようにします。

# chmod g-w test

 今度は、以下の2つの条件を満たすようにパーミッションを変更する方法です。

# chmod 644 test

 これまでは、ファイルtestのパーミッション部分についてのみを解説していて、-rwxrw-r--の先頭の文字には触れていませんでした。しかし、もちろん先頭の文字にも意味があります。先頭の文字は、ファイルなのかディレクトリなのかを表しています。ここに使われる文字には、以下のような意味があります。

  - ファイル
  b ブロック特殊ファイル
  c キャラクタ特殊ファイル
  d ディレクトリ
  l シンボリックリンクファイル
  表4 先頭文字の意味

SUID/SGIDファイルの意味と危険性

 ファイルのパーミッションに「s」という文字が付いているファイルが存在します。これはSUIDSGIDされたファイルであることを意味します。例を見てみましょう。

# ls -l /bin/ping
-rwsr-xr-x  1 root root     22620

 これは、所有者rootにSUIDされたファイルであることを意味します。

# ls -l /usr/bin/man
-rwxr-sr-x  1 root man      35676

 これは、所有グループmanにSGIDされたファイルであることを意味します。

 SUID/SGIDが設定されているファイルは潜在的な危険性を持っています。SUID/SGIDが設定されたファイルは、所有者以外のアカウントで実行されても所有者/所有グループが実行したことになります。つまり、SUID rootが設定されているファイルは、一般ユーザーでもrootとして実行したのと同じになります。これはセキュリティホールとなる可能性があります。

 SUID/SGIDされているからといって、必ずしも変更しなければならないというわけではありません。しかしながら、SUIDされたファイルが新たにできていないかを定期的に確認することをお勧めします。SUID/SGIDが設定されたファイルを見つけるには、以下のコマンドを実行します。なお、SUIDとSGIDを8進数で表すと、それぞれ4000と2000になります。

# find / -perm +4000
SUIDの場合

# find / -perm +2000
SGIDの場合

 SUID/SGIDチェック用のスクリプトを作成し、cronに登録して定期的に確認するようにしましょう。新たにファイルが見つかったら、管理者に通知するような仕組みにしておくと便利です。同様に、.rhostsなどができていないかどうかも確認した方がいいでしょう。

コラム:sudoによる制限の活用
 sudoを使用することで、実行に高い権限が必要なプログラムを特定のユーザーのみが使用するように制限できます。sudoはログに残すことが可能なので、どのユーザーがsudoコマンドを使ってコマンドを実行したかを監査することができます。

 例として、ユーザーuserのみがpingを実行できるように設定します(rootは当然実行できます)。

 まず、pingのSUIDを外します。

# chmod -s /bin/ping

 /etc/sudoersを編集します。編集にはvisudoコマンドを使います。

# visudo

 /etc/sudoersに以下の2行を追加します。

user  localhost=NOPASSWD: /bin/ping

Defaults syslog=auth,    logfile=/var/log/sudo.log

 touchコマンドで、sudo用ログファイル/var/log/sudo.logを作成します。

# touch /var/log/sudo.log

umask値によるデフォルトパーミッション

  Red Hat Linux 7.1Jで新規にファイルを作成すると、デフォルトのパーミッションは一般ユーザーの場合が664rootの場合が644になります。これは、touchコマンドで空のファイルを作成することで確認できます。

$ touch test1
$ ls -l
-rw-rw-r--  1 user user    0 Aug 31 19:00
$ su -
Password:
# touch test2
-rw-r--r--  1 root root    0 Aug 31 19:00

 デフォルトのパーミッションはもっと厳しく設定しましょう。umask値を設定すると、ファイルのデフォルトパーミッションを決めることができます。

 umask値は、「666から与えたいパーミッションを引いた値」を指定します。rootアカウントで作成したファイルには、rootアカウントのみに書き込み、読み取り権を与えるべきでしょう。つまり、デフォルトのパーミッションを「600」にするわけです。すると、

666−600=066

となり、umask値は066になります。

 Red Hat Linux 7.1Jでは、/etc/profileでumask値を設定します。viなどで/etc/profileを開き、下記のようにumask値を修正してください(編注)。

ulimit -S -c 1000000 > /dev/null 2>&1
if [ `id -gn` = `id -un` -a `id -u` -gt 14 ]; then
        umask 022 ←この部分を002から変更
else
        umask 066 ←この部分を022から変更

編注:一般的なUNIXの一般ユーザーumaskは022だが、Red HatではUPG(User Private Groups)により002としている。UPGについては、以下のURLを参照。
http://www.redhat.com/docs/manuals/linux/RHL-7.1-Manual/ref-guide/s1-users-groups-private-groups.html

 アカウント(シェルはbashとする)が複数存在し、アカウントごとにumask値を設定したい場合は、各アカウントのホームディレクトリにある.bash_profileの最後に以下の1行を追加してください。

# vi /root/.bash_profile

umask 066 ←umask値は任意

注:導入したアプリケーションのログを取り、そのログをローテーションさせているのであれば、スクリプト内にumask値を設定するか、新しく生成されるログファイルのパーミッションをrootのみが読み書きできるように設定してください。

 以上で、ホストレベルのセキュリティ対策の解説は終わりです。しかしながら、これがすべてというわけではありません。セキュリティ対策に「絶対」や「終わり」はありません。いかにそれらに近づけるかがとても重要です。

 今回設定方法を紹介したxinetdは、すでに不具合が発見されています。繰り返しますが、最新のものを導入してください。しかしながら、最新だからといって必ずしも不具合がないとはいえませんし、しかるべき設定を行ったからといって絶対に安全とはいえません。セキュリティ情報を常に追いかけ、情報収集を行うこともセキュリティ対策の1つといえるのではないかと思います。