第5章 より安全に運用するために

Last Modified: Tue Sep 21 11:22:10 UTC 2010

この章では OpenSSH をより安全に、快適に運用するためのコツを紹介します。 OpenSSH はデフォルトの設定で使っているだけでもさして不都合はありません。しかし、設定に ちょっとした工夫を加えることでシステムをずっと安全にでき、 ユーザにとっても管理者にとっても快適な環境にできるかもしれません。 具体的な方法は OpenSSH を使う組織や環境によって異なるため、 どの方法がベストとは言えませんが、この章ではさまざまなアイデアを紹介することで、 システム管理者の方々に工夫のヒントを提供することを目指しています。

5.1. セキュリティはユーザとの協力から

ネットワークセキュリティに限らず、システムのセキュリティを保つには ユーザが「安全かつ簡単に」使える仕組みをつくりだすことが非常に重要です。 とくに、セキュリティに関連した機能が「提供する側」と「使う側」に 分かれている場合、単純に「提供する側」の都合だけでつくられた システムは必ず破綻します。 OpenSSH の場合、セキュリティを「提供する」のはシステム管理者であり、 「使う」のはサーバにログインする一般ユーザです。 自宅で個人サーバを立てている場合など、システム管理者とユーザが 同一人物である場合をのぞけば、システム管理者とユーザの 協力および信頼関係はセキュリティを守るために不可欠です。 OpenSSH でセキュリティを守れるかどうかはシステム管理者とユーザとの協力しだいである、といえます。

ただし、これはいたずらに厳しい規則をユーザに課すべきだ という意味ではありません。セキュリティの世界では、昔から 「最小権限の原則 (Principle of Least Priviledge)」 ということが言われています。これは 「各ユーザには、その作業に必要な最小限の権限しか与えない」 という原則ですが、これはそのユーザの便宜までも 最小限に制限してよいという意味ではありません。 システム管理者にとってユーザは「お客様」であり、 ほとんどの場合、一般ユーザにとってセキュリティを意識しなければならないのは苦痛です。 たとえば、もしユーザが長くて覚えにくい パスフレーズを 1 日数十回も入力させられるようでは、 そのユーザはいずれパスフレーズを紙に書いて画面の前に貼りつけてしまうでしょう。 この例でいえば、ユーザが認証エージェントなどの仕組みを使えるように 管理者が積極的に支援するべきであって、 ユーザに複雑なパスフレーズを覚えるよう強要するのは逆効果です。 逆に、数ヶ月に 1 回程度しかログインしないユーザの場合は、 秘密鍵のパスフレーズを忘れてしまうことがあります。 このような場合、ユーザは秘密鍵・公開鍵ペアを作成し直し、管理者はそれをサーバに 登録しなければなりません。これはユーザにとっても管理者にとっても苦痛でしょう。 このような場合は、パスワード認証を使うほうがよいかもしれません。 つまり、セキュリティを守る方法はユーザごとに異なるのです。 システム管理者にはユーザの協力が得られるよう、 ユーザの目的や使用環境、そしてログインする頻度に応じて なるべく使いやすい環境を工夫する姿勢が求められます。

5.1.1. ユーザが必要としているものは何か

システム管理者は、 「ユーザがシステムをどう利用するのか」という問いに対して、 おおまかに答えることができなければなりません。 もちろん、その答えはシステムを運用する組織ごとに大きく異なります。そのため、 システム管理者はユーザの要望とシステムの危険度を勘案して最適な運用方法を考える必要があります。 ただし、規定をあまり細かくしすぎると柔軟な運用が難しくなり、 ユーザにとっても管理者にとっても負担が増してしまいますので、 うまく「落としどころ」を見つける必要があるでしょう。 また、1.2. OpenSSH にはできないこと でも述べたように、OpenSSH がカバーできるセキュリティには上限があります。 サーバに対して物理的にアクセスされてしまうような状況では、 ネットワーク上のセキュリティを考えることは無意味ですので、 あまり非現実的な状況を想定するよりも、日常的に起こりそうなことを 確実に把握して運用方法を決定するようにしましょう。

OpenSSH の設定や運用方法に影響する要素を、 以下にまとめます:

以上の要素をふまえた上で、システム管理者は以下のような項目について検討を加え、運用方針を決定していきます。

システム全体のパスワード認証を許可する必要はあるか

本書ではくりかえし、公開鍵認証を使うことをおすすめしています。 OpenSSH のパスワード認証は手軽ですが、パスワードを設定すると どうしてもユーザとシステム管理者のあいだで直接情報をやりとりする機会が多くなってしまいます。 また、インターネット上の OpenSSH サーバを狙った パスワード推測攻撃は近年急増しています。そのため、システム全体で パスワード認証を許可すれば、システム管理者はそれだけ余計な心配をしなければなりません。 また、パスワード認証では 5.4. ユーザの操作を制限する で述べるようなユーザごとの細かい権限設定ができません。 パスワード認証は公開鍵認証が使えないときの最終手段であると思っておいてください。

ところが、どうしてもパスワード認証が必要な状況も起こりえます。 たとえばシステムにめったにログインしないユーザを一時的にログインさせたいとか、 秘密鍵が使えない状況でログインさせたいなどの場合です。 このような場合はパスワード認証を許可したうえで、 なるべく攻撃されにくく、かつ利便性を損なわない運用方法をみつける必要があるでしょう。 具体的には、なるべく推測されにくく安全なパスワードを使ったり (5.1.4. パスワード認証を安全に使うには 参照)、 ログインするクライアントの IP アドレスを制限したり (5.2.4. システム全体で特定のホストからのみログインを許可する 参照) するとともに、 パスワードの推測攻撃にそなえて防御策を講じておく (5.5.3. パスワード推測攻撃を避ける 参照) 必要があります。

一方、公開鍵認証が使えるユーザにはパスワード認証の使用を禁止できるので (5.3.2. 公開鍵認証を使っているユーザのパスワード認証を禁止する 参照)、なるべくパスワード認証を使う人を限定しておくことも安全性の向上につながります。

ユーザに通常のアカウントを与える必要はあるか

もし、あるユーザのサーバ上における作業がファイル転送や CVS などに限られているなら、 そのユーザの権限をある程度制限するアカウントをつくることにより、 いざというときに秘密鍵またはパスワード漏えいの被害を抑えることができます。 OpenSSH を利用するユーザが数人程度の場合は、目立った効果を得られないかもしれませんが、 CVS やファイル転送のみを利用するユーザが数十人〜百人といった規模でいる場合、 制限アカウントの作成は手間をかけるだけの意義が十分にあるでしょう。 こうした対策は外部からの攻撃に対するセキュリティを上げるとともに、 ユーザのあやまった操作からシステムを守るのに役立ちます。 5.4. ユーザの操作を制限する では、このような制限をもったユーザアカウントの設定方法を紹介します。

どこからでもログインできるようにする必要はあるか

ユーザのログインするクライアントがつねに一定の場合、最小権限の原則により、 そのユーザのログイン元 IP アドレスを制限しておくことは安全性の向上につながります。 これはシステム全体で設定することもできます (5.2.4. システム全体で特定のホストからのみログインを許可する 参照) が、 ユーザごとに設定することもできます (5.4.1. ユーザの公開鍵にオプションを指定する 参照)。 とくに、そのユーザがパスワード認証や、パスフレーズなしの秘密鍵 [脚注: パスフレーズなしの秘密鍵を一般ユーザに利用させてはいけません。しかし、 自動スクリプトなどを用いて、人手を介さずに OpenSSH クライアントを使う場合には必要です。] などの危険な認証方法を使う場合には、IP アドレスをできるだけ制限しておいたほうがよいでしょう。 ただし、こうした IP アドレスによる制限はあくまで補助的な役割しか果たさないことに注意してください。 攻撃者は IP アドレスを詐称できるかもしれないのです。そのため、たとえ IPアドレスを制限していようと、 ユーザは依然として推測されにくいパスワードを使う姿勢が求められます。

root のログインを許可する必要はあるか

OpenSSH を使うと、リモートから root ユーザアカウントでログインすることを許可することができます。 これは root のみがおこなえるシステムのメンテナンスや、ネットワーク経由の 自動バックアップなどの作業をおこなうための機能ですが、 リモートからの root ログインを許可する場合は、一般ユーザの場合よりも厳重な注意が必要です。 最近多発しているパスワード推測攻撃でも root アカウントが標的とされるケースがもっとも多く、 リモートからの root ログインを許可する場合は ログイン元の IP アドレスを制限したり (5.2.4. システム全体で特定のホストからのみログインを許可する 参照)、 強制コマンド実行を使う (5.4.1. ユーザの公開鍵にオプションを指定する 参照) など、 できるかぎりその権限を限定しておくようにしてください。root でログインして シェルを使う場合にはぜひとも公開鍵認証を使うべきです。

5.1.2. 公開鍵認証の使用を推奨する

システムのセキュリティを高めるために重要なのが、 ユーザに公開鍵認証を使ってもらうことです。 このためにはユーザに公開鍵認証の仕組みを理解してもらい、 秘密鍵を作成してもらわなくてはなりません。 しかしユーザによっては、こうした OpenSSH の複雑な仕組みを すべて理解する時間がとれないという場合もあるでしょう。 このような場合は、システム管理者の側である程度、 公開鍵認証を使う準備を肩代りする必要があるかもしれません。

公開鍵認証を使うユーザには、最低限、以下のようなことを説明しておきましょう:

秘密鍵・公開鍵ペアを準備する方法は、 ユーザの使っているクライアントごとに異なります。

ユーザが OpenSSH を使っている場合

ユーザが UNIX 上で OpenSSH クライアントを使っている場合、 ユーザ側のマシンでは以下のような手順を実行してもらう必要があります。

  1. ssh-keygen -t rsa を実行し、パスフレーズを入力する。
  2. ssh-keygen -f -l ~/.ssh/id_rsa.pub を実行し、公開鍵の指紋を確認してもらう。 (指紋は電話などの別の通信手段を利用して確認する)
  3. ~/.ssh/id_rsa.pub の内容をメールで送ってもらう。

また、サーバ側の設定によってはログイン時に接続先のポート番号などをオプションとして指定する必要があります。 このようなときには、ログイン時の入力を簡略化してくれる、~/.ssh/config に置いてもらうのもよいでしょう (4.7. 個人用の設定ファイルでさらに快適に 参照)。

ユーザが OpenSSH 以外のクライアントを使っている場合

ユーザが Windows 上から PuTTY のようなクライアントを使っている場合は、 あらかじめクライアントと秘密鍵ファイル (.ppkファイル) をコピーした フロッピーディスクなどを渡しておくという手もあります。 さいわい、putty.exe は小さいアプリケーションですので、 秘密鍵ファイルや認証エージェントの pageant.exe とともに フロッピーディスク 1 枚(およそ1.4 Mバイト)におさめることができます。

5.1.3. 認証エージェントの使用を支援する

あるユーザが短時間に何度もサーバにログインする場合、 秘密鍵のパスフレーズをそのつど入力するのは面倒でしょう。 このような場合は、そのユーザに認証エージェントの使用をすすめたほうがよいかもしれません。 しかし認証エージェントは使いこなすまでに時間がかかり、とくに UNIX の初心者にとっては難解です。 認証エージェント (4.4. 認証エージェントを使う 参照) を正しく使うには ssh に加えて 2つのコマンド (ssh-agentssh-add) の使い方を覚えなければ なりませんし、認証エージェントの動きそのものも複雑です。かといって、 中途半端に使い方を覚えてしまうと、うっかり終了し忘れた ssh-agent プロセスが 残ってしまい、かえってセキュリティの低下につながりかねません。 このような場合は、ユーザが安全かつ簡単に認証エージェントを使えるよう、 システム管理者が積極的に支援すべきです。
注意
以下の変更は必ずオペレーティングシステムの動作を理解してからおこなってください。 変更の方法を間違えると、ユーザがシステムにログインできなくなることがあります。 また、これらの方法では ssh-agentssh-add が正しく インストールされていることを仮定しています。 かならず ssh-agentssh-add が デフォルトで実行できるパス上にあることを確認してください。

ユーザがログインした瞬間から ssh-agent を走らせる

おそらくほとんどの場合、認証エージェントはユーザがログインしてから ログアウトするまでずっと働き続けているのが普通でしょう。ならば ユーザがローカルマシンにログインした瞬間から、すべてのプログラムが ssh-agent の子プロセスとして起動していれば便利です。 こうすることにより、ユーザがログインしている間はどのプロセスからでも 確実に ssh-agent にアクセスでき、ユーザがログアウトすれば ssh-agent も終了します。

