NFLabs. エンジニアブログ

セキュリティやソフトウェア開発に関する情報を発信する技術者向けのブログです。

ssh接続時の二要素認証の実装について

こんにちは。事業推進部の廣田です。
NFLabs. アドベントカレンダー3日目ということで、 本稿ではssh接続時の二要素認証の実装について書いてみたいと思います。

はじめに

現在私はNTTコミュニケーションズ株式会社からNFLabs.に出向しているのですが、出向してくる前にNFLabs.が実施するセキュリティエンジニア育成研修カリキュラムを長期間みっちりこってりと受講してきました。
研修ではサーバ構築など基本的な内容からソフトウェア開発やペネトレーション等、セキュリティエンジニアに必須となるスキルを体系的に幅広く学ぶことができ、今回はサーバ構築の時に取り組んだ課題についてのお話です。

どんな課題だったの?

研修課題の中でssh鍵認証を実装したWebサーバを構築したのですが、そこで追加課題として次のようなお題をいただきました。

  • 構築したWebサーバをさらにセキュアに設定すること
  • 脆弱な設定があればその修正もよし、使用しているミドルウェアにさらにセキュアな設定をするもよし
  • 上記を満たすように自由に取り組んでOK

とまあ、こんな感じだったので、何をしようかなーとインターネッツを彷徨っていたわけですよ。

出会いは突然に

うーん…どうしたものか。。。 といろいろ眺めていたところ、ふとしたことからGoogle Authenticatorを使って二要素認証を導入できることを知り、その導入過程でterminal画面にババァン!QRコードがでかでかと表示されることに衝撃を受け、ただ衝撃を受けたからという理由でssh接続時の二要素認証の実装をテーマにしようと決めたのでした。

課題設定

テーマも決まったことなので、勉強がてら架空の要件も無駄に絡めつつ自身の取り組み内容を次のように設定してみました。ゴールを明確にするのは大事ですからね。

実施内容

Google Authenticatorを使ったssh接続時の二要素認証の実装

実施目的

構築したサーバでは鍵認証までを実装しているが、さらに鍵ファイルの流出リスクを加味し、Google Authenticatorによるワンタイムパスワード認証を追加した二要素認証の実装により、よりセキュアなサーバとする。

実装要件
  1. 鍵認証+Google Authenticatorの二要素認証を基本設定とする。
    ※パスワード認証は省略し三段階認証にはしない。

  2. 特定のユーザはパスワード認証のみでOKとする。
    ※システムとか特定のアカウントだけはパスワード認証にしたいみたいな要件がある想定で…

  3. 認証の順番は鍵認証 -> Google Authenticatorとする。

実装手順

それでは構築していきましょう。

構築環境
  • 構築したsshサーバのOSはCentOS 7.9 minimalを使用しました。
前提条件
  • ssh鍵認証が実装されたsshサーバをすでに構築しているものとする。
    ※構築方法はググればたくさんの先人たちが教えてくれます。

手順(1). Google Authenticatorのインストール

$ sudo yum install epel-release 
  • Google Authenticatore PAMモジュールをインストールします。
$ sudo yum install google-authenticator 

手順(2). Google Authenticatorの設定

  • Google Authenticatorを設定するユーザで下記コマンドを実行すると対話形式でGoogle Authenticatorの設定が開始します。設定のために順番に質問されますが、基本的にすべて「y」で大丈夫です。
$ google-authenticator
  • y の場合は時間ベースの確認コードが生成され、n の場合はカウンターベースの確認コードが生成されます。今回は時間ベースを選択します。
Do you want authentication tokens to be time-based (y/n) y
  • 下のようなQRコードがでっかく表示されるので、スマートフォンにインストールしたGoogle AuthenticatorアプリでQRコードをスキャンして登録します。 Microsoft Authenticatorアプリの場合も同様にQRコードをスキャンして登録します。(QRコードが表示されない場合は、表示されたURLにアクセスすればQRコードが表示できる)
  • QRコードの下にGoogle Authenticatorアプリが使えなくなった時のための緊急コードが表示されます。何かあった時のために手元に控えておきましょう。 f:id:nfl_mac:20211117223141p:plain
  • WinAuthでTOTPを生成させる際にはここの1の部分にQRコードと一緒に生成されたSecret Codeを入力してください。 f:id:nfl_mac:20211129214902p:plain

  • Google Authenticator の秘密鍵や設定情報を表示されているパスに保存してもよいかの確認です。変更の必要がなければそのままyで進めましょう。

