進行状況の追跡を始めよう
Trailhead のホーム
Trailhead のホーム

コード内の SOQL インジェクションの防止

学習の目的

この単元を完了すると、次のことができるようになります。
  • SOQL インジェクション防止のさまざまなパターンを学習する。
  • string.escapeSingleQuotes() を使用して SOQL インジェクションを防止する。
  • string.escapeSingleQuotes を使用するだけでは不十分な事例を挙げる。

SOQL インジェクションの防止

前の単元で学習したとおり、攻撃者は SOQL インジェクション攻撃を使用して組織内の制限付きデータにアクセスできます。では、これを防ぐにはどうすればよいでしょうか?

SOQL インジェクションを防ぐために使用できる手法は多数ありますが、そうした手法をどのように使うかはクエリで何を実現しようとしているかによって違ってきます。ここでは、次の手法について説明します。

  • 静的クエリとバインド変数
  • String.escapeSingleQuotes()
  • 型キャスト
  • 文字の置換
  • ホワイトリスティング

静的クエリとバインド変数

SOQL インジェクションを防止するための最も推奨される最初の方法は、静的クエリとバインド変数を使用する方法です。次のクエリを考えてみます。

String query = ‘select id from contact where firstname =\’’+var+’\’’;
queryResult = Database.execute(query);

これまで学習したように、SOQL クエリで直接ユーザ入力 (var 変数) を使用すると、アプリケーションが SOQL インジェクションに開放されます。このリスクを軽減するには、クエリを次のような静的クエリに変換します。

queryResult = [select id from contact where firstname =:var];

これにより、ユーザ入力はクエリの実行可能要素ではなく変数として処理されます。データベースがクエリを実行するときに、ユーザが「test’ LIMIT 1」のような値を入力した場合、「test’ LIMIT 1」のファーストネームがデータベースで検索されます。バインド変数を使用した場合、攻撃者は SOQL クエリを抜け出して制御することはできません。

バインド変数の使用は推奨されますが、いくつか制限事項があります。次のタイプの句でのみ使用できます。

  • FIND 句の検索文字列。
  • WHERE 句の条件リテラル。
  • WHERE 句の IN 演算子または NOT IN 演算子の値。値の動的セットを絞り込むことができます。いずれのデータ型のリストでも機能しますが、特に ID または String のリストで使用されます。
  • WITH DIVISION 句のディビジョン名。
  • LIMIT 句の数値。
  • OFFSET 句の数値。

ただし、コードでバインド変数を使用できない場合は、他の軽減手法を使用できます。

型キャスト

SOQL インジェクションを防ぐためのもうひとつの戦略は、型キャストを使用することです。すべての変数を文字列としてキャストすることで、ユーザ入力を思いがけない結果に終わらせることができます。可能な場合に変数を整数またはブールとして型キャストすることで、誤ったユーザ入力は許可されなくなります。クエリに挿入するために、変数は string.valueOf() を使用して再び文字列に変換されます (動的クエリでは、database.query() メソッドは文字列しか受け付けません)。

Kingdom Management 開発者組織でこれを試してみます。

  1. Kingdom Management 開発者組織にログインし、アプリケーションピッカーを使用して [SOQL インジェクション] アプリケーションに移動して、[SOQL インジェクション型キャスト修正] タブを開きます。

    このアプリケーションで、年齢検索条件値を指定して人員を検索するための Visualforce ページが表示されます。このページが SOQL インジェクションに対して脆弱かどうか調べてみましょう。

  2. 単純な SOQL インジェクションペイロード「1 limit 1」を入力して検索します。入力はコードとして処理されるため、クエリで 1 件の結果のみが返されます。
  3. ページ下部にあるリンクを使用して Apex コントローラを表示します。次のコードが表示されます。
    public String textualAge {get; set;}
    [...]
    whereClause+='Age__c >'+textualAge+'';
    whereclause_records = database.query(query+' where '+whereClause);
    

    変数 textualAge は直接クエリに入れられるため、入力をコードとして処理できます。また、クエリで textualAge の前後に単一引用符がないことがわかります。ただし、デモのために string.escapeSingleQuotes() を試してみます。

  4. コントローラ SOQL_Injection_Typecasting_Fix を編集し、string.escapeSingleQuotes() で textualAge をラップします。
  5. コントローラを保存してデモページを再読み込みします。
  6. 同じ SOQL インジェクションペイロード「1 limit 1」を検索に適用すると、SOQL インジェクションがまだ機能していることがわかります。

    次に、実際のクエリでの SOQL インジェクションを示します。

    ‘Select Name, Role__c, Title__c, Age__c from Personnel__c where Age__c > 1 limit 1’
    

    このクエリ内に単一引用符はないため、string.escapeSingleQuotes() は効果がありません。SOQL インジェクションを防ぐには、別のソリューションが必要です。

  7. コントローラ SOQL_Injection_Typecasting_Fix を再び編集し、string.escapeSingleQuotes() を削除します。
  8. textualAge の変数宣言を探し、String 型から Integer 型に変更します (年令は整数であるため、この型キャストは適切です)。
  9. 保存する前に、textualAge がクエリに追加される場所を特定します。クエリは文字列にする必要がありますが、textualAge は現在整数であるため、次のように string.valueOf() で textualAge をラップする必要があります。
    whereClause+='Age__c >'+string.valueOf(textualAge)+'';
    
  10. コントローラを保存してデモページを再読み込みします。
  11. SOQL インジェクションペイロード「1 limit 1」を再び検索領域に入力すると、SOQL インジェクションではなくエラーが表示されます。「1 limit 1」は整数とはみなされないため、SOQL インジェクションは防止されます。

