自己紹介
- Shu OGAWARA(@expajp )
- リンカーズ株式会社
- 社会人3年目
- Rails歴は1年半くらい
- 出身は鳥取で大学は神戸
- 趣味は合唱
正規表現、みなさんご存知ですよね
正規表現(せいきひょうげん、英: regular expression)とは、文字列の集合を一つの文字列で表現する方法の一つである。正則表現(せいそくひょうげん)とも呼ばれ、形式言語理論の分野では比較的こちらの訳語の方が使われる。
引用:正規表現 - https://ja.wikipedia.org/wiki/正規表現
要は、文字列が特定の条件を満たすかどうか調べるための表現
どうやって使うか?
Railsアプリでよくある使われ方
ユーザ登録時に、メールアドレスが正しいかどうか調べる
- example@example.com -> ⭕️
- example.com -> ❌
- example@test.example.com -> ⭕️
- example@test/example/com -> ❌
こんな感じで正規表現を組みたい
できた
regexp = /^([a-z0-9_+-]\.)*[a-z0-9_]+@[a-z0-9\-]+(\.[a-z0-9\-]+)*\.[a-z]+$/
可視化するとこんな感じ
なんとなくよさそう
この正規表現には問題がある
脆弱性がある
正規表現に脆弱性?
正規表現の脆弱性
- ポイントは、正規表現の処理が非常に重いということ
- 1字1字、巨大な文字集合に合致するか調べる
- 一致するかどうかは最後まで調べてみないと分からない
- 特に、「一致しない」場合はすべてのパターンを試さないと分からない
- 正規表現では、(本来は)文字列の長さに制限を設けられない
どういうこと?
長くて正規表現に一致しない文字列をメールアドレス欄に放り込むだけでDoS攻撃ができる
DoS攻撃
DoS攻撃(ドスこうげき)(英:Denial of Service attack)は、情報セキュリティにおける可用性を侵害する攻撃手法のひとつ。
ウェブサービスを稼働しているサーバやネットワークなどのリソース(資源)に意図的に過剰な負荷をかけたり脆弱性をついたりする事でサービスを妨害する。
特に、正規表現を使った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 <- 例えばここ
- test.
長さは正規のメールアドレスの範囲内
どうすればよいのか?
文字列を区切って短くし、
シンプルな正規表現で判定する
ユーザ名の場合
- 使える記号は
. - + _
- 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攻撃をかける攻撃手法がある
- メアドなど、複雑な正規表現は避けるべき
- 仕様をちゃんと読んで、短い単位でチェックをすることを心がけよう