2015年6月9日火曜日

CMS四天王のバリデーション状況を調査したところ意外な結果になった

私がいまさら指摘するまでもなく、グローバル(日本以外の多く)では脆弱性対策としてバリデーションが極めて重視されています。一々参照や引用はしませんが、海外の多くの標準において、バリデーションが重要なセキュリティ施策として指摘されています。Webアプリケーションにおいてバリデーション(以下バリデーションとはアプリケーションの最初の段階で行われる入力値検証、いわゆるフォームバリデーションを指します)が重要であることは私も同意しますが、それが「セキュリティ対策」なのかという点に疑問を持ち、例えば以下のようなプレゼンをしたことがあります。
もう一つの疑問として、「バリデーションは重要なセキュリティ対策である」という考え方が、開発の現場でどのように受け入れられているのだろうかと考え、グローバルに広く使われているアプリケーションにおいて、バリデーションがどのように実装されているかを調査したいと思い立ちました。

そこで、表題のように、主要なCMS4種類(CMS四天王と称します)について、バリデーションの現状を調査しました。ちなみに、CMS四天王とは下記を指します。私の勝手な選択ですので異論は認めますが本稿では以下を四天王の定義とします。
  • WordPress (Ver 4.2.2にて調査)
  • Joomla (Ver 3.4.1 にて調査)
  • Drupal (Ver 7.3.7 にて調査)
  • MovableType (Ver 6.1.1 にて調査)
CMSを題材に選んだ理由は下記のとおりです。
  • もっとも広く使われているウェブアプリケーションの一つである。
  • セキュリティがそれなりに要求される(Drupalはホワイトハウスのサイトに利用されているし…)
  • 過去頻繁に狙われている実績があり、セキュリティ強化が求められている
これらCMSのログイン名について、ユーザ登録時とログイン時で、どのようにバリデーションされているかを調べました。ログイン名について調査した理由は以下の通りです。
  • ログイン機能はセキュリティ上重要な機能である
  • ログイン名は文字種や文字列長等の仕様が明確であるという期待
  • どのCMSにも必ず存在する
調べた内容を書いたところ長文になってしまいましたので、まず特徴的なトピックと、まとめ、結論を書いてから、個別の調査結果を記載するようにします。

ログインIDとして使用できる文字種、文字列長、文字エンコーディング

まずはログイン名の文字種・文字列長等の仕様ですが、意外に明確な定義が見当たらないので、以下はDBの仕様や、実際に動かしてみて調べた結果です。以下はドキュメントやソースコードではなく実際の動作を元に調査しているので、漏れなどがある可能性はあります。

ソフトウェア文字種文字列長
WordPress英数字、空白、- _ @ . 60
Joomla< > \ " ' % ; ( ) &以外の文字150
Drupal記号以外の文字。ただし - ' _ @ はOK60
MovableType< > 以外の文字(制御文字もOK)255

バリデーションでSQLインジェクション攻撃をブロックしないCMSが多い

ログインIDにおける典型的なSQLインジェクション攻撃として、'OR 1=1# をバリデーションがブロックするかどうかを確認しました。ログインIDとして許容される文字を見る限り、WordPress、Joomla、Drupalはブロックしそうですが、結果は下記の通りです。
  • WordPress: ブロックしない
  • Joomla: ブロックする
  • Drupal: ブロックしない
  • MovableType: ブロックしない
ということで、意外なことに、バリデーションでSQLインジェクション攻撃を止めるのはJoomlaのみという結果でした。

ログインIDにヌルバイトや改行が使えるCMSがある

テストをしていてもっともびっくりしたことの一つがこれです。JoomlaとMovableTypeはヌルバイトや改行など制御文字がログインIDとして使えてしまいます。
Joomlaの場合、ユーザが自ら登録する場合制御文字は使えず、管理者が登録する場合のみです。そして、ログイン処理では制御文字はフィルタリングされるので、「登録はできるがログインはできない」ユーザができてしまうことになります。これはまずいですね。
一方、MovalbeTypeは制御文字入りのログインIDが登録、ログインともできます。なんと、首尾一貫していますね。私はMovableTypeに惚れそうになりましたw

100万文字のログインIDを登録しようとしたらどうなるか

