Skip to content

Services, Wiki-Artikel, Blog-Beiträge und Glossar-Einträge durchsuchen

↑↓NavigierenEnterÖffnenESCSchließen

API-Sicherheit: OWASP API Top 10, Authentifizierung, Testing und Best Practices

Comprehensive Guide to API Security: OWASP API Security Top 10 (2023) Fully Explained with Code Examples, API Authentication (API Keys, JWT, OAuth 2.0, mTLS), testing methodologies for REST and GraphQL, tool usage (Burp Suite, Postman, jwt_tool, Nuclei), API discovery, and a security checklist for developers and penetration testers.

Table of Contents (8 sections)

APIs are the backbone of modern software—and the fastest-growing attack vector. While traditional web applications are often well-secured, APIs frequently perform sensitive operations directly, have fewer UI security mechanisms, and are rarely tested thoroughly. According to OWASP, most critical web application security incidents in 2023/2024 occurred via APIs. This article covers the complete OWASP API Security Top 10, authentication methods, testing methodologies, and the proper use of tools.

OWASP API Security Top 10 (2023)

API1: Broken Object Level Authorization (BOLA/IDOR)

Most common and dangerous API vulnerability:

The problem:
  GET /api/orders/12345 → returns order from User X
  GET /api/orders/12346 → returns order from User Y!
  (Attacker only changes the ID → accesses someone else's data)

Vulnerable (Node.js/Express):
  app.get('/api/v1/users/:userId/orders', auth, (req, res) => {
    const orders = db.query('SELECT * FROM orders WHERE user_id = ?', req.params.userId);
    // ERROR: checks "is the user logged in?", not "does this resource belong to the user?"
    res.json(orders);
  });

Secure (User from auth token, not from URL):
  app.get('/api/v1/orders', auth, (req, res) => {
    // req.user.id comes from a validated JWT
    const orders = db.query('SELECT * FROM orders WHERE user_id = ?', req.user.id);
    res.json(orders);
  });

  // Or if URL ID is required: Ownership check!
  app.get('/api/v1/orders/:orderId', auth, async (req, res) => {
    const order = await db.getOrder(req.params.orderId);
    if (!order || order.userId !== req.user.id) {
      return res.status(404).json({ error: 'Not found' });  // 404, not 403!
    }
    res.json(order);
  });

Secure (Python/FastAPI):
  @app.get("/api/orders/{order_id}")
  async def get_order(order_id: int, current_user: User = Depends(get_current_user)):
      order = db.query(Order).filter(
          Order.id == order_id,
          Order.user_id == current_user.id  # Object-level auth!
      ).first()
      if not order:
          raise HTTPException(status_code=404)
      return order

Security Principles:
  → EVERY API endpoint: Check object ownership
  → Better 404 than 403 (prevents enumeration)
  → UUID instead of auto-increment IDs (not a substitute for auth, but reduces brute-force attacks)
  → Centralized authorization layer (not scattered across every endpoint)

API2: Broken Authentication

Weak or missing authentication:

Common issues:
  → No rate limiting on login endpoint → Brute force
  → Weak JWT secret ("secret123" or empty)
  → JWT without expiration date (exp claim missing)
  → API key in URL instead of header (ends up in server logs!)
  → Refresh token never invalidated
  → JWT: Algorithm-None attack (alg: "none")

Secure JWT (Node.js):
  // Create token
  const token = jwt.sign(
    { userId: user.id, email: user.email },
    process.env.JWT_SECRET,     // From environment, not hardcoded!
    {
      expiresIn: '15m',         // Short lifetime for access token
      issuer: 'api.company.com',
      audience: 'api.company.com',
      algorithm: 'RS256'        // Asymmetric → better than HS256!
    }
  );

  // Validate token
  const decoded = jwt.verify(token, process.env.JWT_PUBLIC_KEY, {
    issuer: 'api.company.com',
    audience: 'api.company.com',
    algorithms: ['RS256'],      // Explicitly specify algorithm!
    // → Prevents algorithm confusion attacks (alg: "none")
  });

Secure JWT (Python):
  payload = {
      "sub": user.id,
      "iat": datetime.utcnow(),
      "exp": datetime.utcnow() + timedelta(minutes=15),  # Short!
      "jti": str(uuid4()),  # Unique ID for revocation
  }
  # Decode: DO NOT use algorithms="auto"!
  jwt.decode(token, secret, algorithms=["HS256"])

API Key Management:
  → API Keys: Store as a hash in the database (like passwords!)
  → In HTTP header (X-API-Key), NOT in URL
  → Separate key per service/consumer with scope restrictions
  → Expiration date + regular rotation
  → Audit log: Which key called what?

  # Correct:
  Authorization: Bearer sk_live_abc123xyz
  X-API-Key: sk_live_abc123xyz

  # Incorrect (ends up in logs, proxies, browser history!):
  GET /api/data?api_key=sk_live_abc123xyz

API3: Broken Object Property Level Authorization

Mass Assignment and Excessive Data Exposure (new in OWASP 2023):

Mass Assignment (Over-Posting):
  # User sends:
  PUT /api/users/me
  {"name": "Alice", "role": "admin"}  ← role should not be updatable!
  {"name": "Alice", "balance": 99999} ← Account balance manipulated?

  // Vulnerable (Node.js):
  app.patch('/api/v1/profile', auth, async (req, res) => {
    await db.update('users', { id: req.user.id }, req.body);
    // req.body: { "name": "Alice", "isAdmin": true }
    // → User makes themselves an admin!
  });

  // Secure: Whitelist allowed fields
  const { name, email, bio } = req.body;
  await db.update('users', { id: req.user.id }, { name, email, bio });

  // Or with Zod schema:
  const UpdateProfileSchema = z.object({
    name: z.string().min(1).max(100).optional(),
    email: z.string().email().optional(),
    bio: z.string().max(500).optional(),
    // isAdmin NOT in schema → is ignored!
  });
  const data = UpdateProfileSchema.parse(req.body);

  # Secure (Python/FastAPI with Pydantic):
  class UserUpdateRequest(BaseModel):
      name: Optional[str]
      email: Optional[str]
      # role is missing → cannot be set!

Excessive Data Exposure:
  # API returns too many fields:
  GET /api/users → returns password_hash, internal_id, payment_token!

  # Secure: Response model (FastAPI filters automatically):
  class UserResponse(BaseModel):
      id: UUID
      name: str
      email: str
      # No password_hash, no payment_token!

  @app.get("/api/users/{user_id}", response_model=UserResponse)
  async def get_user(user_id: UUID):
      ...

API4: Unrestricted Resource Consumption

No Rate Limiting → DoS, Costs, Credential Stuffing:

Rate Limiting (Node.js/Express):
  import rateLimit from 'express-rate-limit';

  const generalLimiter = rateLimit({
    windowMs: 15 * 60 * 1000,  // 15 minutes
    max: 100,                   // 100 requests per window
    standardHeaders: true,
    legacyHeaders: false,
    message: { error: 'Too many requests, please try again later' }
  });

  const authLimiter = rateLimit({
    windowMs: 15 * 60 * 1000,
    max: 5,                     // Only 5 login attempts
    skipSuccessfulRequests: true
  });

  app.use('/api/', generalLimiter);
  app.use('/api/auth/login', authLimiter);

Rate Limiting (Python/FastAPI with slowapi):
  from slowapi import Limiter
  from slowapi.util import get_remote_address

  limiter = Limiter(key_func=get_remote_address)

  @app.post("/api/auth/login")
  @limiter.limit("5/minute;20/hour")  # Brute-force protection
  async def login(request: Request, credentials: LoginRequest):
      ...

Enforce pagination:
  # WRONG: GET /api/users → returns all 1,000,000 users
  # CORRECT: Always use limit + offset / cursor!
  @app.get("/api/users")
  async def get_users(
      page: int = 1,
      limit: int = Query(default=20, le=100)  # Max 100!
  ):
      offset = (page - 1) * limit
      return db.query(User).offset(offset).limit(limit).all()

File upload limits:
  ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf']
  MAX_SIZE = 10 * 1024 * 1024  # 10MB

  if file.content_type not in ALLOWED_TYPES:
      raise HTTPException(400, "Invalid file type")
  if file.size > MAX_SIZE:
      raise HTTPException(413, "File too large")

API5: Broken Function Level Authorization

Vertical privilege escalation in APIs:

Typical pattern:
  GET /api/users → normal user list (anyone can)
  DELETE /api/users/{id} → Delete user (admins only!)

  // Vulnerable: no role check on DELETE
  app.delete('/api/v1/admin/users/:id', auth, async (req, res) => {
    await db.deleteUser(req.params.id);
    // ERROR: no is_admin check!
    res.json({ deleted: true });
  });

  // Secure: Separate admin middleware
  const requireAdmin = (req, res, next) => {
    if (req.user.role !== 'admin') {
      return res.status(403).json({ error: 'Forbidden' });
    }
    next();
  };

  app.delete('/api/v1/admin/users/:id', auth, requireAdmin, async (req, res) => {
    await db.deleteUser(req.params.id);
    res.json({ deleted: true });
  });

Securing admin endpoints:
  → Completely separate prefix: /admin/* instead of /api/*
  → Dedicated auth middleware for /admin/*
  → Separate authentication (not the same JWT!)
  → IP whitelist for /admin/* (office IPs/VPN only)

HTTP method restriction:
  @app.api_route("/api/users/{id}", methods=["GET"])  # GET only!

API6-10: Additional Critical Risks

API6: Unrestricted Access to Sensitive Business Flows
  Vulnerability: Business processes can be exploited through automation
  → Rate limiting for business logic (orders, coupons, gift cards)
  → CAPTCHA for public forms
  → Velocity Checking: 100 coupons in 1 minute?
  → Race condition protection for concurrent requests (gift card fraud)

API7: Server-Side Request Forgery (SSRF)
  → URL parameters → querying internal resources (webhook=, callback=, url=, src=)
  → AWS Metadata Endpoint 169.254.169.254 → IAM credentials!

  SSRF protection (Python):
  BLOCKED_RANGES = [
      ipaddress.ip_network("127.0.0.0/8"),
      ipaddress.ip_network("10.0.0.0/8"),
      ipaddress.ip_network("172.16.0.0/12"),
      ipaddress.ip_network("192.168.0.0/16"),
      ipaddress.ip_network("169.254.0.0/16"),  # Link-Local (AWS Metadata!)
      ipaddress.ip_network("::1/128"),
  ]
  # WARNING: DNS Rebinding: the IP may change after DNS resolution!
  # Solution: route all HTTP requests through a proxy that performs IP checks

API8: Security Misconfiguration
  → CORS too permissive: Access-Control-Allow-Origin: * (for internal APIs!)
  → Error details in production: stack traces, SQL errors
  → Debug mode (Flask DEBUG=True) active in production
  → Default credentials: MongoDB without auth, Elasticsearch public

  CORS correctly:
  # Only allowed origins:
  origins = ["https://app.company.com", "https://admin.company.com"]
  # NOT: origins = ["*"]  ← Cookies/auth headers then exposed!

  # NEVER in Express:
  res.setHeader('Access-Control-Allow-Origin', '*');  // If credentials are used!

  # Error Handling in Production:
  @app.exception_handler(Exception)
  async def global_exception_handler(request, exc):
      logger.error(f"Unhandled exception: {exc}", exc_info=True)
      return JSONResponse(status_code=500, content={"error": "Internal server error"})

API9: Improper Inventory Management
  → Undocumented legacy endpoints (v1 still active even though v3 is current)
  → Sandbox/staging environments with live data
  → Shadow APIs (APIs without an owner)
  → API inventory: Enforce OpenAPI/Swagger for ALL endpoints
  → API gateway as single entry point
  → Versioning policy: /api/v1/ → /api/v2/ (v1 deprecated and disabled!)

API10: Unsafe Consumption of APIs (Third-Party)
  → Blind trust in external API responses
  → External API compromised → own app compromised!

  Protection:
  → Validation: always validate external API responses with Pydantic/Zod
  → Timeouts: never allow unlimited time for external calls (max. 10s)
  → Circuit Breaker: in case of errors → Fallback instead of cascade failure

  import httpx
  async with httpx.AsyncClient(timeout=10.0) as client:
      try:
          response = await client.get("https://api.external.com/data")
          response.raise_for_status()
          data = ExternalApiResponse(**response.json())  # Validation!
      except (httpx.TimeoutException, httpx.HTTPStatusError) as e:
          logger.error(f"External API error: {e}")
          return fallback_response()  # Circuit Breaker!

API Authentication - Comparison

1. API Keys:
   Usage: Server-to-server, developer APIs
   Security: Medium (no expiration, broad permissions)

   Secure when:
   → In header (X-API-Key), NOT in URL
   → Separate key per service/consumer
   → Regular rotation
   → Audit log: Which key called what?
   → Store as a hash in the DB (like passwords)

   Example:
   curl -H "X-API-Key: sk-..." https://api.firma.de/data

2. JWT (JSON Web Token):
   Usage: Web/mobile apps, stateless auth
   Security: High when implemented correctly

   Pitfalls:
   → "none" algorithm attack: always use an algorithm whitelist!
   → JWT secret in an environment variable (never hardcoded)
   → Short validity period (15 min for access, 7 days for refresh)
   → No sensitive content in the payload (Base64, unencrypted!)
   → jti claim for token revocation

3. OAuth 2.0 + OpenID Connect:
   Use: Third-party auth, delegation
   Security: High (industry standard)

   Flows:
   → Authorization Code + PKCE: for Web/Mobile (check PKCE code_challenge!)
   → Client Credentials: Server-to-Server
   → Check state parameter for CSRF
   → NEVER: Implicit Flow (obsolete, insecure)

4. mTLS (Mutual TLS):
   Usage: Microservices, B2B APIs
   Security: Very high
   → Both sides authenticate with certificates
   → No bearer tokens that can be stolen
   → Complex setup, but maximum security

GraphQL Security

GraphQL presents its own security challenges:

1. Disable introspection (production):
   // Apollo Server
   const server = new ApolloServer({
     typeDefs, resolvers,
     introspection: process.env.NODE_ENV !== 'production',
   });
   // → Attacker cannot see the schema dump

2. Query Depth Limit (DoS protection):
   import depthLimit from 'graphql-depth-limit';
   const server = new ApolloServer({
     validationRules: [depthLimit(5)],  // Max 5 levels
   });

   // Nested query bomb without limit:
   { user { friends { friends { friends { name } } } } }
   // → Exponentially more expensive, DoS possible!

3. Query Complexity Limit:
   import { createComplexityLimitRule } from 'graphql-validation-complexity';
   const ComplexityRule = createComplexityLimitRule(1000);
   // → Protection against "all users + all orders + all items" in a single query

4. Batching Limit:
   // GraphQL allows multiple queries in a single request:
   POST /graphql
   [
     {"query": "mutation { login(user: \"alice\", pw: \"Password1\") }"},
     {"query": "mutation { login(user: \"alice\", pw: \"Password2\") }"}
   ]
   // → 100 login attempts in a single request → Rate limit bypass!
   const server = new ApolloServer({
     allowBatchedHttpRequests: false,
     // Or: Limit to 10 queries per batch
   });

5. Authorization in resolvers (NOT in the schema!):
   const resolvers = {
     Query: {
       adminData: (_, __, context) => {
         if (!context.user?.isAdmin) {
           throw new ForbiddenError('Unauthorized');
         }
         return db.getAdminData();
       }
     }
   };

6. Test field-level authorization:
   query {
     user(id: "alice") {
       username        # allowed
       email           # allowed
       internalNotes   # allowed? (shouldn't be!)
       passwordHash    # should never be returned!
     }
   }

API Discovery and Reconnaissance

Finding API endpoints (testing phase):

Passive sources:
  → Swagger/OpenAPI documentation: /swagger.json, /openapi.yaml, /api-docs
  → API Blueprint: /api/v1, /api/v2 (common paths)
  → JavaScript bundle analysis:
    Chrome DevTools → Network → Filter: XHR/Fetch → view all API calls
  → Product’s GitHub repository (if OSS): Read API routes directly
  → Mobile app analysis: Decompile APK/IPA → API endpoints in the code
  → Postman collections: Many companies publish on postman.com
    (API keys are often included in requests!)
  → Wayback Machine: https://web.archive.org/web/*/api.target.com/*
    (old API versions, forgotten endpoints)

Directory Brute-Force:
  # feroxbuster with API wordlist:
  feroxbuster -u https://api.target.com \
    -w /usr/share/wordlists/SecLists/Discovery/Web-Content/api/api-endpoints.txt \
    -x json \
    -H "Accept: application/json"

  # kiterunner (API-specific):
  kr scan https://api.target.com -w routes-large.kite

  # ffuf:
  ffuf -w api-wordlist.txt -u "https://api.target.com/FUZZ" \
    -H "Content-Type: application/json" -fc 404

Swagger UI vulnerabilities:
  → /swagger-ui: sometimes available in production (forgotten debug route!)
  → Exposes complete API documentation without an authentication requirement
  → GraphQL introspection: exposes complete schema

OWASP API Top 10 Testing Methodology

API1 - BOLA/IDOR Testing:
  Systematic Testing:
  → Create two accounts (User A + User B)
  → Access User A resources using User B's token
  → Iterate IDs: 1, 2, 3, ... 100 (Burp Intruder!)
  → UUID? Test anyway: are mistakenly considered "secure"

  Burp Intruder BOLA Test:
  GET /api/v1/documents/§123§
  Authorization: Bearer USER_B_TOKEN
  Payloads: 1, 2, 3, ..., 1000
  Grep: Response-Length != Error-Response-Length → Found!

---

API2 - Authentication Testing:
  □ JWT vulnerabilities:
    → alg=none: Remove signature → Is the token accepted?
    → RS256→HS256: Key confusion attack
    → Expired token: Is an expired token still valid?
    → Is the token accepted by another service/user?

  jwt_tool:
  jwt_tool eyJhbGciOiJIUzI1NiJ9... -T  # Tamper Mode
  jwt_tool TOKEN -C -d rockyou.txt      # Crack Mode (HMAC Secret)
  # online: jwt.io → View payload + verify signature

  □ API Key Vulnerabilities:
    → Key in the URL? (stored in server logs!)
    → Rate limiting for API key? Brute force possible?

  □ OAuth2 PKCE:
    → PKCE code_challenge implemented correctly?
    → Check state parameter for CSRF

---

API3 - Mass Assignment Testing:
  Test:
  GET /api/user/me
  → Does the response contain internal fields? (isAdmin, internalId, creditScore)
  → Does the response reveal a password hash or API key?

  PUT /api/user/me
  {"username": "alice", "role": "admin"}  → Role changed?

---

API4 - Rate Limiting Testing:
  □ Login endpoint: 1000 requests without lockout?
    # Burp Intruder Pitchfork: Password wordlist
  □ OTP/reset token: Can it be guessed via brute force?
  □ File upload: No size limit? (DoS: 10GB upload!)
  □ Costly endpoints: /api/report → Flooding?

  Attempt rate limit bypass:
  → X-Forwarded-For header: Rotate IPs
  → Rotate User-Agent
  → Different API versions: /api/v1 vs. /api/v2 separate rate limits?

---

API5 - Function Level Authorization:
  □ Admin endpoints without admin role?
    GET /api/admin/users → with normal user token?
    DELETE /api/admin/users/123 → with normal user token?

  □ HTTP method change:
    GET /api/users/123 → allowed
    DELETE /api/users/123 → also allowed? (with a normal user!)

  □ HTTP/2 vs HTTP/1.1: sometimes different authentication checks!

---

API7 - SSRF Testing:
  → Test all URL parameters for SSRF: webhook=, callback=, url=, redirect=, src=

  POST /api/screenshot {"url": "http://192.168.1.1/admin"}
  POST /api/webhook   {"url": "http://169.254.169.254/latest/meta-data/"}
  # AWS Metadata Endpoint → IAM credentials!

---

API8 - Security Misconfiguration:
  □ CORS Test:
  curl -H "Origin: https://evil.com" \
    -I https://api.target.com/api/data
  # If: Access-Control-Allow-Origin: https://evil.com
  # → CORS Misconfiguration!

  □ HTTP Methods: OPTIONS, TRACE, PUT unintentionally enabled?
  □ Debug Endpoints: /api/debug, /api/healthcheck containing internal information?
  □ Error Messages: Stack traces in 500 errors?

---

API9 - Inventory Management:
  □ Old API versions active? /api/v1 alongside /api/v3?
    → v1 often has lower security standards
  □ Debug versions: /api/beta, /api/internal, /api/dev?
  □ Undocumented endpoints (only in legacy code)

---

API10 - Third-Party API Consumption:
  → Webhook recipient: Does it validate that the payload comes from the expected sender?
  → Third-party API response: Is it processed without validation?

GraphQL Security Testing

GraphQL-specific tests:

Introspection (information leak):
  # Query all types and fields:
  POST /graphql
  {
    "__schema": {
      "types": {
        "name": true,
        "fields": {
          "name": true,
          "type": {"name": true}
        }
      }
    }
  }
  # Returns the complete API schema – should be disabled in production!

Batching attacks:
  POST /graphql
  [
    {"query": "mutation { login(user: \"alice\", pw: \"Password1\") }"},
    {"query": "mutation { login(user: \"alice\", pw: \"Password2\") }"},
    {"query": "mutation { login(user: \"alice\", pw: \"Password3\") }"}
  ]
  # 100 login attempts in a single request → Rate limit bypass!

Nested Queries (DoS via Depth):
  {
    user {
      friends {
        friends {
          friends {
            friends { name }  # 10 levels deep → DoS!
          }
        }
      }
    }
  }

Tools for GraphQL Testing:
  InQL (Burp Extension): GraphQL-specific tests, schema extraction
  graphql-cop: automatic GraphQL security scan
  graphql-voyager: schema visualization
  Altair GraphQL Client: manual testing

Tooling Stack for API Penetration Testing

Burp Suite Pro:
  → HTTP proxy for all API requests
  → Intruder: BOLA tests, brute force
  → Scanner: automated vulnerability detection
  → Extensions: InQL (GraphQL), JWT Editor, AuthMatrix

Postman:
  → Import API documentation (OpenAPI)
  → Pre-Request Scripts: Automate token rotation
  → Test Scripts: Response validation after each request
  → Collection Runner: Test all API endpoints in sequence

  # Pre-Request Script (Token refresh):
  pm.sendRequest({
      url: pm.environment.get("AUTH_URL") + "/token",
      method: "POST",
      body: { mode: "raw", raw: JSON.stringify({
          client_id: pm.environment.get("CLIENT_ID"),
          client_secret: pm.environment.get("CLIENT_SECRET"),
          grant_type: "client_credentials"
      })}
  }, (err, res) => {
      pm.environment.set("ACCESS_TOKEN", res.json().access_token);
  });

  # Security test in Postman (IDOR check):
  pm.test("IDOR: cannot access other user's resource", function () {
      pm.response.to.have.status(404);  # Or 403, never 200!
  });

jwt_tool:
  jwt_tool TOKEN -T  # Tamper mode
  jwt_tool TOKEN -C -d rockyou.txt  # Crack mode

kiterunner:
  kr scan https://api.target.com -w /path/to/routes.kite

Nuclei API Templates:
  nuclei -u https://api.target.com \
    -tags api,jwt,swagger,graphql \
    -t nuclei-templates/

OWASP ZAP API Scan:
  zap-api-scan.py \
    -t https://api.example.com/openapi.json \
    -f openapi \
    -r api-report.html

Continuous API Security (CI/CD):
  # 42Crunch API Security Audit (GitHub Action):
  - uses: 42Crunch/api-security-audit-action@v3
    with:
      api-token: ${{ secrets.API_SECURITY_TOKEN }}
      # Evaluates OpenAPI spec for security: 0-100 score
      # Fails if score is < 75

API Security Testing Checklist

Authentication:
  □ Is every endpoint authenticated? (no accidentally public endpoints)
  □ Brute force rate limiting on auth endpoints?
  □ JWT: short lifetime, secure algorithm (RS256), no "alg: none"?
  □ Tokens invalidated after logout?
  □ API keys in HTTP headers, not in the URL?

Authorization:
  □ BOLA test: test different user IDs in the URL/body
  □ Admin endpoints: accessible without admin privileges?
  □ Mass assignment: are unexpected fields ignored?
  □ Scope check: OAuth scopes checked correctly?
  □ HTTP method restriction: GET vs. DELETE vs. PUT?

Input Validation:
  □ SQL injection on all parameters?
  □ NoSQL injection (MongoDB: $where, $gt operator)?
  □ Schema validation for all request bodies?
  □ File upload: type check, size limit, malware scan?

Rate Limiting:
  □ Rate limiting on ALL endpoints?
  □ Per-user instead of just per-IP?
  □ Business logic rate limiting (coupons, orders)?
  □ Rate limit bypass vectors tested (X-Forwarded-For, User-Agent)?

Security Misconfiguration:
  □ CORS configured correctly (no * when credentials are used)?
  □ HTTP security headers (Helmet.js)?
  □ Error details hidden in response (no stack trace)?
  □ Old API versions disabled?
  □ Swagger/OpenAPI docs: disabled in production or protected by authentication?
  □ Debug endpoints disabled?

OWASP API Top 10 comprehensive check:
  □ API1: BOLA tested (two accounts, ID iteration)
  □ API2: Auth bypass attempted (JWT alg:none, key confusion)
  □ API3: Mass assignment tested, response fields checked
  □ API4: Rate limiting in place and cannot be bypassed
  □ API5: Privileged endpoints checked
  □ API6: Business logic flows tested for automation
  □ API7: SSRF tested (all URL parameters)
  □ API8: CORS, security headers, and debug disabled
  □ API9: API inventory complete, old versions disabled
  □ API10: Third-party responses validated, timeouts set

Sources & References

  1. [1] OWASP API Security Top 10 2023 - OWASP Foundation
  2. [2] OWASP API Security Project - OWASP Foundation
  3. [3] OWASP Testing Guide v4.2 - OWASP Foundation
  4. [4] JWT Security Best Practices - Auth0
  5. [5] OAuth 2.0 Security Best Current Practice - IETF
  6. [6] GraphQL Security Overview - OWASP Foundation

Questions about this topic?

Our experts advise you free of charge and without obligation.

Free Consultation

About the Author

Chris Wojzechowski
Chris Wojzechowski

Geschäftsführender Gesellschafter

E-Mail

Geschäftsführender Gesellschafter der AWARE7 GmbH mit langjähriger Expertise in Informationssicherheit, Penetrationstesting und IT-Risikomanagement. Absolvent des Masterstudiengangs Internet-Sicherheit an der Westfälischen Hochschule (if(is), Prof. Norbert Pohlmann). Bestseller-Autor im Wiley-VCH Verlag und Lehrbeauftragter der ASW-Akademie. Einschätzungen zu Cybersecurity und digitaler Souveränität erschienen u.a. in Welt am Sonntag, WDR, Deutschlandfunk und Handelsblatt.

10 Publikationen
  • Einsatz von elektronischer Verschlüsselung - Hemmnisse für die Wirtschaft (2018)
  • Kompass IT-Verschlüsselung - Orientierungshilfen für KMU (2018)
  • IT Security Day 2025 - Live Hacking: KI in der Cybersicherheit (2025)
  • Live Hacking - Credential Stuffing: Finanzrisiken jenseits Ransomware (2025)
  • Keynote: Live Hacking Show - Ein Blick in die Welt der Cyberkriminalität (2025)
  • Analyse von Angriffsflächen bei Shared-Hosting-Anbietern (2024)
  • Gänsehaut garantiert: Die schaurigsten Funde aus dem Leben eines Pentesters (2022)
  • IT Security Zertifizierungen — CISSP, T.I.S.P. & Co (Live-Webinar) (2023)
  • Sicherheitsforum Online-Banking — Live Hacking (2021)
  • Nipster im Netz und das Ende der Kreidezeit (2017)
IT-Grundschutz-Praktiker (TÜV) IT Risk Manager (DGI) § 8a BSIG Prüfverfahrenskompetenz Ausbilderprüfung (IHK)
This article was last edited on 08.03.2026. Responsible: Chris Wojzechowski, Geschäftsführender Gesellschafter at AWARE7 GmbH. License: CC BY 4.0 - free use with attribution: "AWARE7 GmbH, https://a7.de"

Cookielose Analyse via Matomo (selbst gehostet, kein Tracking-Cookie). Datenschutzerklärung