コンテンツにスキップ

Zen FirewallによるIDOR保護

IDORとは、Insecure Direct Object Reference(安全でない直接オブジェクト参照)の略です。データベースクエリが適切にスコープされていないことにより、あるアカウントが別のアカウントのデータにアクセスできてしまう、アクセス制御バグの一種です。

マルチテナントSaaSアプリケーションでは、通常tenant_id、account_id、organization_idといったカラムでデータが分離されています。クエリがそのカラムでのフィルタを忘れたり、誤った値を使用したりすると、データがアカウントをまたいで漏洩する可能性があります。

Zen FirewallのIDOR保護は、実行時にSQLクエリを解析し、テナントスコープを自動的に強制します。クエリが安全でない場合、Zenは即座にエラーをスローします。

これにより、本番環境でのインシデント発生後ではなく、開発・テスト段階でマルチテナント分離のバグを検出できます。

この脆弱性自体についての詳しい説明は、ブログ記事をご覧ください: IDOR Vulnerabilities Explained

サポートされているデータベース

Section titled “サポートされているデータベース”

NodeJS

現在サポートされているドライバ:

  • mysqlおよびmysql2経由のMySQL
  • pg経由のPostgreSQL
  • SQLite(better-sqlite3およびnode:sqliteパッケージ経由)

これらのドライバを内部で使用するORMまたはクエリビルダはすべてサポートされます。例えばDrizzle、Knex、Sequelize、TypeORMなどです。

Prismaのように独自のクエリエンジンを使用するORMは、サポートされているドライバアダプタを使用するよう設定されていない限りサポートされません。

有効化すると、Zenはすべての関連クエリについて以下を検証します。

  • SELECT、UPDATE、DELETEで正しいテナントカラムをフィルタしているか
  • 正しいテナントID値を使用しているか
  • すべてのINSERTにテナントカラムが含まれているか
  • INSERTでテナントカラムに正しいテナントIDが設定されているか

Zenがブロックする問題の例:

  • テナントフィルタのないSELECT(あるアカウントが別のアカウントのデータを読み取れてしまう)
  • テナントスコープのないUPDATEまたはDELETE
  • テナントカラムを省略したINSERT
  • 誤ったテナントIDを設定するINSERT

Zenはサポートされているデータベースドライバを計装し、実行時にSQLクエリを検査します。各リクエストについて、アクティブなテナントIDを設定します。Zenは、すべての関連クエリが以下を満たすかを検証します。

  1. 設定されたテナントカラムを参照していること。
  2. リクエストに対して設定されたものと同じテナントIDを使用していること。

これらの条件を満たさないクエリがある場合、Zenはエラーをスローし、実行を阻止します。

1. 起動時にIDOR保護を有効化する

Section titled “1. 起動時にIDOR保護を有効化する”

アプリケーション起動時にIDOR保護を有効化します

NodeJS

import Zen from "@aikidosec/firewall";
Zen.enableIdorProtection({
tenantColumnName: "tenant_id",
excludedTables: ["users"],
});

設定オプション:

  • tenantColumnName

    テーブル内でテナントを識別するカラムです。例えばtenant_id、account_id、organization_idなどです。

  • excludedTables

    単一テナントにスコープされていないため、チェック対象外とすべきテーブルです。例えば、全テナントのユーザーを含む共有usersテーブルなどです。

2. リクエストごとにテナントIDを設定する

Section titled “2. リクエストごとにテナントIDを設定する”

IDOR保護が有効な場合、すべてのリクエストにテナントIDが必要です。

リクエストのライフサイクルの早い段階、通常は認証後のミドルウェアでsetTenantIdを呼び出してください。

NodeJS

import Zen from "@aikidosec/firewall";
app.use((req, res, next) => {
// Retrieve the tenant ID from your authentication layer
Zen.setTenantId(req.user.organizationId);
next();
});

3. 特定のクエリでIDORチェックをバイパスする

Section titled “3. 特定のクエリでIDORチェックをバイパスする”

一部のクエリは、クロステナントの分析や社内向け管理レポートなど、意図的にグローバルであることがあります。

特定のコードブロックでIDORチェックをスキップするには、withoutIdorProtectionを使用します。

import Zen from "@aikidosec/firewall";
const result = await Zen.withoutIdorProtection(async () => {
return await db.query(
"SELECT count(*) FROM agents WHERE status = 'running'"
);
});

コールバック内で実行されたクエリのみがIDOR検証から除外されます。

これは慎重に使用し、クロステナントアクセスが明示的に必要な場合にのみ使用してください。

Zen IDOR protection: query on table 'orders' is missing a filter on column 'tenant_id'

クエリが設定済みのテナントカラムでフィルタしていません。これはSELECT、UPDATE、DELETEに適用されます。

例:

SELECT * FROM orders WHERE status = 'active';

tenant_idでフィルタする条件を追加してください。

Zen IDOR protection: query on table 'orders' filters 'tenant_id' with value '456' but tenant ID is '123'

クエリはテナントカラムでフィルタしていますが、値がsetTenantIdで設定されたテナントIDと一致しません。

すべてのクエリが現在のリクエストコンテキストのテナントIDを使用するようにしてください。

INSERTでテナントカラムが欠落している

Section titled “INSERTでテナントカラムが欠落している”
Zen IDOR protection: INSERT on table 'orders' is missing column 'tenant_id'

すべてのINSERTにテナントカラムを含める必要があります。

Zen IDOR protection: INSERT on table 'orders' sets 'tenant_id' to '456' but tenant ID is '123'

INSERTにテナントカラムは含まれていますが、値がアクティブなテナントIDと一致しません。

Zen IDOR protection: setTenantId() was not called for this request. Every request must have a tenant ID when IDOR protection is enabled.

データベースクエリが実行される前に、すべてのリクエストに対してsetTenantIdが呼び出されるようにしてください。

常に許可されるステートメント

Section titled “常に許可されるステートメント”

Zenは行データの読み取りまたは変更を行うステートメントのみをチェックします。

  • SELECT
  • INSERT
  • UPDATE
  • DELETE

以下は認識されますが、IDORエラーをトリガーすることはありません。

  • CREATE TABLE、ALTER TABLE、DROP TABLEなどのDDLステートメント
  • SET、SHOWなどのセッションコマンド
  • BEGIN、COMMIT、ROLLBACKなどのトランザクションステートメント