バリデーションでは長さのチェックが重要とされますが、敢えて100万文字のユーザIDを登録してみました。その結果、JoomlaとMovableTypeは登録が出来ました…といっても、データベースの制限があるので、実際に登録されたのはJoomlaの場合先頭150文字、MovableTypeの場合先頭255文字です。
これに関連して微妙なバグを発見しました。どのCMSもログインIDの重複を許していないのですが、長いログインIDを用いてログインIDの重複が起こる場合があります。以下、説明を簡単にするためにログインIDの列定義がvarchar(10)だとして説明します。

既に 1234567890 という10文字のログイン名が登録されているところに、12345678901 という11文字のログイン名を登録する場合で考えます。SELECT * FROM users WHERE name='12345678901'とい重複チェックが走りますが、これはヒットなしで終わります。次に、INSERT INTO users VALUES ('12345678901', ...) というSQL文で登録処理が走りますが、このSQL文は成功するものの、ログインIDの列が10文字までしか入らないので、登録される文字列は 1234567890 になります。結果、1234567890 というログイン名が二重に登録されます。
実際には、ログイン名の最大長がそれぞれ150文字、255文字と大きいため、現実に問題になるケースはほとんどないと予想します。

100万文字のログイン名でログインしてみる

次に100万文字のログイン名でのログイン処理です。いずれのCMSもログイン名は有限長で最大255文字ですが、実際にやってみると、ログイン名がそのままSQL文として流れていることがクエリログから確認できました。
ログイン時には、どのCMSもログイン名の長さチェックはやっていないことになります。

まとめ

主要なCMS(CMS四天王)がログインIDをどのようにバリデーションするかについて調べました。ログインIDは、典型的には8文字英数字などであり、厳格なバリデーションがしやすい印象がありますが、主要CMSは意外にも登録・ログインともにあまり厳格なバリデーションはしていないことがわかりました。

SQLインジェクション攻撃をバリデーションで弾いていない点については、私は問題だとは思いません。SQLインジェクション攻撃がバリデーションで止まるか否かは、入力値の仕様に依存するので、バリデーションに依存するのではなく、プレースホルダの使用等で根本的に解決することが重要です。今回はたまたまバリデーションでは防げないケースだったということです。

一方、以下の2点は重大な問題だと考えます。
  • Joomlaでは制御文字入りのログインIDが登録できるが、そのIDでログインはできない
  • JoomlaとMovableTypeでは長いログインIDにより、ログインIDの重複が起こり得る
アプリケーションが想定していない入力を受け付けたことにより、データベース等の不整合が起こったことになります。私、バリデーションの本来の目的は、このような不整合を防ぎ、潜在的なバグを減らすことにあると考えます。

ということで、私の結論は以下の通りです。
  • CMS四天王はバリデーションを厳格にしていない
  • その結果、微妙なバグが混入する原因になっている
  • アプリケーションにおいてバリデーションは重要でありサボらずに実装したい
  • バリデーションの目的は、予期しない入力値によりデータベースの不整合その他の不具合を予防することにある


以下は、調査結果のサマリですので、興味のある方はお読みください。

1. シングルクォート

ログインIDとしてシングルクォートを含む文字列を指定した場合の挙動を調べました。シングルクォートは御存知の通りSQLインジェクション脆弱性と関連の深い文字です。

ユーザ登録時
WordPressエラー: このユーザー名は使用できない文字を含んでいるため、無効です…
JoomlaSave failed with the following error: Please enter a valid username.…
Drupal登録可能
MovableType登録可能
ログイン時
WordPressバリデーションは通過するが当該ユーザがないのでログイン失敗(*1)
Joomlaシングルクォートを除去した後ログイン処理
Drupalログイン可能
MovableTypeログイン可能


ご覧のように、WordPressとJoomlaはシングルクォートをログイン名として認めていませんが、DrupalとMovableTypeは認めています。ログイン時については、Joomlaがシングルクォートを除去する(一種のサニタイズ)のに対して、Joomla以外のソフトウェアはシングルクォートをバリデーションでは弾いていません。
ただし、WordPressのログイン処理で生成されるSQL文は下記の通りで、記号が二重にエスケープされています。これは潜在的なバグではないかと想像します。
SELECT * FROM wp_users WHERE user_login = 'O\\\'Reilly'

2. タグ文字

< や > がログイン名に含まれる場合の挙動を調べました。どのCMSも < と > をログイン名として許していませんが、Joomlaのみはユーザ登録時にサニタイズによりタグを取り除くという処理が入っています。
ログイン時の処理は、WordPressとJoomlaはタグのサニタイズ、DrupalとMovableTypeはバリデーションは通過したあと該当ユーザなしという結果になります。

