それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

linuxのログインに総当り対策する。(ロックアウトによる対策)

シリアルコンソールでのログイン

シリアルコンソールでのログインを有効にした。ttyS0を非常用のポートとして確立することができた。

総当りの懸念

物理的にサーバーを強奪・押収されたとき、シリアルコンソール接続のメンテナンス専用機の乗っ取りなどで、「総当たり」で攻撃をできてしまう。

物理的に取られてしまうと、sshやシリアルコンソールに対して、expectなどのコマンドを用いて無限にパスワードの総当りを許すことになる。

sshであればファイアウォールでパケット制限を掛ける事もできる。それを手軽に実現するfail2banのようなソフトウェアもある。fail2banはIPアドレスをBANするものでありシリアルコンソールでは動かない。

シリアルコンソールで総当り対策を入れるのはどうすればいいのか。

ロックアウト機構を入れる。

総当りに対する対策は、「ロックアウト」が挙げられる。真っ先に使われるのはロックアウトである。

iphone のパスコードや、キャッシュカードの暗証番号など、数回間違うとしばらく入力不可能にする。など、あれと同じことをLinuxできたらいいなと。

Linuxのログインの仕組みに処理を挟むPAMという機構があり、そのプラグインとしてfaillockが用意されている。

Debian/11で faillock を使う

debian/11 以降では CentOS/Fedoraと同じ PAM faillock を使う。10までは tally だった。11/bullseyes以降は faillockである。

参考資料に書いてあッた。

failock 概要

failock は PAM のプラグインとして動作する。ログインの失敗が閾値(失敗回数/時間)を超えると、ユーザをロックアウトする。

変更するファイルと注意点

作業前の注意

PAMファイルは、設定を間違えると大変。新規ログインが不可になる。ログイン不可になって詰む。

PAMミスによるログイン不可は、sudo も然りである。設定ミスはsudoすも使えなくなる。なので細心の注意が要求される。

作業前にバックアップ

/etc/pam.d バックアップをとっておく。もしくは仮想マシンで十分に検証する。

sudo 済みのセッションを開けておく

sudo 不可になって慌てないために、root にパスワードを設定してrootユーザーでログインできるようにしておく。

また、ログイン不可になっても、認証確立済みのログイン済みセッション(SSHログイン済み)は有効なので、ログイン済みセッションをターミナルに作っておく

緊急メンテ用のOSがあるといい

ログイン不可になって慌てないために、USBメモリUbuntu、LiveUSBから起動してマウントして、LUKSパスワードを入力し、バックアップからPAMのミスを書き戻す。。そのための緊急マウント手段を確保しておく

事前の準備。

作業前の、安全な退路の確保をする。

  • /etc/pam.d をバックアップとっておく
  • root にパスワードを一時付与している
  • ログイン済み(sudo su or root)の セッションを開けている。
  • USBメモリで緊急ブートOSを用意している。

PAMの設定ミスは即死なので注意すること。

設定ファイル

対象になる設定ファイルは次の3つ

  • /etc/pam.d/common-auth
  • /etc/pam.d/common-account
  • /etc/security/faillock.conf

このうち、pam.d が PAM の設定で、faillock.conf は pamから呼ばれたときの設定である。

/etc/pam.d/common-auth

設定は、次のとおりに変えた。

common-auth / before

root@d01:~# cat /etc/pam.d/common-auth  | grep -vE '^#'
auth    [success=1 default=ignore]  pam_unix.so nullok
auth    requisite           pam_deny.so
auth    required            pam_permit.so

common-auth / after

root@d01:~# cat /etc/pam.d/common-auth  | grep -vE '^#'
auth required pam_faillock.so preauth
auth    [success=2 default=ignore]  pam_unix.so nullok
auth required pam_faillock.so authfail
auth    requisite           pam_deny.so
auth    required            pam_permit.so

/etc/pam.d/common-account

debian は enterpriseを特に意識してないので、common-account  にすべてをまとめている。

/etc/pam.d/common-account | Before

account  [success=1 new_authtok_reqd=done default=ignore]    pam_unix.so
account requisite           pam_deny.so
account required            pam_permit.so

/etc/pam.d/common-account / After

アカウントに処理で、各アカウントで unix ログインの手前で、failock を使ってロック状態を操作する処理を挟む。

account required  pam_faillock.so

