ネットワークセキュリティHACKS Wiki 第六章 ネットワークセキュリティ - careerbeat/dit-ehime GitHub Wiki
- Arpwatchを用いてMAC/IPアドレスペアが変わるなどの異常な振る舞いを検知する
- IPアドレスからイーサネットのMACアドレスの情報を得られるプロトコル
LAN上のコンピュータ間で通信を行うためにはMACアドレスが必要
→ ARPは既知のIPアドレスもとにMACアドレスを問い合わせ(ARPリクエスト)、該当コンピュータからMACアドレスを受け取る(ARPリプライ)
→ IPアドレスとMACアドレスのペアはARPテーブルに記録され、これをもとに通信を行う
ARPリクエストとAPRリプライはその対応を考慮して処理されていない
→ 偽のARPリプライも受け取ってしまう
→ 悪意あるユーザーから大量にARPリプライが送られてくると、常に偽のMACアドレスがARPテーブルに記録されてしまう
→ 正規のコンピュータと通信ができない
Arpwatchは、プロミスキャスモードでネットワークを一定期間監視してMAC/IPアドレスペアを記録することで、記録したペアと差異が出た場合にsyslogにアラートを出す
※プロミスキャスモード:ネットワークインタフェースカード(NIC)の動作モードの1つで、同一ネットワーク内を流れるすべてのパケットを受信して読み込む
下記でArpwatchをインストールしてMAC/IPアドレスペアを監視する方法を解説する
- まず、Arpwatchをインストールします
yum -y install arpwatch
- インストールが完了したら、arpwatchを起動します
/etc/init.d/arpwatch start [自動起動したい場合] chkconfig arpwatch on
以上によりLAN上を飛び交うARPパケットの情報を収集することができます
では、下記コマンドを実行してArpwatchのログを見てみましょう 実行結果にある「new station」がマシンを検知した時のログです
# grep arpwatch /var/log/messages (RedHat系の場合) Feb 16 21:22:01 dojo yum[3296]: Installed: 14:arpwatch-2.1a15-16.el6.x86_64 Feb 16 21:23:17 dojo arpwatch: listening on eth0 Feb 16 21:23:23 dojo arpwatch: new station 192.168.1.125 8:0:27:e8:b8:e7 Feb 16 21:23:23 dojo arpwatch: new station 192.168.1.1 0:1e:31:75:32:6 Feb 16 21:31:52 dojo arpwatch: new station 192.168.1.63 bc:ee:7b:77:5f:1d Feb 16 21:32:29 dojo arpwatch: new station 192.168.1.30 bc:ee:7b:77:66:f3 Feb 16 21:33:47 dojo arpwatch: new station 192.168.1.115 bc:ee:7b:77:5f:51
また、「new station」が検知された場合は、下記の内容のメールが届くので、「mail」コマンドで閲覧できます
# mail From: arpwatch To: root Subject: new station (ホスト名) I/F名 hostname: ホスト名 ip address: IPアドレス interface: I/F名 ethernet address: MACアドレス ethernet vendor: ↑のベンダー名 timestamp: 日時
この情報を元にARPスプーフィングが行われていないかを確認します
- 前HACKでは、ARPスプーフィングを検出する方法を紹介したが、このHACKでは事前にMACアドレスが登録されているARPテーブル(スタティックARPテーブル)を用いてARPテーブルの汚染を防ぐ
- スタティックARPテーブルを用いることで、偽のMACアドレスになっている可能性が大幅に低くなる
ARPテーブルにMACアドレスを手動で登録するには、下記コマンドを実行する
arp -s IPアドレス MACアドレス
スタティックARPテーブルにより十分なセキュリティを確保するためには、LAN上のすべてのデバイスのMACアドレスをARPテーブルに登録しておく必要があるが、上記コマンドでは非常に手間がかかる
そこで、下記に同一ネットワーク上のデバイスのMACアドレスを一括登録する方法を示す
- まず、同一ネットワーク上のデバイスのMACアドレスを一括登録するシェルスクリプトを作成します
※テキストではperlスクリプトを用いていますが、その理解のためにはperlの理解も必要となるので、ここではシェルスクリプトを用います
[s_arp.sh] #!/bin/bash nmap -sP $1 | tee host-list.txt loopCount=$(grep -c 'MAC Address:' host- list.txt) TEXT=$(grep 'Nmap scan report for' host-list.txt | cut -d ' ' -f 5) ipList=( `echo $TEXT | tr -s ',' '\n'`) TEXT=$(grep 'MAC Address:' host-list.txt | cut -d ' ' -f 3) macList=( `echo $TEXT | tr -s ',' '\n'`) rm -f arpList touch arpList for i in `seq ${loopCount}` do num=`expr $i - 1` echo "${ipList[$num]} ${macList[$num]}" >> arpList done arp -f arpList
- 作成したシェルスクリプトを下記のように実行します
# ./s_arp.sh 192.168.1.0/24
すると、「192.168.1.0/24」のネットワーク下のデバイス全てをARPテーブルに登録できます
最後にARPテーブルの中身を確認し、登録が行われていたら成功です
# arp Address HWtype HWaddress Flags Mask Iface 192.168.11.8 (incomplete) eth0 buffalo.setup ether 74:03:bd:f9:c4:fb CM eth0 192.168.11.128 (incomplete) eth0 192.168.11.10 ether 2c:d0:5a:a7:74:54 CM eth0 192.168.11.2 ether 38:71:de:c9:b0:87 CM eth0
- SSHでパスワード認証を行っている場合に、適切な対策を行って総当たり攻撃を防止する
- SSHのポート変更
- パスワード認証から鍵認証に変更する
- SSHDに対するファイアウォールのルールを設定する
※総当たり攻撃:考えられるすべてのパスワードパターンをリストアップして片っ端から検証する攻撃 - 十分強度を持ったパスワードを設定していれば問題ないが、総当たり攻撃によってさらに危険な攻撃が発生してしまうかもしれない
→ そのために総当たり攻撃を防止する対策をとる
SSHのポート変更は、標準の22番ポートではなく別のポートでリスンさせる
それにより、総当たり攻撃によりログに記録される大量のログインエラーを削減でき、重要な攻撃ログの見落としを防止できる
しかし、ポートスキャンなどの手法で変更後のポートを暴かれてしまう可能性もある
パスワード認証から鍵認証に変更すると、秘密鍵がなければSSH接続できなくなるので、不正なログインの防止に役立つ
SSHDに対するファイアウォールのルールを設定すると、鍵認証への変更より柔軟にログインユーザーを制限できる
/etc/ssh/sshd_configを下記のように変更して再起動します
# vi /etc/ssh/sshd_config [sshd_config] Port 22000(22000番ポートに変更) # /etc/init.d/sshd restart
では、ポートが変更されているか確認しましょう
# netstat -luntp : tcp 0 0 0.0.0.0:22000 0.0.0.0:* LISTEN 4843/sshd : tcp 0 0 :::22000 :::* LISTEN 4843/sshd :
続いてSSH接続ができるかどうか確認しましょう
ssh localhost (接続失敗) ssh -p 22000 localhost (接続成功) ※ポート番号を指定しないと接続できなくなります
まず、下記コマンドで公開鍵と秘密鍵を生成しましょう
# ssh-keygen -t rsa [rsa]は暗号方式 Generating public/private rsa key pair. Enter file in which to save the key (/home/dojo/.ssh/id_rsa): (←空Enter[鍵の保存場所]) Enter passphrase (empty for no passphrase): (←パスフレーズを入力) Enter same passphrase again: (←もう一度パスフレーズを入力) Your identification has been saved in /home/dojo/.ssh/id_rsa. Your public key has been saved in /home/dojo/.ssh/id_rsa.pub. The key fingerprint is: 8c:af:3c:25:c4:55:ea:ee:05:4e:00:b1:36:fb:ce:16 [email protected] The key's randomart image is: +--[ RSA 2048]----+ | o. .. | | o .. | | +.... | | . oo= | | ... S | | .E=.. | | .++ . | | +oo . | | .=.. | +-----------------+
次に下記コマンドで「~/.ssh」の配下の公開鍵「id_rsa.pub」の内容を「authorized_keys」に追記しましょう
これにより、毎回パスフレーズを入力する必要がなくなります
cat ~/.ssh/id_rsa.pub >> authorized_keys
次に下記コマンドで「.ssh」と「authorized_keys」のパーミッションを所有者のみ読み書きできるようにします
ここでパーミッションを適切に設定していないと鍵認証に失敗します
chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys
上記コマンドを実行しても鍵認証に失敗する場合は下記コマンドも実行してホームディレクトリのパーミッションを変更しましょう
chmod 755 ~
最後に、パスワード認証を無効にしましょう
# vi /etc/ssh/sshd_config [sshd_config] PasswordAuthentication no
では、sshでログインしてみましょう
下記のようにパスワードを求められなかったら鍵認証の成功です
root@localhost's password:
sshdの接続元を制限する
- まず、下記コマンドで192.168.1.XXX から192.168.1.YYY へsshの応答があることを確認しましょう
ssh 192.168.1.YYY
- 次に、下記コマンドで192.168.1.XXXからのsshのパケットを破棄するルールを設定しましょう
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.XXX -j DROP
- 最後に、下記コマンドで192.168.1.XXX から192.168.1.YYY へsshの応答がないことを確認しましょう
ssh 192.168.1.YYY
ログを解析してIPを制限する
これを実現するにはスクリプトを書かなければ難しいと判断しました。 実際にスクリプトを作ってみたので、下記にアクセスし、3つのスクリプトを入手してください。
auto-block-by-ipaddress-start.sh auto-block-by-ipaddress-init.sh auto-block-by-ipaddress-exe.sh
動作方法としては上記3つのスクリプトを同じディレクトリにコピーし、[auto-block-by-ipaddress-start.sh]を実行します。
動きとしては以下のようになります。
- スクリプトを実行するとログファイルを[inotify]で監視を開始する
- ssh接続のパスワード認証で失敗した時のログを拾い、ファイルに書き出す
- 書き出したファイルを調べ、同じIPアドレスが一定以上検出された場合は、iptablesにそのIPアドレスからのsshの接続を破棄するルールを追加する
上記の方法で[ログを解析してIPを制限する]仕組みを実現しました。
ではそれぞれスクリプトの内容を見て行きましょう。
まず起動スクリプト[auto-block-by-ipaddress-start.sh]です。
#!/bin/bash -eu
trap "exit 0" 3 # QUITシグナルで停止
WATCH_DIR="/var/log/" # 監視するディレクトリ
FILE_PATTERN="secure" # ファイル名正規表現
COMMAND="./auto-block-by-ipaddress-exe.sh" # ファイル更新時に実行するコマンド
# 初期化処理を行う
./auto-block-by-ipaddress-init.sh
# メッセージの出力
echo " inotifywaitによる監視を開始します"
# --formatで"ディレクトリ ファイル名 イベント"の形式で出力するように指定
inotifywait -e MODIFY -m -r ${WATCH_DIR} --format "%w %f %e" | \
while read LINE; do
# --format指定した通りに変数に格納
declare -a eventData=(${LINE})
dirPath=${eventData[0]}
fileName=${eventData[1]}
# ファイル名パターンマッチング (マッチしなかったら無視)
[[ $fileName =~ ${FILE_PATTERN} ]] || continue
# (DEBUG)イベントがあったファイルを表示
echo "${dirPath}${fileName}"
# コマンドの実行
${COMMAND}
done
流れとしては以下のようになります。
- 必要な変数の宣言、初期化
- 初期化処理を行うスクリプト[auto-block-by-ipaddress-init.sh]の実行
- [inotifywait]コマンドを実行し[/var/log/secure]ファイルを監視する
- [/var/log/secure]に変更が行われる度に、[auto-block-by-ipaddress-exe.sh]を実行する
これにより、認証関連のログを出力する[/var/log/secure]にログが書き込まれる度に、ログ解析を行う[auto-block-by-ipaddress-exe.sh]が実行されるようになります。
では[auto-block-by-ipaddress-exe.sh]を見て行く前に初期化処理を行うスクリプト[auto-block-by-ipaddress-init.sh]を見て行きましょう。
#!/bin/bash
# メッセージの表示
echo "初期化処理を開始します..."
# 失敗の限度回数
FAILED_LIMIT=2
# 接続拒否を設定する[hosts.deny]の絶対パス
HOSTS_DENY_PATH="/etc/hosts.deny"
# ログに記載されている接続に失敗したIPを記載するファイルのパス
FailedLogIpPath="FailedLogIP"
# ログに記載されている接続に失敗したIPを記載するファイルのパス
FailedIpListPath="FailedIPList"
# 接続を拒否しているIPが記載されているファイルのパス
RejectIpListPath="RejectIPList"
# 新しく書き込まれたログを洗い出すための差分ファイルを作成
cat /var/log/secure > PreSecureLog
# 各ファイルの初期化
rm -f ${FailedLogIpPath} && touch ${FailedLogIpPath}
rm -f ${FailedIpListPath} && touch ${FailedIpListPath}
rm -f ${RejectIpListPath} && touch ${RejectIpListPath}
# 以下の内容は日付による制限ができてから実装する
#########################################################################################################
## これまでに接続に失敗したことがある接続元IPの一覧を[FailedLogIP]ファイルに書き出す
#cat /var/log/secure | grep ".*sshd.*Failed.*" | rev | cut -d' ' -f4 | rev | sort > ${FailedLogIpPath}
#chmod 600 ${FailedLogIpPath}
#
## これまでに接続に失敗したことがある接続元IPの一覧を[FailedIPList]ファイルに書き出す
#cat ${FailedLogIpPath} | uniq > ${FailedIpListPath}
#chmod 600 ${FailedIpListPath}
#
## これまでに接続に失敗したことがある接続元IPの一覧を配列[srcIpList]に格納する
#srcIpList=($(cat ${FailedIpListPath} | tr -s ',' '\n'))
#
## for文で使用するループ回数
#ipListCount=$(grep -c "" ${FailedIpListPath})
#
## ssh接続の接続に失敗した回数によって、接続元IPアドレスからのsshを拒否する
#for i in $(seq ${ipListCount})
#do
# num=$(expr $i - 1)
# FailedCount=$(grep -c "${srcIpList[$num]}" ./FailedLogIP)
# if [ $FailedCount -gt $FAILED_LIMIT ]; then
# addRule="sshd : ${srcIpList[$num]}"
# echo ${addRule}
# if [ -n "$(grep "${addRule}" ${HOSTS_DENY_PATH})" ]; then
# echo "\"${addRule}\" already exists in hosts.deny"
# else
# echo ${addRule} >> ${HOSTS_DENY_PATH}
# fi
# echo "${srcIpList[$num]}" >> ${RejectIpListPath}
# fi
#done
#########################################################################################################
# メッセージの表示
echo "初期化処理完了"
このスクリプトは基本的にファイルの初期化を行っています。スクリプトを実行するとファイルが作られるのですが、そのファイルに過去のデータが残っていては不整合が発生するので、このスクリプトで初期化を行っています。
そして、大量のコメントアウトされている行があるのですが、これは現在書き出されているログを解析し、IPを制限するスクリプトです。 ただし、このスクリプトは日付による制限や解除されることを全く想定していないので、永遠に接続ができなくなってしまうので、実用性に欠けています。
そのため、日付による制限、あるいは別の手段を用いる方法が実装されるまでは使用しません。あくまで参考程度です。
では、最後にログ解析を行うスクリプト[auto-block-by-ipaddress-exe.sh]を見て行きましょう
#!/bin/bash
# 失敗の限度回数
FAILED_LIMIT=2
# 監視ファイル
WATCH_DIR="/var/log/secure"
# ログに記載されている接続に失敗したIPを記載するファイルのパス
FailedLogIpPath="FailedLogIP"
# ログに記載されている接続に失敗したIPを記載するファイルのパス
FailedIpListPath="FailedIPList"
# 接続を拒否しているIPが記載されているファイルのパス
RejectIpListPath="RejectIPList"
# 新しく書き込まれたログを洗い出すための差分ファイルのパス
PreSecureLogPath="PreSecureLog"
# 検索するルールを確立
rule="^DROP.*${FailedIp}.*tcp dpt:ssh"
# 新規に書き込まれたログの取得
diff ${PreSecureLogPath} ${WATCH_DIR} > temp
# 差分ファイルを更新
cat ${WATCH_DIR} > ${PreSecureLogPath}
# DEBUG
cat temp
# ssh接続失敗のログの場合、IPアドレスの抽出する
FailedSshLog=$(grep ".*sshd.*Failed.*" temp)
#echo ${FailedSshLog}
# 変数[FailedSshLog]が空文字ではない場合
if [ -n "${FailedSshLog}" ]; then
# 出力されたログの中で、IPアドレスだけを抽出する
FailedIp=$(echo ${FailedSshLog} | rev | cut -d' ' -f4 | rev)
# 抽出されたログが既に拒否設定を行っている場合、その旨を表示する
if [ -n "$(grep "${FailedIp}" ${RejectIpListPath})" ]; then
echo "FailedIp[${FailedIp}] already reject"
# そうでない場合
else
# 抽出したアドレスをファイルに追記し、その旨を表示する
echo ${FailedIp} >> ${FailedLogIpPath}
echo "\"${FailedIp}\" add FailedLogIp"
# もし、ファイルに抽出したIPアドレスが既定値をこえていた場合、
# そのIPアドレスからの接続を遮断する設定を行う
if [ $(grep -c "${FailedIp}" ${FailedLogIpPath}) -gt ${FAILED_LIMIT} ]; then
# 現在のiptablesの設定を出力
iptables -L > CurrentIptablesRule
# iptablesに接続元を遮断するルールが追加されている場合、その旨を表示する
if [ -n "$(grep "${rule}" CurrentIptablesRule)" ]; then
echo "\"${rule}\" already exists in iptables"
# そうでない場合、iptablesに追加する
else
iptables -I INPUT -s ${FailedIp} -p tcp --dport ssh -j DROP
fi
echo "${FailedIp}" >> ${RejectIpListPath}
echo "\"${FailedIp}\" add RejectIpList"
fi
fi
fi
流れとしては以下のようになります。
- 必要な変数の宣言、初期化
- 以前のログファイル(初期化処理で作成)と新規ログファイルの差分を出し、新規に書き込まれたログだけを抽出し、差分用ログファイルを更新
- 抽出したログを[grep]コマンドで「sshのログイン認証に失敗したログ」を検索する
- 検索した結果、ログが存在していた場合のみIPアドレスのみを抽出する。そうでない場合は処理を終了する。
- 抽出したIPアドレスが既に拒否設定を行っていた場合、その旨を表示する(異常)。そうでない場合、そのIPアドレスをファイルに記録する。
- そのIPアドレスで記録したファイルを検索し、規定値以上記録されていた場合、iptablesに「そのIPアドレスからのsshを拒否する」ルールを追加する処理を行う。そうでない場合は処理を終了する
- iptablesにそのルールを追加する際、既に同意義のルールが存在していた場合、その旨を表示し、追加を行わない。(異常)。
- 拒否設定を行ったIPアドレスをメモしたファイルに追加する
これにより、認証に失敗しすぎた送信元IPアドレスをiptablesで拒否するルールが自動で追加されるようになります。
では、実際に確認してみましょう。わかりやすくするために、スクリプトをフォルダにまとめましょう。
# mkdir auto-block-by-ipaddress
# cd auto-block-by-ipaddress
# cp {3つのスクリプト} ./
# ./auto-block-by-ipaddress-start.sh
初期化処理を開始します...
初期化処理完了
inotifywaitによる監視を開始します
Setting up watches. Beware: since -r was given, this may take a while!
Watches established.
別の端末 or 新しいタブ # ssh 192.168.1.XXX
[email protected]'s password: (← 空Enter以外の不正なパスワード)
Permission denied, please try again.
[email protected]'s password: (← 不正なパスワード)
Permission denied, please try again.
[email protected]'s password: (← 不正なパスワード)
Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).
初期化処理を開始します...
初期化処理完了
inotifywaitによる監視を開始します
Setting up watches. Beware: since -r was given, this may take a while!
Watches established.
/var/log/secure
775a776
> Mar 16 10:21:23 localhost sshd[3135]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.1.32 user=root
/var/log/secure
776a777
> Mar 16 10:21:25 localhost sshd[3135]: Failed password for root from 192.168.1.32 port 38072 ssh2
"192.168.1.32" add FailedLogIp
/var/log/secure
777a778
> Mar 16 10:21:28 localhost sshd[3135]: Failed password for root from 192.168.1.32 port 38072 ssh2
"192.168.1.32" add FailedLogIp
/var/log/secure
778a779
> Mar 16 10:21:32 localhost sshd[3135]: Failed password for root from 192.168.1.32 port 38072 ssh2
"192.168.1.32" add FailedLogIp
"192.168.1.32" add RejectIpList
/var/log/secure
779a780,781
> Mar 16 10:21:32 localhost sshd[3136]: Connection closed by 192.168.1.32
> Mar 16 10:21:32 localhost sshd[3135]: PAM 2 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.1.32 user=root
/var/log/secure
別の端末 or 新しいタブ # ssh 192.168.1.XXX
(← 応答がなくなっている)
このように、応答がなくなっていれば、成功しています。 監視をしていた端末に「"192.168.1.32" add RejectIpList」が出力されていると思います。このメッセージが出力されると、iptablesにルールを追加されたということになります。
ちなみにsshする時に、最初に[空Enter以外]という記載していますが、[空Enter]を一番最初に入力すると、認証に失敗したという扱いになりません。これはおそらく、Enterを連打しすぎて誤ってパスワードを入力したことを防ぐためにこのような仕様になっていると思われます。(余談ですが、このような事を防ぐ設計のことをフールプルーフといいます)
では実際にiptablesを確認してみましょう。
779a780,781
> Mar 16 10:21:32 localhost sshd[3136]: Connection closed by 192.168.1.32
> Mar 16 10:21:32 localhost sshd[3135]: PAM 2 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.1.32 user=root
/var/log/secure
^C
# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
DROP tcp -- 192.168.1.32 anywhere tcp dpt:ssh
ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED
ACCEPT icmp -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:ssh
REJECT all -- anywhere anywhere reject-with icmp-host-prohibited
Chain FORWARD (policy ACCEPT)
target prot opt source destination
REJECT all -- anywhere anywhere reject-with icmp-host-prohibited
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
[DROP tcp -- 192.168.1.32 anywhere tcp dpt:ssh ]が先頭に追加されているのがわかります。 これで、接続元IPアドレスの制限ができていることが証明されました。
しかし、このスクリプトにはいくつか考慮しなければならない点があります。
1つ目は本にも記載されている「例外的にブロックしないリスト(ホワイトリスト)」の導入です。 信頼できる接続元IPにもかかわらず、そのIPが誤ってパスワードを間違えてしまった場合、接続ができなくなってしまいます。 それを防ぐためにもホワイトリストの導入は必要ですが、現在、このスクリプトは接続拒否のルールを先頭に追加するようになっています。つまり、必ず一番最初にチェックされるようになっているため、ホワイトリストとして導入したiptablesのルールが無視されてします。
そのため、スクリプト[auto-block-by-ipaddress-exe.sh]のiptablesにルールを追加する部分を適切に修正しなけばなりません。
下記のように記述することで、ルールを追加する場所を指定することができます。
iptables -I {番号} INPUT -s ${FailedIp} -p tcp --dport ssh -j DROP
オプション[-I]だけですと、先頭に追加するという意味になってしまいます。その後に追加したい場所を数字で指定することで、任意の場所に追加することができます。 ホワイトリストのルールより後に追加されるように番号を指定することで、ホワイトリストとブラックリストの導入を適切に行うことができるようになります。
そしてもうひとつ考慮しなければならないのはサービス化とアクセス権限です。現在、このスクリプトはフォアグラウンドで実行されるようになっているため、その間そのコンピュータは操作を行うことができません。そのため、バックグラウンドで実行されるようにサービス化を行わなければなりません。
そこにつきまとうリスクがアクセス権限の設定です。適切に行わなければ、悪意あるユーザーがファイルの改ざんを行い、不整合が発生する可能性があります。
このスクリプトを実運用にはまだまだ考慮しなければならない点が多いので、改善、参考にしながらよりよいサービスにできるように改善していきましょう。
単位時間のSYNパケットを制限する
今回は二通りのやり方があったので、その二つを紹介する
一つは[ipt_recent]モジュールを用いた制御法である。
- 指定した条件にマッチしたパケットの source IP アドレスを、リストに記 録する
- 「パケットの source IP アドレスがリスト中に存在したら」という条件を 使えるようにする
- IP アドレスを記録するリストはいくつでも作れるので「このプロトコ ルならこのリスト」等の処理が可能
- また、リストにはパケットの source IP アドレスに加えてタイムスタ ンプも記録されるので、「直近 n 秒の間に記録があったら」や「n 回 以上記録があったら」といった条件も指定できます。
(http://www2s.biglobe.ne.jp/~nuts/labo/inti/ipt_recent.html)
この機能を利用して「単位時間のSYNパケットを制限する」を実現します。
まず、sshのフィルタリングルールがわかりやすくなるように「ssh(port: 22)」パケットを処理する専用のチェイン「SSH」とssh接続を拒否する用のチェイン「SSHATACK」を作りましょう。
# iptables -N SSH
# iptables -N SSHATTACK
そして、宛先ポートがssh(22)のSYNパケットを「SSH」チェインに飛ぶように「INPUT」チェインにルールを追加します。
# iptables -A INPUT -p tcp --dport ssh -m state --state NEW -j SSH
ではさっそくこの「SSH」チェインにルールを追加してきます。ちなみに[ipt_recent]モジュールは[-m recent]で利用することができます。
# iptables -A SSH -m recent --name sshbadcon --rcheck --seconds 600 -j REJECT
# iptables -A SSH -m recent --name sshcon --rcheck --seconds 60 --hitcount 3 -j SSHATTACK
# iptables -A SSH -m recent --name sshcon --set
# iptables -A SSH -j ACCEPT
色々ざっと追加しましたが、一つ一つ解説していきます。
まず、一番上に追加したルールですが、これは接続拒否リスト「sshbadcon」の中身をチェックし、直近600秒以内で該当する送信元IPが記録されていたら「REJECT」するというルールです。
# iptables -A SSH -m recent --name sshbadcon --rcheck --seconds 600 -j REJECT
次に、二番目に追加したルールですが、これは接続履歴リスト「sshcon」の中身をチェックし、直近60秒以内で該当するIPが3回記録されていたら「SSHATACK」チェインに飛ばします。
「SSHATACK」チェインで接続拒否リスト「sshbadcon」に記録させる処理を行います。
# iptables -A SSH -m recent --name sshcon --rcheck --seconds 60 --hitcount 3 -j SSHATTACK
そして、三番目と四番目のルールですが、これは上記のルールに該当しなかった正常な通信ということで、接続履歴リスト「sshcon」に送信元IPを記録して、アクセスを許可します。
# iptables -A SSH -m recent --name sshcon --set
# iptables -A SSH -j ACCEPT
この4つのルールにより、「60秒以内に3回ssh接続を試みた送信元IPを[SSHATACK]チェインに飛ばす」、というルールが実現しました。
あとは「SSHATACK」チェインのルールを設定するだけです。
# iptables -A SSHATTACK -m recent --name sshbadcon --set
# iptables -A SSHATTACK -j REJECT
このルールにより「SSHATACK」チェインに来たパケットの送信元IPを接続拒否リスト「sshbadcon」に登録します。その後、その接続を拒否します。
これで「単位時間のSYNパケットを制限する」という内容を実現できました。
iptablesの設定を確認してみます。
# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
SSH tcp -- anywhere anywhere tcp dpt:ssh state NEW
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain SSH (1 references)
target prot opt source destination
REJECT all -- anywhere anywhere recent: CHECK seconds: 600 name: sshbadcon side: source reject-with icmp-port-unreachable
SSHATTACK all -- anywhere anywhere recent: CHECK seconds: 60 hit_count: 3 name: sshcon side: source
all -- anywhere anywhere recent: SET name: sshcon side: source
ACCEPT all -- anywhere anywhere
Chain SSHATTACK (1 references)
target prot opt source destination
all -- anywhere anywhere recent: SET name: sshbadcon side: source
REJECT all -- anywhere anywhere reject-with icmp-port-unreachable
設定できているので、あとはきちんと動作するか確認してみましょう。
# ssh {設定を行った端末のIPアドレス}
[root@localhost ~]# ssh 192.168.1.32
[email protected]'s password:
Permission denied, please try again.
[email protected]'s password:
Permission denied, please try again.
[email protected]'s password:
Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).
[root@localhost ~]# ssh 192.168.1.32
[email protected]'s password:
Permission denied, please try again.
[email protected]'s password:
Permission denied, please try again.
[email protected]'s password:
Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).
[root@localhost ~]# ssh 192.168.1.32
[email protected]'s password:
Permission denied, please try again.
[email protected]'s password:
Permission denied, please try again.
[email protected]'s password:
Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).
[root@localhost ~]# ssh 192.168.1.32
ssh: connect to host 192.168.1.32 port 22: Connection refused
このように、60秒以内に3回ssh接続をすると、それ以降の通信がREJECTされるようになりました。
もし上手くいかない場合はiptablesの順番を疑ってみてください。iptablesは上から順に処理していくので、正しい順番で追加しないと想定通りの動作をしてくれません。
これで「ipt_recent」による「単位時間のSYNパケットを制限」は完了です。
ではもう一つの方法について説明します。
もう一つは[hashlimit]モジュールを用いた制御法である。
hashlimit制御イメージはこのような感じです
- 通信を行うクライアントは「送信元IPアドレス」と「宛先ポート」でまとめられて対象グループとなります。まとめ方は「--hashlimit-mode srcip,dstport」で指定しています
- 対象グループごとにチケットが割り当てられます。チケットを持っていると対象グループは通信を行うことができます。通信を行うごとにチケットを消費します。
- 初期状態では30枚のチケットを持っています。その枚数は 「--hashlimit-burst 30」で指定しています
- チケットは1分ごとに1枚追加されます。追加のペースは「--hashlimit 1/m」で指定しています
- チケットは最大で30枚持つことができます。あふれたチケットは削除されます。最大数は「--hashlimit-burst 30」で指定しています(初期枚数と同じ) (備考:チケット,対象グループなどの言葉は説明の為に付けたもので、一般的な名称ではありません)
(http://qiita.com/homihomu/items/778770a1c1b3b2371fe0)
つまり、「特定の通信」をするための「権利」を設け、その権利が供給される速度を制限することで、頻繁に通信ができなくなるという仕組みです。
今度はこれを利用して「単位時間のSYNパケットを制限する」を実現します。
まず、方法1と同じようにわかりやすくするためにhashlimit専用のチェイン「HASHCHECK」を作ります。
# iptables -N HASHCHECK
そして、これも方法1と同じように宛先ポートがssh(22)のSYNパケットを「HASHCHECK」チェインに飛ぶようにします。
# iptables -A INPUT -p tcp --dport ssh -m state --state NEW -j HASHCHECK
それができたら「HASHCHECK」チェインにルールを追加しましょう。
# iptables -A HASHCHECK -m hashlimit --hashlimit-name ssh_limit --hashlimit 2/m --hashlimit-burst 3 --hashlimit-mode srcip --hashlimit-htable-expire 120000 -j ACCEPT
少しルールが長いですが、それぞれのオプションには以下の意味があります。
--hashlimit-name ssh_limit ・・・ハッシュテーブル名を「ssh_limit」とする
--hashlimit 2/m ・・・時間あたりの接続数を「2/分」に設定
--hashlimit-burst 3 ・・・バースト値(上記--hashlimit値を無視して接続できる数)を「3」に設定
--hashlimit-mode srcip ・・・制限対象を「送信元IPアドレス」で識別する
--hashlimit-htable-expire 120000 ・・・最後のパケットから120000ミリ秒(120秒=2分)でリセットする
これにより、最初の接続は3回まで無制限で許可されるが、それ以降は1分間に2回の制限が掛るようになります。
最後のパケットから2分たった場合、リセットされるようになります。
あとは、このルールで許可されなかったパケットをREJECTするだけです。
# iptables -A HASHCHECK -j REJECT
これで「単位時間のSYNパケットを制限する」という内容を実現できました。
iptablesの設定を確認してみます。
# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
HASHCHECK tcp -- anywhere anywhere tcp dpt:ssh state NEW
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain HASHCHECK (1 references)
target prot opt source destination
ACCEPT all -- anywhere anywhere limit: up to 2/min burst 3 mode srcip htable-expire 120000
REJECT all -- anywhere anywhere reject-with icmp-port-unreachable
Chain SSH (0 references)
target prot opt source destination
Chain SSHATTACK (0 references)
target prot opt source destination
ではきちんと動作するか確認してみましょう。
# ssh {設定を行った端末のIP}
[root@localhost ~]# ssh 192.168.1.32
[email protected]'s password:
Permission denied, please try again.
[email protected]'s password:
Permission denied, please try again.
[email protected]'s password:
Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).
[root@localhost ~]# ssh 192.168.1.32
[email protected]'s password:
Permission denied, please try again.
[email protected]'s password:
Permission denied, please try again.
[email protected]'s password:
Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).
[root@localhost ~]# ssh 192.168.1.32
[email protected]'s password:
Permission denied, please try again.
[email protected]'s password:
Permission denied, please try again.
[email protected]'s password:
Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).
[root@localhost ~]# ssh 192.168.1.32
ssh: connect to host 192.168.1.32 port 22: Connection refused (3回接続を行ったので制限が掛かった)
[root@localhost ~]# ssh 192.168.1.32
ssh: connect to host 192.168.1.32 port 22: Connection refused
[root@localhost ~]# ssh 192.168.1.32
[email protected]'s password: (30秒経過したので、チケットが追加された)
このように接続を3回行ったあと、30秒毎にしか接続できないようになっていればOKです。
これで「hashlimit」による「単位時間のSYNパケットを制限」は完了です。
単位時間のSYNパケットを制限する方法として2つ紹介しましたが、どちらを使うのが良いのでしょうか。
以下は私の見解です。
- より強い効果を求めるなら[ipt_recent]
- より柔軟な制限を求めるなら[hashlimit]
[ipt_recent]は単位時間辺りに既定値を超えた接続があると、一定時間その送信元IPからの接続を遮断します。
対して[hashlimit]は単位時間当たりに既定値を超えた接続があると、一定時間、連続で接続できる回数を制限します。
つまり、[ipt_recent]は悪意がある攻撃が判明した場合、接続を拒否しますが、[hashlimit]は悪意がある攻撃が判明した場合、接続できる回数を制限するだけです。
よって、接続そのものを遮断できる[ipt_recent]のほうがより強力な制限を掛ける事が可能となるのです。
[hashlimit]でも同じような事はできそうですが、「単位時間当たりに既定値を超えた接続」の時間と「一定時間、連続で接続できる回数を制限」の時間をそれぞれ別々に設定することができません。
そのため、時間を短くすると直ぐに攻撃が再開できるようになり、時間を長くすると一般のユーザーまで「悪意がある攻撃」と判断されかねないリスクが発生します。
このような不都合が発生するのであれば[ipt_recent]を使った方が良いでしょう。
[ipt_recent]は送信元IPによる判断しかできませんが、[hashlimit]はそれに加え以下を組み合わせることができます。
- 送信元ポート: srcport
- 宛先IP : dstip
- 宛先ポート : dstport
送信元だけでIPではなく、宛先IPも組み合わせることで接続先サーバー毎に制限を設けることが可能になります。
このような場合は[hashlimit]を使った方が良いでしょう。
しかし、これはあくまで私個人の見解なので、本来はもっと様々な特徴があるかもしれません。
そのため、あくまで参考程度でお願いします。
余談ではありますが、sshで接続を行う際に標準では3回までのパスワード入力の機会が与えられていますが、以下の設定でチャレンジ回数を制限することができる。
# vim /etc/ssh/sshd_config
#PermitRootLogin yes
#StrictModes yes
MaxAuthTries 1 ----チャレンジ回数を1回に設定
#MaxSessions 10
- Nmapでポートスキャンを行い、ネットワーク上のデバイスサービスを把握する
- Nmapは不正アクセスのための偵察ツールと認識されていることが多いが、ネットワーク管理には必要なツールである
TCPの場合は、スリーウェイハンドシェイク(3ステップ)の機能を利用する
ステップ1:対象のサーバにSYNフラグを立てたパケット(SYNパケット)を送信
※SYNパケット:TCPで接続を確立する際に、クライアントからサーバに送られるパケット
ステップ2:対象サーバーで該当のポート番号がオープン(サービスが起動)であれば、対象サーバーは「SYN+ACKパケット」を
返信
※ACKパケット:接続を許可するパケット
ステップ3:対象サーバーにACKパケットを送信
UDPの場合は、UDPパケットを該当サーバーに送信した時に、 何も返信がなければサービスが起動していると判断する
サービスが停止していてポートがクローズしていると、「ICMP port unreachable」のメッセージが返信される
- 下記コマンドでTCPのコネクトスキャンを行います
$ nmap {ホスト名 or IPアドレス}
- また、以下のようにIPアドレスの範囲やネットワークも指定できます
$ nmap 192.168.1.1-254 $ nmap 192.168.1.0/24
- 実行するとポートとその状態、サービスが分かります
- root権限で実行するとより詳細な結果が得られます
- スリーウェイハンドシェイクをステップ2で終わらせます
- コンピュータのログに記録されません
nmap -sS 192.168.1.XXX
- -Oオプションをつけると、OSの種別判定もできます
- 下記の形式のオプションをつけるとスキャン結果をXMLフォーマットで出力できます
-oX XMLファイル名
nmap -sS -O -oX scandata.xml 192.168.1.XXX
- 出力したxmlはperlを用いることでデータベース化もできる
- Nessusを用いて、ネットワーク上のコンピュータがの各サービスが、不正アクセスを受ける可能性のあるセキュリティホールを調査する
- サービスが正常に稼働していれば問題ないと判断するのは不十分である(不正アクセスを受けたときに不具合が発生する可能性がある)
Nessusはサービスが動作するポートを探り当て、実際のそれらに接続して既知の攻撃コード(エクスプロイトコード)で攻撃を試みる
※エクスプロイトコード:ソフトウェアの保安上の弱点(脆弱性、セキュリティホール)を利用して不正な動作が起きる様子を再現するプログラム
そして、エクスプロイトコードの有効・無効を記録しながらすべてのサービスを検査する
下記でNessusを用いたセキュリティホールの調査方法について解説する
ここでは、最新バージョンのNessus 6.Xを用います
-
まず、
http://www.tenable.com/products/nessus/select-your-operating-system
からRPMパッケージ「Nessus-6.10.1-es6.x86_64.rpm」をダウンロードしましょう -
下記コマンドを実行して「Nessus-6.10.1-es6.x86_64.rpm」をインストールしましょう
rpm -ivh Nessus-6.10.1-es6.x86_64.rpm
-
https://www.tenable.com/products/nessus-home
にアクセスして、無償ライセンス用のアクティベーションコードを取得しましょう
※アクティベーションコード:正規のライセンスの保持確認のために行われる認証処理に必要なコード
-
「Register for an Activation Code」の下部にEmailアドレスなどの必要事項を入力します
-
「I agree to the terms of service」にチェックを入れます
-
「Register」ボタンをクリックします
-
入力したEmailアドレスにアクティベーションコードが届きます
-
下記コマンドを実行してNessusサービスを起動しましょう
service nessusd start
-
https://localhost:8834/
にアクセスして初期設定を行いましょう
[ポイント] ・「Initial Account Setup」では管理画面へのログイン用アカウントを作成します ・「Product Registration」では取得したアクティベーションコードを入力します
-
https://localhost:8834/
にアクセスして管理画面にログインします -
Nessusのスキャンのポリシーを設定します
-
「Policies」→「New Policy」の順でクリックします
-
「Basic Network Scan」をクリックします(ここでは、例として「Basic Network Scan」を選択しています)
-
「Name」を入力して「Save」をクリックします
-
Nessusで脆弱性スキャンを行います
-
「Scans」→「New Scan」の順でクリックします
-
「User」→ 設定済みポリシーの順でクリックします
-
「Name」及び「Targets」を入力して「Launch」(Save右の▼)をクリックしてスキャンを開始します
スキャン終了後「My Scans」の中の該当スキャンをクリックすると結果が表示されます
- NTP(Network Time Protocol)を利用して、自動で時刻の同期を行う
- 複数コンピュータ間で起きた事象を調査するときに、時刻にずれがあるとログの整合性が取りづらく、解析が非表示困難になる
NTPは0.1秒の高精度で時刻の同期を行う
NTPはLinuxやFreeBSD、OpenBSDにオプショナルパッケージとして含まれているので、CentOS6でも設定だけで利用可能である
NTPで時刻を同期するためには/etc/ntp.confで時刻情報を提供しているNTPサーバーを指定しなければならない
サーバーを指定した後は、クライアントntpdで時刻同期を行う
下記にNTPで時刻同期を行う詳細な方法を解説する
- まず、NTPの設定ファイル「ntp.conf」を編集し、NTPサーバを指定します
# vi /etc/ntp.conf [ntp.conf] server ntp.nict.jp ※ntp.nict.jpは日本の標準時刻を提供するNTPサーバー
- 起動時に自動的にntpdが立ち上がるようにして、再起動しましょう
chkconfig ntpd on reboot
以上でNTPサーバの設定は完了して時刻の同期が行われるようになりました
また、ntpdはマシン固有のクロック周期に依存する時刻のずれも自動的に修正します
その設定を有効にするには「/etc/ntp.conf」に以下を追記する必要があります
driftfile /etc/ntp.drift
- 自身の端末内に認証局を構築し、証明書の発行をする
SSLサーバがSSLクライアントから信頼されるためには、クライアントが信頼する認証局(CA)によって
署名を受けたSSL証明書を使用していなければならない。
信頼できる認証局として、ThawteやVeriSignのような第三者機関があり、
セキュリティが必要なサイトのSSL証明書に署名を行うサービスを有償で提供している。
しかし、高度なセキュリティが必要とされる場合でなければ、わざわざ前述の第三者機関による
認証局から署名を受けなくとも、自前で認証局を構築してSSL証明書に署名することで、
SSLによるセキュリティ機能の恩恵に授かることができる。
[root@dojo ~]# yum -y install openssl
自身で認証局(CA)を構築する
[root@dojo ~]# /etc/pki/tls/misc/CA -newca
CA certificate filename (or enter to create)
Making CA certificate ...
Generating a 2048 bit RSA private key
・・・
(略)
・・・
Write out database with 1 new entries
Data Base Updated
途中入力項目がいくつかあるので、適切な値を入力する
以下に今回入力したデータを記載する
入力項目 | 意味 | 入力内容 |
---|---|---|
CA certificate filename(or enter to create) | CAファイルを置く場所 | Enter |
Enter PEM pass phrase: | CAの秘密鍵パスワード | パスワード |
Verifying - Enter PEM pass phrase: | CAの秘密鍵パスワードの確認 | パスワード |
Country Name(2 letter code)[XX]: | 国名 | JP |
State or Province Name (full name)[]: | 都道府県 | Ehime |
Locality Name (eg, city) [Default City]: | 市区町村名 | Matsuyama |
Organization Name (eg, company) [Default Company Ltd]: |
組織名 | Aso.alpha.jp |
Organizational Unit Name (eg, section) []: | 担当部署名 | AsoSystem |
Common Name (eg, your name or your server's hostname) []: |
ホスト名 | x.x.x.41 |
Email Address []: | メールアドレス | Enter |
A challenge password []: | 証明書を破棄する際に必要となるパスワード | Enter |
An optional company name []: | 組織名の略称 | Enter |
Enter pass phrase for /etc/pki/CA/private/./cakey.pem: |
CAの秘密鍵のパスワード | パスワード |
作成されたCA証明書は以下の場所に保存されている
名称 | 場所 |
---|---|
公開鍵 | /etc/pki/CA/private/cacert.pem |
CA秘密鍵 | /etc/pki/CA/private/cakey.pem |
秘密鍵を保存するためのディレクトリを作成し、そこで鍵生成を行う
[root@dojo ~]# mkdir /etc/pki/ssl
[root@dojo ~]# cd /etc/pki/ssl
[root@dojo ssl]# openssl genrsa -out server.key -aes256 2048
Generating RSA private key, 2048 bit long modulus
...................................+++
...................................................................+++
e is 65537 (0x10001)
Enter pass phrase for server.key:
Verifying - Enter pass phrase for server.key:
入力項目 | 意味 | 入力内容 |
---|---|---|
Enter pass phrase for server.key: | 秘密鍵のパスワード | パスワード |
Verifying - Enter pass phrase for server.key: | パスワードの確認 | パスワード |
以上の設定で、認証局を構築することができた
サービスで実際に使用する鍵を生成する
ここで、認証局の組織名と異なる情報を入力してしまうと、署名の際にエラーが出るので注意する。
以下に失敗例を示す
[root@dojo ssl]# openssl req -new -key server.key -out server.csr
Enter pass phrase for server.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
(略)
・・・
入力項目 | 意味 | 入力内容 |
---|---|---|
Enter pass phrase for server.key: | サーバー秘密鍵のパスワード | パスワード |
Country Name (2 letter code) [XX]: | 国名 | JP |
State or Province Name (full name) []: | 都道府県名 | Ehime |
Locality Name (eg, city) [Default City]: | 市区町村名 | Matsuyama |
Organization Name (eg, company) [Default Company Ltd]: |
組織名 | Asoalpha.jp |
Organizational Unit Name (eg, section) []: | 担当部署名 | AsoSystem |
Common Name (eg, your name or your server's hostname) []: |
ホスト名 | x.x.x.41 |
Email Address []: | メールアドレス | Enter |
A challenge password []: | 証明書を破棄する際に必要となるパスワード | Enter |
An optional company name []: | 組織名の略称 | Enter |
サーバ証明書へのCAの署名をし、SSL証明書を作成する
[root@dojo ssl]# openssl ca -config /etc/pki/tls/openssl.cnf -in server.csr -keyfile /etc/pki/CA/private/cakey.pem -cert /etc/pki/CA/cacert.pem -out server.crt
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for /etc/pki/CA/private/cakey.pem:
Check that the request matches the signature
Signature ok
The organizationName field needed to be the same in the
CA certificate (Aso.alpha.jp) and the request (Asoalpha.jp)
CAのOrganizationNameが違うため、SSL証明書が作成できなかったとエラーが出ている
サーバ証明書を作り直す
4.と同様の作業を行う
Organization Nameを統一させるため、Aso.alpha.jpとして作成する
[root@dojo ssl]# openssl req -new -key server.key -out server.csr
Enter pass phrase for server.key:
You are about to be asked to enter information that will be incorporated
・・・
(略)
・・・
Organization Name (eg, company) [Default Company Ltd]:Aso.alpha.jp
(略)
再度、署名をする
[root@dojo ssl]# openssl ca -config /etc/pki/tls/openssl.cnf -in server.csr -keyfile /etc/pki/CA/private/cakey.pem -cert /etc/pki/CA/cacert.pem -out server.crt
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for /etc/pki/CA/private/cakey.pem:
Check that the request matches the signature
Signature ok
・・・
(略)
・・・
Sign the certificate? [y/n]:y
failed to update database
TXT_DB error number 2
エラーが出たので 証明書の要求をrevokeする
[root@dojo ssl]# openssl ca -revoke /etc/pki/CA/newcerts/A48A38AD79BA87F1.pem
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for /etc/pki/CA/private/cakey.pem:
Revoking Certificate A48A38AD79BA87F1.
Data Base Updated
それでも上手くいかない場合は/etc/pki/CA/index.txtの中身を削除する。
再々度、署名をする
[root@dojo ssl]# openssl ca -config /etc/pki/tls/openssl.cnf -in server.csr -keyfile /etc/pki/CA/private/cakey.pem -cert /etc/pki/CA/cacert.pem -out server.crt
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for /etc/pki/CA/private/cakey.pem:
Check that the request matches the signature
Signature ok
・・・
(略)
・・・
Certificate is to be certified until Jun 25 02:05:46 2027 GMT (3650 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
問題なく署名できたので、作業を進める
最後に、それぞれの鍵が生成できているのか確認をする
秘密鍵の確認
[root@dojo ssl]# openssl rsa -in server.key -text
CSR(署名要求書)の確認
[root@dojo ssl]# openssl req -in server.csr -text
証明書の確認
[root@dojo ssl]# openssl x509 -in server.crt -text
このままだと、Apacheの再起動をするたびにパスワードを要求されるようになる
端末を起動する際に、httpdが同時に起動する設定であると、画面が暗いまま動かなくなるので、
パスワードの除去をしておく。# openssl rsa -in server.key -out server.key
Enter pass phrase for server.key:
writing RSA key
CentOS6.5 OpenSSLでオレオレ認証局
$yuzu->log(); OpenSSLでオレオレSSL証明書の作成ログ
failed to update database TXT_DB error number2 発生時の対処
- 構築した認証局で署名されたSSL証明書をクライアント側に配布し、SSL通信を行えるようにする
構築した認証局と信頼関係を気づくためには、認証局で署名をしたSSL証明書wお角アプリケーションに配布する必要がある。
SSLは公開カギ暗号方式を使用するため、SSL証明書を秘密にする必要はない。よって、証明書を単純にWebサーバに配置して、HTTPでクライアントにダウンロードしてもらうことができる。
[root@dojo ssl]# yum install mod_ssl
[root@dojo ssl]# vim /etc/httpd/conf.d/ssl.conf
SSLCertificateFile /etc/pki/ssl/server.crt
SSLCertificateKeyFile /etc/pki/ssl/server.key
httpdの再起動
[root@dojo CA]# service httpd restart
httpd を停止中: [ OK ]
httpd を起動中: httpd: apr_sockaddr_info_get() failed for dojo.local
・・・
(略)
・・・
Enter pass phrase:
OK: Pass Phrase Dialog successful.
[ OK ]
Webブラウザが受け入れることのできるSSL証明書の書式はPEMとDERの2種類であるので、DERを生成する
[root@dojo html]# openssl x509 -in cacert.pem -inform PEM -out cacert.der -outform DER
作成したファイルをvar/www/html/へコピー
[root@dojo ssl]# cp cacert.der /var/www/html/
[root@dojo ssl]# cp cacert.pem /var/www/html/
PEMとDERが証明書であることを認識させるために、Apache の mime.typesの設定を変更する
[root@dojo ssl]# vim /etc/mime.types
AddType application/x-x509-ca-cert .der
クライアントからfirefoxで"xxx.xxx.xxx.xxx/cacert.der"へアクセスすると、信頼するかどうかの確認画面が出る
認証後、https通信が行えるようになることが確認できる
- ArpwatchやSniffDetを用いてネットワークが盗聴されていることを検知する
- ネットワークスニファなどのネットワークアナライザツールは、パケット監視などネットワーク管理に役立つが、悪用された場合にはネットワークが盗聴され、パケットが改ざんされるなどの被害が発生する
ネットワークは接続形態によって以下のような2つに分類できる
- 端末がシェアードハブにつながっている接続形態
- ある1台の端末から送信されたパケットは、全ての端末が受信して自分宛てでない場合は破棄する
- パケットが全ての端末に送信されるため「シェアード」という言葉がついている
- 端末がスイッチングハブにつながっている接続形態
- 現在主流となっている接続形態
- スイッチングハブに接続された各端末のMACアドレスを記憶し、ある1台の端末から送信されたパケットは、MACアドレスによって送信先の該当端末にのみ送信される
- MACアドレスを記憶できる量には限界があり、それを超えるとシェアードハブのような振る舞いをする
ここで、シェアードネットワークのような自分の端末に全てのパケットが送信されてくる場合に、自分の端末のポートをプロミスキャスモードにしておくと、全てのパケットを受信してその内容が見れてしまう(これがいわゆるネットワークの盗聴)
- ネットワークインタフェースカード(NIC)の動作モードの1つで、同一ネットワーク内を流れるすべてのパケットを受信して読み込む
- パケットを受信したことは、真の受信者に気付かれない
簡単に前述したが、スイッチドネットワークにおいてもネットワークが盗聴される可能性はゼロでない
偽のMACアドレスを持つフレームを大量に送信し続けると、スイッチングハブがMACアドレスを記憶できる量に達して真のMACアドレスを記憶できなくなり、その結果同一ネットワーク内の全ての端末にパケットが送られてしまう
まず、偽のMACアドレスを持つフレームが大量に送信されていることを検知する方法として、「Arpwatch」というツールを使用することができる
不審なIPアドレスとMACアドレスの組み合わせがネットワークを流れ続けると、Arpwatchのログもそれによって溢れかえる
そのログを見ることで攻撃を簡単に発見できる
次に、ネットワークを盗聴しているユーザーがいるかどうかを検知するためには、プロミスキャスモードになっているポートを探せば良い
プロミスキャスモードになっているポートを探すには「SniffDet」というツールを用いると良い
SniffDetは下記のような特徴を持つ
- プロミスキャスモードの検出に役立つ
- 複数の手法によってネットワークスニファを検出できる
- ARPテスト:プロミスキャスモードでネットワークスニファを実行しているコンピュータのプロトコルスタックのARPクエリの扱いを調査
- DNSテスト:偽IPアドレスからの逆引きクエリを検知してプロミスキャスモードのコンピュータを検知
CentOS6では、SniffDetのインストールに成功していません
ただし、ifconfigコマンドによって現在操作中の端末のポートがプロミスキャスモードかどうかを確認できる
ifconfigコマンドの実行結果に下記のような記述があればプロミスキャスモードで動作しています
UP BROADCAST RUNNING PROMISC MULTICAST MTU:1500 Metric:1
プロミスキャスモードは下記のコマンドで無効化できます
ifconfig [インタフェース名] -promisc
- ファイアウォールのログをDShieldに提供して、世界全体のネットワーク攻撃の現状を知る
- 世界全体のネットワーク攻撃の現状を知るためには、あらゆる端末のログを集計して分析することが必要である
ネットワーク攻撃は日々多種多様化している
それらの攻撃にいち早く対処するためには、どのポートへのどこからのどのような攻撃が1番多いかを知る必要がある
しかし、個人の環境では統計的な情報を得るのが難しい
そこで、DShieldからネットワーク攻撃の統計的な情報を入手する
- distributed intrusion detection system の略
- ネットワーク上の攻撃の情報を集めているプロジェクト
- 世界中のファイアウォールからデータを収集する
- 一般ユーザーからもWebインタフェース経由でログをアップロードできる
- インターネット上で1番トラブルを起こしている人を示した「Top 10 Most Wanted」や最も攻撃をされたポートを示した「Top 10 Target Ports」などの情報が提供されている
まず、DShieldを実行するための環境を作ります。
最初にiptablesのログが出力されるように設定を行いましょう。
ログを出力したい場合はチェイン先を「LOG」にすることで出力することができます。
例: # iptables -A INPUT -j LOG --log-prefix "[iptables firewall] : " --log-level=info
「--log-prefix」を指定することで、出力されるログメッセージに接頭辞を付けることができ、iptablesのログを判別しやすくなります。
また、「--log-level」を指定することで、syslogのプライオリティを指定することもできます。(HACK#73 参照)
しかし、例に記載しているルールは実際に使わないでください。このルールでは受信したパケット全てをログに出力するようになり、正常な通信までもDShieldに送信することになってしまいます。
それだとこちらの内部情報まで送信する可能性があり、DShield側も攻撃ではない不要なログを収集することになってしまうので、送信するログは選別するようにしましょう。
例えば、HACK#61の「単位時間のSYNパケットを制限する」の内容で、「ブルートフォースアタック」と判断するルールがありましたが、その時にログを出力するようにすれば良いでしょう。
プライオリティも設定してログの出力先を分けるなどするとよりわかりやすくログを管理することができるでしょう。
ログの設定が完了したら、次にDShield用のアカウントの作成をします。
# useradd _dshield # passwd _dshield
そして、作成したアカウントのホームディレクトリの配下にbin
ディレクトリを作成します。
(一度ログインをして作成しているのは、ディレクトリのオーナーが_dshield
になるようにするためです)
既にある場合は作成しなくて構いません。
# su - _dshield $ mkdir /bin $ exit
それができましたら、DShieldのクライアントをダウンロードして展開します。
# wget http://www.dshield.org/clients/framework/iptables.tar.gz # tar xvfz iptables.tar.gz
そして、解凍したフォルダ内に移動し、パールのスクリプトiptables.pl
を作成したアカウントの[bin]ディレクトリ内にコピーします。
それと、ログの送信時の設定を行うファイルdshield.cnf
と拡張子が.lst
のファイル全てを/etc
にコピーします。
# cd iptables # cp iptables.pl ~_dshield/bin/ # cp dshield.cnf *.lst /etc
これで環境の方は完了です。
では次に設定ファイルを編集します。
上記でコピーした/etc/dshield.cnf
を編集します。
DShieldに送信されたログが自分でも確認できるように、cc
に自分宛てのメールアドレスを記述します。
[email protected] (任意)
そして、Sendmailの設定をすれば、準備は完了です。
では実際に実行してみましょう。
本来はcronで定期的にログを送信するのが一般的にですが、とりあえず実行できるということだけ確認します。
# su - _dshield $ cd bin $ ./iptables.pl
実行後、DShieldに送信したログのコピーがcc
に設定したアドレスへ送られてきます。
自分へ送信されてくるまでに時間がかかるようですので、次の日に確認するようになると思います。
※AM10時のログのコピーがPM7時に届きました。
- ClamAVを用いて、Windowsと同様にUnix環境でもウイルススキャンが行えるようにする
- Unix環境上でのウイルス対策は、それほど重視されてこなかったが、近年その重要性が増してきた
Unixを狙うウイルスはWindowsに比べて数が少ないため、感染しないと誤認されてきた
その理由は、単純にWindowsよりも利用者数が少ないからである
例として、金銭を目的とした攻撃は、より多くの金銭を手に入れるためより多くの利用者がいるWindowsをターゲットとする
しかし、UnixはオープンソースのOSであるため、技術者が開発しやすい反面、攻撃者に研究しやすいという問題がある
攻撃手法の研究によって効率の良い手法が確立されれば、攻撃が増加する可能性がある
また、Unixはサーバーとして利用されることも多いため、攻撃の被害が広範囲に及ぶ可能性もある
そこで、Unixにもウイルススキャンツールを導入して、ウイルス対策を行う
- オープンソースで提供されているアンチウイルスソフトウェアである
- ウイルスデータベースは最大1日数回アップデートされて、2011年7月20日現在では1,000,066 件のウイルスパターンを保有している
- ウイルスの検出だけでなく、他のソフトと組み合わせればリアルタイムスキャンをしたり、ウイルス検出時にメール通知することもできる
- まず、EPEL(FedoraのパッケージをRHEL上等で導入するためのパッケージ)リポジトリを導入します
rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm
- 次に、ClamAVをインストールします
yum -y install clamd
- 次に、ウイルス定義ファイルを最新化します - ウイルス定義ファイルの更新設定
- /etc/freshclam.confの8行目付近「Example」をコメントアウト(ウイルス定義ファイル更新機能の有効化)
- /etc/freshclam.confの141行目付近に「NotifyClamd /etc/clamd.conf」を追加(ウイルス定義ファイルの更新をclamdに通知)
- freshclamコマンドを実行してウイルス定義ファイルを最新化する
- 次に、ClamAVを設定します
- /etc/clamd.confの195行目付近「User ~」をコメントアウト(root権限で実行するように)
- 最後に、ClamAVを起動します
/etc/rc.d/init.d/clamd start chkconfig clamd on(自動起動の設定)
試しにウイルススキャンしてみましょう
clamscan --infected --remove --recursive -r /home --infected:ウイルスに感染したファイルのみを出力する --remove:ウィルスを発見次第削除する --recursive:サブディレクトリごと再帰的に検査し、圧縮ファイルは再帰的に解凍して検査する -r /home:スキャン対象ディレクトリ
結果中の「Infected files」がウイルスの検出数です
参考)https://centossrv.com/clamav.shtml
- まず、ClamAVを設定します
- /etc/clamd.confの85行目付近が「LocalSocket /var/run/clamav/clamd.sock」となっていることを確認
- /etc/clamd.confの101行目付近「TCPSocket 3310」をコメントアウト(TCP通信無効化)
- clamdを再起動
/etc/rc.d/init.d/clamd restart
- 次に、clamav-milterをインストールして設定します
yum -y install clamav-milter
- /etc/clamav-milter.confの21行目付近を「MilterSocket /var/run/clamav/clamav-milter.sock」に変更
- /etc/clamav-milter.confの189行目付近に「AddHeader Add」を追加(受信メールのヘッダに情報追加)
- 次に、clamav-milterを起動します
/etc/rc.d/init.d/clamav-milter start chkconfig clamav-milter on(自動起動の設定)
- 最後にsendmailを設定して再起動します
- /etc/mail/sendmail.mcの最終行に下記を追加する
INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav/clamav-milter.sock, F=, T=S:4m;R:4m')dnl define(`confINPUT_MAIL_FILTERS', `clamav')
- sendmail.mcからsendmail.cfを作成する
m4 /usr/share/sendmail-cf/m4/cf.m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf
- sendmailを再起動する
/etc/rc.d/init.d/sendmail restart
試しに自分宛てにメールを送ってみましょう
mail [アカウント名]@[ホスト名]
届いたメールのヘッダに「X-Virus-Status: Clean」という記載があればウイルススキャンされています
- セキュリティ関連のメーリングリストやRSSフィード、Cassandraを用いて、最新の脆弱性情報を入手する
- 常にデバイスの脆弱性の最新情報を把握して、最新のパッチを確実に適用していくのは非常に手間がかかる
デバイスの脆弱性の最新情報の提供は、メーカーとそのようなサポート契約を結んでいない限りは提供されない
デバイスの脆弱性の最新情報を把握できずに、最新のパッチを適用していないと、世の中に脆弱性が公開されてしまっているので、それを狙った攻撃を研究してしかけられる可能性がある
そのためデバイスの脆弱性の最新情報を常に把握して、最新のパッチを適用することはネットワーク上の脅威から資源を守るために必要不可欠である
このHACKではセキュリティ関連のメーリングリストやRSSフィード、Cassandraを用いて、デバイスのあらゆる脆弱性の最新情報を大きな手間をかけずに入手する方法を紹介する
- まずメーリングリストとは、複数の人に同時に電子メールを配信する仕組みである
- オープンソースプロジェクトのベンダ(製品の供給業者)は、セキュリティ勧告やパッチのお知らせをBuqTraqやFull-Disclosureにポスト(投稿)する
- BuqTraq:ベンダがベンダ内部で発見した脆弱性や外部から報告された脆弱性を提供
- Full-Disclosure:ベンダから欠陥修正の協力を得られなかった情報を提供
- 多くのオープンソースプロジェクトはそれ自体にメーリングリストを持っているので、その中からセキュリティに関連するメーリングリストを購読することでデバイスの脆弱性の最新情報を入手する
- まずRSSフィードとは、XMLベースのデータ形式の一種で、サイト内の新着記事の一覧や個々の記事の更新日や本文の要約などを含んでいる
- デバイスの脆弱性の最新情報は、RSSフィードでも提供されている
- このRSSフィードを購読することで情報を入手できる
- Cassandraに登録後、ベンダと製品名を入力すると関連する情報を電子メールで報告してくれる
今回確認することができたメーリングリストは2つあります。
Bugtraq(バグトラック)は、コンピュータセキュリティに関するメーリングリストである。脆弱性に関する議論(攻撃方法やそのexploit、回避方法や解決方法も含まれる)、脆弱性を持つ製品情報などが主な議題として挙げられる。 2009年現在もなお、発表される脆弱性の情報はほとんどBugtraqが網羅しており、最大手の交流の場として知られる。(https://ja.wikipedia.org/wiki/Bugtraq)
- 言語は英語
- 最新の情報が入手できる
- タイトルにCVE番号が入っているものは、米国の団体の「共通脆弱性識別子」
- 深刻度はCVSSでレベル分けされている
- 重要度が高いものは日頃から追いかける
よって、最新の脆弱性情報を入手したいのであればこちらのメーリングリストに登録するのが良い。ただし、言語が英語なので英語ができる必要があります。
登録は下記の手順でできます。
- Bugtraq にアクセス
- 登録するメールアドレスを入力
- 「Bugtraq」のみをチェック
- 「Subscribe」で仮登録
- 登録アドレスに確認メールが届く
- 確認メールに空メールを返信して登録完了
もうひとつは以下である。
JPCERT/CCはインターネットを介して発生する侵入やサービス妨害などのコンピュータセキュリティインシデントを日本国内のサイトに関する報告の受け付け、対応の支援、発生状況の把握、手口の分析、再発防止のための対策の検討や助言などを、技術的な立場から行なっています。(https://www.jpcert.or.jp/about/brief/)
よって収集できる情報は日本国内のものに限りますが、セキュリティインシデントなどの情報を少なからず知ることができます。
前の週のセキュリティ関連情報のうち、JPCERT/CC が重要と判断したものを抜粋してまとめたものです。
深刻且つ影響範囲の広い脆弱性などに関する情報を告知するための文書です。
ゴールデンウィークや年末年始などの長期休暇の前に注意すべき点などを簡単に紹介する文書です。
(https://www.jpcert.or.jp/announce.html)
登録方法は以下である。
- 「[email protected]」 宛に下記のようなメールを送る
これにより登録したメールアドレスに脆弱性や攻撃についての情報が送られてきます。
その他本で挙げられていたサイトや機関に関しては既に活動を停止しており、確認することができませんでした。