ユーザ登録時
WordPressエラー: このユーザー名は使用できない文字を含んでいるため、無効です…
Joomlaタグをサニタイズした後登録される(<s>xss</s> は xss として登録)
DrupalThe username contains an illegal character.
MovableTypeユーザー名には不正な文字が含まれています: < 
ログイン時
WordPressタグをサニタイズした後ログイン処理される(<s>xss</s> は xss としてログイン)
Joomla< > をサニタイズした後ログイン処理される(<s>xss</s> は sxss/s としてログイン)
Drupalバリデーションは通過し、ユーザが存在しないのでログインはできない
MovableTypeバリデーションは通過し、ユーザが存在しないのでログインはできない


3.バックスラッシュ

バックスラッシュはMySQL等のSQLインジェクションやJavaScriptが絡むXSSに関連します。また、evalインジェクション等にも関連します。
MovableTypeのみがログイン名としてバックスラッシュを認めています。
興味深いことに、ログイン時はいずれのCMSもバリデーションを通していて、脆弱性対処としてバリデーションをあてにしていない様子が伺えます。

ユーザ登録時
WordPressエラー: このユーザー名は使用できない文字を含んでいるため、無効です…
JoomlaSave failed with the following error: Please enter a valid username.
DrupalThe username contains an illegal character.
MovableType正常に登録される
ログイン時
WordPressバリデーションは通過するが当該ユーザがないのでログイン失敗(*1)
Joomlaバリデーションは通過するが当該ユーザがないのでログイン失敗
Drupalバリデーションは通過するが当該ユーザがないのでログイン失敗
MovableType正常にログインできる

4.長大な文字列

長大なログイン名を指定した場合にどうなるかを調べました。具体的には、1,048,577文字(1メガバイト+1文字)のログイン名を登録、あるいはログインしてみました。
結果、WordPressは「新規ユーザーを作成しました」と表示されるものの、実際には作成されません。JoomlaとMovableTypeは正常に登録され、100万文字を超える長大なSQL文が流れるので中々壮観ですが、ログイン名はカラムの最大長で切り詰められます。
ログイン時には、いずれもCMSもバリデーションを通過するため、やはり100万文字を超える長大なSQL文が流れ、このテストをしているとログファイルがすぐに膨れ上がりますw

ユーザ登録時
WordPress新規ユーザーを作成しました。(実際には作成されない)
Joomla登録されるが、150文字で切られる(MySQL側で切られる)
Drupalバリデーションの結果 Username cannot be longer than 60 characters
MovableType登録されるが、255文字で切られる(MySQL側で切られる)
ログイン時
WordPressバリデーションを通過し長大なSQL文が生成されるがログイン失敗
Joomlaバリデーションを通過し長大なSQL文が生成されるがログイン失敗
Drupalバリデーションを通過し長大なSQL文が生成されるがログイン失敗
MovableTypeバリデーションを通過し長大なSQL文が生成されるがログイン失敗

前述のように、JoomlaとMovableTypeでは、長大なログイン名が途中でカットされることから、同一のログイン名が多重に登録できてしまう問題があります。


5.不正な文字エンコーディング

UTF-8として不正な文字エンコーディングを含む文字列がどうなるかというテストです。具体的には、abc%E0 という文字列を用いました。登録時にバリデーションで弾いているのは、WordPressとMovableTypeで、これは正しい処理内容だと思います。
JoomlaとDrupalはバリデーションは通過してしまい、Joomlaは不正な文字(%E0)がMySQL側で除去され、DrupalはPDOの例外が発生します。JoomlaとDrupalは、ログイン時にもバリデーションで不正な文字エンコーディングを弾いていません。
ログイン時、MySQLの発行するSQL文を観察すると、%E0が a にサニタイズされています。これはなんだろうと思ったのですが、Latin-1の%E0は à という文字ですので、それをASCIIに変換して a になった、というところでしょうか。

ユーザ登録時
WordPressエラー: このユーザー名は使用できない文字を含んでいるため、無効です…
Joomlaバリデーションを通過するが、MySQL側で不正な文字が除去される
Drupalバリデーションは通過するがPDOの例外が発生する
MovableType不正な要求です。文字コードutf-8に含まれない文字データを送信しています。
ログイン時
WordPress%E0がaにサニタイズされた後ログイン処理
Joomlaバリデーションは通過するが当該ユーザがないのでログイン失敗
Drupalバリデーションは通過するが当該ユーザがないのでログイン失敗
MovableType不正な要求です。文字コードutf-8に含まれない文字データを送信しています。

