OWASP API Security Top 10: A Developer‘s Guide to Hardening APIs

APIs are the lifeblood of modern digital services, powering everything from mobile apps to single-page web applications to B2B integrations. As a full-stack developer, you know that APIs are key to unlocking rich functionality and seamless interoperability. But you also need to be acutely aware that APIs have become the number one attack vector for web applications, accounting for over 80% of breaches according to Salt Security‘s State of API Security Report, Q3 2022.

The hard reality is that APIs tend to be the weakest link in application security, thanks to a perfect storm of rapid proliferation, ever-expanding complexity, and lagging awareness of the unique risks APIs create. Gartner predicts that by 2025, less than 50% of enterprise APIs will be managed, largely due to the rapid growth of API-based applications (Gartner, Predicts 2022: APIs Demand Improved Security and Management). It‘s clear that as a developer, you can no longer afford to treat API security as an afterthought.

Fortunately, the OWASP API Security Top 10 provides a valuable roadmap for understanding and mitigating the most critical API risks. In this guide, we‘ll dive deep into each of the top 10 risks, examining how they manifest in real-world APIs and most importantly, what you can do to harden your APIs against them. We‘ll also explore some additional threats beyond the top 10 and look at strategies for baking security into your API development lifecycle.

API1:2023 – Broken Object Level Authorization (BOLA)

Kicking off the list is the pervasive issue of broken object level authorization, where an API fails to properly verify that the requesting user has permission to access the requested resource. BOLA vulnerabilities are rampant, accounting for over 15% of total API vulnerabilities according to a recent analysis by APIsecurity.io (API Vulnerability Trends 2022).

Here‘s a simple example of a BOLA flaw in a Node.js API using the Express framework:

app.get(‘/api/users/:userId‘, function (req, res) {
  const user = db.getUserById(req.params.userId);
  res.json(user);
});

The problem here is that the /api/users/:userId endpoint doesn‘t perform any authorization checks before returning the user object corresponding to the userId parameter. An attacker could simply iterate through userId values to access other users‘ data.

To fix this, you need to add explicit authorization logic:

app.get(‘/api/users/:userId‘, authMiddleware, function (req, res) {
  if (req.userId !== req.params.userId) {
    return res.status(403).send(‘Forbidden‘);
  }
  const user = db.getUserById(req.params.userId);
  res.json(user);
});

Here we‘ve added an authMiddleware function to verify the authenticated user, and a check to ensure the authenticated userId matches the requested userId. The API returns a 403 Forbidden if the authorization check fails.

Some other best practices for preventing BOLA:

  • Use a well-vetted authorization framework like Casbin rather than rolling your own
  • Prefer declarative policy-based access control rules over scattered imperative checks
  • Write comprehensive authorization tests, including tests for negative cases
  • Use code analysis tools to detect missing authorization checks

API2:2023 – Broken Authentication

Authentication weaknesses are another major vector for API attacks. Common issues include easily guessed passwords, lack of brute-force protection, insecure credential storage, and improper handling of tokens. The 2022 Verizon Data Breach Investigations Report found that over 80% of breaches due to hacking involve brute force or the use of lost or stolen credentials (2022 DBIR Results), underscoring the importance of robust authentication.

Consider this example of insecure JWT handling in a Python Flask API:

@app.route(‘/api/login‘, methods=[‘POST‘])
def login():
    username = request.json.get(‘username‘)
    password = request.json.get(‘password‘)

    user = User.query.filter_by(username=username).first()
    if user and user.check_password(password):
        token = jwt.encode({‘user_id‘: user.id}, app.config[‘SECRET_KEY‘])
        return jsonify({‘token‘: token}) 

This code has several issues:

  • The JWT signing key is hardcoded in the application config
  • The JWT payload only contains the user ID, enabling spoofing if the key is compromised
  • The JWT never expires, allowing indefinite access

To harden this, at a minimum you should:

  • Use a strong, randomly generated secret key stored securely outside the codebase
  • Include additional claims like an expiration time and issuer to limit the JWT‘s validity
  • Implement token revocation by maintaining a deny list of logged out tokens

Example of a more secure token generation:

from datetime import datetime, timedelta
import uuid

# Generate a random UUID for the JWT ID
jti = str(uuid.uuid4())

# Set the expiration time to 30 minutes from now  
exp = datetime.utcnow() + timedelta(minutes=30)

token = jwt.encode({
    ‘user_id‘: user.id,
    ‘exp‘: exp,
    ‘iat‘: datetime.utcnow(),
    ‘iss‘: ‘https://myapp.com‘,
    ‘jti‘: jti
}, app.config[‘SECRET_KEY‘], algorithm=‘HS256‘)

# Store the JTI in Redis with the same expiration as a revocation list
redis_client.setex(jti, timedelta(minutes=30), ‘‘)

General best practices for API authentication:

  • Use standard, battle-tested authentication protocols like OAuth 2.0 or OpenID Connect
  • Require strong, randomly generated secrets and rotate them regularly
  • Enforce multi-factor authentication for sensitive actions
  • Properly validate and sanitize authentication inputs to prevent injection attacks
  • Limit failed login attempts and implement account lockout policies
  • Monitor for unusual authentication patterns that may indicate breach attempts

API3:2023 – Broken Object Property Level Authorization (BOPLA)

BOPLA vulnerabilities occur when an API exposes properties that a user should not be able to view or allows users to modify properties they shouldn‘t have access to. According to Cloudflare, BOPLA was the 8th most common API vulnerability detected in customer traffic between July and December 2022 (API Vulnerabilities Seen in the Wild – H2 2022).

Here‘s an example of a BOPLA vulnerability in a .NET API that allows any authenticated user to see other users‘ email addresses:

