SiteGuard WP Pluginの留意点

WordPressのセキュリティ用プラグインのSiteGuard WP Pluginは

- 「ログインを何度も失敗するユーザーに対してロックを掛ける」など主にログイン画面の機能強化用の複数の対策ができる (複数のプラグインを導入・運用する手間が下がる)
  - WordPressで特に狙われやすいXML-RPCの無効化機能も持っている
  - ひらがなの画像認証機能を持つため日本語を理解していない攻撃者に対して比較的有利
- WAFを提供しているJP-Secureが提供している
  - 法人かつ主事業とマッチしているため品質がある程度見込め、メンテナンスが中長期続く(放棄されない)可能性が高い
  - 日本製のため設定や説明ページが日本語

という比較的価値の高いプラグインです。

使っていて少し留意したほうがよいかもしれない点が2点あったので参考のため記載しておきます。(この文章の記載時点でのSiteGuard WP Pluginのバージョンは1.3.2で、今後修正などがあるかもしれません。また、文章中の誤りがあれば教えて頂けるとありがたいです)


## 1. 「ログインページ変更」でログイン画面のURLを変更してもリダイレクトで容易に変更後URLが把握できる

SiteGuard WP Pluginには、通常のログイン画面のURL( 例えば http://example.com/wp-login.php )のwp-login.phpのアクセスを無効にして、ログイン画面のURLを設定で決めたURLに変更することができる「ログインページ変更」という機能があります。

![ログインページ変更](https://qiita-image-store.s3.amazonaws.com/0/782/85029c54-b5f6-f80f-f7c4-fe63b945b76a.png)

ただ、これで変更したとしても管理画面に直接アクセス( 例えば http://example.com/wp-admin/ )すると変更したログイン画面のURLにリダイレクトするため、すぐにURLがわかってしまいます。不特定に対する攻撃の攻撃対象の1つになることを回避する可能性を上げることには繋がるかもしれませんが、自身のみを標的とされた場合の攻撃には効果がないと思われます。

「管理ページアクセス制限」を合わせてONにしておくとログインしていない限り wp-admin にアクセスしてもリダイレクトではなく404になるため、できるだけ2つを組み合わせておくとよいです。

ログイン画面のURLの変更、というのも基本的に隠蔽によるセキュリティ対策(Security through obscurity)のため、IPアドレス制限などWordPressより下のレイヤーで対策できるのであればそうしたほうがよりよいと思います。(できない場合は「ログインページ変更」でもある程度は有用だと思います)



## 2. 画像認証で生成された画像が生成から最低60分残り続ける(認証に使える)

※この項目は実際にSiteGuard WP Pluginの画像認証を利用した際に外部のセキュリティ会社から懸念指摘があった経緯があり、SiteGuard WP Pluginのソースコードを読みながら理解できた内容を記載しています。


SiteGuard WP Pluginには、ログインやコメント等のページに画像認証を追加する機能があります。手軽に有効無効を設定できて便利な機能です。

(設定画面)

![画像認証](https://qiita-image-store.s3.amazonaws.com/0/782/0ac3c5b5-6775-c3c5-16e5-337b56f45ea0.png)

(画像認証を有効にしたログイン画面)

![ログイン](https://qiita-image-store.s3.amazonaws.com/0/782/ef4968d1-34a9-55fc-1be5-e528a60bcc91.png)

この機能は内部動作として

1. ログイン画面など画像認証が必要になった場合にランダムの文字を生成してから画像、およびMD5ハッシュ(saltはwp_generate_password( 64 )で生成したもの)のテキストファイルを生成
  - 「 wp-content/plugins/siteguard/really-simple-captcha/tmp 」フォルダに9桁数字.pngと9桁数字.txtで生成
  - 画像はパーミッション444、テキストはパーミッション440
2. 入力欄のhiddenフィールドに1のファイル名と同じ9桁数字を含め、POSTされた際に入力文字のハッシュと1のテキストファイル内の値が一致するか見る (wp_authenticate_userなどのフィルタにフックして実現している)

という流れになっていますが、以前生成した古いファイルは新しい画像を生成する際にクリーンアップを掛けており、また、ファイルが削除対象になる条件(古いと判断される条件)は画像ファイルの更新日時 + 60分後です。

(siteguard-really-simple-capthca.php。cleanup()自体は同クラスのgenerate_image()からのみ呼ばれていて、引数なしのため$minutesは60分になる)

```php
	/**
	 * Clean up dead files older than given length of time.
	 *
	 * @param int $minutes Consider older files than this time as dead files
	 * @return int|bool The number of removed files. Return false if error occurred.
	 */
	public function cleanup( $minutes = 60 ) {
		$dir = trailingslashit( $this->tmp_dir );
		$dir = $this->normalize_path( $dir );

		if ( ! @is_dir( $dir ) || ! @is_readable( $dir ) ) {
			siteguard_error_log( $dir . ' is not directory or readable. :' . __FILENAME__ );
			return false;
		}

		$is_win = ( 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) );

		if ( ! ( $is_win ? win_is_writable( $dir ) : @is_writable( $dir ) ) ) {
			siteguard_error_log( $dir . ' is not writable. :' . __FILENAME__ );
			return false;
		}

		$count = 0;

		if ( $handle = @opendir( $dir ) ) {
			while ( false !== ( $filename = readdir( $handle ) ) ) {
				if ( ! preg_match( '/^[0-9]+\.(php|txt|png|gif|jpeg)$/', $filename ) )
					continue;

				$file = $this->normalize_path( $dir . $filename );

				$stat = @stat( $file );
				if ( ( $stat['mtime'] + $minutes * 60 ) < time() ) {
					@unlink( $file );
					$count += 1;
				}
			}

			closedir( $handle );
		}

		return $count;
	}
```

そのため60分間ローカルマシン上での試行時間があり、siteguard_captcha(入力文字)とsiteguard_captcha_prefix(9桁数字)のセットを合わせてPOSTすれば古いファイルで認証が通せる可能性があります。

(siteguard-really-simple-capthca.phpのランダム文字生成部分。)

```php
	/**
	 * Generate and return a random word.
	 *
	 * @return string Random word with $chars characters x $char_length length
	 */
	public function generate_random_word() {

		/* Characters available in images */
		$chars_en = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
		$chars_jp = 'あいうえおかきくけこさしすせそたちつてとなにのひふへまみむもやゆよらりん';

		$word = '';

		if ( 'jp' == $this->lang_mode ) {
			$this->chars = $chars_jp;
		} else {
			$this->chars = $chars_en;
		}

		$chars_size = mb_strlen( $this->chars );
		for ( $i = 0; $i < $this->char_length; $i++ ) {
			$pos = mt_rand( 0, $chars_size - 1 );
			$char = mb_substr( $this->chars, $pos, 1 );
			$word .= $char;
		}

		return $word;
	}
```

char_lengthは4桁固定で、見分けづらい文字を抜いているためか英数字は32文字(小文字はcheck()時に大文字に変換される)、ひらがなは36文字です。ノイズが少ないので、実際に試してはいませんが特に英数字だと解析しやすいのではと思います。(ローカルで60分以内に分かればいいため、ログインロック機能が有効になっているサイトでもローカルで試行して解析完了してからログイン試行できる)


### Really Simple Captcha ?

siteguard-really-simple-capthca.php 内のライセンス表記を見ると分かるように、SiteGuard WP Pluginの画像認証プログラムはContact Form 7で主に使われている「[Really Simple CAPTCHA](https://ja.wordpress.org/plugins/really-simple-captcha/)」のコードを流用して作られています。(ひらがな画像認証の追加とノイズの追加)

もともとReally Simple CAPTCHAは説明文に「このプラグインは、名前の通り "really simple ( 本当にシンプル )" なもので、それほど強力な安全性はありません。堅牢な安全性が必要な際には、他のソリューションをお試しください」とあるような位置づけで、現在のContact Form 7ではより信頼性のある [GoogleのreCAPTCHAを使った連携](http://contactform7.com/ja/recaptcha/) のほうが推奨されています。

画像認証についてはJP-Secureが玩具としての機能提供としか認識していない可能性もありますが、reCAPTCHA対応がされていれば現行のCAPTCHAと比較してセキュリティ向上が見込めると思うので、可能ならSiteGuard WP Pluginでも対応して頂けると良いなと思います。

(参考: 強く推奨するということではありませんが、ログイン画面でのreCAPTCHA連携を実現する別プラグインとしては「[Google Captcha (reCAPTCHA) by BestWebSoft](https://ja.wordpress.org/plugins/google-captcha/)」があります)


ちなみにContact Form 7 4.5でReally Simple Captchaを使った場合は、入力検証時にCAPTCHAのチェックが終わり次第CAPTCHA画像とテキストファイルが削除され、すぐに画像が使用不能になります。(wpcf7_validate_captchar フィルタにフックされている wpcf7_captcha_validation_filter 経由で最終的に ReallySimpleCaptcha の remove() が呼ばれる)


## 終わりに

wp-login.php や wp-admin にIPアドレス制限や別の認証(SSL/TLS + Basic認証など)を掛けられるのであればそれに越したことはないと思いますが、SiteGuard WP Pluginが有用なケースも十分あります。(付け焼刃的にいろいろなプラグインや対策を入れると逆にセキュリティリスクが上がったり運用難度が上がったりすることがある)

「使えるものは使う、ただしよく理解して」というスタンスだとよいかなと思います。