このようにして型キャストを使用して、ユーザがテキストを入力していない場所で多くの種類の SOQL インジェクションを防ぐことができます。

単一引用符のエスケープ

ユーザ制御文字列をクエリに含める開発者が一般的に使用するもうひとつの XSS 緩和オプションは、プラットフォームに付属する escape 関数 string.escapeSingleQuotes() です。

この関数は文字列内で ‘ 引用符が見つかったすべてのインスタンスを、バックスラッシュ (\) エスケープ文字を使用してエスケープします。攻撃者の入力を文字列の境界に拘束して、攻撃者の入力がコードとして処理されるのを防ぎます。

Kingdom Management 開発者組織の例を見てみましょう。

  1. Kingdom Management 開発者組織にログインし、アプリケーションピッカーを使用して [SOQL インジェクション] アプリケーションに移動します。
  2. [SOQL インジェクション文字列修正] タブを選択します。

    前の SOQL インジェクション例と同様に、敬称で人員を検索するための Visualforce ページが表示されます。前のペイロードがこの事例でも機能し、Kingdom で成績の低い人員に関する情報を漏えいするかどうか確認してみましょう。

  3. 前のペイロード %' and Performance_rating__c<2 and name like'% を試してみます。

    前と同様に、1 つの結果が返され、SOQL インジェクションにより、本来取得できないはずの一部の情報を取得できました!

  4. ページ下部にある Apex リンクをクリックして、コードを調べます。コントローラで、次を見つけることができます。
    whereClause += 'Title__c like  \'%'+textualTitle+'%\' ';
    whereclause_records = database.query(query+' where '+whereClause);
    

    検索文字列「textualTitle」は直接クエリ文字列に入れられるため、ユーザ入力を、SOQL インジェクションを可能にするコードとして処理できます。変数は最終クエリで単一引用符でラップされるため、string.escapeSingleQuotes() を介してこの SOQL インジェクションを修正できます。

  5. Apex クラス “SOQL_Injection_String_Fix” を編集し、変数 textualTitle の前後に string.escapeSingleQuotes() を追加します。
  6. コードを保存してデモページを再読み込みします。前の SOQL インジェクションペイロードを再び試してみましょう。

SOQL インジェクションペイロードは機能しなくなりました! 単一引用符エスケープにより、クエリで文字列コンテキストを閉じてクエリ機能を追加することができなくなりました。

今回は、string.escapesinglequotes() を使用して、ユーザが指定した単一引用符がコードではなくデータとして処理されるようにしています。これで、アプリケーションは脆弱ではなくなりました!

ただし、このソリューションは文字列にのみ適用されます。すべての変数が文字列であるとは限らず、すべての SOQL インジェクション攻撃が単一引用符文字列の使用を必要とするわけではありません。これらのタイプのコードで SOQL インジェクションを防止するために、別のソリューションが必要になることもあります。

文字の置換

コードで string.escapeSingleQuotes、型キャスト、およびホワイトリスティングによって SOQL インジェクションを有効に防御できない場合はどうしたらよいでしょう? 最後のアプローチは文字の置換で、これはブラックリスティングとも呼ばれます。このアプローチでは、ユーザ入力から「悪い文字」を削除します。

セキュリティでは、ブラックリスティングはホワイトリスティングほど強力ではありません。考えられるすべての悪い入力を予測するよりも、いくつかの良い入力を予測する方がはるかに簡単であるためです。とは言うものの、文字の置換を介したブラックリスティングによって簡易な問題を効果的に緩和できることもよくあります。次のコードを考えてみましょう。

String query = ‘select id from user where isActive=‘+var;

ここでは型キャストやホワイトリスティングが有効ですが、提供された入力からすべてのスペースを削除することも、同じくらい有効なアプローチです。このようにして、次の SOQL インジェクションペイロード

true AND ReceivesAdminInfoEmails=true

