証券会社を狙う不正取引問題が続く中で、ログイン時のセキュリティ対策として、
多要素認証が「必須化」
となってしまった……。
これで困るのは自動化システムを作っている個人投資家達。
- 売買取引システム(システムトレード)
- IPO注文システム
- 取引&資産管理システム
- 株価データ監視システム
- Xのポストシステム
SBI証券としては嬉しくない客かもしれないが、用途は多岐にわたる。
そもそもセキュリティ対策してるテスタ氏も乗っ取られてる。
本当にフィッシング詐欺が原因なのかな……そっちを究明して欲しい。

そもそも多少の抑止力にはなるけどニ段階認証突破する詐欺サイト作成なんて全く難しくない。
二段階認証のキーを実際の証券サイトから発行させる仲介的なシステムを作れば良い。
話は戻って二段階認証を突破して自動処理をする方法もチラホラ出てきた。

この人はログインは手動で行い、セッションが切れるまでは取引を自動化する……という方法のようだ。
完全自動化ではないので私の目的と異なる。
ここでは完全自動で二段階認証を突破する方法を紹介する。
※ スマホ認証の突破は大変なので「FIDO(スマホ認証)」の設定は行ってない。
二段階認証を突破する全体概要
手順は次のとおりでメールサーバーが必要。
何だよ……メールサーバー無いよ。
という人もいるかもしれない。
Yahooメールのスクレイピングも考えたが、そもそもYahooログインがスマホの二段階認証だった。
メールサーバー側の設定
手順は次の通り。
- 専用メールアドレスを転送用として作成
- SBI証券に専用メールアドレスを登録
- .mailfilterの設定変更
- 処理用のスクリプトを準備する
次のサイトを参考にした。
さくらインターネットを利用しているが、どのメールサーバーも同じ仕組み。
SBI証券に専用メールアドレスを登録
転送用として作成したメールアドレスをSBI証券に登録する。
加えて認証キー受診のメールアドレスとしても登録する。
/home/ユーザ名/MailBox/メールアカウント/.mailfilter の設定変更
メールボックスのフォルダを見てみるとアカウント毎に次のようなファイル構成になっている。
この中の「.mailfilter」を変更する。
転送用メール設定を独自のスクリプトを呼ぶように変更する。
【Before】
1 2 |
cc "!◯◯@yahoo.co.jp" exit |
【After】
1 2 |
to "| /usr/local/bin/python /home/◯◯/△△/mailhook.py" exit |
処理用のスクリプトを準備する
ここでメール処理するmailhook.pyは次の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
import sys import email from email import policy from email.parser import BytesParser import re import os import time def process_email(): """ 標準入力からメールを受け取り、解析して内容をテキストファイルに追記する関数 """ try: output_file = '<ファイルを置くパス>' with open(output_file, 'w', encoding='utf-8') as f: # 標準入力からメールデータを読み取る raw_email = sys.stdin.buffer.read() # メールをパース msg = BytesParser(policy=policy.default).parsebytes(raw_email) # ヘッダー情報を取得 subject = msg['subject'] # 件名 from_address = msg['from'] # 送信者 to_address = msg['to'] # 宛先 # f.write(f"件名: {subject}\n") # f.write(f"送信者: {from_address}\n") # f.write(f"宛先: {to_address}\n") # 本文を取得(プレーンテキスト部分) body = "" if msg.is_multipart(): # マルチパートの場合、各パートを確認 for part in msg.iter_parts(): if part.get_content_type() == 'text/plain': body = part.get_payload(decode=True).decode(part.get_content_charset()) break else: # シングルパートの場合 body = msg.get_payload(decode=True).decode(msg.get_content_charset()) # 認証コードを抽出 auth_code = "" match = re.search(r'認証コード\s*([A-Z0-9]+)', body) if match: auth_code = match.group(1) # f.write(f"認証コード: {auth_code}\n") # テキストファイルに追記 f.write(f"{auth_code}\n") f.flush() # フラッシュ # 30秒後にファイルを削除 time.sleep(30) os.remove(output_file) except Exception as e: with open('<ファイル名>', 'w', encoding='utf-8') as f: f.write(f"エラー: {str(e)}\n") if __name__ == "__main__": process_email() |
sys.stdin.buffer.read()だけで、標準入力からメール情報全て(ヘッダー、本文、送信先、送信元)を受け取ることができる。
1 2 3 4 5 6 7 8 9 10 |
# 標準入力からメールデータを読み取る raw_email = sys.stdin.buffer.read() # メールをパース msg = BytesParser(policy=policy.default).parsebytes(raw_email) # ヘッダー情報を取得 subject = msg['subject'] # 件名 from_address = msg['from'] # 送信者 to_address = msg['to'] # 宛先 |
SBI証券から受け取ったメールはテキストなので、本文に対して認証キーをスクレイピングするだけ。
なお老婆心ながらセキュリティ対策で30後には自動破棄も追加してる。
SBI証券ログインを自動で行うスクリプト例
二段階認証の認証キーがテキストデータで手に入るのだから何も難しくない。
なお非力サーバーで動かす為にSeleniumでなくPlaywrightを利用している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# pip install playwright # playwright install chromium from playwright.sync_api import sync_playwright import time import requests def login_to_sbi(context): """ SBI証券にログインする関数 """ # 新しいページを開く page = context.new_page() # SBI証券のログインページにアクセス page.goto("https://www.sbisec.co.jp/ETGate/") # ユーザーIDを入力 page.fill('input[name="user_id"]', 'ユーザID') # パスワードを入力 page.fill('input[name="user_password"]', 'パスワード') # ログインボタンをクリック page.click('input[name="ACT_login"]') return page def get_auth_key_from_url(): # 10秒スリープ time.sleep(10) # URLから認証キーを取得 response = requests.get("URLを入力") auth_key = response.text.strip() return auth_key def main(): """ メイン処理 """ with sync_playwright() as playwright: # ChromiumブラウザをGUI表示で起動 browser1 = playwright.chromium.launch(headless=False) # headless=TrueでGUI表示 context1 = browser1.new_context() # SBI証券のログイン処理 sbi_page = login_to_sbi(context1) # 認証キーを取得 auth_key = get_auth_key_from_url() print("取得した認証キー:", auth_key) # 認証キーを入力 sbi_page.fill('input[name="device_code"]', auth_key) # 認証ボタンをクリック(必要に応じてボタンのセレクタを変更) sbi_page.click('input[type="submit"]') # 10秒スリープ time.sleep(30) # ブラウザを閉じる browser1.close() if __name__ == "__main__": main() |
処理としてはこのあたり。
説明するまでも無いね。パスワード入力処理が1つ増えたようなものだから。
1 2 3 4 5 6 7 8 9 |
# 認証キーを取得 auth_key = get_auth_key_from_url() print("取得した認証キー:", auth_key) # 認証キーを入力 sbi_page.fill('input[name="device_code"]', auth_key) # 認証ボタンをクリック(必要に応じてボタンのセレクタを変更) sbi_page.click('input[type="submit"]') |
おわりに
SBI証券のデバイス認証(メール送信によるキー認証)であれば、特に難しい要素はない。
なお、
FIDO(スマホ認証)未設定のお客さまに順次「電話番号認証」を適用します。なお、電話番号認証の解除はできません(デバイス認証設定済みの場合を除く)。
WEBサイトへのログイン時に「電話番号認証」→「ユーザーネーム/ログインパスワード」→「デバイス認証」の手順が必要になります 。
と書かれており強制的に電話番号認証がSBI証券によって適用されるっぽい。電話番号認証は自動化が難しい。電話契約必須だし。
その場合は、セキュリティリスクは上がるが電話番号認証を解除すれば大丈夫なのかな……。
蛇足だけど、楽天証券もメール送信だけど認証キーではなく変なアイコン合わせが必要。
これ……フィッシングサイト対策ではなくて個人の自動処理対策だよね。