mcpField
P
performing-api-security-testing-with-postman

performing-api-security-testing-with-postman

github.com/mukul975安全35.2k226 次安装8 周前更新
代码仓库

关于 performing-api-security-testing-with-postman

面向 API 开发与安全团队的 Postman API 安全测试流程指南,涵盖认证、授权、数据暴露和限速测试,并说明如何通过 Newman 在 CI/CD 中运行测试。

主要功能

  1. 01按角色配置测试环境
  2. 02覆盖 BOLA 与认证测试
  3. 03检查数据暴露和越权
  4. 04测试批量赋值与限速
  5. 05使用 Newman 运行 CI/CD 测试

适用场景

  1. 01为 API 建立安全回归测试集
  2. 02在 CI/CD 流水线执行安全测试
  3. 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 外,安装时一起下载的参考资料和脚本。

LICENSE
references/
api-reference.md
scripts/
agent.py

详细介绍

使用 Postman 执行 API 安全测试

适用场景

  • 构建可重复的 API 安全测试套件,以覆盖 OWASP API 安全 Top 10
  • 创建通过 Newman 在 CI/CD 流水线中运行的自动化安全回归测试
  • 系统性地跨多个用户角色测试 API 认证与授权
  • 将 Postman 与 OWASP ZAP 代理集成,实现手动与自动化安全测试相结合
  • 在部署前为新的 API 端点建立基线安全测试集合

请勿在未经授权的情况下针对生产 API 使用。Postman 安全测试会发送潜在的恶意载荷。

前置条件

  • 具有活动工作区的 Postman 桌面应用或 Web 应用
  • 目标 API 具备 OpenAPI/Swagger 规范,可用于导入集合
  • 至少三种角色的测试账户:未认证、普通用户、管理员
  • 已安装 Newman CLI,用于 CI/CD 集成:npm install -g newman
  • 已配置 OWASP ZAP 作为本地代理(localhost:8080),用于 Postman 代理集成
  • 用于 base 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 请求,附带测试脚本,可共享、进行版本控制并自动执行
NewmanPostman 的命令行配套工具,支持在 CI/CD 流水线中运行集合并生成测试报告
前置请求脚本在 Postman 请求之前执行的 JavaScript 代码,用于动态认证和测试数据设置
测试脚本在 Postman 响应之后执行的 JavaScript 代码,用于针对响应验证安全断言
集合运行器Postman 功能,可按顺序执行集合中的所有请求,并可配置迭代次数和延迟
环境变量作用域为 Postman 环境的键值对,用于为不同目标、角色和配置参数化请求

工具与系统

  • Postman:用于构建、测试和记录 API 的 API 平台,内置脚本和集合管理功能
  • Newman:Postman 集合的 CLI 运行器,支持多种报告器(HTML、JUnit、JSON),用于 CI/CD 集成
  • OWASP ZAP:开源安全代理,可配置为 Postman 的代理,以被动扫描所有请求
  • newman-reporter-htmlextra:Newman 的增强型 HTML 报告器,可生成包含请求/响应详细数据的测试报告
  • Postman Flows:可视化工作流构建器,用于通过条件逻辑串联复杂的安全测试序列

常见场景

场景:面向 CI/CD 的 API 安全回归测试套件

背景:一个开发团队每两周发布一次 API 更新。他们需要一个自动化安全测试套件,在每次拉取请求时运行,以在合并前捕获授权和身份验证回归问题。

方法:

  1. 将 OpenAPI 规范导入 Postman,生成包含所有端点的基础集合
  2. 创建三个环境:未认证、普通用户、管理员,并配置相应的凭据
  3. 为每个请求添加安全测试脚本:BOLA 检查、身份验证验证、数据暴露扫描、标头安全
  4. 创建一个专门的“安全测试”文件夹,包含注入载荷、批量赋值测试和速率限制检查
  5. 将集合和环境导出到仓库
  6. 在 GitHub Actions 中配置 Newman,使其在每次影响 API 代码的 PR 上运行
  7. 将流水线设置为在任何安全测试失败时失败,从而阻止合并

陷阱:

  • 在集合中硬编码身份验证令牌,而不是使用预请求脚本动态生成令牌
  • 未使用所有用户角色进行测试——仅测试已认证与未认证会遗漏基于角色的授权问题
  • 针对生产环境而不是预发环境运行安全测试
  • 添加新端点时未更新集合,导致覆盖范围出现缺口
  • 在 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