X11 を使っている場合、これはユーザのログインスクリプトを書き換えるか、 xdmgdm、あるいは xinit などに 付属するスクリプトを書き換えることにより実現できます (図 xsession)。 [脚注: なお Red Hat Linux (バージョン 9 以上) ではこの方法がすでに採用されており、 ssh-agent がインストールされている場合はユーザが gdm あるいは kdm 経由でログインすると必ず ssh-agent が起動しています。]


図 xsession. すべてのプロセスを ssh-agent の子プロセスとして起動する

たとえばユーザのログインに xdm を使っている場合なら、 xdm のスクリプト Xsession [脚注: ディストリビューションによって異なりますが、 たとえば FreeBSD では /usr/X11R6/lib/X11/xdm/Xsession です] を以下のように書きかえます:

変更前:

if [ -x "$startup" ]; then
    exec "$startup"
else
    exec /bin/sh "$startup"
fi

変更後:

if [ -x "$startup" ]; then
    exec ssh-agent -t 3600 "$startup"
else
    exec ssh-agent -t 3600 /bin/sh "$startup"
fi

このように $startup スクリプトを ssh-agent の子プロセスとして 実行させることにより、ユーザのホームディレクトリにある .xsession スクリプトが ssh-agent の子プロセスとして実行されるようになります。 ここでは ssh-agent-t オプションをつけることで、 秘密鍵のデフォルトの生存時間を 3600秒 (1時間) にしています。 秘密鍵の生存時間は ssh-addコマンドでも指定できますが、 ssh-add で生存時間を指定しない場合、ssh-agent コマンドの 生存時間が使われます。デフォルトでは秘密鍵の生存時間は無限大 (ssh-agent を終了するまで存在しつづける) になっていますが、長時間席をはずしたときのことを考えると運用上妥当と思われる程度の 制限時間を設定しておいたほうが安全です。

別のやり方として、システム管理者が新しいユーザアカウントを作成する際に カスタマイズした .xsession.xinitrc をユーザの ホームディレクトリに置いておく方法もあります。この場合は、以下のようにすることで 複数のコマンドを ssh-agent の子プロセスとして実行できます。

変更前:

twm &
xclock -geometry 50x50-1+1 &
xterm -geometry 80x50+494+51 &
xterm -geometry 80x20+494-0 &
exec xterm -geometry 80x66+0+0 -name login

変更後:

exec ssh-agent sh -c 'twm &
xclock -geometry 50x50-1+1 &
xterm -geometry 80x50+494+51 &
xterm -geometry 80x20+494-0 &
exec xterm -geometry 80x66+0+0 -name login'

認証エージェントを自動的に使うよう ssh コマンドを置き換える

ssh-agent がデフォルトで走るようになったら、 つぎは sshコマンドと ssh-addコマンドを統合してみましょう。 ssh を使うときに認証エージェントの有無を確認し、 もし認証エージェントが起動していて、秘密鍵が登録されていない場合は 自動的に ssh-addコマンドを実行するように設定するのです。 これは以下のようなシェルスクリプトで実現できます。
ssh のラッパー・スクリプト
#!/bin/sh
SSH_IDENTITY=${SSH_IDENTITY:-~/.ssh/id_rsa}
SSH_AUTH_DURATION=${SSH_AUTH_DURATION:-3600}
# (環境変数 SSH_AUTH_SOCK の指すソケットと秘密鍵ファイルが存在しており、
#  なおかつ認証エージェントが秘密鍵を保持していなければ、ssh-add を実行する)
if [ -e "$SSH_AUTH_SOCK" -a -f "$SSH_IDENTITY" ] && ! ssh-add -l >/dev/null 2>&1; then
  echo "Adding a private key to the authentication agent."
  ssh-add -t $SSH_AUTH_DURATION "$SSH_IDENTITY" || exit $?
fi

# (本来の ssh コマンドを実行する)
exec ssh.bin "$@"

ここではまず $SSH_AUTH_SOCK の値を調べることにより 認証エージェントの存在を、そして ssh-add -l を実行することにより エージェントが鍵を保持しているかどうかを判断しています。 もし認証エージェントが存在しているのに鍵を持っていない場合は if節の中が実行され、ssh-add が 指定された生存時間で秘密鍵を登録します。登録する秘密鍵ファイルの名前は SSH_IDENTITY 環境変数に、鍵のデフォルト生存時間は SSH_AUTH_DURATION 環境変数にそれぞれ格納されていることを想定しています。 これらの環境変数が設定されていない場合、秘密鍵ファイルは ~/.ssh/id_rsa に、 鍵の生存時間は 3,600秒 (1時間) になります。 [脚注: この例では、ssh-agent でデフォルトの鍵の生存時間が指定されていなかったときに備えて ssh-add でも同じ生存時間を指定しています。]

このスクリプトを、たとえば /usr/local/bin/ssh というパス名に保存し、 もともとの sshssh.bin のように変更すれば ユーザは普通に ssh コマンドを使っているだけで、 自動的に認証エージェントに秘密鍵を渡すことができます。もちろん 1時間に 1回だけパスフレーズを入力すればよいことになります。

実行例:

client$ ssh yusuke@server.example.com     (サーバにログインする)
Adding a private key to the authentication agent.
Enter passphrase for /home/yusuke/.ssh/id_rsa:      (1回目は秘密鍵のパスフレーズを尋かれる)
Identity added: /home/yusuke/.ssh/id_rsa (/home/yusuke/.ssh/id_rsa)
Lifetime set to 3600 seconds
Last login: Mon Apr  3 16:01:53 2006 from xx.xx.xx.xx
server$ exit
logout
client$ ssh yusuke@server.example.com     (もう一度ログインする)
Last login: Mon Apr  3 16:03:09 2006 from xx.xx.xx.xx      (秘密鍵のパスフレーズを尋かれない)
server$

sshon コマンドを作る

X のログインスクリプトを書き換えられない場合は、 以下のような "sshon" コマンドを作るという方法もあります。
sshon コマンド
#!/bin/sh
SSH_IDENTITY=${SSH_IDENTITY:-~/.ssh/id_rsa}
SSH_AUTH_DURATION=${SSH_AUTH_DURATION:-3600}
# (認証エージェントがすでに起動している場合は ssh-add を実行する)
if [ -e "$SSH_AUTH_SOCK" -a -f "$SSH_IDENTITY" ] && ! ssh-add -l >/dev/null 2>&1; then
  echo "Adding a private key to the authentication agent."
  ssh-add -t $SSH_AUTH_DURATION "$SSH_IDENTITY" || exit $?
else
  # (まだ起動していない場合は認証エージェントを起動し、子プロセス内で ssh-add を実行する)
  exec ssh-agent -t $SSH_AUTH_DURATION sh -c "ssh-add $SSH_IDENTITY && exec $SHELL"
fi

このコマンドは認証エージェントが存在していなければ 認証エージェントが使える子プロセスのシェルを起動し ssh-add を 実行して秘密鍵を追加します。認証エージェントが存在している場合は ssh-add のみを実行します。環境変数 SSH_IDENTITYSSH_AUTH_DURATION の意味は 先ほどの ssh スクリプトと同じです。 このようなスクリプトを使うと、ユーザはリモートホストにログインしたい時に まず sshon を実行し、パスフレーズを入力すれば、 その後 1時間のあいだはパスフレーズなしでログインできるようになります。 鍵の生存時間が切れた場合は、ふたたび sshon を実行すればよいのです。

実行例:

yusuke@client$ sshon                                 (sshon を実行する)
Enter passphrase for /home/yusuke/.ssh/id_rsa:      (秘密鍵のパスフレーズを入力する)
Identity added: /home/yusuke/.ssh/id_rsa (/home/yusuke/.ssh/id_rsa)
Lifetime set to 3600 seconds
yusuke@client (SSH) $ ssh server                     (サーバにログインする)
...
yusuke@client (SSH) $ exit                           (sshon から抜ける)
yusuke@client$

さらに、以下の内容をシェルの設定ファイルに追記しておけば、 sshon で認証エージェントが 有効になっている (SSH_AUTH_SOCK 環境変数に値が設定されている) 間は プロンプトの表示を変えておくこともできます。
bash の場合 (~/.bashrc)
if [ "$PS1" ]; then 
  if [ "$SSU_AUTH_SOCK" != "X" ]; then 
    PS1="\u@\h (SSH) \\$ "
  else
    PS1="\u@\h\\$ "
  fi
fi
tcsh の場合 (~/.cshrc)
if ( $?prompt ) then
  if ( $?SSH_AUTH_SOCK ) then
    set prompt="%n@%m (SSH) %% "
  else
    set prompt="%n@%m%% "
  endif
endif

5.1.4. パスワード認証を安全に使うには

OpenSSH のパスワード認証は公開鍵認証ほど安全ではありませんが、 この手軽さが捨てがたい場合があるのもまた事実です。 たとえば OpenSSH をあまり使わない初心者に公開鍵認証の仕組みとメリットを理解してもらうのは難しい 場合もあるでしょうし、秘密鍵ファイルをもっていないユーザに ログインしてもらわなければならない場合もあるでしょう。

