API Design

API Security · Lesson 11

Security: OWASP API Top 10 Deep Dive

While standard web application security focuses on injection and cross-site scripting, API security has different weak spots. The OWASP API Security Top 10 outlines risks unique to resource-oriented interfaces. We will deep-dive into three of the most common and destructive modern API vulnerabilities: Server-Side Request Forgery (SSRF), Mass Assignment, and Unrestricted Access to Sensitive Business Flows.

⏱ ~16 min Advanced Prereq: sec-01, sec-03, sec-05

By the end you'll be able to

1. Server-Side Request Forgery (SSRF)

SSRF (OWASP API7:2023) occurs when an API endpoint accepts a user-supplied URL and makes a request to it from the backend server. Since the server operates inside the company's internal private network, an attacker can supply local IP addresses (e.g. http://127.0.0.1 or http://169.254.169.254) to access internal metadata services, databases, or administration panels that are closed to the public internet.

⚠️ The Cloud Metadata Trap

In AWS, Google Cloud, and Azure, a link local address 169.254.169.254 hosts the Instance Metadata Service (IMDS). A request to http://169.254.169.254/latest/meta-data/iam/security-credentials/ returns the temporary IAM credentials of the virtual machine. If your API fetches a user-supplied profile image URL without strict filtering, an attacker can extract these credentials and compromise your cloud account.

SSRF Mitigation Checklist

  1. IP Address Validation. Resolve the hostname to an IP address before establishing the socket. Reject the request if the resolved IP points to private ranges: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.169.254.
  2. DNS Rebinding Protection. Attackers can bypass initial checks by returning a public IP during resolution, then immediately swapping it to a private IP when the client makes the connection. Fix: pin the resolved IP address and connect directly to it, disabling automatic host header re-lookup.
  3. Isolate Fetchers. Run the URL-fetching service in an isolated network sandbox or VPC lane with zero access to internal databases.

2. Mass Assignment

Mass Assignment (OWASP API3:2023 - Broken Object Property Level Authorization) occurs when an API framework automatically binds user-supplied JSON properties directly to internal database objects or ORM models. If the database object includes columns like is_admin or role, an attacker can insert these keys into their API request payloads to escalate their privileges.

// ❌ VULNERABLE: Direct model binding
func UpdateProfile(w http.ResponseWriter, r *http.Request) {
    var user User
    json.NewDecoder(r.Body).Decode(&user) // Decodes whatever keys the client sent!
    db.Save(&user)
}

// Payload sent by attacker to elevate status:
{
  "display_name": "Attacker",
  "is_admin": true
}

Mass Assignment Defenses

3. Unrestricted Access to Sensitive Business Flows

Sensitive Business Flow Abuse (OWASP API6:2023) occurs when an API exposes actions that have high business impact or cost (e.g., ticket booking, promo code redemption, SMS OTP verification, account creation) and fails to limit how quickly or frequently they can be executed by automated bots.

Unlike standard rate limiting which blocks sudden volumetric DDOS attacks, abusers of sensitive flows often crawl slowly (low and slow) using rotated proxies to bypass IP-based limiters, draining company funds (e.g. paying for SMS gateway fees) or scraping inventory.

Under the hood: SSRF connection hijacking

This diagram shows how an attacker utilizes a vulnerable webhook-validator API to fetch database credentials from an internal AWS metadata server.

Attacker curl / scripts Vulnerable API No IP checks Implicit trust Cloud Metadata 169.254.169.254 Internal IMDS Internal DB Private IP 1. POST request url=169.254.169.254 2. Fetch URL 3. Return AWS Keys 4. Print Keys in response body

By the numbers: SMS gateway abuse budget math

Let's calculate the financial impact of unrestricted business flow exploitation and compute velocity limit thresholds.

Governing Equations

Scenario Parameters

Worked Calculations: Financial Loss under IP Limiting

  1. Compute the bypassed request rate: Since the attacker can make 5 calls per IP before triggering a block, they spread requests across 20,000 IPs: $$Rate_{bypass} = 20,000 \text{ IPs} \times 5 \text{ reqs/min} = 100,000 \text{ requests/minute}$$ $$Event_{rate} = 1,666.6 \text{ requests/second}$$
  2. Compute the cost rate per minute: $$Cost_{minute} = 100,000 \text{ requests/min} \times \$0.04 = \$4,000 / \text{minute}$$
  3. Compute total 8-hour exposure cost: $$Total_{cost} = \$4,000/\text{min} \times 480 \text{ minutes} = \mathbf{\$1,920,000}$$ This is a massive financial blow for a single night!