account [success=1 new_authtok_reqd=done default=ignore]    pam_unix.so
account requisite           pam_deny.so
account required            pam_permit.so

設定ポイント

1ファイル目common-auth 初期設定は、次のようになっている。

1 auth   [success=1 default=ignore]  pam_unix.so nullok
2 auth  requisite           pam_deny.so
3 auth  required            pam_permit.so

1行目 auth はpam_unix へ流し、成功したら次の1行をスキップ、失敗したら順番に実行で次行へ。
2行目 auth で ここに来たら pam_deny に流してその後をスキップ
3行目 auth でここに来たら、pam_permit に流しておわり

ここに処理を挟む。

挟んだ処理は2行。ただし、success時にスキップ先が変わるため、変更箇所は3点である。

1 auth required pam_faillock.so preauth
2 auth  [success=2 default=ignore]  pam_unix.so nullok
3 auth required pam_faillock.so authfail
4 auth  requisite           pam_deny.so
5 auth  required            pam_permit.so

1 行目 pam_faillock の preauth 処理をする(現在のロック状態ですね)
2 行目 pam_unix に流す、成功したら2行スキップする(5行目に行く)
3 行目 faillock に失敗を流す(記録ですね)
4 行目 pam_deny に処理を流す。(deny = 拒否です)
5 行目 pam_permit に処理を流す。 ( permit = 許可です)

2ファイル目 /etc/pam.d/common-account の設定は次のようになった。

account required  pam_faillock.so

account [success=1 new_authtok_reqd=done default=ignore]    pam_unix.so

pam_unix に流す前に、failock を起動してアカウントの状態を保存する。

もう一つの別の方法

参考資料によると、common-accunt を編集せずに、common-authだけで済ませることも出来る。

今回、使ったのは、次の組み合わせである。

auth  pam_faillock.so preauth
auth  pam_faillock.so authfail
account  pam_faillock.so

それとは、別に、次の組み合わせで使うことも出来る。

auth  pam_faillock.so preauth
auth  pam_faillock.so authfail
auth pam_faillock.so authsucc

こちらは、common-auth だけで完結する。

実際にロックアウトされてみる

ログイン画面をだして、実際にロックアウトを試す。

デフォルト設定では、3回のログイン失敗でログインから締め出される(ロックアウト)状態になる。

再起動すればロックアウトは解除される(失敗回数はリセットされる)

ロックアウトの解除

締め出されたユーザーに再びログインを可能にするには、専用のコマンドで失敗記録をリセットする。

faillock --dir /var/log/faillock --user takuya --reset

ロックアウトの解除の別の方法

ロックアウト解除は、デフォルトで /var/run/faillock/takuya ファイルに記載された失敗回数と時刻を元に算出される。そのためファイルを消せば失敗記録を消すことが出来る。

また、そのファイルは/var/run に存在する。再起動で消失する揮発性記録である。

再起動後にログインが出来るのが便利と考えるか、ゆるい設定と考えるかは利用者次第だと思う。再起動後もロック継続する方法もある(dir設定)

失敗回数の確認

失敗回数の確認もリセット同じコマンドで行う

faillog  --user takuya 

ファイルを見ることで、失敗回数がわかる。

失敗と判定される条件

失敗回数/時間 で判定される。初期値では失敗頻度は、3回/15分間に設定されている。

15分以上前に失敗したものは、期限切れで失敗とみなされない。総当り対策なので、単位時間あたりのログイン失敗件数を重視するようです。

ログインの失敗記録は、記録上限回数分だけ保存される。 上限回数を超えた分は、ログがローテーションされる。

失敗件数を faillog で確認してたときの表示例

root@d01:~# faillock --user takuya
takuya:
When                Type  Source Valid
2022-02-05 20:39:42 RHOST            I
2022-02-05 20:39:47 RHOST            I
2022-02-05 21:14:51 RHOST            V

記録されたログは、その失敗時刻を評価される。

デフォルトのfail_interval = 900 に設定している場合、failog の中から、900sec(15min) の失敗分をカウントする。

表示例をもう一度確認する。

When Type Source Valid
2022-02-05 20:39:42 RHOST I
2022-02-05 20:39:47 RHOST I
2022-02-05 21:14:51 RHOST V

Valid と Invalid であろうと思われる I / V があるのがわかる。

