セキュリティ脆弱性の予防
ソフトウェアの脆弱性はシステムに深刻な被害をもたらす可能性があり、悪意のある者に悪用された場合、ウイルス感染、データの漏洩や破損のリスク、さらには直接的または間接的な経済的損失に直面する可能性があります。では、これらの脆弱性をどのように予防すべきでしょうか?脆弱性の問題を深く掘り下げる前に、まず脆弱性とは何かを明確にする必要があります。
脆弱性とは?
脆弱性とは、ソフトウェア、システム、またはネットワークに存在するセキュリティ上の弱点やエラーのことで、これらの弱点によってシステムが攻撃を受けたり、不正に使用されたりする可能性があります。コンピュータセキュリティの分野では、脆弱性は通常、プログラミングエラー、設計上の欠陥、または設定ミスに起因します。
オブジェクトリレーショナルマッピング(ORM)フレームワークにとって、脆弱性とは通常、設計や実装におけるセキュリティ上の問題を指し、これらの問題によってアプリケーションが SQL インジェクション攻撃のリスクにさらされる可能性があります。
どのような場合に SQL インジェクション攻撃が発生するのでしょうか?通常は以下の場合です:
- テーブル構造部分:通常、テーブルフィールド、テーブル名などの固定コンテンツを含みます。
- テーブルフィールドパラメータ/変数部分:様々な動的 SQL パラメータを含みます。
通常、ORM における SQL インジェクション脆弱性は、上記の 2 つの部分がフロントエンドからのパラメータ渡しを許可することによって発生します。
脆弱性の予防方法
脆弱性が発生する主な原因を理解すれば、テーブル構造とパラメータに関連するデータを適切に制御し、フロントエンドへの露出を避けることで脆弱性攻撃を防ぐことができます。
テーブルフィールド部分
テーブルフィールド部分は通常、バックエンドで制御されるべきですが、一部のシステムでは十分な柔軟性を維持するために、フロントエンドからデータベースフィールド名を動的に渡すことを許可しています。この方法はシステムの柔軟性についての要件を満たしますが、SQL インジェクションの重大なリスクに直面します。
リスクを回避するためには、システム設計者や開発者が自らフィールドのセキュリティを制御する必要があり、フロントエンドから任意の文字列を SQL フィールドに直接変換することは絶対に避けるべきです。フィールドマッピングロジックを通じて攻撃を防ぎ、フロントエンドインターフェースから渡されたフィールドの内容が直接 SQL コンパイル段階に入って最終的な SQL を生成することを避ける必要があります。
フィールドパラメータ/変数部分
フィールドパラメータ部分について、ORM フレームワークは通常、SQL インジェクション攻撃を防ぐためのプリコンパイルロジックを持っています。MyBatis では、$
プレースホルダーではなく #
プレースホルダーを使用することで SQL インジェクション攻撃を回避します。
MyBatis-Plus は関連する SQL を生成する際、基盤となる機能は同様に MyBatis から来ているため、同じように #
プレースホルダーを使用して攻撃を回避できます。ただし、この手順は MyBatis-Plus によって自動的に完了されます。
ユーティリティクラスによる予防
一般的に、上記の処理で SQL インジェクション攻撃を回避できますが、さらに安全を期したい場合は、MyBatis-Plus が提供するユーティリティクラス SqlInjectionUtils.check(内容)
を使用して、文字列に SQL インジェクションが存在するかどうかを検証できます。存在する場合は対応する例外がスローされます。
// SQL インジェクションの自動チェックを有効化(3.5.3.2+ バージョンでサポート)Wrappers.query().checkSqlInjection().orderByDesc("フロントエンドから渡される任意のフィールド。チェックの漏れがある可能性があるため、ホワイトリスト処理を推奨します")
// 手動検証方法(3.4.3.2+ バージョンでサポート)SqlInjectionUtils.check("フロントエンドから渡される任意のフィールド。チェックの漏れがある可能性があるため、ホワイトリスト処理を推奨します")
悪意のある脆弱性に関する説明
MyBatis-Plus の関連コードと Jar パッケージに対して、悪意のある人々が CVE 脆弱性を提出しました。以下でこれらの脆弱性について公式の声明を行います。
注意!「公式に認められていない CVE 脆弱性」がフレームワーク自体、ユーザー、プロジェクトの納品に非常に大きな影響を与えることにご注意ください。あなたの「他人を傷つけて自分の利益にならない行為」は他者に非常に大きな経済的損失をもたらします。
安全でない設計がある場合、最善の方法は イシューまたはプルリクエスト
を通じて公式が早急に修正できるよう協力することです。
公式ドキュメントでも 繰り返し
強調していますが、SQL フラグメント
は必ずセキュリティチェックを行う必要があります。JDBC を含むすべての ORM フレームワーク
では 文字列を直接 SQL に連結する
ことが許可されているため、フロントエンドから SQL フラグメントを渡さないことを推奨します。
CVE-2024-35548
詳細リンク:CVE-2024-35548
この「脆弱性」もフロントエンドから SQL フラグメント
を渡すことによる SQL インジェクション攻撃です。フレームワークの QueryWrapper
UpdateWrapper
フィールド部分はサブクエリを許可しているため、フロントエンドから SQL フラグメントを渡すことを人為的に許可することはできません。
このような要件がある場合は、SqlInjectionUtils.check(内容)
または xxWrapper.checkSqlInjection()
メソッドを使用してチェックできます。チェックに通過した場合、例外はスローされません。
フレームワークは非常に厳格な条件コンストラクタ LambdaQueryWrapper
LambdaUpdateWrapper
も提供しており、これらの使用を推奨します。
CVE-2023-25330
詳細リンク:CVE-2023-25330
この「脆弱性」は、いわゆるマルチテナントプラグインによって引き起こされる脆弱性について説明しており、マルチテナントプラグインが SQL インジェクション攻撃を引き起こすと述べています。これがどのように操作されるのか見てみましょう。
この「脆弱性」の提出者は、テナント ID をフロントエンドに悪意を持って露出し、フロントエンドからテナント ID を渡すことを許可し、コンテキストに保持させ、プラグインの実行段階で直接読み取って使用します。
テナント分離に関連する要件を実装したことがある場合、通常の方法はユーザーがログインした後、バックエンドが自らユーザーに対応するテナントを照会し、自らコンテキストを保持してマルチテナントプラグインの正常な動作を保証することを理解しているはずです。
テナントの切り替え操作を行う必要がある場合でも、フロントエンドから渡される切り替えテナントの ID を直接プラグインに使用することはありえず、切り替えが可能かどうかをチェックする必要があります。
これを問題だと言うなら、それは使用時の考慮不足によって引き起こされたものであり、基盤となるフレームワークとしては、使用者がこれらの機能をどのように使用するかを制約することはできません。すべてを基盤となるフレームワークに頼るのであれば、誰もが依存するだけで、オープンソースなど行う必要はありません。
CVE-2022-25517
詳細リンク:CVE-2022-25517、元の脆弱性リポジトリは削除されているため、ここをクリックして詳細分析を確認できます
この「脆弱性」はさらに滑稽で、テーブルフィールドをフロントエンドから渡せる部分として直接連結し、これを脆弱性だと強引に主張しています。理由は MyBatis-Plus が String 型のフィールドパラメータを開放しているため、SQL 攻撃スクリプトを渡すことができるというものです。
私たちは皆、MyBatis-Plus が LamdbaQueryWrapper を提供しており、LamdbaQueryWrapper を使用して Type-Safe なクエリを実行できることを知っています。大多数の人もこのように使用していると信じています。たとえ通常の QueryWrapper を使用し、String 型のフィールドがあったとしても、それは絶対にフロントエンドから私たちに渡されるものではありません。フィールドすべてをフロントエンドから渡すのであれば、バックエンドは何のために必要なのでしょうか?フロントエンドに直接 SQL を書かせればいいでしょう。
本当の脆弱性の問題であれば、私たちは必ず積極的に修正します。しかし、上記の 2 つのような初歩的なエラーを、私たちと事前に連絡を取ることもなく直接 CVE 脆弱性申請を提出することは、これらの脆弱性が善意の提案であるとは信じがたく、私たちから見れば、これは純粋な悪意です。