[HttpGet]
[Authorize]
public IActionResult GetUser(int id)
{
    var user = _context.Users.Find(id);
    if (user == null)
    {
        return NotFound();
    }

    return Ok(new {
        Id = user.Id,
        Username = user.Username,
        Email = user.Email
    });
}

To fix this, you need to selectively expose properties based on the authenticated user‘s permissions:

[HttpGet]
[Authorize]
public IActionResult GetUser(int id)
{
    var user = _context.Users.Find(id);
    if (user == null)
    {
        return NotFound();
    }

    if (user.Id != int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value))
    {
        // Only return public properties for other users
        return Ok(new {
            Id = user.Id,
            Username = user.Username
        });
    }

    // Return all properties for the authenticated user
    return Ok(new {
        Id = user.Id,  
        Username = user.Username,
        Email = user.Email
    });
}

Another common BOPLA flaw is allowing users to modify sensitive properties:

[HttpPut("{id}")]
[Authorize] 
public async Task<IActionResult> PutUser(int id, User user)
{
    if (id != user.Id)
    {
        return BadRequest();
    }

    _context.Entry(user).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!UserExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

This endpoint allows any authenticated user to update any property of their user object, including sensitive fields like IsAdmin. To prevent privilege escalation, you need to whitelist the specific properties that users are allowed to modify:

[HttpPut("{id}")]
[Authorize]
public async Task<IActionResult> PutUser(int id, UserUpdateModel model)
{
    var user = await _context.Users.FindAsync(id);

    if (user == null) 
    {
        return NotFound();
    }

    if (user.Id != int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value))
    {
        return Forbid();
    }

    user.Username = model.Username;
    user.Email = model.Email;
    // Only allow updating safe properties, exclude sensitive ones like IsAdmin

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!UserExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

In this refactored version, we use a UserUpdateModel that only includes the properties users are allowed to update, and explicitly map those properties to the User entity.

Some additional tips for preventing BOPLA:

  • Define clear access control rules for each property based on user roles
  • Use DTOs or view models to control which properties are exposed by API responses
  • Avoid using entity types directly as input models to prevent over-posting
  • Validate all incoming properties to ensure they are allowed to be set by the caller

Secure API Development Lifecycle

Ultimately, as a developer, you need to bake security into every phase of the API development lifecycle:

  • Design: Conduct threat modeling exercises to identify potential risks early. Define clear authentication and authorization requirements.
  • Implementation: Follow secure coding guidelines. Perform peer code reviews. Use static analysis tools to catch common vulnerabilities.
  • Testing: Write comprehensive unit and integration tests, including negative test cases. Perform penetration testing before deployment.
  • Deployment: Implement robust logging and monitoring. Use Web Application Firewalls and API gateways to enforce security policies.
  • Operation: Keep dependencies updated. Perform regular security audits. Have an incident response plan for breaches.

By making API security a core concern from design to deployment, you can catch and prevent vulnerabilities before they become headline-making breaches.

One major challenge is the rapidly evolving nature of API technology itself. The proliferation of microservice and serverless architectures has led to an explosion of API endpoints, making it difficult to maintain a consistent security posture. In these environments, it‘s crucial to implement security at the gateway level to provide a unified point of control.

Another challenge is the use of third-party APIs, which can introduce vulnerabilities outside your direct control. It‘s essential to thoroughly vet the security of any external APIs you rely on and have contingency plans in place for when those APIs are compromised or unavailable.

Ultimately, API security is a complex, multifaceted problem that requires vigilance and proactivity from developers and organizations alike. But by staying abreast of emerging threats, following best practices, and prioritizing security throughout the development lifecycle, you can unlock the power of APIs while protecting the sensitive data they access.

API Security Testing Tools

Fortunately for developers, a growing ecosystem of API security testing tools is emerging to help automate the detection of vulnerabilities. Some popular open-source and commercial tools include:

  • OWASP Zed Attack Proxy (ZAP): An open-source web app scanner that includes an API testing mode for finding vulnerabilities in APIs.

  • Postman: Primarily an API development platform, Postman also offers built-in security scanning for SPAs, SOAP services, and REST APIs.

  • Astra: A commercial API security testing tool that can detect a wide range of API vulnerabilities like BOLA, BFLA, injection, and authentication issues.

  • APIsec: An AI-powered API security platform that can automatically detect vulnerabilities during development and testing.

  • Burp Suite: A comprehensive web app security testing platform with tools for API security testing and vulnerability scanning.

While these tools can catch many common API vulnerabilities, they are not a silver bullet. Manual penetration testing and code review are still essential for identifying business logic flaws and design-level issues that automated tools may miss.

It‘s also important to incorporate API security testing into your continuous integration and delivery (CI/CD) pipeline to catch vulnerabilities before they reach production. This can involve running SAST (static application security testing) and DAST (dynamic application security testing) tools as part of your build process, as well as implementing runtime protection like RASP (runtime application self-protection).

Conclusion

As APIs become ever more central to modern application architecture, API security has become a critical concern for developers and organizations alike. The OWASP API Security Top 10 provides a valuable framework for understanding and mitigating the most pressing API threats, from broken object level authorization to inadequate logging and monitoring.

But as we‘ve seen, API security is a complex and evolving challenge that requires ongoing vigilance and proactivity. As a developer, you have a critical role to play in baking security into every phase of the API development lifecycle, from design and implementation to testing and deployment.

By staying up-to-date on emerging threats, following best practices, and leveraging the growing ecosystem of API security tools, you can help ensure that the APIs powering your applications are resilient, secure, and trustworthy. In a world where APIs are increasingly the lifeblood of digital services, this is no longer an optional concern—it‘s an essential responsibility for any developer building the applications of tomorrow.

Similar Posts