1.問題設定

前回お話した通り、現在、弊社は自動巡回型のWebアプリケーション脆弱性診断サービスを開発中です。直近では、認証機能に関連する以下のような機能追加を行いました。

  1. 簡単なパスワードを使用できないか調べる検査を追加
  2. 高頻度でログインを試行できないか調べる検査を追加
  3. CAPTCHAを簡単に突破できないか調べる検査を追加

今回は3に関連して、有名なCAPTCHAを突破できないか実験してみたいと思います。

CAPTCHAとは、画像や音声等の読み取りを利用者に行わせることで、ボットなどの自動化されたプログラムのアクセスを防ぐ仕組みです。有名なものでは、Google社の提供する「reCAPTCHA v2」があります。これは、提示されたテーマに合致する画像を選択することで、人間によるアクセスであることを示す仕組みです。

この他にも、画像に書かれた文字を読み取る方式もよく見かけます。

今回は、CAPTCHAの中でも比較的シンプルで解きやすそうに見える、「securimage」(https://www.phpcaptcha.org/)というCAPTCHAを突破の対象にします。securimageはPHPで書かれたオープンソースのCAPTCHAであり、”PHP CAPTCHA”で検索すると上位に表示される有名なCAPTCHAです。securimageという名前は聞いたことがないという方も、以下のような画像は見たことがあるかもしれません。散らばった黒い点と不規則に入る線、歪んだ文字が特徴的です。

securimageは文字の表示される場所や大きさ、形が画像毎に異なります。そのため、プログラマがCAPTCHAの特徴をもとに文字識別のルールを考え、それをプログラムに書き起こすというアプローチでは突破がやや難しそうです。

深層学習は、画像を与えて文字の特徴そのものを学習させることができるため、文字の場所や大きさ、形が変わる場合にも対応できそうです。デメリットとして、学習データを用意する必要がありますが、securimageはオープンソースであり、手元で自由に画像データを作成することができるため、今回は問題にはなりません。そのため、今回は深層学習のアプローチで突破を行います。

なお、今回は2013年リリースの古いバージョンであるsecurimage ver.3.5をデフォルト設定で利用するケースを対象とします。

2.サンプルデータ作成

まずサンプルデータを集めます。これはCAPTCHAの画像とその答えのペアを集めるということです。

サンプルデータを集めるためには、securimageを少し修正する必要があります。なぜならsecurimageは、HTTPサーバに設置する想定で作られており、リクエストを受け取ると画像データのレスポンスを返すように作られているためです。そのままではレスポンスヘッダ等の不要なデータも含めてを返してしまいます。また、答えの情報もPHPのセッションに保存されるのみで外部には出力されません。そのため、今回は、答えを名前に持つpngファイルを出力するコマンドライン用のスクリプト(https://github.com/AsaiKen/securimage_solver_for_blog/blob/master/securimage/securimage_save.php)を作成します。

ファイルを出力させる方法は、

https://github.com/dapphp/securimage/blob/3.5/securimage.php#L1389

imagepng($this->im);

imagepng($this->im, “{$this->output_dir}/{$this->code_display}.png”);

に変えて、ファイル出力するようにしています。

このスクリプトを利用して、訓練用500000枚、訓練中の評価用に10000枚、テスト用に10000枚を用意しました。

3.モデルの選択

続いて、深層学習のモデルを選びます。自分で試行錯誤して作るのも面白いとは思いますが、今回は時短のために、今回の問題と似た問題を高い精度で解いている既存モデルを利用します。

「text recognition deep learning」などで検索して良さそうなモデルを探します。有り難いことに、モデルを一覧表で比較しているページがありました。

https://github.com/hwalsuklee/awesome-deep-text-detection-recognition

今回は、このリストの中で

https://github.com/clovaai/deep-text-recognition-benchmark

を使うことにしました。理由は、比較的新しいモデルである、スコアが高い、pytorch利用なので個人的に扱いやすい、そして使い方が詳細に記載されているためです。

さて、この「deep-text-recognition-benchmark」のソースを見てみると、縦横が32×100の画像を入力するモデルであること、gt.txtというサンプルデータを一覧化したファイルを用意する必要があることがわかります。そこで以下のスクリプトを追加し、画像サイズの変換、gt.txtの作成を行いました。

https://github.com/AsaiKen/securimage_solver_for_blog/blob/master/deep-text-recognition-benchmark/securimage/resize.py

https://github.com/AsaiKen/securimage_solver_for_blog/blob/master/deep-text-recognition-benchmark/securimage/create_gt_file.py

4.訓練と評価

サンプルデータとモデルの用意ができたので、モデルを訓練します。ここは、https://github.com/clovaai/deep-text-recognition-benchmark

に使い方が詳細に書いてあるため、その通りに実行するだけです。なお、ここでも「deep-text-recognition-benchmark」のソースを一部修正する必要がありましたが、些細な修正なので割愛します。

$ CUDA_VISIBLE_DEVICES=0 python3 train.py \
–train_data ../../data/resize/train –valid_data ../data/resize/validate \
–select_data / –batch_ratio 1 \
–Transformation None –FeatureExtraction VGG –SequenceModeling BiLSTM
–Prediction CTC \
–sensitive

続いてテストです。

$ CUDA_VISIBLE_DEVICES=0 python3 test.py \
> –eval_data ../data/resize/test \
> –Transformation None –FeatureExtraction VGG –SequenceModeling BiLSTM –Prediction CTC \
> –sensitive \
> –saved_model saved_models/None-VGG-BiLSTM-CTC-Seed1111/best_accuracy.pth
No Transformation module specified
model input parameters 32 100 20 1 512 256 63 25 None VGG BiLSTM CTC
loading pretrained model from
saved_models/None-VGG-BiLSTM-CTC-Seed1111/best_accuracy.pth
dataset_root:    ../data/resize/test     dataset: /
sub-directory:  /.       num samples: 10000
97.45

サンプルデータを用意して手順通りに学習するだけで、正解率97.45%になりました。この正解率は、もしかすると人間の私より高いかもしれません。ハイパーパラメータをチューニングすればもう少し上がるかもしれませんが、十分な性能ですので、今回はここでやめておきます。

5.サービス化

せっかくなのでWebサービスにして、外部からCAPTCHAソルバとして利用できるようにしてみます。pythonであればflaskライブラリを使うことで簡単にこれを実現できます。

https://github.com/AsaiKen/securimage_solver_for_blog/blob/master/deep-text-recognition-benchmark/web/app.py

さっそく起動して試してみましょう。以下のコマンドで起動します。

$ python3 web/app.py

http://localhost:5000/ にアクセスします。

以下のファイルをアップロードしてみます。

正解です。最後のpをRと間違えてしまうのではないかと思いましたが、ちゃんとpと認識できています。凄い。

6.最後に

いかがでしたでしょうか。今回はsecurimageを深層学習で突破できないか試してみました。その結果、性能の良い既存モデルを利用することで、特に工夫をしない状態でも97%の正解率で突破できることがわかりました。

ただし、今回の実験では、securimageをデフォルト設定で利用しています。securimageは設定でノイズの度合いや文字の種類を変更できるため、今回のモデルでは別設定のsecurimageを突破できないかもしれません。汎用的なsecurimageのソルバを作る場合は、ランダムに設定を変えてサンプルデータを集めるなどの工夫が必要となると思います。

ソースコード一式は以下のURLに置いておきます。

https://github.com/AsaiKen/securimage_solver_for_blog/tree/master/deep-text-recognition-benchmark