本サイトは、快適にご利用いただくためにクッキー(Cookie)を使用しております。
Cookieの使用に同意いただける場合は「同意する」ボタンを押してください。
なお本サイトのCookie使用については、「個人情報保護方針」をご覧ください。
MBSDでWebアプリケーションスキャナの開発等の業務をしている寺田です。
先日、CookieのPrefixについての記事を書きましたが、その中で最初に紹介したのがCookie名のCase(英字の大文字・小文字)の問題でした。Cookie名だけでなく、英字のCaseがWebアプリケーションのセキュリティ上の問題につながることがあります。今回はそういった問題の中でDB(データベース)に関連するものについて書きます。
セキュリティチェックのバイパス
まずは、筆者が2020年にベンダに報告したLaravel(6.18.34/7.23.2未満)の脆弱性について紹介します。
Security Release: Laravel 6.18.34, 7.23.2 - The Laravel Blog(CVE-2020-24940)
Mass Assignment対策(guarded)とバリデーションのバイパスが可能だった問題です。
Mass Assignmentとは、リクエストパラメータの追加により、アプリ開発者が意図しないデータが更新されてしまう脆弱性です。例えば、Laravelを利用したアプリケーションが、ユーザ情報を更新するリクエストにて、以下のようにMass-assignをしているとします。
$user->update($request->all()); // $request->all() はリクエストの全パラメータを取得するメソッド
このリクエストに「admin=1」というパラメータを追加した場合、DBのテーブルのadminカラムが変更されてしまい、そのユーザが一般権限のユーザから管理者に変わってしまうかもしれません。あるいは「id=123」を追加した場合に他のユーザのデータが更新されてしまうかもしれません。
Laravelにはこの種の攻撃を防ぐ仕組みが備わっています。代表的なものはModelのfillable, guardedという2つのプロパティです。FillableはAllowlistで、guardedはDenylistで項目を絞ります(公式のドキュメント)。筆者が報告したのはguardedに関する脆弱性で、前述のとおりMass Assignment対策とバリデーションルールがバイパス可能でした。複数のバイパス方法がありましたが、そのうちの一つは下のように英字のCaseを変えるというものです。
【前提】
protected $guarded = ['admin']; // 「admin」は更新させない
【失敗する攻撃】
追加パラメータ:admin=1 <= guardedによって守られる
【成功する攻撃】
追加パラメータ:ADMIN=1 <= guardedが効かない
実行されるSQL文(MySQL): UPDATE `users` SET `name`='hoge', `ADMIN`='1', ...
SQL文の末尾のカラム名が、本来のカラム名(admin)の英字を大文字にした名(ADMIN)になっています。MySQLを含む一部のDB(MySQL、SQLite、SQL Server)はクォートされたカラム名のCaseを区別しないので、これでadminカラムの内容が更新されてしまいます。
Mass Assignmentに限らず、何らかのセキュリティチェックをバイパスするというのは、英字のCaseを利用する攻撃でよく見られるものです。例えば、ディレクトリ「/aaa」に管理者のみアクセス可能という制限がかかっているとして、大文字にした「/AAA」でそれをバイパスする、というような攻撃です。
※ 本件を受けてLaravel側では、DBのテーブルのカラム名として存在しない「ADMIN」のようなキーをAssignしないようにする、という対策を取りました。
※ Laravelの脆弱性と類似しているものとしては、Spring FrameworkのCVE-2022-22968があります。これも、パラメータ名を「Admin」のようにすることで、DenylistによるMass Assignment対策をバイパスします(ただしこのバグはDBのカラム名とは無関係です)。
なりすまし
次は、SSO(シングルサインオン)をしているサイトの診断で筆者が発見した、英字のCaseを利用してなりすましが可能だった脆弱性です。
前提は以下のとおりです。
- SSOにおいて、サイトAがID Provider、サイトBがConsumerである。
- なりすましの被害者は、サイトAの「aaa」というアカウントでサイトBに会員登録している。
攻撃方法は非常に単純です。攻撃者はサイトAで「AAA」という英字のCaseを変えたアカウントを作成します。そのアカウントでログインしてサイトBに遷移することで、サイトBに「aaa」でログインできてしまいました。IdPであるサイトA側はアカウント名の英字Caseを区別するのに対して、SSO先のサイトBはCaseを区別しなかったために発生した脆弱性です。
ブラックボックス診断だったため脆弱性の原因は不明ですが、サイトB側でカラムの値の英字Caseを区別しないDBを使用していたために生じた脆弱性だったと推測しています(アプリケーション側で小文字化している等の可能性もありますが、そのような処理をする必要性も無いため)。
カラムの値のCaseを無視するかは、テーブルやカラム定義、または個々のSQL文で指定されているCollationに依存します。MySQLとSQL Serverのように、デフォルトでCaseを区別しないDBもあります。例えば、筆者の手元のMySQL 8.0.31(Ubuntu 20.04)では、デフォルトのCollationが「utf8mb4_0900_ai_ci」になっています。末尾の「ci」はCase Insensitiveの略で文字の大小を区別しないことを意味しています。
参考まで、MySQLのutf8mb4_0900_ai_ciでは、ASCIIの「A」「a」だけでなく、非ASCII(全角)の「A」「a」や「あ」「ぁ」「ア」もそれぞれ同一とみなされます。また、このCollationは「ai」(Accent Insensitive)であるため「A」「à」や「は」「ば」「ぱ」がそれぞれ同一とみなされます。SQLでキーワード検索機能を実装するような状況では便利ですが、識別子等を厳密に比較したい時には向いていないCollationでしょう。
トークン/セッションIDの強度低下
本来は英字Caseを区別すべきランダムな文字列の照合において、Caseが区別されない脆弱性です。それほど多くはありませんが診断でも実際に発見されることがあります。
下はBase64エンコードされた128ビットのトークンの例です。
token=kdavAcxQVc3xo6DJHlc0Tg
Caseを区別しない時は以下も受け入れてしまいます。
token=kdavacxqvc3xo6djhlc0tg
上のようなBase64エンコードされた128ビットの文字列に対して、Caseを区別しない比較が行われる場合、ビット数は112ビットほどに減ってしまいます。つまりトークンの強度は低下します(減っても112ビットあるので、リスクは低い問題といえますが)。
この手の問題も、先のなりすましバグと同じく、英字Caseを区別しないDB(MySQLやSQL Server)によるものが少なからずあると推測しています。
同様に、セッション情報をDBに保存する処理に問題がある場合もあります。例としてMySQLに保存したPHPのセッション情報を読み込むコードを以下に示します。
//Database
CREATE TABLE `Session` (
`Session_Id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`Session_Expires` datetime NOT NULL,
`Session_Data` text COLLATE utf8_unicode_ci,
PRIMARY KEY (`Session_Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8_unicode_ci;
<?php
class SysSession implements SessionHandlerInterface
{
…(snip)…
public function read($id)
{
$result = mysqli_query($this->link, "SELECT Session_Data FROM Session WHERE Session_Id = '". $id. "' AND Session_Expires > '". date('Y-m-d H:i:s'). "'");
上記はPHPマニュアルのUser Contributed Notesに掲載されているコードの抜粋です。
このコードでは、カラムのCollationがutf8_unicode_ci(「ci」が付いているため英字Caseを区別しない)であるため、セッションIDの「abc」と「ABC」を区別できません。デフォルトではPHPのセッションIDは全て小文字になっており英字のCaseを気にする必要はありませんが、PHPの設定を変えた場合、あるいは大小文字が含まれるセッションIDを独自に生成している場合は、Caseを区別する必要があります。
PHP設定については、session.sid_bits_per_character
(PHPマニュアル)がCaseに関連しており、この値を下のように「6」にすると生成されるセッションIDに英字の大文字が含まれるようになります。
生成されるセッションIDの例
4: 027acadccc532112f2e00c4a33(4ビット/文字 0-9 a-f)
5: 83hotm6l8fu1q7s6cnoitvb3tl(5ビット/文字 0-9 a-v)
6: LnA0zji,ZSJiuRvmBB-PQvixXN(6ビット/文字 0-9 a-z A-Z , -)
トークンやセッションIDに限らず、DBが英字のCaseを区別しないことを考慮せずに書かれているコードはかなりあるのではないかと思います。過去に筆者がDBアプリケーションを開発していた時のことを振り返ってみても、このようなDBの挙動についてはキーワード検索機能を開発する時に思い出すくらいでしたので、DBの英字Caseは意識から漏れやすい問題ではないかと思います。
なお、稀にパスワードの英字Caseを区別しないアプリケーションもあります。Caseを区別しない旨を画面上でユーザに伝えているのならば仕様ともみなせますが、そうでないのであればCaseを区別すべきでしょう。また、もしハッシュ化せずにDBに保存しているならばそれ自体も問題です。
※ 余談ですが、上で引用したコードは文字列連結でSQL文を生成しています。実際にはSQLインジェクションはできないものの、Prepared Statementを使う方がよいです。またこのコードにはセッションの排他処理(PHPマニュアル1, 同2)がありません。PHPでセッションの排他をしないことが問題になることは殆ど無いと思いますが、セッションの使い方によっては排他処理を追加する必要があります。
データの不整合
DB内のデータとともに、ファイルシステムのファイルも削除するようなデータの削除処理があるとします。削除するデータの識別子が「I00001234
」だったとして、これをキーにDBとファイルを削除するという想定です(イメージは以下)。
DBデータの削除: DELETE FROM images WHERE `id`='I00001234' -- 削除OKだったらファイルの削除に進む
ファイルの削除: rm /var/www/html/public/images/I00001234
きちんとしたトランザクションがアプリケーションに実装されていないならば、先頭の英字を小文字にした「i00001234
」を識別子のパラメータとして与えると、英字Caseを区別しないDBのデータだけは削除され、Caseを区別するファイルだけが削除されずに残ってしまうということもありえます。
なお、MySQL(utf8mb4_0900_ai_ci)であれば、アクセント記号付きの英字や、無視される特殊な文字(例: U+200E Left-to-right mark等)を使っても同様の状況を引き起こせるため、上記は英字Caseに限った話ではありません。
まとめ
本記事では、英字のCaseが要因となる脆弱性/攻撃のうち、DBに関連するものについて書きました。上記のように、DBの種類によってはデフォルトでカラムの名前・値ともに英字のCaseを区別しないようになっており、それが脆弱性の原因となることがあります。
通常のアプリケーションでは、カラム名のCaseが問題となることは殆どないと思いますが、カラムの値についてはCaseの考慮が漏れやすいこともあり注意が必要でしょう。開発時には、それぞれのデータについて、Caseを区別すべきなのか否かを明確にして、チェックや正規化をする必要があります。識別子を保存するDBカラムのCollationについて言うと、Caseを区別せずに識別子を比較すべき状況は少ないので、Caseを区別するBin系のCollationで定義しておいた方が間違いが少ないと思います。
ちなみに、英字のCaseに関連する脆弱性は過去にそれなりに発見されており、CWEにもそれ専用のカテゴリであるCWE-178: Improper Handling of Case Sensitivityが存在します。このカテゴリの脆弱性の例は、CWEやCVE Detailsで見ることができます。
おすすめ記事