performing-api-security-testing-with-postman について
開発チームやセキュリティチーム向けの、Postman を使った API セキュリティテスト手順です。認証、認可、データ露出、レート制限を確認し、Newman による CI/CD 実行も扱います。
主な機能
- 01ユーザーロール別の環境設定
- 02BOLA と認証のテスト
- 03データ露出と認可の確認
- 04一括割り当てとレート制限のテスト
- 05Newman による CI/CD テスト実行
活用シーン
- 01API セキュリティ回帰テストの構築
- 02CI/CD パイプラインでのテスト実行
- 03リリース前の新規 API 端点の評価
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill performing-api-security-testing-with-postmanインストール後、Claude Code で/performing-api-security-testing-with-postmanを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEreferences/api-reference.mdscripts/agent.py詳細
Postman で API セキュリティテストを実施する
使用する場面
- OWASP API Security Top 10 を網羅する、再現可能な API セキュリティテストスイートを構築する場合
- Newman を使って CI/CD パイプラインで実行する、自動化されたセキュリティ回帰テストを作成する場合
- 複数のユーザーロールにわたる API 認証と認可を体系的にテストする場合
- 手動テストと自動セキュリティテストを組み合わせるため、Postman を OWASP ZAP プロキシと連携する場合
- デプロイ前に、新しい API エンドポイント用の基本的なセキュリティテストコレクションを用意する場合
許可なく本番 API に対して使用しないでください。Postman のセキュリティテストでは、悪意のある可能性があるペイロードを送信します。
前提条件
- アクティブなワークスペースがある Postman Desktop またはウェブアプリケーション
- コレクションのインポート用の OpenAPI/Swagger 仕様がある対象 API
- 少なくとも 3 つのロール(未認証、一般ユーザー、管理者)のテストアカウント
- CI/CD 連携用の Newman CLI:
npm install -g newman - Postman プロキシ連携用にローカルプロキシ(localhost:8080)として設定された OWASP ZAP
- ベース URL、トークン、テストデータ用の API 環境変数
ワークフロー
ステップ 1:環境とコレクションのセットアップ
複数のロールをテストするため、Postman 環境を作成します。
// Environment: API Security Test - Regular User
{
"values": [
{"key": "base_url", "value": "https://target-api.example.com/api/v1"},
{"key": "auth_token", "value": ""},
{"key": "user_email", "value": "[email protected]"},
{"key": "user_password", "value": "TestPass123!"},
{"key": "user_id", "value": ""},
{"key": "other_user_id", "value": "1002"},
{"key": "admin_endpoint", "value": "/admin/users"},
{"key": "test_order_id", "value": ""},
{"key": "other_user_order_id", "value": "5003"}
]
}
自動認証用のプレリクエストスクリプト:
// Collection-level pre-request script for auto-login
if (!pm.environment.get("auth_token") || pm.environment.get("token_expired")) {
const loginRequest = {
url: pm.environment.get("base_url") + "/auth/login",
method: "POST",
header: {"Content-Type": "application/json"},
body: {
mode: "raw",
raw: JSON.stringify({
email: pm.environment.get("user_email"),
password: pm.environment.get("user_password")
})
}
};
pm.sendRequest(loginRequest, (err, res) => {
if (!err && res.code === 200) {
const token = res.json().access_token;
pm.environment.set("auth_token", token);
pm.environment.set("user_id", res.json().user.id);
}
});
}
ステップ 2:BOLA(API1)テストコレクション
// Test: Access other user's profile (BOLA)
// Request: GET {{base_url}}/users/{{other_user_id}}
// Auth: Bearer {{auth_token}}
// Test script:
pm.test("BOLA: Cannot access other user profile", function() {
pm.expect(pm.response.code).to.be.oneOf([401, 403]);
});
pm.test("BOLA: No user data leaked on denial", function() {
if (pm.response.code === 200) {
const body = pm.response.json();
pm.expect(body).to.not.have.property("email");
pm.expect(body).to.not.have.property("phone");
pm.expect(body).to.not.have.property("address");
// Flag as BOLA if full profile returned
console.error("BOLA VULNERABILITY: Full profile returned for other user");
}
});
// Test: Access other user's order
// Request: GET {{base_url}}/orders/{{other_user_order_id}}
pm.test("BOLA: Cannot access other user order", function() {
pm.expect(pm.response.code).to.be.oneOf([401, 403, 404]);
});
// Test: Modify other user's resource
// Request: PATCH {{base_url}}/users/{{other_user_id}}
// Body: {"name": "Hacked"}
pm.test("BOLA: Cannot modify other user profile", function() {
pm.expect(pm.response.code).to.be.oneOf([401, 403]);
});
ステップ 3:認証(API2)テストコレクション
// Test: Token validation
// Request: GET {{base_url}}/users/me
// Auth: Bearer invalid_token_value
pm.test("Auth: Invalid token rejected", function() {
pm.expect(pm.response.code).to.be.oneOf([401, 403]);
});
// Test: Expired token handling
// Request: GET {{base_url}}/users/me
// Auth: Bearer {{expired_token}}
pm.test("Auth: Expired token rejected", function() {
pm.expect(pm.response.code).to.equal(401);
});
// Test: Missing authentication
// Request: GET {{base_url}}/users/me
// Auth: None
pm.test("Auth: Unauthenticated request rejected", function() {
pm.expect(pm.response.code).to.equal(401);
});
// Test: SQL injection in login
// Request: POST {{base_url}}/auth/login
// Body: {"email": "' OR 1=1--", "password": "test"}
pm.test("Auth: SQLi in login rejected", function() {
pm.expect(pm.response.code).to.not.equal(200);
pm.expect(pm.response.text()).to.not.include("token");
});
// Test: Account enumeration
// Pre-request: Send login with valid email + wrong password, then invalid email + wrong password
pm.test("Auth: No account enumeration", function() {
// Compare with stored response from valid email attempt
const validEmailResponse = pm.environment.get("valid_email_response");
const currentResponse = pm.response.text();
pm.expect(currentResponse).to.equal(validEmailResponse);
});
ステップ 4:データ露出(API3)と BFLA(API5)のテスト
// Test: Excessive data exposure check
// Request: GET {{base_url}}/users/me
pm.test("Data Exposure: No sensitive fields in response", function() {
const sensitiveFields = [
"password", "password_hash", "passwordHash",
"ssn", "social_security", "credit_card",
"api_key", "secret_key", "mfa_secret",
"refresh_token", "session_id"
];
const responseText = pm.response.text().toLowerCase();
sensitiveFields.forEach(field => {
pm.expect(responseText).to.not.include('"' + field + '"');
});
});
pm.test("Data Exposure: Security headers present", function() {
pm.expect(pm.response.headers.has("X-Content-Type-Options")).to.be.true;
pm.expect(pm.response.headers.has("X-Frame-Options")).to.be.true;
pm.expect(pm.response.headers.get("X-Content-Type-Options")).to.equal("nosniff");
});
pm.test("Data Exposure: No server info leaked", function() {
pm.expect(pm.response.headers.has("Server")).to.be.false;
pm.expect(pm.response.headers.has("X-Powered-By")).to.be.false;
});
// Test: BFLA - Admin endpoint access
// Request: GET {{base_url}}{{admin_endpoint}}
// Auth: Bearer {{auth_token}} (regular user)
pm.test("BFLA: Regular user cannot access admin endpoint", function() {
pm.expect(pm.response.code).to.be.oneOf([401, 403]);
});
// Test: BFLA - Admin function execution
// Request: DELETE {{base_url}}/users/{{other_user_id}}
// Auth: Bearer {{auth_token}} (regular user)
pm.test("BFLA: Regular user cannot delete other users", function() {
pm.expect(pm.response.code).to.be.oneOf([401, 403]);
});
ステップ 5:マスアサインメントとレート制限のテスト
// Test: Mass assignment via profile update
// Request: PUT {{base_url}}/users/me
// Body: {"name": "Test", "role": "admin", "is_admin": true}
pm.test("Mass Assignment: Role field not accepted", function() {
if (pm.response.code === 200) {
const user = pm.response.json();
pm.expect(user.role).to.not.equal("admin");
pm.expect(user.is_admin).to.not.equal(true);
}
});
// Test: Rate limiting enforcement
// This test should be run with the Collection Runner at high iteration count
pm.test("Rate Limiting: Returns 429 when limit exceeded", function() {
// This test expects to be rate-limited after many iterations
const iterationCount = pm.info.iteration;
if (iterationCount > 50) {
// After 50 iterations, we should see rate limiting
if (pm.response.code === 429) {
pm.expect(pm.response.headers.has("Retry-After")).to.be.true;
console.log("Rate limiting enforced at iteration " + iterationCount);
}
}
});
// Test: Rate limit headers present
pm.test("Rate Limiting: Rate limit headers present", function() {
const hasRateHeaders = pm.response.headers.has("X-RateLimit-Limit") ||
pm.response.headers.has("X-Rate-Limit-Limit") ||
pm.response.headers.has("RateLimit-Limit");
pm.expect(hasRateHeaders).to.be.true;
});
ステップ 6:Newman の CI/CD 連携
# Run security test collection via Newman CLI
newman run "API-Security-Tests.postman_collection.json" \
--environment "Security-Test-Environment.postman_environment.json" \
--reporters cli,htmlextra,junit \
--reporter-htmlextra-export ./reports/security-test-report.html \
--reporter-junit-export ./reports/security-test-results.xml \
--iteration-count 1 \
--timeout-request 10000 \
--delay-request 100 \
--bail
# Run with different user roles
for role in "regular_user" "admin_user" "unauthenticated"; do
echo "Testing with role: $role"
newman run "API-Security-Tests.postman_collection.json" \
--environment "Security-Test-${role}.postman_environment.json" \
--reporters cli,junit \
--reporter-junit-export "./reports/security-${role}.xml"
done
GitHub Actions との連携:
# .github/workflows/api-security-test.yml
name: API Security Tests
on:
pull_request:
paths: ['src/api/**', 'openapi.yaml']
jobs:
security-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm install -g newman newman-reporter-htmlextra
- name: Run API Security Tests
run: |
newman run tests/postman/api-security.json \
--environment tests/postman/env-staging.json \
--reporters cli,htmlextra,junit \
--reporter-htmlextra-export reports/security.html \
--reporter-junit-export reports/security.xml
- uses: actions/upload-artifact@v4
if: always()
with:
name: security-reports
path: reports/
主要な概念
| 用語 | 定義 |
|---|---|
| Postman コレクション | 共有、バージョン管理、自動実行が可能な、テストスクリプト付き API リクエストの整理されたグループです |
| Newman | CI/CD パイプラインでのコレクション実行とテストレポート生成を可能にする、Postman 用コマンドラインツールです |
| プレリクエストスクリプト | Postman リクエストの実行前に実行される JavaScript コードです。動的認証とテストデータのセットアップに使用します |
| テストスクリプト | Postman のレスポンス受信後に実行される JavaScript コードです。レスポンスに対するセキュリティアサーションの検証に使用します |
| コレクションランナー | 設定可能な反復回数と遅延を使って、コレクション内のすべてのリクエストを順番に実行する Postman の機能です |
| 環境変数 | Postman 環境にスコープされたキーと値のペアです。対象、ロール、設定に応じてリクエストをパラメーター化します |
ツールとシステム
- Postman:組み込みのスクリプト機能とコレクション管理機能を備えた、API の構築、テスト、ドキュメント作成のための API プラットフォームです
- Newman:複数のレポーター(HTML、JUnit、JSON)に対応し、CI/CD 連携が可能な Postman コレクション用 CLI ランナーです
- OWASP ZAP:Postman のプロキシとして設定し、すべてのリクエストをパッシブスキャンできるオープンソースのセキュリティプロキシです
- newman-reporter-htmlextra:リクエストとレスポンスのデータを含む詳細なテストレポートを生成する、Newman 用の拡張 HTML レポーターです
- Postman Flows:条件分岐ロジックを使って複雑なセキュリティテスト手順を連結する、ビジュアルなワークフロービルダーです
よくあるシナリオ
シナリオ:CI/CD 向け API セキュリティ回帰テストスイート
背景:開発チームは隔週で API の更新をリリースしています。マージ前に認可と認証のリグレッションを検出するため、すべてのプルリクエストで実行される自動セキュリティテストスイートが必要です。
アプローチ:
- OpenAPI 仕様を Postman にインポートし、すべてのエンドポイントを含む基本コレクションを生成する
- 適切な認証情報を設定した、未認証、一般ユーザー、管理者の 3 つの環境を作成する
- 各リクエストにセキュリティテストスクリプトを追加する:BOLA チェック、認証の検証、データ漏えいスキャン、ヘッダーセキュリティ
- インジェクションペイロード、一括割り当てテスト、レート制限チェックを含む専用の「Security Tests」フォルダーを作成する
- コレクションと環境をリポジトリにエクスポートする
- GitHub Actions で Newman を設定し、API コードに影響するすべての PR で実行する
- セキュリティテストが 1 つでも失敗した場合にパイプラインを失敗させ、マージをブロックする
落とし穴:
- 動的なトークン生成にプレリクエストスクリプトを使わず、コレクションに認証トークンをハードコードする
- すべてのユーザーロールでテストしないこと。認証済みと未認証の比較だけでは、ロールベースの認可の問題を見逃します
- ステージング環境ではなく、本番環境に対してセキュリティテストを実行する
- 新しいエンドポイントが追加されたときにコレクションを更新せず、カバレッジに抜けが生じる
- CI/CD で Newman の終了コードを無視し、失敗したセキュリティテストを気付かれないまま通過させる
出力形式
## API Security Test Report - Postman/Newman
**Collection**: API Security Tests v2.3
**Environment**: Staging - Regular User
**Date**: 2024-12-15
**Total Requests**: 85
**Total Tests**: 234
**Passed**: 219
**Failed**: 15
### Failed Tests Summary
| # | Request | Test Name | Severity |
|---|---------|-----------|----------|
| 1 | GET /users/1002 | BOLA: Cannot access other user profile | Critical |
| 2 | GET /orders/5003 | BOLA: Cannot access other user order | Critical |
| 3 | GET /admin/users | BFLA: Regular user cannot access admin endpoint | Critical |
| 4 | PUT /users/me | Mass Assignment: Role field not accepted | High |
| 5 | GET /users/me | Data Exposure: No sensitive fields in response | High |
| 6 | POST /auth/login | Auth: No account enumeration | Medium |
| ... | ... | ... | ... |
### Recommendations
1. Fix BOLA on /users/{id} and /orders/{id} - add object-level authorization checks
2. Fix BFLA on /admin/users - enforce role-based access control middleware
3. Fix mass assignment on PUT /users/me - implement field allowlist
4. Remove password_hash and mfa_secret from user serialization
5. Standardize login error messages to prevent account enumeration
コメント
Markdown 対応