The API Security Challenge
APIs power modern digital experiences—from mobile apps to microservices to third-party integrations. But this ubiquity makes them prime targets for attackers. The OWASP API Security Top 10 provides a framework for understanding and mitigating API-specific risks.
OWASP API Security Top 10 (2023)
API1: Broken Object Level Authorization (BOLA)
# Legitimate request
GET /api/v1/users/123/orders
# Attack - change user ID
GET /api/v1/users/456/orders
# Returns another user's orders!@app.route('/api/users/<user_id>/orders')
def get_orders(user_id):
# Check if requesting user owns this resource
if current_user.id != user_id and not current_user.is_admin:
return {"error": "Forbidden"}, 403
orders = Order.query.filter_by(user_id=user_id).all()
return jsonify(orders)API2: Broken Authentication
# Credential stuffing
POST /api/auth/login
{"email": "[email protected]", "password": "password123"}
# Try millions of combinations without rate limiting
# Token theft
# JWT stored in localStorage, stolen via XSS
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...from flask_limiter import Limiter
limiter = Limiter(app, default_limits=["100/hour"])
@app.route('/api/auth/login', methods=['POST'])
@limiter.limit("5/minute") # 5 attempts per minute
def login():
# Login logicAPI3: Broken Object Property Level Authorization
// GET /api/users/123 returns too much data
{
"id": 123,
"name": "John",
"email": "[email protected]",
"password_hash": "...", // Sensitive!
"role": "user",
"salary": 75000, // Sensitive!
"ssn": "123-45-6789" // Very sensitive!
}
// PUT /api/users/123 allows role change
{
"name": "John",
"role": "admin" // Mass assignment attack!
}class UserSchema(Schema):
class Meta:
fields = ('id', 'name', 'email') # Only expose needed fields
# Exclude: password_hash, role, salary, ssnALLOWED_UPDATE_FIELDS = {'name', 'email', 'phone'}
@app.route('/api/users/<id>', methods=['PUT'])
def update_user(id):
data = request.json
# Only allow specified fields
filtered_data = {k: v for k, v in data.items()
if k in ALLOWED_UPDATE_FIELDS}
user.update(filtered_data)API4: Unrestricted Resource Consumption
@app.route('/api/products')
def get_products():
page = request.args.get('page', 1, type=int)
per_page = min(request.args.get('per_page', 20, type=int), 100)
products = Product.query.paginate(
page=page, per_page=per_page, max_per_page=100
)
return jsonify({
'items': [p.to_dict() for p in products.items],
'total': products.total,
'page': page,
'pages': products.pages
})# Different limits for different endpoints
@limiter.limit("100/minute", key_func=get_user_id)
def standard_endpoint(): pass
@limiter.limit("10/minute", key_func=get_user_id)
def expensive_endpoint(): passAPI5: Broken Function Level Authorization
# Normal user shouldn't access admin endpoints
GET /api/admin/users
DELETE /api/admin/users/456
POST /api/admin/settingsfrom functools import wraps
def admin_required(f):
@wraps(f)
def decorated(*args, **kwargs):
if not current_user.is_admin:
return {"error": "Admin access required"}, 403
return f(*args, **kwargs)
return decorated
@app.route('/api/admin/users')
@admin_required
def list_all_users():
# Admin-only logicAPI6: Unrestricted Access to Sensitive Business Flows
@app.route('/api/purchase', methods=['POST'])
def purchase():
fingerprint = request.headers.get('X-Device-Fingerprint')
if not validate_fingerprint(fingerprint):
return {"error": "Invalid request"}, 400
# Process purchaseAPI7: Server Side Request Forgery (SSRF)
# Legitimate use - fetch preview of external URL
POST /api/preview
{"url": "https://example.com/image.jpg"}
# SSRF attack - access internal services
POST /api/preview
{"url": "http://169.254.169.254/latest/meta-data/"}
# Returns AWS metadata!
POST /api/preview
{"url": "http://localhost:6379/"}
# Access internal Redis!ALLOWED_DOMAINS = ['example.com', 'trusted-cdn.com']
def validate_url(url):
parsed = urlparse(url)
if parsed.hostname not in ALLOWED_DOMAINS:
raise ValueError("Domain not allowed")
if parsed.scheme not in ['http', 'https']:
raise ValueError("Invalid scheme")API8: Security Misconfiguration
@app.errorhandler(Exception)
def handle_error(error):
# Log detailed error internally
app.logger.error(f"Error: {error}", exc_info=True)
# Return generic message to client
return {"error": "An error occurred"}, 500@app.after_request
def add_security_headers(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['Content-Security-Policy'] = "default-src 'self'"
response.headers['Strict-Transport-Security'] = 'max-age=31536000'
return responseAPI9: Improper Inventory Management
# Clear versioning strategy
/api/v1/users # Deprecated, rate limited
/api/v2/users # Current version
/api/v3/users # Beta, auth required
# Deprecation headers
X-API-Deprecated: true
X-API-Deprecation-Date: 2024-06-01API10: Unsafe Consumption of APIs
# Dangerous - trusting external API
def get_user_data():
response = requests.get('https://third-party-api.com/user')
user_data = response.json()
# Directly using unvalidated data
db.execute(f"INSERT INTO users VALUES ('{user_data['name']}')")
# SQL injection from third-party!from pydantic import BaseModel, validator
class ExternalUserData(BaseModel):
name: str
email: str
@validator('name')
def validate_name(cls, v):
if len(v) > 100 or not v.isalnum():
raise ValueError('Invalid name')
return v
def get_user_data():
response = requests.get('https://third-party-api.com/user')
user_data = ExternalUserData(**response.json()) # ValidatedAPI Security Testing Checklist
Conclusion
API security requires a comprehensive approach addressing authentication, authorization, input validation, and operational security. The OWASP API Security Top 10 provides an excellent framework for prioritizing your efforts.
Remember: every API endpoint is an attack surface. Treat them accordingly.
Asfaleia-Tech offers comprehensive API security assessments including automated scanning and manual penetration testing. Contact us to secure your APIs.