次の方法で共有


SQL インジェクション攻撃の緩和

コミュニティの関心グループが Yammer から Microsoft Viva Engage に移行されました。 Viva Engage コミュニティに参加し、最新のディスカッションに参加するには、「 Finance and Operations Viva Engage Community へのアクセスを要求する 」フォームに入力し、参加するコミュニティを選択します。

SQL インジェクション攻撃は、悪意のあるデータ値をクエリ文字列で Microsoft SQL Server に渡したときに発生します。 それらの値により、データベースに多くの損害が発生することがあります。 SQL インジェクションは、クエリを使用して、ユーザー入力などの制御されていないソースからのデータを SQL Server に渡す方法に注意しないと発生する可能性があります。 通常、X++ の組み込みデータ アクセス ステートメントにより回避されるため、財務と運用アプリで SQL インジェクションは問題になりません。 ただし、ダイレクト SQL を使用する場合、SQL コードがそのままサーバーに渡されるときに SQL インジェクションが発生する可能性があります。

新しい API は、これらの攻撃を軽減するのに役立ちます。 財務と運用アプリのバージョン 10.0.17 用のプラットフォーム更新プログラム (2021 年 4 月) から API が使用可能です。

問題

開発者が次のコードを作成して、姓に基づいて顧客の名を検索するというシナリオを考えてみます。

public str GetFirstName(str name)
{
    str sqlStatementText = "SELECT TOP(1) firstName FROM Customer WHERE customer.Name = '" + name + "'";

    // Create a connection to the SQL Server
    var connection = new Connection();

    // Create a statement and submit the sql statement to the server:
    Statement statement = connection.createStatement();
    var results= statement.executeQuery(sqlStatementText);

    // Get the first record:
    results.next();
   
    // Harvest the results.
    return results.getString(1);
   
   // Close statement
    statement.close();
    
    return result;
}

さらに、ユーザーが文字列フィールドに顧客名を入力できるページか、サーバーに名前を入力できるようにするサービス エンドポイントがあります。

このシナリオでは、ユーザーが「Jones」のような有効な名前を入力すると、すべての機能が有効になります。ただし、悪意のあるユーザーが次の文字列を名前として入力する可能性があります。

'; drop table Customer --

この場合、サーバーが実行する最後のクエリを次に示します。

SELECT TOP(1) firstName FROM Customer WHERE customer.Name = ''; drop table Customer --'

ユーザーが探している名前を含む文字列の中で、指定された文字列の最初の引用符は、その文字列リテラルを終わらせるためにのみ存在します。 その後、セミコロン (;)、ステートメント ターミネータ トークン) により、別の SQL ステートメントが実行されます。 この 2 つ目のステートメントは顧客テーブルとそれに含まれるすべてのデータを完全に削除します。 最後に、コメント文字 (--) により、最後の単一引用符で構文エラーが発生しないことを確認します。 したがって、文字列は有効な Transact SQL (T-SQL) になります。

SQL インジェクションは、実行時のテーブル、ビュー、およびストアド プロシージャの作成または削除を行う操作の実行を回避できるように、SQL Server への接続が制限されないために発生します。 そのため、組織は、開発者が自分の行動を知っている合理的な人であるという前提に頼る必要があります。

ソリューション

SQL Server は、ステートメント パラメーターを使用して脅威を緩和します。 ステートメント パラメーターは、結果の文字列のテキストが変更される可能性があるリテラルを使用することはありません。 代わりに、コンテキストに応じて実際の内容を持った名前付きパラメーターが提供されます。 このリリースでは、コードで SQL 文字列をビルドする代わりにパラメーターを含めるために使用できる新しい API が追加されました。

次の例で、これらの変更が組み込まれた後、前の例のコードがどのように見えるかを示します。

public str GetFirstName(str name)
{
    str sqlStatementText = "SELECT TOP(1) firstName FROM Customer WHERE customer.Name = @Name";

    // Create a connection to the SQL Server
    var connection = new Connection();

    // Submit the sql statement to the server:
    Statement statement = connection.createStatement();

    // Create a mapping from parameter names onto values
    Map paramMap = SqlParams::create();
    paramMap.add('Name', name);

    // Execute the query, providing both the query
    // and the parameters.
    var results= statement.executeQueryWithParameters(sqlStatementText, paramMap);

    // Capture the results:
    results.next();
  
    // Close statement
      statement.close();

    return results;
}

更新された例では、パラメーターを使っていなかった古い API の代わりに、新しい executeQueryWithParameters API を使用しています。 このコードは、パラメーター名からパラメーター値へのマッピングを含むマップをビルドします。 この場合、 Name は SQL の @Name の値です。 名前の値は、任意のものにすることができます。

ステートメント型の関連メソッドは、行ではなく整数値を返すステートメントを実行します。 通常、整数値は影響を受けた行数を示します。 次の例では、executeQueryWithParameters API を使用した X++ データ ステートメントを使用しています。

public void InsertWithStrParameter()
{
    var connection = new Connection();
    Statement statement = connection.createStatement();

    connection.ttsbegin();

    str sql = @"
        UPDATE Wages
        SET Wages.Wage = Wages.Wage * @percent
        WHERE Wages.Level = @Level";

    Map paramMap = SqlParams::create();
    paramMap.add('percent', 1.1);        // 10 percent increase
    paramMap.add('Level', 'Manager');    // Management increase

    int cnt = statement.executeUpdateWithParameters(sql, paramMap);
    statement.close();

    connection.ttscommit();
}

まとめ

Microsoft が新しいメソッドを導入すると、既存のメソッド (つまり、パラメーターのないメソッド) も古いものとしてマークされます。 通常の廃止期間が適用されます。 そのため、パラメーターによって提供される新しい保護を利用するようにコードを更新します。

新しい executeQueryWithParameters API は、顧客を災害から保護するのに役立ちますが、使用する必要はありません。 文字列を連結し、空のパラメーター セットを提供することもできます。 ただし、この場合、パラメーターを提供することによるメリットはありません。 この機会に、コード内の危険な使用を排除します。