これについて時間を変えて調べてみたところ、fail_interval に含まれる失敗ログが V 、失敗があるが、fail_intervalを超過したのでノーカウントになったのが I で記載されるようです。

なので、例に出てきた。 I/V は次のようにtrue/falseになっていると思われる。

When Valid 経過時間 fail_interval内か?
2022-02-05 21:14:42 I 16min false
2022-02-05 21:14:47 I 16min false
2022-02-05 21:39:51 V 1min true

設定ファイル/etc/security/faillock.conf、閾値の確認

閾値の設定は、pam に引数として記載することも可能であるが、専用の設定ファイルを使ったほうが簡単だと思われる。

設定ファイルはdebianの場合 /etc/security/faillock.conf に設置されていた。

重要な設定は、unlock_timeとfail_intervalだと思います。

fail_interval=900 が初期値で fail_interval間に何度失敗したら、それ以上の試行を止めるか、unlock_time=600 が初期値で、何分間ロックアウトするか。である。

この設定がややこしいのであるが、fail_intervalは、現在時刻から何分前までの失敗を、有効な失敗(カウント対象)とするかであり、3回失敗してても16分前であればカウント対象外になる

問題点・懸念点

sudo 失敗時にも記録される。

sudo でパスワードが訊かれる際も PAMを経由するので、sudoで失敗もカウントされるので注意が必要である。sudoersをうまく設定しておきたい。

閾値の設定と、期限の設定

unlock_timeは、何分間締め出すか、ユーザーにパスワードを入力させない時間を作るものである。この値をunlock_time=0にすると失敗件数が一定に達するとユーザは二度とログインできなくなる。resetで解除するまでパスワードを受け付けない。

たとえば、fail_interval=60 でunlock_time=120ならば、1分間に3回失敗すると次にログイン出来るのは、最後の失敗から2分後である。

たとえば、fail_interval=300 でunlock_time=600ならば、1分間に3回失敗すると次にログイン出来るのは、最後の失敗から10分後である。この場合は、600秒後に、ログインが出来るようになった時点で、fail_interval=300秒も経過しているので、失敗回数はリセットされているはずである

たとえば、fail_interval=100 でunlock_time=10ならば、100秒間に3回失敗すると次にログイン出来るのは、最後の失敗から10秒後である。この場合は、10秒後に、ログインが出来るようになった時点で、fail_interval=100を超過していないので、失敗回数は継続でカウント加算されるはずである

この設定の調整は上手に使い方を考えておきたい。

パスワードの複雑性、人間の失敗傾向と、expect等による機械的ログインの速さを、これらを勘案し、上手に区別して総当りでパスワードが割り出されないバランスを見極めた調整が必要かと思う。

SSHでのロックアウト済みを表示

sshd はUsePAPM設定では、PAMに処理を流し、PAMから結果を受け取るだけなので、エラー・メッセージ(ロック済で認証に移れない場合)が「ロックされてます」を表示できない。解決策としてはChallengeResponseを使ってPAMにすべてを投げることにすればよいが、一部のSSHの機能を犠牲にすることになる( PermitRootLogin prohibit-passwd など)

SSH公開鍵でログインした場合

パスワードなしでSSH公開鍵でログインした場合、sshdがusePAMをしてfaillockに処理が映る場合、common-accountが反応して、失敗件数はすべてリセットされる。

再起動でデータが消える。

再起動でリセットされるのは、 /var/run に保存するからであり、別の場所に設定を移せば良い。たとえば、/var/log/faillock を作りそちらに保存することで再起動後も失敗件数を継続することが出来る。ただしこの場合、失敗件数の確認とリセットのコマンドが変化するので注意が必要。

例:再起動後もロック維持するため /var/log/faillock に保存する

/etc/security/faillock.conf にディレクトリの指定をする

root@d01:~# cat /etc/security/faillock.conf  | grep dir
# The directory where the user files with the failure records are kept.
dir =  /var/log/faillock

コマンドから使うときも --dir オプションを必ず付ける。

faillock --dir /var/log/faillock --user takuya 
faillock --dir /var/log/faillock --user takuya  --reset

紛らわしいもの

  • faillock
  • faillog
  • fail2ban

似たような機能をもつ、似たような名前のツールだが、全て違うもので違うレイヤで動こいてる。

failog はpam tally用コマンドだけが残っていて、実質には動かせない。
fail2ban はIPアドレスベースなのでSSHやSFTPには有効だが、シリアルコンソールなど物理的な強奪・押収には無力である。

