このワークショップでは、CVE-2021-21380 で文書化された XWiki プラットフォームの評価コンポーネントにおける SQL インジェクションを見つけるために、構文解析と意味解析を使用します。
可能であれば、ワークショップの前にこのセクションを完了してください。
Visual Studio Code をインストールします。
CodeQL 拡張機能 for Visual Studio Code をインストールします。
CodeQL CLI をインストールする 必要はありません: 拡張機能が自動的に処理します。
このリポジトリをクローンします:
git clone https://github.com/advanced-security/codeql-workshop-cve-2021-21380.git
xwiki-platform-CVE-2021-21380.zip ファイルを右クリックし、CodeQL: Set Current Database コマンドを選択します。Cmd/Ctrl+Shift+P) から、CodeQL: Install Pack Dependencies コマンドを検索して実行します。github と入力してリストをフィルタリングします。cve-2021-21380-exercises、cve-2021-21380-exercises-tests、cve-2021-21380-solutions、cve-2021-21380-solutions-tests の横のチェックボックスをオンにします。ワークショップはいくつかの演習に分かれており、Java の QL 言語サポートを紹介し、既知の SQL インジェクションを見つけるための最終的なクエリで終了します。 これらの演習では以下を学びます:
このワークショップでは、XWiki プラットフォーム の評価 API コンポーネントにおける既知の SQL インジェクションの脆弱性 を探します。このような脆弱性は、外部ユーザーによって制御される情報が、安全でない方法で SQL クエリを構築して実行するアプリケーションコードに到達した場合に発生する可能性があります。
このワークショップで扱う既知の SQL インジェクションは、GitHub Advisory Database の GHSA-79rg-7mv3-jrr5 でレビューされています。SQL インジェクションとその可能性のある亜種を見つけるために、以下のサブ問題に取り組みます:
最初のいくつかの演習では、抽象構文木 (AST) を使用して構文情報を推論し、以下を特定します:
プログラム内で getAverageRating という名前のすべてのメソッドとその宣言型を見つけるには、クエリ exercise1.ql を完成させます。
java モジュールは、プログラム内のメソッドを推論するためのクラス Method を提供します。Method は、メソッドの名前を推論するためのメンバー述語 getName および hasName を提供します。Method は、メソッドを宣言する型を推論するためのメンバー getDeclaringType を提供します。解答はクエリ exercise1.ql にあります。
演習 1 の解答はメソッドのリストを返します。そのうちのいくつかは RatingsManager というインターフェースで定義され、いくつかはクラス AbstractRatingsManager および RatingsScriptService で定義されています。
クエリが返す情報と XWiki コンポーネントのドキュメント から、以下を推測できます:
Role で注釈されたインターフェースと、Component で注釈された実装で構成されます。ScriptService を拡張するコンポーネントは、スクリプトを通じて Wiki ページからアクセス可能になります。注釈 Component で注釈されたすべてのクラスを見つけるには、クエリ exercise2.ql を完成させます。
注釈の型の完全修飾名は org.xwiki.component.annotation.Component であることに注意してください。
this を使用してアクセスできます。Class クラスは、関連する注釈を取得するためのメソッド getAnAnnotation を提供します。Annotation クラスは、その型を推論するためのメンバー述語 getType を提供します。Type クラスは、型の名前を推論するためのメンバー述語 getName および hasName を提供します。RefType クラス (クラスとインターフェースを表す) は、reftype の完全修飾名を推論するためのメンバー述語 getQualifiedName および hasQualifiedName を提供します。解答はクエリ exercise2.ql にあります。
ScriptService インターフェースを実装するすべてのコンポーネントを見つけるには、クエリ exercise3.ql を完成させます。
Class 型は、クラスのスーパー型 (つまり、extends または implements する型) を推論するためのメンバー述語 getASuperType を提供します。解答はクエリ exercise3.ql にあります。
この時点で、ユーザーが呼び出すことができ、そのパラメータをワークショップの後半で信頼されていないデータのソースと見なすメソッドを構文的に特定しました。
次の演習では、可能性のあるシンクを調査および特定します。演習 1 の結果から、実装の 1 つがメソッド getAverageRatingFromQuery を呼び出していることがわかります。同様のクエリを使用して getAverageRatingFromQuery の宣言型を見つけることができ、それにより AbstractRatingsManager クラスの実装が search メソッドに渡される SQL ステートメントを構築していることを確認できます。
search メソッドは依存関係で実装されているため、その実装は利用できません。次の演習では、利用可能な型情報を使用して、この search メソッド呼び出しとその宣言型を特定します。
メソッド search へのすべてのメソッド呼び出しを見つけ、その宣言型を特定するには、クエリ exercise4.ql を完成させます。
MethodCall 型は、メソッド呼び出しを推論する手段を提供します。MethodAccess 型は、メソッド呼び出しのターゲットを推論するためのメンバー述語 getMethod を提供します。解答はクエリ exercise4.ql にあります。
インターフェース XWikiStorageInterface (修飾名 com.xpn.xwiki.store.XWikiStoreInterface) によって宣言されたメソッドへのすべてのメソッド呼び出しを見つけるには、クエリ exercise5.ql を完成させます。
instanceof キーワードを使用して、値が型によって表される値のセットに属することを表明できます。
例えば、インターフェースメソッドへのすべての呼び出しを特定するには:
import java
from MethodCall ma
where ma.getMethod.getDeclaringType() instanceof Interface
select ma
解答はクエリ exercise5.ql にあります。
この時点で、QL を使用して可能性のあるソースとシンクを構文的に記述しました。プログラム内のこれらのポイント間で情報が流れるかどうかを判断するために、taint 追跡を使用してプログラムを意味解析します。 サポートする言語の標準ライブラリは、2 つのデータフローメカニズムを提供します:
DataFlow モジュールは、値を保持する情報の流れをサポートします。TaintTracking モジュールは、値が変更された場合でも情報の流れをサポートします。後者は、SQL インジェクションなどのインジェクション脆弱性では、信頼されていないデータが操作対象となるより大きなステートメントの一部になることが一般的であるため、重要です。
このワークショップでは、既存の SQL インジェクション taint 追跡設定を再利用し、モデル化したソースとシンクで拡張して SQL インジェクションを見つけます。設定を拡張する方法を理解するために、まず以下の設定の定義を見てみましょう。
class QueryInjectionFlowConfig extends TaintTracking::Configuration {
QueryInjectionFlowConfig() { this = "SqlInjectionLib::QueryInjectionFlowConfig" }
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof QueryInjectionSink }
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or
node.getType() instanceof BoxedType or
node.getType() instanceof NumberType
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
any(AdditionalQueryInjectionTaintStep s).step(node1, node2)
}
}
設定で使用される isSource と isSink の両方の述語は、これまでに見た instanceof キーワードを使用してプログラムの位置を特定します。RemoteFlowSource と QueryInjectionSink の両方のクラスは abstract クラスです。
これは標準ライブラリでよく見られるパターンであり、このパターンにより RemoteFlowSource および QueryInjectionSink クラスによって表される値のセットを拡張できます。
abstract クラス を拡張する QL の class は、スーパークラスによって表される値のセットを絞り込むのではなく、サブクラスによって表される値をスーパークラスに追加します。
次の演習では、これらのサブクラスを実装し、最終的なクエリを構築します。
演習 3 で特定したコンポーネントクラスのパブリックメソッドのパラメータで RemoteFlowSource の値セットを拡張するには、クエリ exercise6.ql を完成させます。
instanceof キーワードを使用して、値が型によって表される値のセットに属することを表明できます。
例えば、インターフェースメソッドへのすべての呼び出しを特定するには:
import java
from MethodCall ma
where ma.getMethod.getDeclaringType() instanceof Interface
select ma
解答はクエリ exercise6.ql にあります。
演習 5 で特定したストレージインターフェースのメソッドへの呼び出しの引数で QueryInjectionSink の値セットを拡張するには、クエリ exercise7.ql を完成させます。
exists 式を使用すると、一時変数を導入し、exists のスコープ内で推論できます。
次の例では、exists 式を使用して、メソッドのセットを呼び出されるメソッドに絞り込んでいます。
from Method m
where m.hasName("foo") and exists(MethodCall ma | ma.getMethod() = m)
select m
MethodCall クラスは、メソッドアクセスの修飾子を推論するためのメンバー述語 getQualifier を提供します。
解答はクエリ exercise7.ql にあります。
前の演習の解答を組み合わせて、クエリ exercise8.ql を完成させることで最終的な解答にします。
解答はクエリ exercise8.ql にあります。
XWikiStoreInterface の直接使用に限定しました。インターフェースの実装の直接使用を含めるようにシンクを拡張してください。Class クラスはメンバー述語 extendsOrImplements を提供し、Method クラスはメンバー述語 overridesOrInstantiates を提供します。これらが役立つ可能性があります。