Do you want me to update your "/home/test/.google_authenticator" file? (y/n) y
  • 同じ確認コードを複数回使うことを禁止する設定です。yにすると30秒ごとに1回のログインに制限されます。
Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) y
  • クライアント(Google Authenticatorをインストールしたスマートフォン)とサーバで最大4分の時間のずれを許容する設定です。スマートフォンの時間がずれることは稀にあるので y にしておくのがよいと思います。。
By default, a new token is generated every 30 seconds by the mobile app.
In order to compensate for possible time-skew between the client and the server,
we allow an extra token before and after the current time. This allows for a
time skew of up to 30 seconds between authentication server and client. If you
experience problems with poor time synchronization, you can increase the window
from its default size of 3 permitted codes (one previous code, the current
code, the next code) to 17 permitted codes (the 8 previous codes, the current
code, and the 8 next codes). This will permit for a time skew of up to 4 minutes
between client and server.
Do you want to do so? (y/n) y
  • 30秒ごとに3回までのログイン試行に制限する設定です。つまり確認コードを3回間違えるとその確認コードは無効になります。y にする場合は、あわせて Google Authenticator PAMモジュールの echo_verification_code オプションを付けておくのがよいです。
If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting? (y/n) y

手順(3). チャレンジアンドレスポンス認証と二要素認証の有効化
- チャレンジアンドレスポンス認証が有効化されるようsshd_configの修正をします。

$ sudo vi /etc/ssh/sshd_config
ChallengeResponseAuthentication no
↓
ChallengeResponseAuthentication yes 
  • 二要素認証が有効となるようsshd_configの最終行に次の1行を追加します。
AuthenticationMethods publickey,keyboard-interactive
  • sshdを再起動します。
$ sudo systemctl restart sshd

手順(4). NTPサーバの時刻同期状態の確認

サーバの時間が時刻同期されていないとGoogle Authenticatoreの認証がスマートフォンの時間とずれるため失敗してしまいます。

  • 時刻同期状態を確認します。
$ timedatectl

NTP synchronized: yesとなっていることを確認します。

  • NTP synchronized: noとなっている場合はchronydを再起動します。
  • 少し時間が経つとyesになるはず。
$ sudo systemctl restart chronyd
$ timedatectl

chronydを再起動後NTP synchronized: yesとなっていることを確認します。

手順(5). sshdのPAMモジュールへのGoogle AuthenticatorのPAMモジュールの組み込み
- SSH認証の際にGoogle AuthenticatorのPAMモジュールが呼び出されるようにsshdのPAMモジュールのファイルの最後に以下の行を追記します。

$ sudo vi /etc/pam.d/sshd
auth required pam_google_authenticator.so nullok echo_verification_code

ここまでの設定により、SSHログインの際に鍵認証に加えて、Google Authenticatorの確認コード(keyboard-interactive)の認証も求められるようになります。ただし、keyboard-interactiveにはパスワード認証も含まれるため、鍵認証→パスワード認証→確認コードの3段階認証となっています。


(補足)PAMモジュールオプションについて
nullok
Google Authenticator を設定していないユーザーが、認証コードなしでログインできることを許可します。このオプションが設定されていないとGoogle Authenticatorを導入していないユーザの認証が失敗してしまうため、特定ユーザのためにこのオプションが必要です。

echo_verification_code
入力時に認証コードを表示します。確実に認証コードを入力できるように付けておくのがよいです。認証コードの有効期間は30秒なので表示しても特に問題はないと思います。


手順(6). 特定ユーザの例外処理の実装とパスワード認証の停止

  • 特定ユーザはパスワード認証のみとなるようにsshd_configに以下の行を追記します。(特定ユーザがaaaa、bbbbと存在する場合の例)
