This post is Private. Only a writer or those who know its URL can access this post.

その正規表現、異議あり!〜 正しいメールアドレスを判別しよう

自己紹介

  • Shu OGAWARA(@expajp )
    • リンカーズ株式会社
    • 社会人3年目
    • Rails歴は1年半くらい
    • 出身は鳥取で大学は神戸
    • 趣味は合唱

正規表現、みなさんご存知ですよね


正規表現(せいきひょうげん、英: regular expression)とは、文字列の集合を一つの文字列で表現する方法の一つである。正則表現(せいそくひょうげん)とも呼ばれ、形式言語理論の分野では比較的こちらの訳語の方が使われる。

引用:正規表現 - https://ja.wikipedia.org/wiki/正規表現


要は、文字列が特定の条件を満たすかどうか調べるための表現


どうやって使うか? :thinking:


Railsアプリでよくある使われ方

ユーザ登録時に、メールアドレスが正しいかどうか調べる


こんな感じで正規表現を組みたい


できた

regexp = /^([a-z0-9_+-]\.)*[a-z0-9_]+@[a-z0-9\-]+(\.[a-z0-9\-]+)*\.[a-z]+$/

可視化するとこんな感じ

regexp01.png


なんとなくよさそう


igiari.png


この正規表現には問題がある


脆弱性がある


正規表現に脆弱性? :thinking:


正規表現の脆弱性

  • ポイントは、正規表現の処理が非常に重いということ
    • 1字1字、巨大な文字集合に合致するか調べる
    • 一致するかどうかは最後まで調べてみないと分からない
    • 特に、「一致しない」場合はすべてのパターンを試さないと分からない
  • 正規表現では、(本来は)文字列の長さに制限を設けられない

どういうこと?

naruto.jpg


長くて正規表現に一致しない文字列をメールアドレス欄に放り込むだけでDoS攻撃ができる


DoS攻撃

DoS攻撃(ドスこうげき)(英:Denial of Service attack)は、情報セキュリティにおける可用性を侵害する攻撃手法のひとつ。

ウェブサービスを稼働しているサーバやネットワークなどのリソース(資源)に意図的に過剰な負荷をかけたり脆弱性をついたりする事でサービスを妨害する。

DoS攻撃 - https://ja.wikipedia.org/wiki/DoS攻撃

特に、正規表現を使ったDoSはReDoSと呼ばれる


実際にやってみよう

# 引用:正規表現でのメールアドレスチェックは見直すべき – ReDoS – yohgaki's blog 
# https://blog.ohgaki.net/redos-must-review-mail-address-validation

# 文字列の長さ:処理時間 で順に出力する
n = 5;
while n < 12 do
  s = "username@host"+".abcde"*n+"."
  start = Time.now();
  p /^([a-z0-9_+-]\.)*[a-z0-9_]+@[a-z0-9\-]+(\.[a-z0-9\-]+)*\.[a-z]+$/i =~ s
  p s.length.to_s + ': ' + (Time.now() - start).to_s;
  n += 1
end


さらに悲しいお知らせ


メールアドレスの定義

RFC5321(Simple Mail Transfer Protocol)(日本語訳)によると、

4.5.3.1.1. Local-part
ユーザー名または他の local-part の最大長は 64 オクテットである。

4.5.3.1.2. ドメイン
ドメインの名前または数値の最大長は 255 オクテットである。

4.5.3.1.3. パス
reverse-path または forward-path の最大長は 256 オクテットである(句読点や要素区切りを含む)。

また、RFC1035(DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION)(日本語訳)によると、ドメイン名は

2.3.4. サイズ制限
DNS の様々なオブジェクトやパラメータはサイズ制限を持つ。それらを以下に示す。簡単に変更できるものもあれば、根本的なものもある。

ラベル 63 オクテット以下

名前 255 オクテット以下


メールアドレスの定義

つまり、

  • メールアドレス全体は256文字以内
  • ユーザ名(@の前)は64文字以内
  • ドメインは255文字以内
  • ドメインのラベルは63文字以内
    • test.example.com <- 例えばここ

長さは正規のメールアドレスの範囲内


どうすればよいのか?


文字列を区切って短くし、
シンプルな正規表現で判定する


ユーザ名の場合

  • 使える記号は. - + _
  • 64文字以内
  • 記号以外の部分は英数字

ユーザ名の場合

email = 'hoge_hoge.piyo-piyo+fuga@example.com'

# ユーザ名を切り出し
username = email.split('@').first

# 64文字以上はアウト
return false if username.length > 64

# 記号で区切り、それぞれ英数字かどうか調べる
validities = username.split(/[\.\+\-\_]/).reject(&:empty?).map{ |s| /^[a-z0-9A-Z]+$/ === s }

# すべてtrueでないならfalseを返す
return false unless validities.inject(&:&)

# 今度は文字で区切り、それぞれ1文字の記号かどうか調べる
validities = username.split(/[a-z0-9A-Z]+/).reject(&:empty?).map{ |s| /^[\.\+\-\_]$/ === s }

# すべてtrueでないならfalseを返す
return false unless validities.inject(&:&)


まとめ


まとめ

  • 正規表現でDoS攻撃をかける攻撃手法がある
  • メアドなど、複雑な正規表現は避けるべき
  • 仕様をちゃんと読んで、短い単位でチェックをすることを心がけよう

参考文献

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.