# Zen FirewallによるIDOR保護

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

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

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

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

脆弱性そのものの詳しい説明については、ブログ記事をご覧ください：[IDOR Vulnerabilities Explained](https://www.aikido.dev/blog/idor-vulnerability-explained)

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

{% tabs %}
{% tab title="NodeJS" %}
現在サポートされているドライバー：

* mysqlおよびmysql2経由のMySQL
* pg経由のPostgreSQL
* SQLite（better-sqlite3およびnode:sqliteパッケージ経由）

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

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

{% hint style="info" %}
ESMを使用している場合は、[ESMに関する注意事項](https://github.com/AikidoSec/firewall-node/blob/main/docs/esm.md)を確認してください。インストルメントされていないESMのサブ依存関係内のクエリは、Zenでチェックできません。
{% endhint %}
{% endtab %}

{% tab title="Ruby" %}
現在サポートされているgem：

* `mysql2`および`trilogy`経由のMySQL
* `pg`経由のPostgreSQL
* `sqlite3`経由のSQLite

内部でこれらのgemを使用するORMやクエリビルダーはすべてサポートされています。例：ActiveRecord。

独自のデータベースエンジンを使用するORMは、サポートされているドライバーアダプターを使用するよう設定しない限り、サポートされません。
{% endtab %}
{% endtabs %}

## 制限事項

* [NodeJS](https://github.com/AikidoSec/firewall-node/blob/main/docs/idor-protection.md#limitations)

## Zenが保護する対象

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

* SELECT、UPDATE、DELETEで正しいテナントカラムによるフィルタリングを行っていること
* 正しいテナントIDの値を使用していること
* すべてのINSERTにテナントカラムが含まれていること
* INSERTでテナントカラムに正しいテナントIDが設定されていること<br>

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

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

{% hint style="warning" %}
IDOR保護は、ブロックモードか検出モードかに関係なく、違反時に常にErrorをスローします。テナントフィルターの欠落は外部からの攻撃ではなく、開発者のバグです。
{% endhint %}

## 仕組み

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

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

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

## セットアップ

{% stepper %}
{% step %}
**起動時にIDOR保護を有効にする**

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

{% tabs %}
{% tab title="NodeJS" %}
```js
import Zen from "@aikidosec/firewall";

Zen.enableIdorProtection({
  tenantColumnName: "tenant_id",
  excludedTables: ["users"],
});
```

設定オプション：

*   `tenantColumnName`

    テーブル内でテナントを識別するカラム。例：tenant\_id、account\_id、organization\_id。
*   `excludedTables`

    単一のテナントにスコープされていないため、チェック対象外とするテーブル。例：すべてのテナントのユーザーを含む共有のusersテーブル。
{% endtab %}

{% tab title="Ruby" %}
```ruby
Aikido::Zen.config.idor_protection_enabled = true
Aikido::Zen.config.idor_tenant_column_name = "tenant_id"
Aikido::Zen.config.idor_excluded_table_names = ["users"]
```

設定オプション：

*   `idor_tenant_column_name`

    テーブル内でテナントを識別するカラム。例：tenant\_id、account\_id、organization\_id。
*   `idor_excluded_table_names`

    単一のテナントにスコープされていないため、チェック対象外とするテーブル。例：すべてのテナントのユーザーを含む共有のusersテーブル。
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}
**リクエストごとにテナントIDを設定する**

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

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

{% tabs %}
{% tab title="NodeJS" %}
```js
import Zen from "@aikidosec/firewall";

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

  next();
});
```

{% hint style="warning" %}
リクエストに対してsetTenantIdが呼び出されていない場合、SQLクエリの実行時にZenはErrorをスローします。
{% endhint %}
{% endtab %}

{% tab title="Ruby" %}
例えば認証後のミドルウェアで、`Aikido::Zen.set_tenant_id`を呼び出します：

```ruby
Aikido::Zen.set_tenant_id(1)
```

{% hint style="warning" %}
リクエストに対して`Aikido::Zen.set_tenant_id`が呼び出されていない場合、SQLクエリの実行時にZenは`Aikido::Zen::IDOR::Error`を発生させます。
{% endhint %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}
**特定のクエリでIDORチェックをバイパスする**

テナント横断の分析や社内向けの管理レポートなど、意図的にグローバルなクエリもあります。

特定のコードブロックでIDORチェックをスキップするには：

{% tabs %}
{% tab title="NodeJS" %}
withoutIdorProtectionを使用します：

```js
import Zen from "@aikidosec/firewall";

const result = await Zen.withoutIdorProtection(async () => {
  return await db.query(
    "SELECT count(*) FROM agents WHERE status = 'running'"
  );
});
```

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

{% tab title="Ruby" %}
`Aikido::Zen.without_idor_protection`を使用します：

```ruby
result = Aikido::Zen.without_idor_protection do
  db.execute("SELECT count(*) FROM agents WHERE status = 'running'")
end
```

ブロック内で実行されたクエリのみがIDOR検証から除外されます。
{% endtab %}
{% endtabs %}

この機能は慎重に、テナント横断のアクセスが明示的に必要な場合にのみ使用してください。
{% endstep %}
{% endstepper %}

## トラブルシューティング

### テナントフィルターの欠落

```
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でフィルタリングする条件を追加してください。

### 誤ったテナントIDの値

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

クエリはテナントカラムでフィルタリングしていますが、その値が`setTenantId`（Node.js）または`Aikido::Zen.set_tenant_id`（Ruby）で設定されたテナントIDと一致しません。<br>

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

### INSERTでのテナントカラムの欠落

```
Zen IDOR protection: INSERT on table 'orders' is missing column 'tenant_id'
```

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

#### INSERTでの誤ったテナントID

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

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

### \`setTenantId\`呼び出しの欠落

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

データベースクエリが実行される前に、すべてのリクエストでsetTenantIdが呼び出されていることを確認してください。

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

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

* SELECT
* INSERT
* UPDATE
* DELETE

以下は認識されますが、IDORエラーを発生させることはありません：

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