6.ヌルバイト

ヌルバイト(%00)はヌルバイト攻撃という形で攻撃に悪用される場合があります。
Joomlaは管理画面からユーザ登録する場合はヌルバイトを含むログインIDを許容しています。一方、セルフサービスで登録する場合はヌルバイトが除去されます。この違いの理由はよくわかりません。MovableTypeは常にヌルバイトを含むログインIDを許容しているようでびっくりしました。

ユーザ登録時
WordPressエラー: このユーザー名は使用できない文字を含んでいるため、無効です…
Joomlaヌルバイトを含むユーザ名が登録される / ヌルバイトが除去される
Drupalバリデーションの結果 The username contains an illegal character.
MovableTypeヌルバイトを含むユーザ名が登録される
ログイン時
WordPressバリデーションは通過するが当該ユーザがないのでログイン失敗(*1)
Joomlaヌルバイトが除去されログイン処理は継続
Drupalバリデーションは通過するが当該ユーザがないのでログイン失敗
MovableType正常にログインできる

7.改行

改行(%0D, %0A)はメールヘッダインジェクションやHTTPヘッダインジェクションの攻撃に使われます。当然バリデーションできっぱり弾かれると思いきや、WordPressでの登録処理以外は、バリデーションで改行を弾いていません。下表にあるとおり、改行をサニタイズで取り除く例も多いのですが、MovableTypeは改行を含むログインIDの登録を許容し、改行入りのログインIDでログインもできてしまいます。

ユーザ登録時
WordPressエラー: このユーザー名は使用できない文字を含んでいるため、無効です…
Joomla改行を含むユーザ名が登録される / 改行が除去される
Drupal改行が除去されて登録
MovableType改行を含むユーザ名が登録される
ログイン時
WordPress改行が空白にサニタイズされた後ログイン処理
Joomla改行が除去されログイン処理は継続
Drupal改行が除去されログイン処理は継続
MovableType正常にログインできる

8.配列

通常ログインIDは name=admin 等の形でWebアプリに渡されますが、name[0]=alice&name[1]=bob 等の形にすると、文字列ではなく配列の形でアプリに渡されます。
注目は Joomla でして、セルフサービスのユーザ登録時とログイン処理において、配列形式でログイン名を渡すと、ログイン名が Array と指定されたことになります。


ユーザ登録時
WordPressエラー: ユーザー名を入力してください。
エラー: このユーザー名は使用できない文字を含んでいるため、無効です…
JoomlaSave failed with the following error: / Array というユーザ名
DrupalUsername field is required.
MovableTypeユーザー名は必須です。 
ログイン時
WordPressエラー: ユーザー名を入力してください。
JoomlaArrayというユーザ名でログイン処理
DrupalUsername field is required.
MovableTypeサインインできませんでした。


Drupalは 7.35まではバリデーションとして配列のチェックをしていませんでしたが、7.36以降でテキスト文字列を受け取る文脈では入力が配列でないことのチェックが入るようになりました(参照)。
Drupal7.35でユーザ名として配列を与えると以下の例外が発生します。これはDrupageddon脆弱性と関連します(参照)。

PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ' 'bob' AND status = 1' at line 1: SELECT * FROM {users} WHERE name = :name_0, :name_1 AND status = 1; Array ( [:name_0] => alice [:name_1] => bob ) in user_login_authenticate_validate() (line 2154 of /var/www/drupal735/modules/user/user.module).

私は自分のブログ記事で以下のように書きました。
Drupalは必要最小限のバリデーションのみをしているように見えますが、クエリ文字列が(配列ではなくスカラの)文字列であることのチェックくらいはした方がよいと思います。これはアプリケーション要件として必要なチェックだと考えます。
Drupal 7.36での修正は、まさにこの修正であり、結果として私の忠告を聞いて下さり良い気分ですw

【HASHコンサルティング広告】
HASHコンサルティング株式会社は、セキュリティエンジニアを募集しています。
興味のある方は、twitterfacebookのメッセージ、あるいは問い合わせページからお問い合わせください。


0 件のコメント:

コメントを投稿

フォロワー