が次のようになります。

trueANDRecievesAdminInfoEmails=true

文字列からすべてのスペースを削除するコードは、次のように記述できます。

String query = 'select id from user where isActive='+var.replaceAll('[^\w]','');

防御の最前線ではありませんが、開発はさまざまな問題に対する柔軟なソリューションであり、これは有用なソリューションのひとつと言えるでしょう。

ホワイトリスティング

先程確認したように、string.escapeSingleQuotes() を使用しても、必ずしもすべての形式の SOQL インジェクションを防ぐことはできません。しかし、前の型キャストのソリューションは、文字列以外の入力に対してのみ有効でした。ユーザ制御値をテキストにする必要があるが、単一引用符がない場合はどうしたらよいでしょうか? これは一般に、select fields や from object のように、クエリの他の部分がユーザの制御下に置かれる場合に発生します。

string.escapeSingleQuotes() なしで SOQL インジェクションを防ぐためのもうひとつの方法は、ホワイトリスティングです。ユーザに入力を許可する、すべての「既知の良い」値のリストを作成します。ユーザが別の値を入力した場合、応答を却下します。

これを調べるための最後のデモを Kingdom Management 開発者組織で試してみましょう。

  1. Kingdom Management 開発者組織にログインし、アプリケーションピッカーを使用して [SOQL インジェクション] アプリケーションに移動して、[SOQL インジェクションホワイトリスティング修正] タブを開きます。
    このページでは、人員、プロパティのクエリまたはオブジェクトの指定を行うことができます。
  2. 選択リストから人員オブジェクトを選択し、[検索の実行] を選択して結果を確認します。

    URL を見ると、次のことがわかります。

    /apex/soql_injection_whitelist_fix?object=Personnel__c
    

    オブジェクト名は URL パラメータを介してユーザの制御下にあるように見えます。Apex コントローラを表示して、"object" パラメータが SOQL クエリに直接連結されていることを確認できます。

  3. SOQL インジェクションを試みるには、where Performance_rating__c<2 を URL に追加して、ページを再読み込みします。URL は次のように変更されます。
    /apex/soql_injection_whitelist_fix?object=Personnel__c%20where%20Performance_rating__c<2
    

    また、SOQL インジェクションが実行されことがわかり、パフォーマンスレビュー評価を推測できます。

  4. コントローラリンクを使用して、Apex コントローラ “SOQL_Injection_Whitelist_Fix” を表示します。次のコードが表示されます。
    public String objectName {get;set;}
    [...]
    string obj = ApexPages.currentPage().getParameters().get('object');
    if(obj != null){ 
        string query = 'select id, name from '+obj+' limit 10';
        [...]
    }
    

    変数 obj が URL パラメータオブジェクトから取得されて直接クエリに渡され、SOQL インジェクションが可能になります。SOQL インジェクションには単一引用符が含まれていないため、string.escapeSingleQuotes() は効果がありません。変数 obj は文字列であり、ブール型や整数ではないため、型キャストも効果がありません。別のソリューションが必要です!

    このデモでは、3 つのオブジェクトの定義済みリストからオブジェクトを選択しています。適切なソリューションはホワイトリスト、すなわちユーザ入力が一致すべき既知の良い値のリストです。ホワイトリストは一般的なセキュリティアプローチで、Salesforce では多くの場所で使用しており、主な例として IP 範囲の制限があります。

    この SOQL インジェクションを防ぐには、ホワイトリスティングを追加し、オブジェクト URL パラメータの値が期待値 Personnel__c、Property__c、Supply__c のいずれかと一致することを確認します。

  5. Apex コントローラ “SOQL_Injection_Whitelist_Fix” を編集します。既存の If 入力規則を見つけます。
    if(obj != null){
    
  6. この if ステートメントを変更して、予想される入力をチェックします。コードは次のようになります (検証する適切な値を入力する必要があります)。
    if(obj=='...'||obj =='...'||obj =='...'){
    
  7. コントローラを保存してページを再読み込みします。
  8. 再び URL を介して SOQL インジェクションを送信すると、ペイロードがデータを返さなくなることがわかります。入力は期待値と一致しないため、クエリは実行されません。

ホワイトリスティングは、ユーザ入力が一連の期待値と一致することを検証するための優れたアプローチで、SOQL インジェクションを防ぐのに非常に有用です。

リソース

Apex 開発者ガイド - SOQL インジェクション

Open Web Application Security Project (OWASP) - SQL Injection (オープン Web アプリケーションセキュリティプロジェクト (OWASP) - SQL インジェクション)

Flower icon used to indicate that the content is for Salesforce Classic

このモジュールは Salesforce Classic 向けです。ハンズオン組織を起動するときには、Salesforce Classic に切り替えてから、この Challenge を実行してください。

retargeting