$ sudo vi /etc/ssh/sshd_config
AuthenticationMethods publickey,keyboard-interactive
↓
AuthenticationMethods publickey,keyboard-interactive   
Match User aaaa
 AuthenticationMethods keyboard-interactive
Match User bbbb
 AuthenticationMethods keyboard-interactive

特定ユーザが複数いる場合はMatch User xxxx、AuthenticationMethods・・・の2行を追加していきます。

  • sshdを再起動します。
$ sudo systemctl restart sshd
  • 特定ユーザ以外はパスワード認証を実施しないようpassword-authのPAMモジュールに以下の行を追記します。(特定ユーザがaaaa、bbbbと存在する場合の例)
$ sudo vi /etc/pam.d/password-auth
auth        required      pam_faildelay.so delay=2000000
auth        sufficient    pam_unix.so nullok try_first_pass
↓
auth        required      pam_faildelay.so delay=2000000
auth        sufficient    pam_succeed_if.so login notin aaaa:bbbb
auth        sufficient    pam_unix.so nullok try_first_pass

この設定により特定ユーザに一致しない場合は、パスワード認証が走る前にpassword-authのPAMを抜けることができます。特定ユーザがさらに存在する場合はaaaa:bbbb:cccc:・・・と追加していくか、特定ユーザ用のユーザグループを用意して例外処理を実施します。


(おまけ) teratermで二要素認証接続を利用したいとき

teratermを通常使用した場合、二要素認証接続ができない仕様らしく、ちょっとした工夫が必要となります。

解決方法その1

これを言ってしまうと元も子もないのですが、実際puttyであれば普通に二要素認証も対応できます。おとなしくteratermの利用を諦めるのも一つの手です。

解決方法その2

  • 二要素認証用のteraterm起動batファイルの作成

以下のようなbatファイルを作成します。

"C:\Program Files (x86)\teraterm\ttermpro.exe" <接続先サーバのIPアドレス>:<SSHのポート番号> /ssh2 /auth=publickey /ask4passwd /keyfile="<秘密鍵ファイルのパス>"

作成したbatファイルを起動するとteratermが立ち上がるので、ユーザ名とパスフレーズを入力し、まず鍵認証での接続を試みます。
「認証に失敗しました。再試行してください」と返されるが、気にせず認証方式を「キーボードインタラクティブ認証を使う」に設定し接続すると二要素認証の認証コードを求められるので、正しいコードを入力するとログインできます。


(おまけ2) authy-sshを使った二要素認証

これまでGoogle AuthenticatorのPAMモジュールを使った二要素認証の話をしてきましたが、 他にも二要素認証を実現する方法はあり、一例としてauthy-sshを使って二要素認証ができます。
導入方法の詳細は割愛しますが、公式サイトgithubを 参考にして、頑張ってAPIキーをとってきたりしながらsshサーバ側にauthy-sshを導入すれば、 先ほどまでとはまた違った形で二要素認証を導入することができます。
f:id:nfl_mac:20211130000534p:plain どんな感じに動くのかは公式サイトの動画を見ていただくとイメージがつくと思いますが、設定した特定のユーザにssh接続しようとすると authy-sshの認証プラグインが間に入ってきて二要素認証を実現することができます。


以上の手順で設定した要件を満たしssh接続時の二要素認証の実装を実現することができました。
特定ユーザの制御の実装については、対象の特定ユーザが大量に出てくるような環境だと運用がしんどく、本当はもう少し賢いやり方があるのかもしれません。

まとめ

  • ssh接続時の二要素認証を実装したセキュアなサーバを構築できた

  • 単純な実装ではなく、あるかもしれない運用要件を想像し、その要件も取り入れて実装を実現した

本稿を記載するにあたり参考にさせていただいたURL

https://blog.apar.jp/linux/12502/
http://naoberry.com/tech/two-step-authentication/
https://www.bigbang.mydns.jp/ssh-ga-x.htm
http://www.linux-pam.org/Linux-PAM-html/sag-pam_succeed_if.html
http://blog.techfirm.co.jp/2018/05/23/
https://www.authy.com/integrations/ssh/