【Django】ブルートフォース攻撃対策

django-axes-title CODE

みなさん、Djangoアプリケーションの開発する場合、ブルートフォース攻撃対策していますか?

ブルートフォース攻撃(総当たり攻撃)とは、暗号解読方法のひとつで、可能な組合せを力づくで全て試すやり方です。

ユーザーにログイン用のパスワードを設定しているアプリケーションの場合、この対策を行っていないと攻撃に遭い、個人情報の漏洩やシステムの改ざんに繋がる恐れがあります。

 

Djangoアプリケーションで対策をする場合、 django-axesライブラリでアカウントロック機能を搭載する方法があるので実装方法を紹介します。

 

なお、この記事で使うPython・Django・django-axesのバージョンは以下のとおりです。

Python 3.7
Django 2.1.7
django-axes 5.0.12

 

ブルートフォース攻撃の主な対策は?

主な対策方法は以下が挙げられます。

  • パスワードの桁数を増やす
  • パスワードの文字種を複数にし、パターンを増加させる
  • アクセス元を制限する
  • ワンタイムパスワード、セキュリティトークンの仕様
  • パスワードの試行回数を制限する

 

今回紹介するdjango-axesの対策では、パスワードの試行回数を制限し一定回数連続で間違えるとアカウントがロックされ、ログインできないようにするものです。

django-axesのアカウントロックはIPアドレスごとに制限されるので、一度アカウントをロックされると別のユーザー名でログインすることもできません。

 

django-axesの使い方

ここからは、django-axesの設定方法と挙動を説明していきます。

 

まず、django-axesのパッケージをインストール。

pip install django-axes

# バージョンを指定する場合は以下
pip install django-axes==5.0.13

 

続いて、 settings.py のINSTALLED_APPS にaxesを追加。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app',
    'axes',   # axes追加。追加する位置はどこでもok
]

AUTHENTICATION_BACKENDS = [
    'axes.backends.AxesBackend',   # 認証に使用するのでここにも追加。必ず先頭に。
    'django.contrib.auth.backends.ModelBackend',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'axes.middleware.AxesMiddleware',   # ミドルウェアも追加。追加する位置は最後が推奨。
]

Djangoでは、 settings.py のAUTHENTICATION_BACKENDS により指定される認証バックエンドのリストの先頭から順に認証を試みるので、 axesを認証に組み込むには追加する必要があります。

MIDDLEWAREには、アカウントロック信号を読み取り可能な403応答にマップするAxesMiddlewareクラスを追加。

これだけでアカウントロック機能は使用可能です。

 

デフォルトのロック回数は3回なので、3回パスワードを間違えると以下のような画面が表示され、アカウントがロックされます。

 

もちろんIPアドレスで判断しているので他のアカウントで試行してもログインできません。

ロック回数を指定したい場合や、デフォルトのアカウントロック画面をカスタマイズしたい場合は以下の様に設定。

# キャッシュの設定。django.core.cache.backends.locmem.LocMemCache を使っている場合は設定 
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
    },
    'axes_cache': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
}
AXES_CACHE = 'axes'
AXES_FAILURE_LIMIT = 5   # ログイン失敗5回まで
AXES_LOCKOUT_TEMPLATE = 'axes_locked.html'   # 自作したテンプレートを指定
AXES_COOLOFF_TIME = 24   # ログインを連続で失敗した場合は24時間アカウントがロック
# デフォルトのロガーは'axes.watch_login'となっている。
# カスタマイズする場合は、以下にロガーインスタンスを設定
AXES_LOGGER = 'custom_logger'

 

ロックを解除する方法

アカウントのロックを解除する方法はいくつかありますが、開発段階であれば、

python manage.py axes_reset

でロックを解除できます。

 

これは、実行したクライアントのIPアドレスに紐づくaxesのアクセスログを消去することでロックを解除しています。

具体的には、

1.パスワードを間違い認証失敗。axes_accessattemptテーブルに以下のレコードが追加

mysql> select * from axes_accessattempt status\G;
*************************** 1. row ***************************
                  id: 15
          user_agent: Mozilla/5.0 ...
          ip_address: 127.0.0.1
            username: admin@admin.com
         http_accept: text/html,application/xhtml+xml,...
           path_info: /accounts/login/
        attempt_time: 2019-09-07 08:55:40.594878
            get_data:
           post_data: csrfmiddlewaretoken=NUw1kBhwQ8pj3iVLs17username=admin@admin.com
failures_since_start: 1   # これがパスワードの失敗回数。
1 row in set (0.00 sec)

 

2. パスワードを設定した許容回数間違えると、アカウントがロック。

mysql> select * from axes_accessattempt status\G;
*************************** 1. row ***************************
                  id: 15
          user_agent: Mozilla/5.0...
          ip_address: 127.0.0.1
            username: admin@admin.com
         http_accept: text/html,application/xhtml+xml,...
           path_info: /accounts/login/
        attempt_time: 2019-09-07 09:12:06.811478
            get_data:---------
           post_data: csrfmiddlewaretoken=NUw1kBhwQ8pj3iusername=admin@admin.com---------
failures_since_start: 5   #失敗回数が設定したAXES_FAILURE_LIMITと同じ値に
1 row in set (0.00 sec)

 

3.  python manage.py axes_reset でクライアントのIP(ここでは127.0.0.1)に紐づくレコードを消去

mysql> select * from axes_accessattempt status\G;
Empty set (0.00 sec)

ERROR:
No query specified

これがロック解除までの流れになります。

 

運用上の使用例

実際にアカウントロック機能を搭載したアプリケーションを運用する場面では、複数のユーザーが存在すると思います。

もしユーザーがパスワードを間違いアカウントがロックされた場合は、root権限を持つユーザーがアプリケーション上でアクセスログを消去してロックを解除する仕様が望ましいでしょう。

 

これはDjango管理画面からaxesのレコードを削除することで可能です。

 

管理画面のaxesアプリに移動。

django-axes管理画面

 

AXESアプリのAccess attemptsに入ると以下のようなアクセスログが。

django-axes管理画面

削除したいレコードを選択し、削除ボタンを押せば該当IPアドレスに紐づいたロックが解除されます。

この画面を参照できるユーザーを管理者権限のみに制御することがdjango-axes運用時のベストプラクティスの一つです。

 

以上、django-axesの使い方でした。

サーバー環境によってはクライアントのIPアドレスを参照できない場合もあるので、必要に応じて  settings.py  AXES_META_PRECEDENCE_ORDER を設定してください。