🛡️ Designing multi-dimensional velocity limiting

To defend the sensitive flow, we must design a rate-limiting schema that tracks multiple keys simultaneously:

How to debug & inspect it

To inspect and identify OWASP API vulnerabilities, capture and analyze request payloads and DNS resolutions using common network utilities.

# 1. Test for Mass Assignment by sending custom json payloads in curl $ curl -X PATCH https://api.example.com/v1/users/self \ -H "Content-Type: application/json" \ -H "Authorization: Bearer token" \ -d '{"display_name": "New Name", "is_admin": true, "role": "superuser"}' < HTTP/1.1 200 OK < Content-Type: application/json {"id": 42, "display_name": "New Name", "is_admin": true, "role": "superuser"} # Verdict: API is VULNERABLE to Mass Assignment (accepted is_admin modification). # 2. Test SSRF URL resolution limits (using mock server trace log) $ curl -X POST https://api.example.com/v1/webhooks/test \ -d '{"url": "http://localhost:8080/admin"}' < HTTP/1.1 400 Bad Request "Error: URL resolves to private IP range (127.0.0.1) and is blocked."

Use the guide below to trace and repair vulnerabilities in your API schemas and networks:

Vulnerability Symptom Fix
Server-Side Request Forgery (SSRF) Server establishes outgoing TCP requests to internal IP addresses or cloud metadata points Implement DNS resolution checks prior to socket connection; drop requests to local/RFC1918 IPs.
Mass Assignment / Broken Property Authorization Users can modify internal status, database keys, or billing variables during POST/PATCH Construct strict input schemas (DTOs). Disallow model properties from binding directly to incoming json objects.
SMS/OTP Flood Abuse Spikes in SMS billing fees; SMS carrier pools depleted by bot traffic Enforce velocity limits keyed on phone numbers, and implement Captcha/PoW hurdles for public entry points.

🧠 Quick check

1. Why does simple host blocklisting (e.g., matching "localhost") fail to prevent SSRF?

Attacker hostnames can bypass string filters but still resolve to private IP addresses (like `127.0.0.1` or internal subnet targets). Effective mitigation requires resolving hostnames to IP addresses first and verifying they do not fall within private/reserved network blocks.

2. How does the use of Data Transfer Objects (DTOs) prevent Mass Assignment?

DTOs serve as a security buffer. Instead of mapping requests directly to GORM/ActiveRecord models, requests map to intermediate schemas that exclude security-sensitive columns like `is_admin` or `account_tier`.

3. Which mitigation addresses DNS Rebinding attacks during URL fetching?

To prevent DNS Rebinding, resolve the DNS name, validate the resulting IP, and instruct your HTTP client to connect directly to that resolved IP rather than resolving the hostname again during the connection phase.

4. Why do volumetric rate limits fail to protect Sensitive Business Flows from professional botnets?

By utilizing rotated IP proxy pools, attackers can spread the attack (e.g. 100,000 calls per minute) so that each individual IP only makes a single request, slipping underneath standard volumetric threshold limiters.

✍️ Exercise: design the SSRF validator function

Write out the pseudocode for a safe URL-fetching validation function that checks against SSRF and DNS rebinding attacks.


Model answer:

A secure URL fetch function must implement resolution validation and connect directly to the resolved IP address to prevent rebinding:

function safe_fetch_url(user_url_str) {
    // 1. Parse URL structure
    parsed_url = parse_url(user_url_str)
    if parsed_url.scheme not in ["http", "https"]:
        raise ValueError("Invalid scheme")
    
    // 2. Resolve hostname to IP address
    resolved_ips = dns_resolve(parsed_url.hostname)
    if not resolved_ips:
        raise ValueError("Could not resolve host")
    
    target_ip = resolved_ips[0]
    
    // 3. Verify IP is not in private/reserved ranges (RFC 1918)
    if is_private_ip(target_ip) or is_link_local_ip(target_ip) or is_loopback_ip(target_ip):
        raise ValueError("Access to private/local network ranges is forbidden")
        
    // 4. Perform the HTTP request directly to target_ip
    // Set 'Host' header to parsed_url.hostname so the server receives correct routing headers
    http_headers = { "Host": parsed_url.hostname }
    response = execute_http_get(connection_target=target_ip, port=parsed_url.port, headers=http_headers)
    
    return response
}

Key takeaways

Sources & further reading