ディスクの暗号化

failock を実施していても、物理的にHDDからデータが抜かれると無力である。そのためfaillockはLUKSのような暗号化と組み合わせない限り、効力を発揮しないと思われる。

リモートアクセスのセキュリティを考えるのであれば、 fail2banで十分であると思われる。

時刻の偽装

ロックアウトはタイムベースで行われる。ハードウェア・クロックを操作されると効果は落ちるかもしれない。

NTPを偽装されて時刻を狂わされる事もあるかもしれない。それらの場合でもハードウェアクロックを操作しながら総当りを行うのはなかなか大変であると思われる。

failockは失敗記録が時刻を過ぎても残されるので、仮に時刻を変えられたとしても前の失敗記録は残るので総当り対策をかなり防ぐことが出来る。

もし時刻が不安ならGPSモジュールを使って自分でStratum1になる手もある。そこまでしなくてもハードウェア構成に変化があれば起動しないようにTPMUEFIでロックを掛けておけば、仮想マシンで起動されて、ハードウェア時刻をいじられる心配も無いだろう。

ログインを代替手段にする。

またパスワードでロックアウトせずとも、ユーザー鍵をTPMキーやメモリで管理で行い、その鍵を指紋などでかんたんに取り出せるようにしておけばいい。それらを簡易に使えるようにしたのが、macのTouchIDであり、WindowsのPINコード/Heloである。鍵をハードウェアと紐付けてしまえば、ストレージを強奪されても仮想マシン起動によるオンライン攻撃にさらされる危険性は減る。オフラインでストレージの暗号化キーを探すしか手段がなくなる。

まとめ

faillock 自体の設定

faillock自体の設定はファイルでやる。

dir を /var/run にすると再起動でリセットできる。(ただし再起動自体に時間がかかる)

/etc/security/faillock.conf

+ dir = /var/log/faillock
+ deny = 3
+ fail_interval = 7200
+ unlock_time = 600
+ even_deny_root

failログ保存先を作る。

mkdir -p /var/log/faillock/

リセットと確認

# dir変更している場合
faillock --dir /var/log/faillock --user takuya --reset
# dir 変更してないとき。
faillock --user takuya --reset 

PAM設定

pam 設定は慎重にやる。設定前に認証済みセッションを用意しておく。

/etc/pam.d/common-auth

+ auth required pam_faillock.so preauth
- auth    [success=1 default=ignore]      pam_unix.so nullok
+ auth    [success=2 default=ignore]      pam_unix.so nullok
+ auth required pam_faillock.so authfail
auth    requisite                       pam_deny.so
auth    required                        pam_permit.so

/etc/pam.d/common-account

+ account required  pam_faillock.so
account [success=1 new_authtok_reqd=done default=ignore]        pam_unix.so
account requisite                       pam_deny.so
account required                        pam_permit.so

ssh 経由のとき

ssh がPAMを使うよう構成する

/etc/ssh/sshd_config

usePAM yes

ロックされたとき

次のようにログに出てくる。

tail -f /var/log/auth.log
Feb  8 21:30:48 raspi-ubuntu sshd[2576]: pam_faillock(sshd:auth): Consecutive login failures for user takuya account temporarily locked

2023-05-12

debian でこの機能を入れていたが、unattended upgrades で自動更新されてしまい、common-auth が書き戻された。書き換わったためにログインができなくなっていた。

具体的には、つぎのように書き換わっていた。 このように書いた箇所が

# here are the per-package modules (the "Primary" block)
auth    [success=2 default=ignore]  pam_unix.so nullok
# here's the fallback if no module succeeds
auth required pam_faillock.so authfail
auth    requisite           pam_deny.so

このように変わっていた。

# here are the per-package modules (the "Primary" block)
auth    [success=1 default=ignore]  pam_unix.so nullok
# here's the fallback if no module succeeds
auth required pam_faillock.so authfail
auth    requisite           pam_deny.so

よりによって、 success=2に変えている箇所がsuccess=1に変わってしまった。

Diff/patchでも当てられたのだろうか。いつ書き換わったのか。発火時期がわからない。

非常にめんどくさい。何かいい方法はないのかしら。

参考資料

https://nishy-software.com/ja/dev-sw/pam-pam_faillock-2/