OData セキュリティ ガイダンス (翻訳)

ASP.NET 公式サイトの OData Security Guidance (by Mike Wasson) を翻訳したものです。


この記事では、OData を通じてデータセットを公開する際に検討すべき、いくつかのセキュリティの議論について記述します。

EDM のセキュリティ

クエリのセマンティクスは、元のモデル型ではなく、Entity Data Model (EDM) に基づきます。
EDM からはプロパティを除外でき、その場合はクエリには公開されません。
例えば、モデルの中に Salary プロパティを持つ Employee 型があるとします。
クライアントから隠すため、EDM からこのプロパティを除外したいでしょう。

EDM からプロパティを除外する方法は 2 つあります。
モデル クラスのプロパティには、[IgnoreDataMember] 属性を設定できます。

public class Employee
{
    public string Name { get; set; }
    public string Title { get; set; }
    [IgnoreDataMember]
    public decimal Salary { get; set; } // EDM では非表示
}

また、プログラム上で EDM からプロパティを除外できます。

var employees = modelBuilder.EntitySet<Employee>("Employees");
employees.EntityType.Ignore(emp => emp.Salary);

クエリのセキュリティ

意地悪な利用者や経験不足な利用者は、実行に時間のかかるクエリを作るかもしれません。
最悪の場合、サービスへのアクセスが中断されることがあります。

[Queryable] 属性は、クエリを解析、検証、そして適用するためのアクション フィルターです。
このフィルターはクエリ オプションを LINQ 式に変換します。
OData コントローラーが IQueryable 型を返す場合、IQueryable LINQ プロバイダーは LINQ 式をクエリに変換します。
したがって、パフォーマンスは LINQ プロバイダーに依存し、またデータセットやデータベースのスキーマの特性にも依存します。

ASP.NET Web API における OData クエリ オプションの利用についての詳細は
Supporting OData Query Options を参照してください。

もしすべての利用者を信頼できる (例えば、社内環境) か、またはデータセットが小さい場合、
クエリのパフォーマンスは問題にならないでしょう。
そうでないのであれば、以下に紹介する内容を検討すべきです。

  • さまざまなクエリでサービスをテストし、データベースをプロファイルします。
  • サーバー駆動ページングを有効にして、1 回のクエリで大きなデータセットを返すことを避けます。
    詳細は Server-Driven Paging を参照してください。

// サーバー駆動ページングを有効にします。
[Queryable(PageSize = 10)]

  • $filter や $orderby は必要でしょうか?
    $top や $skip を使用するクライアント ページングは許可しても、
    他のクエリ オプションを無効にするようなアプリケーションもあるでしょう。

// クライアント ページングを許可しますが、他のクエリ オプションを除外します。
[Queryable(AllowedQueryOptions = AllowedQueryOptions.Skip | AllowedQueryOptions.Top)]

  • $orderby を、クラスター化インデックスの設定されたプロパティに限定することを検討します。
    大きなデータをクラスター化インデックスなしでソートするには時間がかかります。

// $orderby で許可されるプロパティを設定します。
[Queryable(AllowedOrderByProperties = "Id,Name")] // コンマ区切りのリスト

  • 最大ノード数: [Queryable] の MaxNodeCount プロパティでは、$filter 構文木で許可される最大ノード数を設定します。
    既定値は 100 ですが、ノード数が大きいとコンパイルが遅くなるため、これよりも小さな値を設定するほうがよいでしょう。
    このことは、LINQ to Objects (メモリ内のコレクションにおける LINQ クエリで、中間的な LINQ プロバイダーを利用しない)
    を利用する場合にとくに当てはまります。

// 最大ノード数を設定します。
[Queryable(MaxNodeCount = 20)]

  • any() および all() 関数を無効にすることを検討します。遅くなる可能性があるためです。

// any() および all() 関数を無効にします。
[Queryable(AllowedFunctions = AllowedFunctions.AllFunctions &
    ~AllowedFunctions.All & ~AllowedFunctions.Any)]

  • プロパティが大きな文字列を含んでいる場合 (例えば、商品の説明やブログのエントリ)、
    文字列関数を無効にすることを検討します。

// 文字列関数を無効にします。
[Queryable(AllowedFunctions = AllowedFunctions.AllFunctions &
    ~AllowedFunctions.AllStringFunctions)]

  • ナビゲーション プロパティによるフィルターを無効にすることを検討します。
    ナビゲーション プロパティによるフィルターは、データベースのスキーマによっては時間のかかる結合処理を招くでしょう。
    次のコードは、ナビゲーション プロパティによるフィルターを防ぐためのクエリ バリデーターを示します。
    クエリ バリデーターについての詳細は Query Validation を参照してください。

// ナビゲーション プロパティによるフィルターを防ぐためのバリデーターです。
public class MyFilterQueryValidator : FilterQueryValidator
{
    public override void ValidateNavigationPropertyNode(
        Microsoft.Data.OData.Query.SemanticAst.QueryNode sourceNode,
        Microsoft.Data.Edm.IEdmNavigationProperty navigationProperty,
        ODataValidationSettings settings)
    {
        throw new ODataException("No navigation properties");
    }
}

  • データベースに対して特化されたバリデーターを記述することにより、$filter クエリを制限することを検討します。
    例えば、これらの 2 つのクエリを考えます:

    ・ 姓が ‘A’ から始まる俳優が出演するすべての映画。
    ・ 1994 年に公開された映画。

    映画が俳優でインデックスされていない限り、
    1 つ目のクエリはデータベース エンジンにすべての映画のリストをスキャンさせるでしょう。
    一方、2 つ目のクエリは、映画が公開年でインデックスされていると仮定すれば許容できるでしょう。

    次のコードは、"ReleaseYear" および "Title" プロパティによるフィルターのみを許容するバリデーターを示します。

// $filter 式で利用可能なプロパティを制限するバリデーターです。
public class MyFilterQueryValidator : FilterQueryValidator
{
    static readonly string[] allowedProperties = { "ReleaseYear", "Title" };

    public override void ValidateSingleValuePropertyAccessNode(
        SingleValuePropertyAccessNode propertyAccessNode,
        ODataValidationSettings settings)
    {
        string propertyName = null;
        if (propertyAccessNode != null)
        {
            propertyName = propertyAccessNode.Property.Name;
        }

        if (propertyName != null && !allowedProperties.Contains(propertyName))
        {
            throw new ODataException(string.Format("Filter on {0} not allowed", propertyName));
        }
        base.ValidateSingleValuePropertyAccessNode(propertyAccessNode, settings);
    }
}

  • 一般的に、どの $filter 関数が必要かを検討します。
    もし利用者が $filter のすべての表現を必要としないのであれば、許可する関数を制限できます。

参照
OData : The Official Microsoft ASP.NET Site
ASP.NET and Web Tools 2012.2
ASP.NET Web APIのODataでQueryable APIにバリデーションを適用する

カテゴリー: .NET Framework. タグ: , . Leave a Comment »

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。