Defending API
Defending API
Defending API
COM
Defending APIs
· Feb 26, 2024 · 38 min read
Table of contents
Securely Handling JSON Web Tokens (JWTs) in Your API
1. Purposeful Usage of JWTs
2. Secure Storage Mechanisms
3. Explicit Declaration of Token Usage
4. Expiration Timestamp Management
Securely Using Algorithms in API Development
Explicit Algorithm and Key Specification
Signing and Verification
Secure Key Management
Utilizing Key Vaults
Utilizing Libraries Effectively for JWT Handling
JWT Generation in Python
JWT Decoding on the Client Side
Secure Usage Patterns
Enhancing Password and Token Security
Increasing Complexity and Length
Utilizing Secure Password Storage Solutions
Time to Crack Passwords: A Deterministic Improvement
Using Cryptographically Secure Pseudo-Random Number Generators
.NET Core Implementation
Java Implementation
Securing the Password Reset Process
Importance of a Secure Password Reset Process
Best Practices for Password Reset Processes
Implementation in .NET Core
.NET Core Example
Implementation in Java
Java Example
Implementing Authentication
Authentication in FastAPI (Python)
Python Code (FastAPI)
Authentication in .NET Core
.NET Core Code Example
Authentication in Java
Java Code Example (Spring Boot)
Authentication in Node.js (Express)
JavaScript Code (Node.js with Express)
Leveraging the OpenAPI Specification for Secure API Design
API-First vs. Design-First vs. Code-First
Data Modeling with OAS
Combining Schemas
Ensuring Data Security
Enhancing API Security with the OpenAPI Specification
Supported Security Schemes
Applying Security Directives
Audit and Remediation
Code Generation
API Portal
Embracing the Positive Security Model for API Protection
Negative Security Model
The Strength of the Positive Security Model
Leveraging the OpenAPI Specification
Implementing Positive Security in .NET Core and Java
.NET Core Example
Java Example
Conducting Threat Modeling of APIs: A Shift-Left Approach to Security
Threat Modeling
Importance of Threat Modeling in API Security
Practical Approach: Threat Modeling with .NET Core and Java
Threat Modeling in .NET Core
Threat Modeling in Java
Automating API Security: Enhancing Development Lifecycle with Automated Tools
Automated API Security
CI/CD Integration for Automated API Security
GitHub Actions
Semgrep
Thinking Like an Attacker
Exploring OpenAPI Generator for API Code Generation
Introduction to OpenAPI Generator
Installing OpenAPI Generator
Generating Server Stubs
Generating Schema
Generating Documentation
Using Templates and Custom Generators
Integration with Frameworks
Java
.NET Core
Python
Node.js
Object-Level and Function-Level Vulnerabilities in APIs
Object-Level Vulnerabilities
Understanding the Vulnerability
Mitigation Strategies
Practical Implementation
Function-Level Vulnerabilities
Understanding the Vulnerability
Mitigation Strategies
Practical Implementation
Using Authorization Middleware
Key Components of Authorization Frameworks
Recommended Authorization Frameworks
Understanding Data Vulnerabilities in APIs: Mitigation Strategies and Best Practices
Data Propagation in APIs
Excessive Data Exposure
Coding Securely
Classifying Data
Mass Assignment
Understanding and Mitigating API Vulnerabilities: Injection, SSRF, Logging, and Resource
Consumption
Injection Vulnerabilities
Validate User Input
Sanitize User Input
Utilize OpenAPI Definitions
Server-Side Request Forgery (SSRF)
Allow List for URLs
Restrict URL Schema and Ports
Disable HTTP Redirections
Insufficient Logging and Monitoring
Protecting Against Unrestricted Resource Consumption
Rate Limiting
Scalability
Understanding Your Stakeholders in API Security
Roles in the Security Domain
CISO (Chief Information Security Officer)
Head of AppSec
DevSecOps Team
Pentest/Red Team
Risk and Compliance Team
Roles in the Business or Development Domain
CIO (Chief Information Officer)
Product Owner
Technical Lead
Solution Architect
DevOps Team
Roles in the API Product Domain
API Product Owner
API Platform Owner
API Architect
Distributing Ownership of API Security
The 42Crunch Maturity Model for API Security
Overview of the Maturity Model
1. Inventory
2. Design
3. Development
4. Testing
5. Protection
6. Governance
Planning Your Program
Assessing Your Current State
Building a Landing Zone for APIs
Building Your Teams
Tracking Your Progress
Integrating with Existing AppSec Programs
Resources
Show less
we embark on a journey to fortify our APIs against common vulnerabilities that lurk at
every stage of the Software Development Lifecycle (SDLC). Drawing from insights
gained through dissecting past breaches and understanding the ramifications of
insecure APIs, our focus now shifts to cultivating a defensive mindset aimed at
crafting robust and resilient APIs from the ground up.
Throughout the chapters preceding this one, we've delved into the arsenal of
techniques attackers employ to compromise APIs, setting the stage for our defensive
endeavors. Now, armed with this knowledge, we are poised to confront each class of
vulnerability head-on, equipping ourselves with best practices, cautionary tales of
common pitfalls, recommendations for essential tools and libraries, and illustrative
code samples showcasing key defensive strategies.
For developers, this aticle represents a pivotal milestone in your learning journey,
empowering you to architect APIs that not only meet functional requirements but also
stand as bastions of security in an ever-evolving threat landscape. Meanwhile, for
those seeking a broader understanding of API security, this chapter offers invaluable
insights into foundational defensive techniques that underpin the resilience of modern
digital ecosystems.
Building upon the critical lessons learned in "Attacking APIs," where we underscored
the indispensable role of authentication and authorization in safeguarding APIs, the
initial focus of this chapter is to reinforce these fundamental pillars of API security.
Fortunately, the path to enhancing authentication and authorization mechanisms is
paved with well-established practices, patterns, and readily available supporting
libraries, ensuring that API builders can swiftly bolster their defenses against
unauthorized access and misuse.
import jwt
import time
This code snippet demonstrates how to generate a JWT token with a specified
payload, expiration time, secret key ( JWT_SECRET ), and algorithm ( JWT_ALGORITHM ).
Ensure that these values are retrieved securely from storage to prevent unauthorized
access.
This code snippet decodes the JWT token using the same secret key and algorithm. It
verifies the signature and checks the expiration time ( expires ) to ensure the token is
still valid. If the token is valid, the decoded payload is returned; otherwise, None is
returned.
COPY
using System;
using System.Security.Cryptography;
Java Implementation
COPY
import java.security.SecureRandom;
password.append(ALLOWED_CHARS.charAt(random.nextInt(ALLOWED_CHARS.leng
th())));
}
return password.toString();
}
}
COPY
[HttpPost]
[Route("confirm")]
public IActionResult ConfirmReset([FromBody]
ResetConfirmationModel resetConfirmation)
{
// Validate reset confirmation
// Reset user's password
// Invalidate reset token or PIN
// Log reset confirmation
Implementation in Java
Java Example
COPY
@RestController
@RequestMapping("/reset")
public class PasswordResetController {
@PostMapping("/request")
public ResponseEntity<String> requestPasswordReset(@RequestBody
ResetRequest resetRequest) {
// Validate reset request
// Generate a random token or PIN
// Send reset instructions via email
// Log reset activity
return ResponseEntity.ok("Reset instructions sent
successfully.");
}
@PostMapping("/confirm")
public ResponseEntity<String> confirmPasswordReset(@RequestBody
ResetConfirmation resetConfirmation) {
// Validate reset confirmation
// Reset user's password
// Invalidate reset token or PIN
// Log reset confirmation
Implementing Authentication
Authentication is a critical aspect of securing API endpoints, ensuring that only
authorized users can access protected resources. In this article, we'll explore how to
handle authentication in code using examples in .NET Core and Java.
COPY
app = FastAPI()
# Mock database
posts = []
class PostSchema(BaseModel):
id: int
title: str
content: str
class JWTBearer(HTTPBearer):
async def __call__(self, request):
credentials: HTTPAuthorizationCredentials = await
super().__call__(request)
if credentials:
if not credentials.scheme == "Bearer":
raise HTTPException(status_code=403, detail="Invalid
authentication scheme.")
if not self.verify_jwt(credentials.credentials):
raise HTTPException(status_code=403, detail="Invalid
token or expired token.")
return credentials.credentials
else:
raise HTTPException(status_code=403, detail="Invalid
authorization code.")
COPY
// Startup.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System.Text;
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new
TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key)
};
});
Authentication in Java
In Java, authentication is commonly implemented using frameworks like Spring
Security, which provides comprehensive support for securing web applications and
APIs. Spring Security offers various authentication mechanisms, including form-
based, HTTP Basic, and JWT authentication. Let's see how we can implement JWT
authentication using Spring Security.
Java Code Example (Spring Boot)
COPY
// SecurityConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import
org.springframework.security.config.annotation.web.builders.HttpSecuri
ty;
import
org.springframework.security.config.annotation.web.configuration.Enabl
eWebSecurity;
import
org.springframework.security.config.annotation.web.configuration.WebSe
curityConfigurerAdapter;
import
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST,
"/api/authenticate").permitAll() // Allow authentication endpoint
.anyRequest().authenticated()
.and()
.addFilter(new
JwtAuthenticationFilter(authenticationManager()))
.addFilter(new
JwtAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATE
LESS);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
This code configures JWT authentication using Spring Security in a Spring Boot
application.
COPY
// Mock database
const secrets = [{ id: 1, name: "Secret 1" }];
number
integer
boolean
array
object
Components:
schemas:
Messages: # Dictionary
type: object
additionalProperties:
$ref: '#/components/schemas/Message'
Message: # Object
type: object
properties:
code:
type: integer
text:
type: string
Here, Messages is a dictionary with string keys and Message objects as values. The
$ref directive allows for referencing common definitions, promoting reusability and
maintainability.
Combining Schemas
OAS allows for combining schemas using directives like oneOf , anyOf , allOf , and
not . For instance:
COPY
paths:
/pets:
patch:
requestBody:
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
This snippet specifies that the request body can be either a Cat or a Dog , but not
both. Additionally, the enum directive can be used to constrain values, enhancing data
validation.
components:
securitySchemes:
BasicAuth:
type: http
scheme: basic
BearerAuth:
type: http
scheme: bearer
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
OAuth2:
type: oauth2
flows:
authorizationCode:
authorizationUrl: ...
tokenUrl: ...
scopes:
read: Grants read access
write: Grants write access
admin: Grants access to admin operations
paths:
/billing_info:
get:
summary: Gets the account billing info
security:
- OAuth2: [admin]
responses:
'200':
description: OK
'401':
description: Not authenticated
/ping:
get:
security: []
responses:
'200':
description: Server is up and running
default:
description: Something is wrong
In this example, the /billing_info endpoint is protected with OAuth2 within the
admin scope, while the /ping endpoint remains unprotected.
Code Generation
Once the OpenAPI definition is audited and validated, developers can leverage code-
generation tools like Swagger Codegen or OpenAPI Generator to automatically
generate client-side and server-side code. This design-first approach streamlines API
development and ensures consistency across implementations.
API Portal
A significant advantage of using the OpenAPI Specification is the ability to generate
interactive API portals within applications. These portals allow consumers to explore
and experiment with APIs before developing client applications, facilitating easier
adoption.
Embracing the Positive Security Model for API
Protection
In the realm of API security, the positive security model stands out as a robust
approach to ensuring data integrity and protecting against malicious attacks. Unlike
the traditional negative security model, which relies on blocklists to identify and block
known malicious data, the positive security model operates based on an allowlist
principle. In this article, we'll delve into the benefits of leveraging the positive security
model, particularly within the context of API development, and explore how the
OpenAPI Specification serves as a foundation for implementing this model effectively.
[HttpGet("{id}")]
public ActionResult<UserDto> GetUserById(int id)
{
// Implement positive security logic to retrieve user by ID
// Validate ID against OpenAPI definition
if (OpenAPISecurityValidator.ValidateRequest(Request,
"GetUserById"))
{
var user = _userService.GetUserById(id);
if (user == null)
{
return NotFound();
}
return Ok(user);
}
else
{
return Unauthorized();
}
}
}
Java Example
COPY
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUserById(@PathVariable int id) {
// Implement positive security logic to retrieve user by ID
// Validate ID against OpenAPI definition
if (OpenAPISecurityValidator.validateRequest(request,
"getUserById")) {
UserDto user = userService.getUserById(id);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(user);
} else {
return
ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
}
In both examples, we validate the incoming requests against the OpenAPI definition
to ensure that only allowed operations and inputs are processed, adhering to the
positive security model.
Threat Modeling
Threat modeling is a systematic approach to identifying potential security threats and
vulnerabilities in a system or application. It involves asking critical questions about the
system's design, functionality, and potential attack vectors. The key steps in threat
modeling can be summarized as follows:
1. Identify Assets and Scope: Determine the assets being protected and define the
boundaries of the system under consideration.
2. Enumerate Threats: Identify potential threats and vulnerabilities that could
compromise the security of the system.
3. Evaluate Risks: Assess the likelihood and impact of each threat on the system's
security posture.
4. Mitigate Risks: Develop strategies and countermeasures to mitigate identified
risks and enhance the system's security.
5. Review and Iterate: Continuously review and refine the threat model as the
system evolves, ensuring ongoing protection against emerging threats.
COPY
COPY
@PostMapping("/authenticate")
public ResponseEntity<?> authenticateUser(@RequestBody Credentials
credentials) {
// Threat modeling considerations:
// - Injection attacks (e.g., SQL injection, XSS) on
authentication endpoint
// - Insufficient authentication controls (e.g., lack of rate
limiting, weak password policies)
// - Exposure of sensitive information in error responses
In both examples, we identify potential threats and vulnerabilities specific to the user
authentication endpoint, such as brute-force attacks, injection attacks, and insecure
storage of credentials. By incorporating threat modeling considerations directly into
the code, developers can address security concerns proactively during the
development phase.
Automating API Security: Enhancing Development
Lifecycle with Automated Tools
Ensuring the security of APIs is a critical aspect of software development, given the
myriad of potential vulnerabilities and attack vectors that APIs can be exposed to.
However, with the right tools and practices in place, developers can automate
security checks throughout the development lifecycle to detect and mitigate potential
threats early on. In this article, we'll explore the concept of automating API security
and demonstrate practical examples using .NET Core and Java.
jobs:
security_audit:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Run Security Audit
uses: 42Crunch/action-security-audit@v1
with:
apiKey: ${{ secrets.CRUNCH_API_KEY }}
Semgrep
Semgrep is a powerful static analysis tool for detecting security vulnerabilities in
code. It offers a rich set of rules for identifying common security flaws. Below is an
example of using Semgrep to detect JWT-related vulnerabilities in Java code:
COPY
openapi-generator generate -
i book_sample_1.yml -g python-flask -o out/
This command generates a Python Flask project based on the provided OpenAPI
specification.
Generating Schema
OpenAPI Generator also allows generating database schema from data definitions
within an OpenAPI specification. For example, given a well-specified user entity in the
API definition, you can generate a corresponding MySQL schema:
COPY
This command produces a MySQL script based on the data definitions in the OpenAPI
specification.
Generating Documentation
OpenAPI Generator excels at producing human-readable documentation from
OpenAPI definitions. You can generate HTML documentation using a command like:
COPY
Java
For Java APIs, frameworks like Spring Boot and Micronaut offer built-in support for
generating OpenAPI documentation. Libraries like Springdoc-OpenAPI and Micronaut
OpenAPI provide comprehensive support for generating and customizing API
documentation.
.NET Core
In the .NET Core ecosystem, packages like NSwag and Swashbuckle enable the
generation of OpenAPI definitions from existing code bases. These packages offer
features for producing API documentation and client libraries.
Python
Python developers can leverage packages like apispec for generating OpenAPI
documentation. Depending on the chosen framework (e.g., Flask or aiohttp),
additional support packages may be required for seamless integration.
Node.js
For Node.js applications, libraries like swagger-ui-express facilitate the generation
and display of OpenAPI documentation. These libraries require an OpenAPI definition
for generating documentation.
Object-Level Vulnerabilities
Object-level vulnerabilities, as defined in the OWASP API Security Top 10, occur when
APIs grant access to objects (typically data) not owned by the calling user or client.
Despite their severity, addressing these vulnerabilities is relatively straightforward
with proper validation mechanisms.
Understanding the Vulnerability
Object-level vulnerabilities often stem from incomplete or improper validation of
access rights. For instance, trusting session identifiers, parameters, or JWT tokens
without explicit validation can lead to unauthorized access.
Mitigation Strategies
To mitigate object-level vulnerabilities, it's crucial to always explicitly validate access
to objects. Let's consider a code sample in Ruby on Rails:
COPY
Practical Implementation
In .NET Core, you can implement similar validation logic in C#:
COPY
[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
if
(!_userService.UserHasAccess(HttpContext.User.Identity.Name, id))
{
return Unauthorized();
}
Here, UserHasAccess method validates whether the authenticated user has access to
the requested user ID before returning the user data.
Function-Level Vulnerabilities
Similar to object-level vulnerabilities, function-level vulnerabilities arise when APIs fail
to properly validate access rights for certain operations or endpoints. These
vulnerabilities can allow unauthorized users to execute privileged functions.
Understanding the Vulnerability
Function-level vulnerabilities often occur when APIs allow access to high-privilege
endpoints without adequate authorization checks. For example, granting access to
/admin endpoints without verifying the user's administrative privileges can lead to
unauthorized operations.
Mitigation Strategies
Mitigating function-level vulnerabilities involves explicitly validating access rights for
privileged endpoints. Let's examine a Python/FastAPI code example:
COPY
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
@app.get("/admin")
async def do_admin(current_user: User = Depends(get_current_user)):
if not AuthZ.user_has_admin_access(current_user):
raise HTTPException(status_code=401, detail="User does not
have admin privileges")
else:
# Perform admin operations
pass
In this example, the do_admin endpoint checks whether the current user has admin
access before executing privileged operations.
Practical Implementation
In .NET Core, you can implement function-level authorization checks as follows:
COPY
Here, the [Authorize(Roles = "Admin")] attribute ensures that only users with the
"Admin" role can access the /admin endpoint.
Using Authorization Middleware
While implementing authorization checks directly in code can address many
vulnerabilities, managing authorization at scale requires robust frameworks.
Authorization frameworks abstract policy logic from application code, enabling
centralized management and extensibility.
Classifying Data
Classify data based on sensitivity and intended audience. Review access to APIs
based on business needs, adhering to the principle of least privilege. Enhance API
test fixtures to monitor data types and flag potential data leakage.
Mass Assignment
Mass assignment occurs when APIs accept more data than intended, potentially
allowing attackers to modify sensitive fields. Mitigate this vulnerability by being
specific about allowed fields and avoiding implicit assignments.
In Java, you can implement explicit field assignment to prevent mass assignment
vulnerabilities. Here's an example using Spring Boot:
COPY
Injection Vulnerabilities
Injection attacks, such as SQL injection and command injection, exploit systems that
trust user input without proper validation. To mitigate these risks, follow these best
practices:
Validate User Input
In .NET Core, use parameterized queries to prevent SQL injection:
COPY
COPY
if (allowedUrls.Contains(userInputUrl))
{
// Perform the redirection
}
else
{
// Reject the URL
}
COPY
if (allowedUrls.contains(userInputUrl)) {
// Perform the redirection
} else {
// Reject the URL
}
Rate Limiting
Limit the number of requests per client within a given window:
COPY
COPY
// Java implementation with Spring Boot
@Bean
public FilterRegistrationBean rateLimitFilter() {
FilterRegistrationBean registrationBean = new
FilterRegistrationBean();
registrationBean.setFilter(new RateLimitFilter());
registrationBean.addUrlPatterns("/api/*");
return registrationBean;
}
Scalability
Design APIs to gracefully handle variable loads by leveraging cloud-native platforms
for automatic scaling.
DevSecOps Team
Integrates and operates security tools within the automated software
development lifecycle (SDLC) environment.
Ensures that security is incorporated throughout the development process.
Pentest/Red Team
Conducts offensive testing of product releases using black box techniques.
Identifies vulnerabilities and weaknesses in the organization's systems and
applications.
Technical Lead
Manages the technical team responsible for developing and maintaining
products.
Provides technical guidance and support to team members.
Solution Architect
Supports and evangelizes the product to customers.
Designs and implements technical solutions that align with business goals.
DevOps Team
Operates the build and release process through automation.
Facilitates collaboration between development and operations teams.
API Architect
Defines the overall API strategy, including authentication, authorization, and
architecture.
Designs APIs to meet business and technical requirements.
1. Inventory
Maintaining an up-to-date and accurate inventory of APIs is fundamental for visibility
into the organization's risk and attack surface. Key activities include:
Introduction and tracking of new APIs
Discovering API inventory from source code repositories
Runtime discovery of APIs through network traffic inspection
At lower maturity levels, only a basic inventory is maintained, often via manual
tracking. As maturity increases, a centralized platform is utilized, and shadow/zombie
APIs are actively managed.
2. Design
Addressing security issues at the design phase is cost-effective and crucial for
building secure APIs. Key elements of secure API design include:
Authentication methods
Authorization models
Data privacy and exposure requirements
Compliance considerations
Threat modeling exercises
Lower maturity levels may lack a formal design process, while higher levels
emphasize security as a first-class element of API design, incorporating standard
practices like threat modeling.
3. Development
The development phase is where specifications are implemented, making it vital to
follow security best practices. Key considerations include:
Choice of languages, libraries, and frameworks
Correct configuration of frameworks
Defensive coding practices
Central enforcement of authentication and authorization
At lower maturity levels, developers may be unaware of security concerns, while
higher levels see proactive adoption of secure coding practices.
4. Testing
Effective API security testing is essential to identify vulnerabilities before deployment.
Key aspects of testing include:
Authentication and authorization bypass testing
Data exposure and handling of invalid requests
Rate limiting and quota enforcement
Integration with CI/CD processes
Lower maturity levels may lack specific API security testing, while higher levels tightly
integrate testing into all stages of the SDLC.
5. Protection
Despite efforts in earlier stages, APIs remain susceptible to attacks and require
dedicated protection mechanisms. Key aspects of protection include:
JWT validation
Secure transport options
Brute-force protection
Logging and monitoring of protection mechanisms
Lower maturity levels may rely solely on standard firewalls, while higher levels
implement dedicated API firewalls for enhanced protection.
6. Governance
A robust governance process ensures that APIs are developed and maintained
according to organizational standards. Key principles of governance include:
Consistency in API usage
Standard processes for API development and testing
Compliance with data privacy requirements
Lifecycle management of APIs
Lower maturity levels may lack standardized processes, while higher levels enforce
proactive governance and address deviations.
Resources
Defending APIs by Colin Domoney