推測されにくいパスワードを紙に書け
このような場合、とにかくできるだけ安全な (推測されにくい) パスワードを 選ぶことが重要です。また、なりすまし攻撃を避けるために、 ホスト公開鍵の指紋をあらかじめユーザに配布し、ログインのさいには 必ず確認してもらうようにすることも必要です。 しかし OpenSSH に慣れないユーザに これら 2つのことを快く協力してもらうためにはどうすればよいでしょうか。 推測されにくいパスワードは、同時に覚えにくいことがほとんどです。 セキュリティ研究者の Bruce Schneier氏は 「パスワードは推測しにくいものを紙に書いておけ」と述べています。
[脚注: http://itpro.nikkeibp.co.jp/free/ITPro/Security/20050722/165135/
原文は http://www.schneier.com/crypto-gram-0507.html#7 ]
また OpenSSH でパスワード認証を使う場合には、パスワードを紙に書いておけば 同時にホスト公開鍵の指紋も一緒に書きこめるという利点があります。 ただ、ユーザ名やサーバのホスト名までも書くとさすがに危険でしょうから、 それくらいは覚えてもらいましょう。

ここではランダムなパスワードを生成するために pwgen というツールを使ってみます。 pwgen はソースコードで配布されており、どの UNIX 系オペレーティングシステムでも 一般的な手順でコンパイルできます。[脚注: http://sourceforge.net/projects/pwgen/ ] これを使うと、恣意的な文字列を避けて安全なパスワードを選ぶことができます。 pwgen は、デフォルトでは数字とアルファベットを使った、 なるべく覚えやすい (英単語ではないが、英語の発音に近い) パスワードを生成するように設定されています。 パスワードを紙に書く場合はもっと覚えにくいパスワードでもよいでしょうから、 -y オプション (記号入りのパスワードを生成する) や -s オプション (発音の規則を無視し、覚えにくいパスワードを生成する) を 使うことをおすすめします。

$ pwgen -1         (パスワードをひとつ生成する)
eib8xo7E
$ pwgen -y -1      (記号入りのパスワードを生成する)
Bei9nu{f
$ pwgen -s -y -1   (記号入りの覚えにくいパスワードを生成する)
-bApbB0?

こうして生成されたパスワードと、サーバのホスト公開鍵の指紋を一緒に 名刺サイズの紙に印刷して、以下のような「ログイン用カード」をつくります。
ホスト公開鍵の指紋とパスワードを記録したログイン用カード
サーバを確認: 2048 xx:xx:xx:xx:...

パスワード:

これをユーザの財布に入れてもらうようにすれば、そのユーザはパスワードを (少なくとも現金やクレジットカード番号と同じくらいには) 大切にしてくれるだろうと期待できます。 万が一カードをなくしたときは、すぐにシステム管理者に 連絡してもらうよう頼んでおくことも大切です。 またシステム管理者はパスワード認証を許可するにあたって、 安全なパスワードをつけるだけでなく、 パスワード推測攻撃の被害を避けるために sshd サーバの 設定を強化したり (5.5.3. パスワード推測攻撃を避ける 参照)、 パスワード認証を使わないユーザにはパスワードの使用を禁止したりしておく (5.3.2. 公開鍵認証を使っているユーザのパスワード認証を禁止する 参照) などの工夫が必要になります。

5.2. sshd を安全に設定する

sshd サーバデーモンは、起動時に OpenSSH 設定ファイル用のディレクトリにある sshd_config ファイルを読み込みます。 通常このファイルのパーミッションは root だけが読み込み、および変更できるように設定されています。

このファイルはテキスト形式で、各行にひとつの設定項目が書かれています。
sshd_config ファイルの例
# $OpenBSD: sshd_config,v 1.73 2005/12/06 22:38:28 reyk Exp $
#
# This is the sshd server system-wide configuration file.  See
# sshd_config(5) for more information.
#
# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin

Protocol 2,1
AddressFamily any
ListenAddress 0.0.0.0

# HostKey for protocol version 1
HostKey /etc/ssh/ssh_host_key
# HostKeys for protocol version 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key

# Lifetime and size of ephemeral version 1 server key
KeyRegenerationInterval 1h
ServerKeyBits 768
...

各行は「設定項目 値」のような形式となっており、# 以降行末まではコメントです。 通常、設定項目の名前は大文字と小文字が混じったかたちで記述されていますが、 実際の設定ファイルの中では設定項目名の大文字・小文字は区別されません。

設定ファイルを変更した場合は、sshd サーバデーモンを 再起動する必要があります。 sshd プロセスに SIGHUP シグナルを送信した場合も プロセスはいったん終了し、同じファイルがふたたび実行されます。 ただし設定ファイルに文法上のミスがあると sshd は起動後すぐに終了してしまうため、あらかじめ設定ファイルの チェックをおこなっておくことをおすすめします。これには sshd -t コマンドを使います。
設定ファイルの文法をチェックする
# /絶対パス名/sshd [-d] -t

(-d オプションを指定するとデバッグ出力を表示します)

なお sshd を終了しても、現在すでにログインしているセッションは そのまま継続しますので、リモートホストからログインして sshd を 再起動しても現在の接続が切られることはありません。

5.2.1. システム全体でパスワード認証を禁止する

システム全体でパスワード認証を禁止するには、 sshd_config設定ファイルの PasswordAuthentication設定項目に no を指定します。また、PAM が使用可能になっている場合は PAM のチャレンジ・レスポンス認証がパスワード認証と同じ機能をもっているため、 ChallengeResponseAuthentication にも no を指定して 禁止する必要があります。
注意
パスワード認証を禁止する場合は、PasswordAuthentication設定項目とともに、かならず ChallengeResponseAuthentication設定項目にも no を指定してください。 ChallengeResponseAuthentication設定項目が yes を設定されていると、 PAM を使っている場合にパスワードによるログインを受けつけてしまうことがあります。
システム全体でパスワード認証を禁止する
PasswordAuthentication no
ChallengeResponseAuthentication no

なお、特定のユーザのパスワード認証だけを禁止する方法は 5.3.2. 公開鍵認証を使っているユーザのパスワード認証を禁止する を参照してください。

5.2.2. 特定のユーザのログインを禁止する

OpenSSH のデフォルトの設定では、sshd は すべてのユーザをログインさせるようになっています。 [脚注: デフォルトで root をログインさせるかどうかは オペレーティングシステムやパッケージによって違っています。] しかし、サーバにログインするユーザや、そのユーザの所属するグループが あらかじめわかっている場合には、ログインできるユーザやグループを 制限しておくことをおすすめします。これによってパスワード推測攻撃による 被害を最小限におさえることができるからです。

sshd_config設定ファイルの AllowUsersDenyUsersAllowGroupsDenyGroups の 各設定項目を使えば、特定のユーザやグループのログインを禁止したり、 あるいは特定のユーザやグループだけをログインさせることができます。
特定のユーザまたはグループのログインを禁止する
DenyUsers [ユーザ名1] [ユーザ名2] ...
DenyGroups [グループ名1] [グループ名2] ...
特定のユーザまたはグループのログインのみを許可する
AllowUsers [ユーザ名1] [ユーザ名2] ...
AllowGroups [グループ名1] [グループ名2] ...

AllowUsersAllowGroups は、 そのユーザ (あるいはそのユーザが所属するグループ) だけが ログインできることを意味します。 したがって、AllowUsersAllowGroups 設定項目を 指定した場合、ここに載っていないユーザやグループは必ずログインが禁止されます。

また、これらの設定項目では、つねに「許可」よりも「禁止」が優先します。 したがって、たとえあるユーザ名を AllowUsers で許可していても、 そのユーザ名 (あるいはそのユーザが所属するグループ名) が DenyUsersDenyGroups で禁止されている場合は そのユーザはログインが禁止されます。 なお、OpenSSH では root だけは特別扱いしており、たとえ root を AllowUsersAllowGroups で許可していても、 次項で説明する PermitRootLogin 設定項目の値によっては root ログインが禁止されます。

Root のログインが必要ない場合、一般的には、 たとえばリモートからログインする可能性のあるユーザを remote などというグループにまとめておき、

AllowGroups remote
としておけば、root を含むいっさいの他のユーザアカウントでの ログインを禁止できます。

また OpenSSH では、ユーザのパスワードのハッシュ文字列が !! (Red Hat)、* (FreeBSD) あるいは *LK* (Solaris) に設定してある場合や、そのユーザの (/etc/passwd ファイルで指定されている) ログインシェルが実行できない場合は、そのアカウントは「禁止された」ものとみなされ、 いかなる場合もログインが拒否されます。通常 bindaemonadm などのユーザアカウントは、 たとえ root 権限を持っていなくとも特別な権限をもっている場合があり、 [脚注: 現在ではそれほど一般的ではありませんが、 これらのアカウントはかつて /bin/ 以下のファイルの所有者として 使われたり、デーモンプロセスを走らせるユーザとして使われていました。] ログインされると危険です。 たいていのオペレーティングシステムでは、こうしたアカウントは デフォルトで使用不可能に設定されているはずです。

5.2.3. Root のログインを禁止する

OpenSSHでは、いくつかの条件を設定しながら、リモートホストからの root ログインを禁止あるいは許可することができます。 この条件は sshd_config設定ファイルの PermitRootLogin設定項目によって指定できます。

なお、root ログインの禁止に関しては 前項で述べた AllowGroupsAllowUsers 設定項目の値が意味する設定が、 PermitRootLogin のそれよりも優先されます。たとえ PermitRootLogin設定項目を許可したとしても root が AllowGroupsAllowUsers などの設定項目で 許可されていない場合は root ログインはすべて禁止されます。

いっさいの root ログインを禁止する

UNIX や Linux をインストールしたクライアントマシンや、 管理者がつねに常駐している場所で動かしているサーバマシンなどでは、 リモートからの root ログインを許可する必要はありません。 この場合は、PermitRootLogin設定項目に no を指定することで root のログインをいっさい禁止できます。
いっさいの root ログインを禁止する
PermitRootLogin no

root ログインを許可するが、シェルは使用させない

OpenSSH では、制限された条件下で root ログインを許可することができます。 具体的には、リモートからなんらかの自動スクリプトを root 権限で実行させる必要があるものの、 シェルは使わせたくないという場合に、強制コマンド実行 (5.4. ユーザの操作を制限する 参照) を使った ログインのみを許可することが可能です。なお、この場合は公開鍵認証を使う必要がありますので root のホームディレクトリ [脚注: 通常は /root/ です。] に ~/.ssh/authorized_keys ファイルを作成し、 そこに公開鍵と強制コマンド実行オプションを記述しておくことで root ログインが可能になります。 強制コマンド実行オプションの設定方法については 5.4.2. 一般ユーザに特定のコマンドだけを実行させる を参照してください。
強制的コマンドを実行するときのみ root ログインを許可する
PermitRootLogin forced-commands-only

root ログインを許可する

サーバをリモートからメンテナンスする必要がある場合、 root でログインしてシェルを使う必要があります。 しかし、最近多発しているパスワード推測攻撃でも root はもっとも多く 標的にされているユーザですので、root ログインを許可する場合は 接続元の IP アドレスを制限したり (5.2.4. システム全体で特定のホストからのみログインを許可する)、 root のパスワード認証を禁止したりすることをおすすめします。

本来、特定のユーザのパスワード認証を禁止したい場合は そのユーザのパスワードを使用禁止にするという方法を採れますが (5.3.2. 公開鍵認証を使っているユーザのパスワード認証を禁止する 参照)、 root に対しては必ずパスワードを設定する必要があります。そのため、 OpenSSH では特別に root のパスワード認証を禁止する設定項目が用意されています。 PermitRootLoginwithout-password を指定すると パスワード認証をつかって root ログインをすることは (たとえパスワードが正しくとも) 拒否され、 公開鍵認証による root ログインのみが許可されます。この場合は一般ユーザと同じように、 root 用の ~/.ssh/authorized_keys を作成し、root でログインしたいユーザの 公開鍵を登録しておく必要があります。
root ログインを許可する
公開鍵認証のみを許可する場合:
PermitRootLogin without-password
公開鍵認証・パスワード認証ともに許可する場合:
PermitRootLogin yes

実行例:

client$ ssh root@server.example.com
Enter passphrase for key '/home/yusuke/.ssh/id_rsa':  (秘密鍵のパスフレーズを入力する)
Last login: Mon Mar 14 15:12:43 2006 from xx.xx.xx.xx
server#

PermitRootLogin 設定項目に値 yes を指定すると、 root はパスワード認証でもログインできるようになります。 これは危険ですので、通常は使わないほうがよいでしょう。 なお、この場合でも sshd 全体のパスワード認証が禁止されていると ログインは許可されません (5.2.1. システム全体でパスワード認証を禁止する 参照)。

5.2.4. システム全体で特定のホストからのみログインを許可する

tcp_wrappers をサポートするようにコンパイルされた OpenSSH では [脚注: Red Hat, FreeBSD, Solaris にデフォルトでインストールされている OpenSSH はすべて tcp_wrappers をサポートしています。] 、システム全体で ログインを許可するクライアントを 特定のホストに限定することができます。 tcp_wrappers をサポートする OpenSSH は クライアントの接続時に /etc/hosts.allow ファイルと /etc/hosts.deny ファイルを検査し、 接続が許可されていないホストであることが分かればただちに通信を切断します。
注意
IPアドレスやホスト名による制限の有効性はそれほど高くありません。 攻撃の危険性を引き下げることはできますが、攻撃者は IPアドレスや DNS を詐称することで、依然としてここで説明する制限を回避することできてしまうのです。
/etc/hosts.allow または /etc/hosts.deny ファイルの書式
sshd: ALL                                (すべてのホストを指定)
sshd: ホスト名                           (例: donttrust.evil.gov)
sshd: IPアドレス                         (例: 10.2.3.4)
sshd: ドメイン名                         (例: .godbless.mil)
sshd: 部分IPアドレス                     (例: 192.168.)
sshd: IPアドレス/ネットマスク            (例: 192.168.1.0/30)

sshd_config設定ファイルの AllowUsersDenyUsers 設定項目の場合とは逆に、 これらのファイルでは「禁止」よりも「許可」が優先されることに注意してください。 したがって、あるホストが /etc/hosts.allow ファイルで許可されている場合、 /etc/hosts.deny ファイルの設定にかかわらず接続は許可されます。 また、そのホストが /etc/hosts.allow ファイルで許可されていない場合でも、 /etc/hosts.deny ファイルで禁止されていなければ接続は許可されます。
例 denythesehost. 特定のホストからのログインを禁止する場合
/etc/hosts.deny ファイル
sshd: donttrust.evil.gov
sshd: .godbless.mil
/etc/hosts.allow ファイル
(sshd 用のエントリなし)
例 permitonlyhost. 特定のホストからのログインのみを許可する場合
/etc/hosts.allow ファイル
sshd: 192.168.
sshd: 10.2.3.4
sshd: .goodguy.dom
/etc/hosts.deny ファイル
sshd: ALL

特定のネットワークインターフェイスでログインを待ち受ける

sshd サーバデーモンは、デフォルトでは TCP/IP ネットワークのすべてのインターフェイスから 接続を待ち受けます。しかしゲートウェイなどで複数のネットワークインターフェイスを 持っているマシンの場合は、sshd_config 設定ファイルの ListenAddress 設定項目を使用することにより、 接続の待ち受けを特定のインターフェイスのみに限定することができます。 なお、この設定は tcp_wrappers のサポートの有無に関係なく、つねに使用可能です。
接続の待ち受けを特定のインターフェイスのみに限定する
ListenAddress ネットワークアドレス

5.3. ユーザアカウントを管理する

ネットワークの利用がコンピュータ利用者にとって一般的となった今日では、 ユーザアカウントの管理手法も一様ではありません。 直接面識のない人物にアカウントを発行するのは日常茶飯事となりましたし、 ネットワーク利用者数の増加に伴い、ユーザの環境やアクセス手法、 それからユーザのシステム利用目的も多様化しています。 こうした状況にあっても、管理者は効率的 かつ安全にシステムを運用しなければなりません。 この節では、典型的なユーザアカウントの管理手法をいくつか紹介し、 管理作業を効率化するスクリプトを作成してみようと思います。

5.3.1. リモートからログインするユーザアカウントを作成する

現在の UNIX では、管理者が useradd コマンド (あるいは adduser コマンド) を使って 新規ユーザアカウントをデータベースへ追加し、 そのユーザ用のホームディレクトリを作成するという手順で、新しいユーザをシステムに追加します。 しかし、リモートからログインしてシステムを利用するユーザを追加する場合は、 まずそのユーザに公開鍵を (電子メールなどで) 送ってもらい、 次に管理者が直接ユーザのホームディレクトリにある ~/.ssh/authorized_keys ファイルに公開鍵を登録するという手順を踏むことが一般的となっています。

たとえば、以下の例では管理者はユーザ yusuke のアカウントを作成したあと、 そのユーザから送られてきた公開鍵の指紋を (電話などで) 確認し、 直接 ~/.ssh/authorized_keys ファイルに公開鍵を追加します。 同時に管理者はこのマシンのホスト公開鍵の指紋をユーザ yusuke に伝えます。

システム管理者がユーザ yusuke の公開鍵を登録する:

# ssh-keygen -l -f id_rsa.pub                       (公開鍵の指紋を確認する)
2048 23:56:6e:90:f3:98:fc:7b:2a:92:fb:46:bf:20:98:71 id_rsa.pub
# mkdir /home/yusuke/.ssh/                          (yusuke の ~/.ssh/ ディレクトリを作成する)
# cat id_rsa.pub >> /home/yusuke/.ssh/authorized_keys  (公開鍵を追加する)
# chown -R yusuke:users /home/yusuke/.ssh           (~/.ssh/ の所有者を yusuke に変更する)
# chmod 700 /home/yusuke/.ssh/                      (~/.ssh/ ディレクトリのパーミッションを設定する)
# chmod 600 /home/yusuke/.ssh/authorized_keys       (authorized_keys ファイルのパーミッションを設定する)

この例では、ユーザ yusuke の ~/.ssh/ ディレクトリ以下を yusuke のみが 読み書き可能なパーミッションに設定しています。これは必ずしも必要ではありませんが、 ~/.ssh/ 以下のディレクトリにはほかにも重要な情報が入ることが多いため、 なるべく制限されたパーミッションに設定しておくのが得策でしょう。 ただしこのような一連の作業をすべて手動でおこなうのは、管理者にとって 手間がかかるうえにミスも起こりやすくなります。そこで、 たとえば以下のようなシェルスクリプト install_publickey を用意し、 管理者の負担を軽減することを考えます。このスクリプトは与えられたユーザの ホームディレクトリに (そのユーザの所有で) ~/.ssh/ ディレクトリを作成し、 公開鍵ファイルを追加します。
ユーザの公開鍵を登録する install_publickey スクリプト
#!/bin/sh
# 使用方法: install_publickey 公開鍵ファイル ホームディレクトリ
if [ $# != 2 ]; then echo "usage: $0 public_key home_directory"; exit 1; fi
pubkey=$1
homedir=$2
# (公開鍵ファイルとホームディレクトリが存在していることを確認する)
if [ ! -f $pubkey ]; then echo "public key not found: $pubkey"; exit 2; fi
if [ ! -d $homedir ]; then echo "home directory not found: $homedir"; exit 3; fi

# (そのホームディレクトリ内に .ssh を作成する - エラーは無視)
mkdir $homedir/.ssh/
# (authorized_keys ファイルに公開鍵を追加する)
cat $pubkey >> $homedir/.ssh/authorized_keys
if [ `whoami` = root ]; then
  # (root権限で実行している場合は、.ssh ディレクトリ以下の所有者をそのユーザに変更する)
  set -- `ls -ld $homedir`
  chown -R $3:$4 $homedir/.ssh/
fi
# (念のため .ssh ディレクトリのパーミッションを設定する)
chmod 700 $homedir/.ssh/
chmod 600 $homedir/.ssh/authorized_keys
ユーザの公開鍵を登録する
$ install_publickey 公開鍵ファイル 公開鍵を登録するユーザのホームディレクトリ

実行例:

# ssh-keygen -l -f id_rsa.pub                 (公開鍵の指紋を確認する)
2048 23:56:6e:90:f3:98:fc:7b:2a:92:fb:46:bf:20:98:71 id_rsa.pub
# install_publickey id_rsa.pub /home/yusuke/  (ユーザ yusuke のホームディレクトリに公開鍵を登録する)

5.3.2. 公開鍵認証を使っているユーザのパスワード認証を禁止する

伝統的な UNIX では、一般ユーザがログインする唯一の方法は、 そのユーザのパスワードを正しく入力することでした。 しかし OpenSSH の公開鍵認証を使ってログインする場合、 サーバ上でそのユーザに設定されているパスワードは使用されません。 それどころか、そもそもユーザのパスワード入力によるログインが不可能な場合でも、 公開鍵認証が正しく行われれば sshd はログインを許可します。 このことは逆に利用すれば「パスワード認証を禁止して公開鍵認証だけを使ってもらう」ことが可能です。

UNIX ではユーザのパスワードは crypt や md5 などのアルゴリズムによってハッシュされた文字列として登録されています。 [脚注: FreeBSD では利用するハッシュアルゴリズムを crypt や md5 よりも強力な blowfish に変更することができます。詳しくは以下を参照してください: http://filter.rackeasy.com/articles/2005/11/30/setup-freebsd-to-use-blowfish ] ログイン時にユーザが入力したパスワードのハッシュ文字列が、システムに 登録されたものと一致すれば、そのユーザは正しいパスワードを入力したとみなされます。 この仕組みを逆手にとって、登録しているハッシュの値を「算出不可能な」文字列に書き換えてしまえば、 ユーザがどんな文字列を入力しても登録されたハッシュ文字列に一致させることはできなくなります。 したがってパスワードを使ってログインすることは不可能になります。 OpenSSH ではこのトリックを利用して、ユーザのパスワードに 「算出不可能な」ハッシュ文字列を設定することで、パスワード認証を事実上禁止することができます。

昔の UNIX では、パスワードをはじめユーザアカウントに関する情報はすべて /etc/passwd に格納されていましたが、現在の UNIX の多くは、 /etc/passwd にはパスワード以外のユーザアカウントに関する情報が格納されており、 パスワードの情報は /etc/shadow (Red Hat, Solaris の場合) または /etc/master.passwd (FreeBSD) などのファイルに書きこまれています。 [脚注: これらのファイルはどれも root しか読むことができません。] オペレーティングシステムにより若干ちがいがありますが、 これらのファイルの各行は、おおむね以下のような形式になっています:

/etc/shadow あるいは /etc/master.passwd ファイルの例:

(ハッシュに crypt関数を使っている場合 - Red Hat)
yusuke:abcd1234efghi:12212:0:99999:7:::

(ハッシュに md5関数を使っている場合 - Red Hat)
yusuke:$1$E0jrvGZ26nCy/Up$Wymmj6W3UB/a7lQ:12212:0:99999:7:::

(ハッシュに blowfish暗号化アルゴリズムを使っている場合 - FreeBSD のみ)
yusuke:$2$aRN.5.8fsv$LGrPpsn8ouZtRCk03/Fl3:1000:1000::0:0:Yusuke Shinyama:/home/yusuke:/bin/bash

これら各行のうち、コロン (:) で区切られている 2番目の項目がハッシュ文字列です。 どのアルゴリズムを用いても、生成されるハッシュ文字列は必ず 3 文字以上となる特徴をもっています。 そこで 2 番目の項目に、たとえば "NP" のような 2文字の文字列を登録すると、 そのようなハッシュ文字列は算出できず、したがってパスワード認証は利用不可能になります。
注意
OpenSSH では、パスワードファイル内のハッシュ文字列として !! (Red Hat)、* (FreeBSD) あるいは *LK* (Solaris) が設定されているユーザは「禁止 (disabled)」されているとみなし、 そのユーザのログインをすべて拒否します。したがって パスワード認証だけを禁止したい場合はこのようなハッシュ文字列を使わないようにしてください。

5.3.3. ひとつのアカウントを複数のユーザで共有する

多人数でファイルやディレクトリを介して協調作業する場合、 ふつうは UNIX のグループを使うか、CVS などのバージョン管理システムを使うのが一般的です。 しかし、ひとつのアカウントを何人ものユーザで共有して使いまわしたい場合もあるでしょう。 このような場合はそのアカウントのパスワードを各ユーザに教えるよりも、 各ユーザに異なる秘密鍵・公開鍵ペアを作ってもらい、 これらをその共有アカウントの ~/.ssh/authorized_keys に登録しておくほうが安全です (図 shared-account)。 こうすることにより、そのアカウントにはシステムパスワードを設定せずにすみますし、 また、特定のユーザを利用者から除外したい場合も、 ただそのユーザの公開鍵を ~/.ssh/authorized_keys から削除するだけですみます。


図 shared-account. アカウントを複数のユーザで共有する

5.4. ユーザの操作を制限する

ユーザがサーバ上で一般的なシェルを必要とせず、つねにある決まったコマンドだけを実行したり、 ユーザがそのマシンをファイルサーバとして使っていてファイル転送だけをおこなう場合は、 最小権限の原則により、そのユーザに他のコマンドを実行させないよう設定しておくことは 安全性の向上につながります。

5.4.1. ユーザの公開鍵にオプションを指定する

sshd サーバデーモンは、ユーザが公開鍵認証でログインする際に そのユーザの ~/.ssh/authorized_keys ファイルを検査し、 そこに登録されている公開鍵のどれかで (対応する秘密鍵によって) ユーザがログインできるかどうかを判定します。 OpenSSH では authorized_keysファイルに格納されているそれぞれの公開鍵にオプションを指定することができます。 公開鍵のオプションはそのユーザの権限を制限するためのもので、 特定のホストから接続要求があった場合のみ当該公開鍵の利用を許したり、 X11転送やポート転送などの操作を禁止したりすることができます。 この仕組みを利用すれば、 ユーザがログインに使う公開鍵の内容に応じて、異なる処理をおこなわせることができます。 4.x では ~/.ssh/authorized_keys ファイルに公開鍵を登録する方法を 説明しましたが、~/.ssh/authorized_keys ファイルの各行には 公開鍵だけでなく、その公開鍵に対するオプションも記述することができます。 公開鍵のオプションは ~/.ssh/authorized_keys ファイル中で、 その公開鍵の情報が記録されている行の先頭に オプション文字列を追加することによって設定します。 表 authorized-keys-option. に公開鍵のオプション一覧を示しました。
公開鍵にオプションを指定する
~/.ssh/authorized_keys ファイル:
オプション1,オプション2,...  公開鍵の文字列

(注意: オプション間のカンマ , の前後にはスペースを入れない)

設定例:

no-pty,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2E...(中略)...TYlZC91Lmw== yusuke@client
    (この公開鍵でログインしたとき、仮想端末の使用とポート転送を禁止する)
表 authorized-keys-option. 公開鍵のオプション
オプション機能
no-pty 仮想端末の割り当てを禁止する。
no-port-forwarding ポート転送を禁止する。
no-x11-forwarding X11転送を禁止する。
no-x11-forwarding 認証エージェントの転送を禁止する。
command="コマンド文字列" ログイン後に指定されたコマンド文字列を強制的に実行し、ログアウトする(5.4.2. 一般ユーザに特定のコマンドだけを実行させる 参照)。
from="パターン1,パターン2,..." 特定のホストのみからログインを許可する。 各パターンはホスト名あるいは IPアドレスで、 ワイルドカード (* または ?) が使用できる。 パターンの先頭に ! を追加するとそのパターンは否定として扱われる。
environment="変数名1=値1,変数名2=値2,..." 指定された環境変数に値を設定する。
permitopen="ホスト名:ポート番号" ローカル→リモート (L) ポート転送を指定されたホスト名とポート番号のみに制限する。(6.2.3. 応用1 - HTTP のみに対応した簡易 VPN を構築する 参照)
tunnel="インターフェイス番号" トンネリングのさいに指定されたインターフェイス番号を使用する。

以下の項ではとくに公開鍵のオプションのひとつである強制コマンド実行オプション (command="..." オプション) の 使い方を紹介します。強制コマンド実行オプションを使うと、 ユーザがそのオプションの付いた公開鍵 (および対応する秘密鍵) を利用してログインする際に、 あらかじめ決められたコマンド文字列だけを強制的に実行してログアウトさせることができます。 ユーザのアカウントを作成するときに管理者があらかじめ強制コマンド実行オプションをつけた 公開鍵を登録してさえいれば、そのユーザが通常のシェルでログインする可能性はなくなります。 これは万が一そのアカウントが乗っとられたときのリスクを軽減でき、 また、ユーザ側のミスによるリスクも最小限に抑えることができます。
注意
OpenSSH は強制コマンド実行オプションで指定されたコマンド文字列を実行するのに、 そのユーザの /etc/passwd エントリで指定されたログインシェルを使用します。 そのため強制コマンド実行オプションを使うときでも、ユーザのログインシェルには 必ず一般的なシェルを指定するようにしてください。 ユーザのログインシェルが実行できない場合、 OpenSSH はそのユーザのログインを最初から拒否します。

5.4.2. 一般ユーザに特定のコマンドだけを実行させる

あるユーザが特定の公開鍵でログインしたとき、強制的にコマンドを実行させるには、 ~/.ssh/authorized_keys ファイル中でその公開鍵が登録されている行を 以下のように変更します。
特定のコマンドだけを実行させる
変更前の行:
公開鍵の文字列

変更後の行:

command="コマンド文字列",no-pty,no-x11-forwarding,no-port-forwarding,no-agent-forwarding  公開鍵の文字列

(注意: 途中で改行しない、カンマ , や等号 = の前後にはスペースを入れない)

ユーザがつねに同じコマンドを実行する場合

あるユーザがログインしたとき、つねにサーバ上で同じコマンドを実行することが分かっているなら、 たとえば authorized_keys ファイルを以下のように設定します。 この例ではユーザがログインすると /usr/bin/vmstat が引数なしで強制的に実行され、 その後自動的にログアウトします。

authorized_keys 設定例: [脚注: 実際にはこれはぜんぶつながった 1行です。]

command="/usr/bin/vmstat",no-pty,no-x11-forwarding,no-port-forwarding,no-agent-forwarding
  ssh-rsa AAAAB3NzaC1yc2E...(中略)...TYlZC91Lmw== yusuke@client
  (/usr/bin/vmstat を強制的に実行させる)

ここで指定している command="..." が強制コマンド実行オプションです。 指定されたコマンド文字列は、サーバ上でそのユーザのログインシェルを介して (-c オプションを使って) 実行されます。 このとき実行されるプロセスは、リモートコマンド (4.1.4. リモートコマンドを実行する 参照) と同じように 標準入力と標準出力を介してクライアント側の ssh コマンドと通信します。 なお、ここでは command 以外にも no-pty (仮想端末割り当ての禁止)、no-x11-forwarding (X転送の禁止)、 no-port-forwarding (ポート転送の禁止)、no-agent-forwarding (エージェント転送の禁止) などのオプションを同時に指定しています。
注意
強制コマンド実行オプションを指定するときは、 必ず no-pty, no-x11-forwarding, no-port-forwarding, no-agent-forwarding の 4つのオプションも同時に指定してください。 なぜなら OpenSSH はユーザが強制コマンドを実行する場合でも、ポート転送などの操作は依然として 許可しているため、これらのオプションをつけないと予期しない権限をユーザに与えてしまう可能性があるのです。

実行例:

client$ ssh yusuke@server.example.com
Enter passphrase for key '/home/yusuke/.ssh/id_rsa':  (秘密鍵のパスフレーズを入力する)
   procs                      memory    swap          io     system         cpu
 r  b  w   swpd   free   buff  cache  si  so    bi    bo   in    cs  us  sy  id
 1  0  0      0 265356 231720 2913976   0   0     2     7    6     3   1   1   0
client$                                               (vmstat を実行したあとログアウトした)

ユーザにいくつかのコマンドを実行させる場合

OpenSSH では、強制コマンド実行時に、サーバ側で実行されるプロセスの環境変数 SSH_ORIGINAL_COMMAND が設定されるようになっています。 ここにはクライアントが本来実行を意図していたリモートコマンド文字列が格納されます (ユーザが通常のシェルを実行しようとしたときには空文字列が格納されます)。 そのため、この環境変数 SSH_ORIGINAL_COMMAND を調べ、 その内容に応じて強制実行するコマンドを変えることができます。 具体的には SSH_ORIGINAL_COMMAND の値によって特定のコマンドを 実行するシェルスクリプトを作っておき、command="..." オプションで このシェルスクリプトを指定すればよいのです。
注意
環境変数 SSH_ORIGINAL_COMMAND の値を使うときは、 その値にシェルの特殊文字 (&|;> など) が含まれていないかどうか確認してください。 これらの文字を直接シェルに渡してしまうと、 予期しないコマンドを実行される恐れがあります。

たとえば、ユーザにリモートコマンドとして ls あるいは cat だけを実行させたい場合は次のような シェルスクリプト ls-or-cat.sh を作り、 このパス名を command="..." オプションに指定しておきます。 なお、以下の例ではサーバ上の ls あるいは cat コマンドの 終了状態がそのままクライアント側の ssh コマンドの終了状態になるよう、 最後に exec を使ってコマンドを実行しています。
サーバ側の ls-or-cat.sh スクリプトの例
#!/bin/sh
# (SSH_ORIGINAL_COMMAND の値を表示する)
echo "SSH_ORIGINAL_COMMAND: $SSH_ORIGINAL_COMMAND"

# (SSH_ORIGINAL_COMMAND を分解して配列に代入する)
set -- $SSH_ORIGINAL_COMMAND
# (コマンド名は $1 に格納されている)
case "$1" in
ls) 
    ;;
cat)
    ;;
*) 
    # (ls または cat 以外の場合はエラーを表示して終了)
    echo "Only ls or cat is permitted."; exit 1;;
esac

# (指定されたコマンドを実行する)
exec "$@"

authorized_keys 設定例: [脚注: 実際にはこれはぜんぶつながった 1行です。]

command="/home/yusuke/ls-or-cat.sh",no-pty,no-x11-forwarding,no-port-forwarding,no-agent-forwarding
   ssh-rsa AAAAB3NzaC1yc2E...(中略)...TYlZC91Lmw== yusuke@client  

実行例:

client$ ssh yusuke@server.example.com             (普通にログインしようとする)
SSH_ORIGINAL_COMMAND:                             (SSH_ORIGINAL_COMMAND の内容は空)
Only ls or cat is permitted.                      (エラーメッセージが表示される)
client$ ssh yusuke@server.example.com ls /        (リモートコマンドとして ls を実行する)
SSH_ORIGINAL_COMMAND: ls /
bin
boot
dev
etc
home
lib
mnt
proc
root
sbin
tmp
usr
var
client$ ssh yusuke@server.example.com /sbin/halt  (/sbin/halt を実行しようとする)
SSH_ORIGINAL_COMMAND: /sbin/halt
Only ls or cat is permitted.                      (エラーメッセージが表示される)
client$ ssh yusuke@server.example.com "ls > /tmp/foo ; echo bar"  (メタ文字を含むコマンドを実行する)
SSH_ORIGINAL_COMMAND: ls > /tmp/foo ; echo bar
ls: >: No such file or directory                  (シェルのメタ文字もファイル名として扱われる)
ls: /tmp/foo: No such file or directory
ls: ;: No such file or directory
ls: echo: No such file or directory
ls: bar: No such file or directory

なお、6.1. OpenSSH と組みあわせて使うソフトウェア では限定されたユーザアカウントを CVS や Subversion と組み合わせて使う方法を説明します。

5.4.3. 一般ユーザに SFTP によるファイル転送だけを使わせる

OpenSSH の SFTP によるファイル転送は、 クライアント側の sftp コマンドと、 サーバ側で実行される sftp-server プログラムが通信することにより実現されています。 ユーザが SFTP によるファイル転送しか行わない場合、 サーバ側で SFTP を強制コマンドに設定しておくことで SFTP 以外のプログラムの実行を禁止できます。 ただしログイン時にいきなり sftp-server プログラムを実行するように設定しておくと、 ユーザが ssh コマンドで直接シェルを使おうとして、 サーバ側のプログラムに予期しないデータを入力してしまう 危険があります。そこで、ユーザが SFTP 以外の方法でログインしようとしたときは、 ユーザに SFTP 以外の利用は禁止されている旨のメッセージを表示してから ログアウトさせるのが望ましいでしょう。

ユーザが SFTP によるファイル転送をおこなう場合、 環境変数 SSH_ORIGINAL_COMMAND には sftp-server プログラムのパス名が格納されます。 [脚注: sftp-server プログラムのパス名は sshd_config設定ファイル中の Subsystem設定項目で指定されています。] そこで、環境変数SSH_ORIGINAL_COMMAND の内容を検査し、 この値が sftp-server プログラムのパス名と異なっている場合には メッセージを表示して終了するようなシェルスクリプトを用意しておきます。

なお、以下の例では sftp-server プログラムが /usr/libexec ディレクトリに置かれていることを想定しています。
SFTP のラッパプログラム force-sftp-server.sh
#!/bin/sh
# (環境変数 SSH_ORIGINAL_COMMAND の値が /usr/libexec/sftp-server と等しいかどうか検査する)
if [ "/usr/libexec/sftp-server" != "$SSH_ORIGINAL_COMMAND" ]; then
  # (それ以外ならメッセージを表示し終了)
  echo "Sorry, only sftp is permitted."
  exit 1
fi

# (sftp-server プログラムを引数なしで実行する)
exec /usr/libexec/sftp-server

このようなシェルスクリプトを、たとえば /usr/libexec/force-sftp-server.sh のような名前で保存し、実行可能にしておきます。この後、SFTP のみを許可するユーザの authorized_keys ファイルを以下のように設定します。

authorized_keys ファイルの設定例: [脚注: 実際にはこれはぜんぶつながった 1行です。]

command="/usr/libexec/force-sftp-server.sh",no-pty,no-x11-forwarding,no-port-forwarding,no-agent-forwarding
  ssh-rsa AAAAB3NzaC1yc2E...(中略)...TYlZC91Lmw== yusuke@client  

ユーザがパスワード認証を使っている場合

ユーザがパスワード認証を使っている場合は、 authorized_keys ファイルを使った制御はできません。 このような場合は /etc/passwd ファイルに書かれているそのユーザのログインシェルの指定を 強制実行するコマンドへのパス名に書き換えれば、公開鍵認証を使わなくとも強制的にコマンドを実行させることは可能です。 ただし、この場合ユーザの端末割り当てや X11転送 (4.5. X11 転送を使う 参照)、ポート転送 (6.2. ポート転送 参照) などは 禁止されないため、あらかじめ sshd_config 設定ファイルを以下のように修正して、 システム全体でポート転送や X11 転送を禁止しておく必要があります。
システム全体のポート転送と X 転送を禁止する
AllowTcpForwarding no
X11Forwarding no

/etc/passwd エントリの例:

yusuke:*:1000:1000:Yusuke Shinyama:/home/yusuke:/usr/libexec/force-sftp-server-password.sh

この例では、ユーザ yusuke がログインするとつねに force-sftp-server-password.sh が実行されますので、 ユーザは一般のシェルにアクセスすることはできません。 なお、ユーザのログインシェルを書き変えた場合は環境変数 SSH_ORIGINAL_COMMAND が使えませんが、 ユーザがリモートで本来実行しようとしたコマンド名は、ログインシェルの第2引数 ($2) に 格納されるようになっているため、この変数を事実上 SSH_ORIGINAL_COMMAND と同様に使えます。 [脚注: これは、ユーザが ssh コマンドでリモートコマンドを 実行しようとすると、サーバ上では ログインシェル -c "コマンド文字列" というコマンドが実行されるためです。]
SFTP のラッパプログラム force-sftp-server-password.sh (パスワード認証用)
#!/bin/sh
# (2番目のコマンドライン引数の値が /usr/libexec/sftp-server と等しいかどうか検査する)
if [ "/usr/libexec/sftp-server" != "$2" ]; then
  # (それ以外ならメッセージを表示し終了)
  echo "Sorry, only sftp is permitted."
  exit 1
fi

# (sftp-server プログラムを引数なしで実行する)
exec /usr/libexec/sftp-server

5.4.4. 特定のユーザの権限を部分的に使用させる

強制コマンド実行オプションをうまく使うと、あるアカウントの「部分的な」機能だけを ネットワーク上のユーザに与えることができます。この方法は たとえばディスクのバックアップやシステムの監視などといった、 「root 権限が必要な作業をネットワーク上から制御したいが、 root のシェルは使ってほしくない」という場合に重宝します。 一般ユーザの時と同じく、ここでも root の authorized_keysファイルに command="..." オプションをつけた公開鍵を登録しておくことによって、 特定の機能だけを root 権限で実行させることが可能です。

まず適当な秘密鍵・公開鍵ペアをつくり、 root の ~/.ssh/authorized_keys ファイルに 登録します。

server# ssh-keygen -t rsa                                  (秘密鍵・公開鍵ペアを生成する)
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): id_seelog
Enter passphrase (empty for no passphrase):                 (パスフレーズを入力する)
Enter same passphrase again:                                (もう一度入力する)
Your identification has been saved in id_seelog.
Your public key has been saved in id_seelog.pub.
The key fingerprint is:
25:c1:87:f4:0e:cf:b1:18:5f:2b:c9:98:62:4c:f5:6f root@server
server# cat id_seelog.pub >> ~/.ssh/authorized_keys            (公開鍵を登録する)

つぎにこの authorized_keys ファイルの最終行をテキストエディタで 以下のように変更します。以下の設定例では /var/log/messages ファイルの内容をダンプしています:

/root/.ssh/authorized_keys の設定例: [脚注: 実際にはこれはぜんぶつながった 1行です。]

command="cat /var/log/messages",no-pty,no-x11-forwarding,no-port-forwarding,no-agent-forwarding
  ssh-rsa AAAAB3NzaC1yc2E...(中略)...TYlZC91Lmw== root@server

この後、秘密鍵ファイルである id_seelog をしかるべきユーザに配布します。 一方、秘密鍵を受けとったユーザは、この秘密鍵を使ってサーバに root でログインを試みます。 [脚注: このときサーバは root ログインを許可している必要があります。 sshd_config 設定ファイルの PermitRootLogin 設定項目を yeswithout-password または forced-commands-only に設定してください (5.2.3. Root のログインを禁止する 参照)。]

実行例:

client$ ssh -i id_seelog root@server.example.com | less
(...サーバ の /var/log/messages が表示される...)
注意
root に強制コマンド実行オプションを指定するときは、 そのコマンドのふるまいが予測可能であり、つねに一定の動作をするよう細心の注意を払ってください。 とくにユーザから何らかのオプションを受けつけるようなプログラムは その動作を予測することがむずかしく、セキュリティホールを作ってしまう危険性があります。

6.1.3. rsync を使ってネットワーク経由でバックアップする ではこの機能の応用例として、サーバ上の特定のディレクトリだけを rsync コマンドを使ってクライアントにバックアップする方法を紹介します。

5.5. sshd を監視する

システム管理者は問題が発生したとき、なるべく迅速にシステムやユーザの状況を把握できることが 求められます。このため、日頃から管理者はシステム監視をおこなう何らかの仕組みをつくっておく必要があります。 オペレーティングシステムによっては、毎日のログをスキャンして不審な挙動をメールで管理者に 報告する cron スクリプトが含まれています。 [脚注: Red Hat では /etc/cron.daily/00-logwatch、 FreeBSD では /etc/periodic 以下のスクリプトなどが該当します。] 本節では sshd サーバデーモンを監視する手段について説明します。

5.5.1. sshd のプロセス

sshd サーバデーモンは通常 PREFIX/sbin/sshd のプロセスを ひとつだけ動かし、クライアントが接続してくると子プロセスを fork します。 ps コマンドを使ってこれらの子プロセスを観察することで、 現在何人のユーザがログインしているかを確認することができます。 sshd はユーザごとに計 3個 (シェルも含めると 4個) の 子プロセスを fork します (図 sshd-process)。 これらのプロセスはそのユーザの状態に応じて ps コマンド上の名前 (コマンドライン) を変化させます。


図 sshd-process. sshdが起動するプロセス (括弧内はプロセスの権限)

クライアントが接続したばかりの状態

クライアントが接続した直後、まだ認証プロセスが始まっていない状態のプロセスは "sshd: [accepted]" と表示されます。

$ ps auxww | grep sshd
root     22098  0.0  0.0  3012 1400 ?        S    Feb24   1:05 /usr/sbin/sshd       (最初に起動された親プロセス)
root     10724  0.2  0.0  3008 1380 ?        S    20:37   0:00 sshd: [accepted]     (リモートの接続を受けつける子プロセス)

認証を行っている状態

クライアントがユーザ名を提示すると、fork された sshd 子プロセスは その特定のユーザの認証おこなう状態になり、ユーザ名と [priv] という文字列が追加されます ("sshd: yusuke [priv]")。 このとき、ネットワーク上のデータを処理するための特権分離された [脚注: 特権分離に関する詳細は http://www.citi.umich.edu/u/provos/ssh/privsep.html を参照してください。] プロセスが一時的に作成されます。このプロセスはユーザ sshd の権限で走っており、 ps コマンドでは "sshd: yusuke [net]" のように表示されています。

$ ps auxww | grep sshd
root     22098  0.0  0.0  3012 1400 ?        S    Feb24   1:05 /usr/sbin/sshd       (最初に起動された親プロセス)
root     10724  0.1  0.0  4392 1604 ?        S    20:37   0:00 sshd: yusuke [priv]  (yusuke の認証をおこなうプロセス)
sshd     10744  0.0  0.0  4388 1528 ?        S    20:37   0:00 sshd: yusuke [net]   (ネットワーク上のデータを処理するプロセス)

ログインが受けつけられた状態

ユーザの認証が成功し、セッションが始まると [net] プロセスは終了し、 かわりにそのユーザ権限のプロセスが fork します。このプロセスは "sshd: yusuke@pts/7" などの、 「ユーザ名 @ 端末名」のような名前で表示されます (端末が割り当てられない場合は yusuke@notty のようになります)。 このプロセスがユーザのシェルを起動し、ポート転送などのサービスを提供します。 このユーザプロセスと最初の [priv] プロセスはユーザがログアウトするまで走りつづけます。

$ ps auxww | grep sshd
root     22098  0.0  0.0  3012 1400 ?        S    Feb24   1:05 /usr/sbin/sshd       (最初に起動された親プロセス)
root     10724  0.4  0.0  5656 1668 ?        S    20:37   0:00 sshd: yusuke [priv]  (yusuke の認証をおこなうプロセス)
yusuke   10764  0.2  0.0  5688 1860 ?        S    20:37   0:00 sshd: yusuke@pts/7   (yusuke のセッションを管理するプロセス)

5.5.2. sshd のログを見る

sshd はデーモンプロセスなので、システム管理者といえども通常は 直接その動きを目にすることはありません。しかし、なにか異変が生じた場合には 管理者は sshd のログをチェックすることで、不審者によるログインや アタックの徴候などを察知できる場合があります。 sshd プロセスは syslog デーモン経由でログを残します。 このログには sshd の起動・終了やユーザのログイン情報などが記録されています。

ログの見方

sshd が出力するすべてのログには、かならず時刻のあとに "sshd" という文字列と、sshd サーバデーモンが クライアントからの接続を受けたあとに fork したプロセス番号が現れます。 [脚注: したがって、sshd サーバデーモンを継続して走らせている場合でも これらログに現れるプロセス番号は毎回異なっています。] 各行はそれぞれ独立した出来事を表しており、 これらを見ることで特定の時刻に sshd サーバデーモン内で なにが起こったかを知ることができます。

sshd が出力するログの例:

Mar 26 11:20:39 server sshd[6272]: Invalid user test from xx.xx.xx.xx
Mar 26 11:20:39 server sshd[6272]: error: Could not get shadow information for NOUSER
Mar 26 11:20:39 server sshd[6272]: Failed password for invalid user test from xx.xx.xx.xx port 34125 ssh2
...
Mar 28 01:21:49 server sshd[22164]: Accepted publickey for yusuke from yy.yy.yy.yy port 54571 ssh2

上のログには、何者かが3月26日に test というユーザ名で パスワード認証をつかってログインしようとしたこと、そして ユーザ yusuke が 3月28日に公開鍵認証で 正しくログインしたことが記録されています。ログによく現れるメッセージの一覧を 表 logmessages. に示しました。 なお、sshd はログインの失敗や、拒否された IP アドレスからの接続などは 記録しますが、ポートスキャンやパスワード推測攻撃といった 継続的な攻撃は記録しません。このような攻撃を自動的に検出したい場合は、 5.5.3. パスワード推測攻撃を避ける で説明する denyhosts や、 Snort [脚注: http://www.snort.org/ ] などの侵入検知システムの導入が必要です。
表 logmessages. 代表的なログのメッセージ
メッセージ意味
Server listening on 待ち受けIPアドレス port 待ち受けポート番号 sshd サーバデーモンが接続の待ち受けを開始した。
Accepted password for ユーザ from 接続元 IPアドレス port 接続元ポート番号 ユーザがパスワード認証でログインを試み、成功した。
Accepted publickey for ユーザ from 接続元 IPアドレス port 接続元ポート番号 ユーザが公開鍵認証でログインを試み、成功した。
Accepted hostbased for ユーザ from 接続元 IPアドレス port 接続元ポート番号 ユーザが Hostbased 認証 (6.6. Hostbased 認証 を使う 参照) でログインを試み、成功した。
Failed password for ユーザ from 接続元 IPアドレス port 接続元ポート番号 ユーザがパスワード認証でログインを試みたが、失敗した。
Failed password for invalid user ユーザ from 接続元 IPアドレス port 接続元ポート番号 何者かがシステム上に存在しない ユーザ でパスワード認証を試み、失敗した。 (これはパスワード推測攻撃の可能性があります。)
User ユーザ from 接続元 IPアドレス not allowed because 理由 AllowUsers, AllowGroups, DenyUsers, DenyGroups の いずれかの設定により、ユーザ のログインは許可されていない。 あるいは、ユーザのログインシェルが実行できない。
refused connect from 接続元ホスト (接続元 IPアドレス) 接続元ホストからの接続は /etc/hosts.deny ファイルによって拒否された。

ログの表示設定

OpenSSH では、ログの表示レベルおよび syslog の出力先 (facility) は、 それぞれ sshd_config設定ファイルの LogLevel 設定項目と SyslogFacility 設定項目で指定できます。
ログのレベルと出力先を指定する
LogLevel レベル
SyslogFacility 出力先

OpenSSH のログには、それぞれの出来事がどれくらい重要であるかを表す 「レベル」がつけられています。重要でないログが必要以上に出力されてしまうと 読みにくくなるため、OpenSSH ではどの程度まで詳細な記録を残すかをレベルによって指定できます。 ログのレベルは LogLevel設定項目に値として指定します。指定できるレベルにはいくつかの段階があります。 代表的なものを表 loglevel. に示しましたが、ここに示されている値は、 下へ行くほど詳細な内容を表示するようになっています。
表 loglevel. よく使われるログのレベル一覧
レベル説明
QUIETログをまったく表示しない。
FATALsshd が終了せざるを得ないような致命的なエラーのみを表示する。
ERRORFATAL に加えて、sshd が要求された操作を実行できなかったエラーを表示する。
INFOERROR に加えて、ユーザのログイン記録のような情報を表示する。
VERBOSEINFO に加えて、詳細な出力を表示する。

ログの出力先ファイルは、そのレベルによって変わることがあります。 OpenSSH が指定できるのはそのログの「syslog 出力先 (facility)」だけであり、 そのログが実際にどのファイル (あるいは端末画面) に出力されるかは、 syslogデーモンの設定ファイル [脚注: 通常は /etc/syslog.conf。] によって規定されています。

sshd のログのデフォルトの出力先は、 それぞれオペレーティングシステムとログのレベルごとに違っています:

5.5.3. パスワード推測攻撃を避ける

近年、sshd サーバデーモンを対象としたパスワード推測攻撃が 非常に多くなっています。そのため、やむをえずパスワード認証を許可している場合は、 パスワード推測攻撃を避けるためになんらかの対策を施しておくことをおすすめします。
パスワード推測攻撃の例
Mar 28 04:00:45 server sshd[2399]: Invalid user test from xx.xx.xx.xx
Mar 28 04:00:45 server sshd[2399]: error: Could not get shadow information for NOUSER
Mar 28 04:00:45 server sshd[2399]: Failed password for invalid user test from xx.xx.xx.xx port 50589 ssh2
Mar 28 04:00:45 server sshd[2401]: Invalid user samba from xx.xx.xx.xx
Mar 28 04:00:45 server sshd[2401]: error: Could not get shadow information for NOUSER
Mar 28 04:00:45 server sshd[2401]: Failed password for invalid user samba from xx.xx.xx.xx port 51017 ssh2
Mar 28 04:00:46 server sshd[2403]: Invalid user tomcat from xx.xx.xx.xx
Mar 28 04:00:46 server sshd[2403]: error: Could not get shadow information for NOUSER
Mar 28 04:00:46 server sshd[2403]: Failed password for invalid user tomcat from xx.xx.xx.xx port 51039 ssh2
Mar 28 04:00:46 server sshd[2405]: Invalid user webadmin from xx.xx.xx.xx
Mar 28 04:00:46 server sshd[2405]: error: Could not get shadow information for NOUSER
Mar 28 04:00:46 server sshd[2405]: Failed password for invalid user webadmin from xx.xx.xx.xx port 51068 ssh2
Mar 28 04:00:48 server sshd[2413]: Invalid user oracle from xx.xx.xx.xx
Mar 28 04:00:48 server sshd[2413]: error: Could not get shadow information for NOUSER
Mar 28 04:00:48 server sshd[2413]: Failed password for invalid user oracle from xx.xx.xx.xx port 51201 ssh2

上の出力結果は、パスワード推測攻撃が行われたときの sshd のログを抜粋したものです。 これを見ると、攻撃者は "samba" や "webadmin" など、一般のサーバによく存在しがちな ユーザアカウント名での侵入 (ログイン) を試していることがわかります。 典型的なパスワード推測攻撃の手口はおそらく以下のようなものです:

ここで重要なのは「なるべく多くのホストに接続する」という部分です。 インターネット上には sshd を運用しているサーバが数多くあり、 中にはいいかげんな管理をしているサーバが一定の割合で存在します。 攻撃者はこうした簡単に侵入できるサーバを探すため、 なるべく多くのホストに接続する必要があるのです。 最近では、一回の攻撃で数千もの異なるユーザ名が試されるようになっており、 その中には日本人向けと思われる名前も混じるようになってきました。 そのため複数のユーザが使っているシステムでは、その中の誰かがたまたま ここで試されるユーザ名を使っているという可能性も否定しきれなくなってきました。 これらのユーザがもし簡単に推測できるパスワードを使っていれば、 そのユーザのアカウントは攻撃者によって簡単に乗っとられてしまいます。 しかも管理がいいかげんなサーバは通常セキュリティ・パッチが当たっていないことも多く、 一般ユーザが不正に root 権限を取得できるような欠陥が数多く残されている場合があります。 このような場合、攻撃者にそのマシンの root 権限を取得されてしまうのは時間の問題です。

パスワード推測攻撃を避ける方法は大きく分けて 2つあります。 ひとつは sshd サーバデーモンの待ち受けポートを変更することによって、 通常の方法ではアクセスできないようにする (接続を拒否される) 方法と、 パスワード推測攻撃をなるべく早期に検知して、そのホストからのログインを禁止する方式です。 本項ではこれら両方の方法を紹介します。

sshd の待ち受けポートを変更する

先に説明したように、パスワード推測攻撃がその目的を果たすには、なるべく多くのホストに対して しらみつぶしに攻撃を行う必要があります。そのため、現在出回っている攻撃プログラムの ほとんどは sshd がデフォルトで接続を受けつけている TCP の 22番ポートのみに アクセスし、それが失敗した場合はそのホストへの攻撃をあきらめます。 [脚注: これは筆者がログを分析したうえでの推測です。] なぜなら、世の中のほとんどの sshd はまだ 22番ポートで 接続を受けつけており、攻撃者の立場にたって考えてみると複数のポートを試すよりは別のホストに移ったほうが効率的だからです。 しかし SSH は Webサーバやメールサーバとは異なり、限られた人数のユーザのみを 対象にしたサービスであるため、とくにこの待ち受けポートが 22番でなければならないという 理由はありません。したがって、この「22番ポートを試す」という特徴を逆手に取って、 sshd を22番ポート以外のポートで待ち受けさせることで、 この手の攻撃を避けることができます。 ただし、いずれもっと高度なスキャンが行われるようになり、 デフォルト以外のポートも攻撃されるようになる危険性はあります。

sshd サーバデーモンの待ち受けポート番号を変更するには、 sshd_configファイルの Port設定項目を使います。
sshd の待ち受けポート番号を変更する
Port ポート番号

設定例:

Port 999                    (TCP の 999番ポートで接続を待ち受ける)
注意
ここで挙げたポート番号はあくまで一例です。 実際には、よく使われるポート番号に偏りがあってはいけないので、 かならず自分でランダムな番号を選ぶようにしてください。 ポート番号は、すでに他のデーモンによって使用されている番号 以外であればどこでもかまいません。

なお、sshd サーバデーモンのポート番号を変更した場合は、 sshコマンドでログインするさい、 以下のようにポート番号を指定するようユーザに周知徹底する必要があります。

$ ssh -p999 server.example.com   (server.example.com の 999番ポートに接続する)

しかしログインのたびに -p オプションを使用するのは 面倒なため、このような場合は個人設定ファイルを使うことをおすすめします (4.7. 個人用の設定ファイルでさらに快適に 参照) 。

denyhosts でパスワード推測攻撃を検知して阻止する

パスワード推測攻撃を防ぐ方法がもうひとつあります。 パスワード推測攻撃によって発生するログパターンを検出し攻撃を阻止するという方法です。 なんらかの理由により、 sshd サーバデーモンの待ち受けポート番号をデフォルトの 22番から変更できない場合に有用です。パスワード推測攻撃を検出するには パケットフィルタを使う方法と、ユーザレベルのプログラムを使う方法が ありますが、本書ではオペレーティングシステムに依存しない denyhosts というソフトウェアを使った方法を紹介します。 [脚注: パケットフィルタを使った方法については、 http://www.unixuser.org/%7Eharuyama/security/openssh/20051108.html を参照してください。]

denyhosts [脚注: http://denyhosts.sourceforge.net/ ] は定期的に /var/log/messages などに現れる sshd のログをスキャンし、 図 passwordattack に見られるようなパスワード推測攻撃のパターンを検出します。 その後、denyhosts は不審なホストの IPアドレスを /etc/hosts.deny に追加します。 なお、denyhosts は /etc/hosts.denyファイルを使うため、 OpenSSH のコンパイル時に tcp_wrappers のサポートが必要です。 Red Hat 用には rpm パッケージが配布されています。

denyhosts を動かすには、 cron デーモンにより定期的に起動する方法と、 denyhosts.py 自身をデーモンとして実行する方法の 2 通りがありますが、 ここでは denyhosts.py 自身をデーモンとして走らせる方法を紹介します。 ソースコードからインストールする場合は、DenyHosts ディレクトリ中で以下のように実行します。
denyhosts をインストールする
# python setup.py install

denyhosts の関連ファイルは /usr/share/denyhosts 以下にインストールされます。 ここにあるサンプル設定ファイル denyhosts.cfg-distdenyhosts.cfg などの名前で複製して設定を書き換えます。 この設定ファイルは、デフォルトでは Red Hat 用に書かれていますので、 それ以外のオペレーティングシステムをお使いの方は以下の表を参考に 必要な箇所を変更してください。
表 denyhosts-settings. denyhosts.cfg の代表的な設定項目
項目説明
SECURE_LOG sshd サーバデーモンのログが記録されるファイル。 (例. /var/log/secure)
LOCK_FILE denyhosts デーモンのロックファイル。 (例. /var/lock/subsys/denyhosts)
ADMIN_EMAIL denyhosts が攻撃を検出したとき、通知先となる管理者のメールアドレス。 この値を空にしておくとメールは送信されない。
WORK_DIR denyhosts の作業用ディレクトリ。存在しない場合は自動的に作られる。 (例. /var/denyhosts)
DAEMON_LOG denyhosts がデーモンとして動作するときのログファイル。 (例. /var/log/denyhosts)
表 daemon-control-settings. daemon-control スクリプトの設定項目
項目説明
DENYHOSTS_BIN denyhosts プログラムのパス名。 (例. /usr/bin/denyhosts.py)
DENYHOSTS_CFG denyhosts.cfg 設定ファイルのパス名。 (例. /usr/share/denyhosts/denyhosts.cfg)
DENYHOSTS_LOCK denyhosts デーモンのロックファイル。 denyhosts.cfg 設定ファイルの LOCK_FILE の値に揃える。

設定ファイルを修正したら、つぎにデーモン起動用のスクリプト daemon-control-dist も同様に複製して修正します (表 daemon-control-settings)。 このスクリプトを denyhosts のような名前に変更し、 /etc/init.d (Red Hat、Solaris) や /etc/rc.d (FreeBSD) のようなシステム起動時に実行される スクリプト用のディレクトリに移動します。 その後、chkconfig コマンドなどを使って、 このデーモンを自動的に起動するよう設定しておけばインストールは完了です。

denyhosts はパスワード推測攻撃を検出すると、 ログファイルに以下のような出力を残し、/etc/hosts.deny に 攻撃元の IP アドレスを追加します:

2006-04-09 20:30:03,009 - denyhosts   : INFO     new denied hosts: ['xx.xx.xx.xxx']

また、ADMIN_EMAIL メールアドレスを指定した場合は システム管理者宛に以下のような電子メールが送られます。

Date: Sun, 09 Apr 2006 20:30:03 -0400
From: DenyHosts <nobody@localhost>
Subject: DenyHosts Report
To: root@server.example.com

Added the following hosts to /etc/hosts.deny:

xx.xx.xx.xx (attacker.evil.dom)

もしユーザが頻繁にパスワードを間違えるなどして、 ほんらい許可されるべき IP アドレスが追加されてしまった場合には、 まず WORK_DIR で指定されたディレクトリに allowed-hosts というファイルを作成し、 そこに許可されるべき IP アドレスを追加します。 その後 /etc/hosts.deny ファイルを編集して その IP アドレスを消去してください。 [脚注: しかし denyhosts に検出されるほど頻繁にログインするユーザは、 公開鍵認証と認証エージェントを使用したほうがよいでしょう。]

5.6. OpenSSH を更新する

5.6.1. OpenSSH を新しいバージョンに上げる

OpenSSH を新しいバージョンにアップグレードするときは、以下のような注意が必要です:

ソースコードから make install でインストールした場合には、 既存の設定ファイルやホスト鍵ファイルはそのまま残されます。 Red Hat で rpm ファイルからバイナリをインストールした場合には 既存の設定ファイルやホスト鍵ファイルは残され、新しい設定ファイルが sshd_config.rpmnew のような名前でインストールされます。

なお、アップグレード後は sshd を再起動する必要がありますが、 sshd はいったん開始したセッションを子プロセスとして独立させるため、 最初に起動した sshd プロセスを終了しても現在ログインしている ユーザがログアウトさせられてしまうことはありません。

5.6.2. サーバのホスト鍵を変更する

2章で説明したように、サーバのホスト鍵は (ホスト公開鍵・ホスト秘密鍵ともに) 通常よほどの理由がない限り変更すべきではありません。 なぜなら OpenSSH にとってはホスト鍵だけがその通信相手を 信頼する唯一の手がかりであり、IPアドレスや DNS のホスト名による判別ではなりすまし攻撃を 受ける可能性があるからです。しかし、将来計算機の処理速度が上がり、 ホスト鍵の暗号強度を現在より大きくする必要性が出てきた場合には、 現在のホスト鍵をより長い (暗号強度の大きい) ホスト鍵で置き換える必要があるかもしれません。

この場合、ホスト鍵が変更されることになるので、 現在そのサーバを SSH で使用しているすべてのユーザに 変更後のホスト公開鍵の指紋を通知し、古いホスト公開鍵を ~/.ssh/known_hosts ファイルから削除してもらう必要があります。 クライアント側で known_hosts ファイルから特定のホスト公開鍵を削除するには ssh-keygen -Rコマンドを使います。
特定のホスト公開鍵を known_hosts ファイルから削除する
$ ssh-keygen -R ホスト名あるいはIPアドレス

実行例:

$ ssh-keygen -R server.example.com                      (server.example.com のホスト公開鍵を削除する)
/home/yusuke/.ssh/known_hosts updated.               (known_hosts ファイルが更新された)
Original contents retained as /home/yusuke/.ssh/known_hosts.old

変更前の known_hosts ファイルの内容は、 known_hosts.old ファイルに保存されます。

5.7. 攻撃を受けたときは

5.7.1. なりすまし攻撃を受けたときは

OpenSSH は通信に暗号を使っているので、通信の盗聴による情報の漏曳を防ぐことができます。 しかし第三者がネットワーク上の通信に干渉するなりすまし攻撃に対しては、 OpenSSH は攻撃が行われたことを検出して通信を停止することはできるものの、 なりすまし攻撃の犯人を特定したり、攻撃をやめさせたりすることはできません。

何者かがネットワーク中で不正な ssh コマンド (あるいは scp, sftp コマンド) を 実行した際は、次のようなメッセージが表示されます。

$ ssh yusuke@server.example.com
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @              (ホスト鍵が変わっている旨の警告)
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!                    (なりすまし攻撃の危険がある)
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that the RSA host key has just been changed.         (ホスト鍵が本当に変えられた可能性もある)
The fingerprint for the RSA key sent by the remote host is
fe:fe:fe:fe:af:bb:fe:fe:fe:fe:3e:fe:cc:fe:fe:ff.                         (管理者に連絡して、ホスト鍵の指紋を確認すること)
Please contact your system administrator.
Add correct host key in /home/yusuke/.ssh/known_hosts to get rid of this message.
Offending key in /home/yusuke/.ssh/known_hosts:19                        (known_hosts ファイルの 19行目にある鍵と矛盾する)
Password authentication is disabled to avoid man-in-the-middle attacks.  (パスワードの入力は禁止される)
Keyboard-interactive authentication is disabled to avoid man-in-the-middle attacks.

上のメッセージはホスト server.example.com のホスト公開鍵が ~/.ssh/known_hosts ファイルの 19行目に格納されているものと 食い違っていることを示しています。このような場合はまずネットワーク管理者に連絡し、 ネットワークに異常がないか確認してもらうようにしてください。 同一ネットワーク上で Ethernet ハブなどを共有しているマシンが乗っとられた場合、 そのマシンは簡単に IP アドレスを詐称することができるので、他のマシンが なりすまし攻撃を受ける危険性があります。

5.7.2. マシンに侵入されたときは

不幸にしてマシンに侵入されてしまったときは、すぐにシステムを止めてネットワークから切り離し、 chkrootkit [脚注: http://www.chkrootkit.org/ ] などの 検査ツールでマシンをチェックしましょう。クラッカーが root 権限の取得に成功した場合は ログが消去されたり、lsps などのコマンドがすべて (侵入者の手がかりを表示させないような) 偽物にすり替えられてしまっている可能性があるので、オペレーティングシステムを含む / パーティションはすべて消去して再インストールすることをおすすめします。 なお、このような事態に備えてユーザのホームディレクトリなどを含む「データ用」のパーティションと、 オペレーティングシステム自体が入っているパーティションは あらかじめ分けておくほうが望ましい管理方法であるといえます。

さらなる攻撃に使われる可能性のあるファイル

クラッカーが侵入した場合、以下のファイルに 書かれているホスト名もクラックの対象になる場合があります。 なぜなら、これらのファイルに含まれているマシンは そのマシンと同じユーザ名を共有している可能性が高いためです。

また、ユーザのホームディレクトリにある以下のようなファイルは ホスト名やパスワード情報を含んでいるため、同様に悪用される危険性があります:

OpenSSH に関連するファイルとしては、ユーザがこれまでに ログインしたホストのホスト公開鍵を記録しておく ~/.ssh/known_hosts ファイルが悪用される危険性があります。 この問題については、known_hosts ファイルのハッシュ化によって 悪用を防ぐことができます (5.8.2. known_hosts ファイルの内容をハッシュ化する 参照)。

5.8. クライアントを安全に使う

当然ながら、OpenSSH を使ったセキュリティは最終的にはユーザの行動に依存します。 ユーザがなるべく推測されないパスワードやパスフレーズを 使うことはもちろん大切ですが、もうひとつ大切なのは、ユーザがクライアントを 安全に使うということです。 この節では、ユーザがクライアントマシンをなるべく安全に使うための対策を挙げていきます。

5.8.1. 信頼できないマシン (あるいは環境) には重要な情報を残さない

これは以下の項目のすべてに共通することですが、 信頼できないクライアントや環境を用いる場合は、基本的にはパスワードや秘密鍵などに 関連する重要な情報を残さないことが大切です。 たとえば衆人環境に置かれている Windows マシンから PuTTY を使ってログインするような場合、 そのマシンに秘密鍵ファイルを保存したり、パスワードを入力しているところを他人に見られたりするのが 危険なのは言うまでもありません。信頼できないサーバ上で秘密鍵ファイルを保存したり、 認証エージェントの転送 (4.4.4. 認証エージェントを使う際の注意 参照) や X11 転送 (4.5. X11 転送を使う 参照) を 許可したりすることも同様に危険です。また Linux や FreeBSD を クライアントマシンとして使っている場合は、サーバと同様、 セキュリティ・アップデートを適用したり、必要のないサービスを走らせないなどといった 対策を取ることをおすすめします。

5.8.2. known_hosts ファイルの内容をハッシュ化する

多数のホストに対して ssh を使う場合、 クライアントの known_hosts ファイルにはしだいに ログイン先のホスト名とホスト公開鍵が蓄積されていきます。 このファイルは通常クライアント上に置かれていますが、 もしこのマシンが侵入されたらどうなるでしょうか? 攻撃者は各ユーザの ~/.ssh/known_hosts に そのユーザのログインしたホスト名が格納されていることを知っている (しかもサーバ上のユーザ名はクライアントのユーザ名と同じであることが多い) ので、 次に攻撃すべき手軽な標的を入手できてしまうことになります。

OpenSSH では、このような事態を防ぐために known_hosts ファイルに書かれているホスト名を「ハッシュ化」して、 攻撃者の目から隠すことができます。
known_hosts ファイルのハッシュ化

ハッシュ化されていない known_hosts ファイル:

server.example.com,11.22.33.44 ssh-rsa AAAAB3NzaC1yc2E...(中略)...

ハッシュ化された known_hosts ファイル:

|1|gzhiUVog1SQpbN9C5Xy+tAtLFyw=|WMlwbGczEmwT+9HGRjRusUYGsn8= ssh-rsa AAAAB3NzaC1yc2E...(中略)...
|1|DlCiwr7NfqMrnJo6VftMPfmVfu8=|4mdblOcCifOb8vhDlTz4QzzVJJQ= ssh-rsa AAAAB3NzaC1yc2E...(中略)...

sshコマンドはユーザが入力したホスト名を known_hosts 中に記されているホスト名と正確に比較するため、 ホスト名をハッシュ化した文字列どうしを比較した場合でも対応するホスト公開鍵を発見できます。 しかも、ハッシュ化した文字列から元のホスト名を復元することはできません。

すでに使われている known_hosts ファイルについて、記録されているホスト名をハッシュ化するには ssh-keygen コマンドを使います。 古いファイルの内容は known_hosts.old というファイルに移されます。 いちどハッシュ化した known_hosts ファイルを元の形式に戻すことはできませんので 注意してください。
known_hosts ファイルに含まれるホスト名をすべてハッシュ化する
$ ssh-keygen -H

いっぽう、これから新しく追加されるホスト名をすべてハッシュ化する場合は、 ~/.ssh/config あるいは ssh_config設定ファイルに HashKnownHosts 設定項目を追加します。
新しく known_hosts ファイルに追加されるホスト名をハッシュ化する
HashKnownHosts yes

OpenSSH は known_hosts ファイルを検査するとき ハッシュ化されたホスト名と、ハッシュ化されていないホスト名を自動的に識別します。 そのため、ハッシュ化されたホスト名と、ハッシュ化されていないホスト名の行が 混在していてもかまいません。

ホスト名をハッシュ化することの欠点は、従来の形式とは違って known_hosts ファイルを人間がテキストエディタなどで直接 編集できなくなることです。そのため、なんらかの事情によりホスト公開鍵が変わり known_hosts ファイルから削除しなければならない場合は ssh-keygen -R コマンドを使う必要があります。 詳しくは 5.6.2. サーバのホスト鍵を変更する を参照してください。

5.8.3. .netrc, .fetchmail などの設定ファイルにパスワードを記入しない

侵入者のさらなる攻撃の手助けをしてしまうファイルは、 OpenSSH 以外のソフトウェアにも存在します。 とくに ftpfetchmail などのソフトウェアは リモートホストのユーザ名とパスワードを ~/.netrc~/.fetchmail などのファイルに記録するので、攻撃者はこれらのファイルを利用して簡単にリモートホストを 攻撃できてしまいます。したがって、これらのファイルはなるべく利用しない方針とすることをおすすめします。

5.8.4. X11 サーバのポートを閉じる

古いオペレーティングシステムでは、X サーバがデフォルトで TCP の 6000番ポートから ネットワーク上の他のホストに接続を許している (listen している) ものがあります。 これはセキュリティ上、望ましくありません。通常、クライアントが Xサーバを使用するには 認証が必要ですので、ネットワーク上の他のホストに接続を許したからといって 必ずしもすぐに侵入されるわけではありません。しかし OpenSSH の X11転送を使えば 外部からローカルな Xサーバに直接接続する必要はなくなるので、必要のないポートは なるべく閉じておいたほうが無難です。

最近のオペレーティングシステムでは、デフォルトで X サーバが ネットワーク上の他のホストに接続を許さないように設定されています。 これを確かめるには netstat -an コマンドを実行して、 TCP の 6000番ポートが LISTEN 状態になっているかどうかを見ればよいのです。

X サーバの listen しているポートを確認する (Linux):

$ netstat -an | grep 6000
tcp     0     0 0.0.0.0:6000          0.0.0.0:*            LISTEN

上の例では Xサーバが TCP の 6000番ポートを listen していることを示しています。 このような場合は、Xサーバを最初に起動するときに -nolisten tcp というオプションをつけることで TCP を listen しないように指定することができます。 xdmgdm などのディスプレイマネージャを使っている場合は、 /usr/X11R6/lib/X11/xdm/Xservers などの設定ファイルに -nolisten tcp オプションを追加します。 また、startx でコマンドラインから 直接 Xサーバを起動するときは、コマンドラインでも -nolisten tcp オプションを指定できます。
TCP を listen せずに Xサーバを起動する
$ startx -- -nolisten tcp
コラム - 新山もクラックされました
ここでは筆者自身の失敗体験についてお話ししましょう。 筆者は 2004年 9月ごろに自分がふだん使っている Linux マシンに 侵入されました。攻撃元はおそらく東欧で、クラッカーは 筆者がうっかりテスト用に作成していた "mysql" というユーザ名でまんまとログインすることに成功したのです。 しかし幸運にも root権限を取得されることはなく、 間抜けなことにクラッカーは自分のシェル履歴をそのまま残していました。 その履歴を調べてみると、クラッカーはマシンにログインしてから 以下のようなことをしていることがわかりました:
  1. OS の種類を調べる。
  2. その OS の (ローカルな) セキュリティホールを悪用して、 一般ユーザが root権限を取得できるような悪用プログラム (exploit) を片っぱしからダウンロードして実行してみる。 (なお、このダウンロード用サイトもすでに乗っとられたマシンだったようです)
  3. root権限を取るのに失敗すると、一般ユーザ権限でポートスキャンを 走らせようと試みる。
  4. それも失敗すると、隠しディレクトリで 簡単な webサーバと IRC サーバを起動させ、ログアウトする。

筆者の場合はマシンを最新のカーネルにアップデートしていました。 そのためクラッカーは root 権限を取るのに失敗し、 ログも消されず、他のマシンへの直接攻撃にも使われなかったようですが、 webサーバはツールの配布に利用される恐れがありますし、 最近の研究によれば、IRC チャネルは乗っとられた大量の「ボット」 コンピュータの制御に利用されているということです。 したがって、たとえ自分のシステムにほとんど影響がなくても 間接的にクラッカーを支援していることになってしまうのです。 このときはつくづく恥じ入りました。


Yusuke Shinyama