【ITニュース解説】Exfiltrating a Hidden Secret Through a Search Bar with UNION SELECT
2026年10月02日に「Dev.to」が公開したITニュース「Exfiltrating a Hidden Secret Through a Search Bar with UNION SELECT」について初心者にもわかりやすく解説しています。
ITニュース概要
検索バーへのSQLインジェクション攻撃で、データベースの機密情報が不正に抜き取られる脆弱性を解説。入力値を直接SQLクエリに組み込むのが原因で、UNION SELECTなどにより内部データが漏洩する。パラメータ化クエリの使用が対策となる。
ITニュース解説
この解説では、ウェブサイトの検索バーに見られる「SQLインジェクション」という脆弱性を利用して、どのようにデータベースに隠された秘密の情報が抜き取られるかを詳細に説明する。これはシステムエンジニアを目指す初心者にとって、セキュリティの基礎と安全なプログラミングの重要性を理解する上で非常に役立つ情報である。
まず、SQLインジェクションとは、ウェブアプリケーションがユーザーからの入力を適切に処理せず、データベースへの命令文(SQLクエリ)に直接組み込んでしまうことで発生する脆弱性である。これにより、悪意のあるユーザーが意図しないSQLクエリを実行させ、データベースから情報を盗み出したり、データを改ざんしたり、最悪の場合はシステムを破壊したりすることが可能になる。
具体的な攻撃のプロセスを見ていこう。攻撃の標的となるのは、商品検索バーのような、ユーザーが自由な文字列を入力できる箇所である。この検索バーに入力された文字列は、通常、バックエンドで以下のようなSQLクエリに組み込まれることを想定している。
SELECT id, name, description, price, "imageUrl" FROM products WHERE name LIKE '%検索キーワード%' OR description LIKE '%検索キーワード%' ORDER BY name ASC LIMIT 50
このクエリでは、%検索キーワード% の部分にユーザーの入力がそのまま挿入される。もしウェブアプリケーションがこの入力を無害化せずに直接SQLクエリに組み込むと、脆弱性が生まれる。
攻撃は、まず検索バーに通常のキーワードを入力し、正しく動作することを確認することから始まる。次に、悪意のある文字列、つまり「ペイロード」を投入する。例えば、' UNION SELECT 1,2,3,4,5--という文字列を入力してみる。この文字列は、いくつかの重要な役割を果たす。
'(シングルクォート): これは、元のSQLクエリのLIKE '%検索キーワード%'の部分にある、検索キーワードを囲むシングルクォートを強制的に閉じ、それ以降の文字列を新たなSQL命令として認識させる。UNION SELECT: これは、元のSELECT文の結果と、別のSELECT文の結果を結合するSQLの命令である。攻撃者はこの命令を使って、データベース内の他のテーブルから情報を引き出そうとする。1,2,3,4,5:UNION SELECT文では、結合する両方のSELECT文が同じ数のカラム(列)を持つ必要がある。この数字の並びは、元のクエリが何カラムを返しているかを推測するために使われる。もしカラム数が合わなければ、データベースはエラーを返し、そのエラーメッセージから正しいカラム数を特定できる場合がある。記事の例では、カラム数が一致しない場合に「SELECTs to the left and right of UNION do not have the same number of result columns」というエラーが返され、5カラムであることが判明した。--(ハイフン2つ): これはSQLのコメント記号であり、これ以降の文字列はデータベースによって無視される。元のクエリの残りの部分が意図せず実行されるのを防ぐために使用される。
このペイロードが成功すると、検索結果の中に1,2,3,4,5という行が表示される。これは、元のクエリが改ざんされ、攻撃者の意図するデータが検索結果に混ぜ込まれたことを意味する。
次に、このUNION SELECTを使って実際のデータを抜き出す段階に進む。たとえば、DELIVERED' UNION SELECT id, email, password, role, addressId FROM users--というペイロードを投入する。これにより、productsテーブルの検索結果に加えて、usersテーブルからユーザーのID、メールアドレス、パスワードなどが抽出され、ウェブサイトの検索結果として表示されてしまう。アプリケーションは、これらのデータがどこから来たのかを区別せずに表示してしまうため、ユーザーの機密情報が露呈する。
さらに、データベースの構造全体を調査することも可能である。SQLiteデータベースの場合、sqlite_masterという特殊なテーブルに、データベース内のすべてのテーブル名やそのスキーマ(テーブルの構造定義)が格納されている。' UNION SELECT 1, group_concat(name), 'x', 1, 'y' FROM sqlite_master WHERE type='table'--のようなペイロードを使うと、データベース内のすべてのテーブル名(users,products,carts,flags,internal_secretsなど)をリストアップできる。
この記事の目的である「隠された秘密」は、internal_secretsというテーブルに格納されていることが判明する。そのテーブルのスキーマを' UNION SELECT 1, sql, 'x', 1, 'y' FROM sqlite_master WHERE name='internal_secrets'--で確認すると、id, slug, tokenというカラムがあることがわかる。
最終的に、internal_secretsテーブルから目的の秘密情報(トークン)を抜き出すために、' UNION SELECT 1, token, 'x', 1, 'y' FROM internal_secrets WHERE slug='product-search-sql-injection'--というペイロードを使用する。これにより、product-search-sql-injectionというslugに対応するtokenの値が検索結果に表示され、秘密情報の取得に成功する。このトークンは、そのウェブサイトが意図する「フラグ(成功の証)」であり、攻撃が成功したことを示す。
このようなSQLインジェクションが発生する根本的な原因は、開発者がSQLクエリを構築する際に、ユーザーからの入力を文字列として直接連結してしまっている点にある。記事の例では、WHERE name LIKE '%${query}%'のように、queryというユーザー入力をそのまま文字列テンプレートに埋め込んでいる。また、データベースへのアクセスにPrismaというORM(Object-Relational Mapping)を使用しているが、$queryRawUnsafeという、その名の通り「安全でない生のクエリ」を実行する関数を使っていることが問題である。この関数は、Prismaが提供する安全なパラメータ化の仕組みをスキップしてしまう。
このような脆弱性は、CWE-89(Improper Neutralization of Special Elements used in an SQL Command: SQLコマンドで使用される特殊要素の不適切な無害化)として知られている。
この脆弱性を防ぐための対策は明確である。
最も重要なのは、SQLクエリを文字列連結で構築しないことである。代わりに、ORMのクエリビルダーを使用するか、あるいは「パラメータ化されたクエリ(Prepared Statement)」を利用すべきである。パラメータ化されたクエリでは、ユーザーからの入力データはSQL命令の一部ではなく、「データ」として扱われるため、特殊文字がSQL命令として解釈されることはない。Prismaの場合、prisma.product.findManyのような安全なメソッドを使うか、生SQLが必要な場合は$queryRaw(パラメータ化に対応)を使用し、$queryRawUnsafeは避けるべきである。
さらに、セキュリティ層を厚くするために、データベースにアクセスするユーザーの権限を最小限に制限することも重要である。これにより、仮にSQLインジェクションが発生しても、攻撃者がアクセスできる情報の範囲を狭め、被害を最小限に抑えることができる。異常なクエリパターンをログに記録し、監視することも、攻撃を早期に検知するために役立つ。
SQLインジェクションは、ウェブアプリケーションで最も一般的かつ危険な脆弱性の一つである。システムエンジニアを目指す者として、その仕組みを理解し、安全なコードを記述するための対策を学ぶことは、非常に重要である。