Introduction
In the asn_coverage project, a high-severity vulnerability was discovered in the asn_coverage/uv.lock dependency file. The project was using orjson version 3.10.16, a high-performance JSON serialization library for Python. This version contained CVE-2025-67221, a denial of service vulnerability that allows attackers to crash the application by sending deeply nested JSON documents. The vulnerability affects any code path that processes JSON input from untrusted sources—a common pattern in web APIs, data processing pipelines, and configuration parsers.
The specific issue lies in how orjson 3.10.16 handles recursive descent during JSON deserialization. When parsing nested structures like {"a": {"b": {"c": {...}}}} or [[[[...]]]], the parser calls itself recursively for each level. Without a depth limit, an attacker can craft a payload with thousands of nesting levels that exhausts the Python call stack, triggering a RecursionError and crashing the application.
The Vulnerability Explained
CVE-2025-67221 is a classic example of uncontrolled recursion (CWE-674). Here's what makes this vulnerability dangerous:
The Vulnerable Code Pattern
In orjson 3.10.16, the JSON deserialization logic recursively processes nested structures without enforcing a maximum depth. While the exact implementation is in Rust (orjson's core is written in Rust for performance), the vulnerability manifests when Python code calls orjson.loads() on malicious input:
import orjson
# Vulnerable code using orjson 3.10.16
def process_json_data(json_string):
# No depth validation before parsing
data = orjson.loads(json_string)
return data
The problem isn't in the application code—it's in the library itself. When orjson.loads() encounters deeply nested JSON, it recursively descends through each level:
orjson.loads() → parse_object() → parse_value() → parse_object() → parse_value() → ...
Attack Scenario
Consider the asn_coverage application, which likely processes JSON data related to ASN (Autonomous System Number) coverage information. An attacker could exploit this in several ways:
Scenario 1: API Endpoint Attack
# Attacker sends a POST request to /api/coverage
POST /api/coverage HTTP/1.1
Content-Type: application/json
{"data": {"level1": {"level2": {"level3": { ... 5000 more levels ... }}}}}
Scenario 2: Malicious Configuration File
If the application reads JSON configuration files, an attacker with write access could replace a config file with:
[[[[[[[[[[[[[[[[[[[[...2000 levels deep...]]]]]]]]]]]]]]]]]]]]
Scenario 3: Data Processing Pipeline
If asn_coverage processes JSON from external sources (S3 buckets, API responses, etc.), a compromised upstream service could inject malicious JSON:
# Application code in asn_coverage
import boto3
import orjson
s3 = boto3.client('s3')
response = s3.get_object(Bucket='asn-data', Key='coverage.json')
data = orjson.loads(response['Body'].read()) # Vulnerable!
Real-World Impact
The impact of this vulnerability is significant:
- Service Availability: A single malicious request can crash the entire application, causing downtime
- Resource Exhaustion: Stack overflow errors can leave the process in an unstable state
- Cascading Failures: In containerized environments, repeated crashes can trigger restart loops
- Security Monitoring Blind Spots: DoS attacks can mask other malicious activity
The vulnerability is particularly dangerous because:
- JSON is ubiquitous in modern applications
- The payload can be surprisingly small (a few KB can contain thousands of nesting levels)
- It requires no authentication—any endpoint accepting JSON is vulnerable
- It's trivial to exploit with automated tools
The Fix
The fix is straightforward but critical: upgrade orjson from version 3.10.16 to 3.11.6. Here's what changed:
Before (Vulnerable Code)
# asn_coverage/pyproject.toml
dependencies = [
"arrow>=1.3.0",
"boto3>=1.37.36",
"click>=8.1.8",
"orjson>=3.10.16", # Vulnerable version
"requests>=2.32.3",
]
After (Secure Code)
# asn_coverage/pyproject.toml
dependencies = [
"arrow>=1.3.0",
"boto3>=1.37.36",
"click>=8.1.8",
"orjson>=3.11.6", # Fixed version with recursion limits
"requests>=2.32.3",
]
Lock File Updates
The fix also updates the uv.lock file to ensure consistent dependency resolution:
version = 1
-revision = 1
+revision = 3
requires-python = ">=3.12"
The revision bump from 1 to 3 indicates that the dependency tree was recalculated to incorporate the security fix.
How This Change Solves the Problem
orjson 3.11.6 introduces recursion depth limits during JSON deserialization. The library now tracks nesting depth and rejects payloads that exceed safe thresholds (typically 1024 levels). This prevents stack exhaustion while still supporting legitimate use cases:
# With orjson 3.11.6
import orjson
# Legitimate JSON: works fine
data = orjson.loads('{"user": {"profile": {"settings": {"theme": "dark"}}}}')
# Malicious JSON: raises exception before stack overflow
malicious = '{"a":' * 5000 + '{}' + '}' * 5000
try:
orjson.loads(malicious)
except orjson.JSONDecodeError as e:
print(f"Rejected: {e}") # "Rejected: recursion limit exceeded"
Why Both Files Changed
The fix modified two files for complete protection:
pyproject.toml: Specifies the minimum safe version (>=3.11.6) so future installations get the fixuv.lock: Locks the exact resolved version to ensure reproducible builds and prevent accidental downgrades
This two-file approach is critical for Python projects using modern dependency management. Without updating the lock file, developers might still install the vulnerable version despite the pyproject.toml change.
Key Takeaways
- orjson 3.10.16 allows unbounded recursion when parsing deeply nested JSON, enabling trivial DoS attacks against any endpoint accepting JSON input
- The asn_coverage project's dependency files (
pyproject.tomlanduv.lock) required synchronized updates to enforce the minimum safe version across all environments - Small payloads can cause big damage: A JSON document with 5000 nesting levels can be just a few kilobytes but will crash most applications
- Library updates alone aren't enough: Implement application-level depth checks, size limits, and monitoring to detect exploitation attempts
- Lock files are security-critical: Always commit and update lock files (
uv.lock,poetry.lock,Pipfile.lock) when fixing dependency vulnerabilities to prevent version drift
How Orbis AppSec Detected This
- Source: JSON input from external sources (API requests, S3 objects, configuration files) processed by the asn_coverage application
- Sink:
orjson.loads()calls in version 3.10.16, which lacks recursion depth limits during deserialization - Missing control: No maximum nesting depth validation before or during JSON parsing
- CWE: CWE-674 (Uncontrolled Recursion)
- Fix: Upgraded orjson dependency from 3.10.16 to 3.11.6, which enforces recursion limits and rejects excessively nested JSON documents
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
CVE-2025-67221 demonstrates how a seemingly simple operation—parsing JSON—can become a critical security vulnerability when recursion isn't properly bounded. The upgrade from orjson 3.10.16 to 3.11.6 is essential for any Python application processing JSON from untrusted sources. Beyond this specific fix, developers should adopt a defense-in-depth approach: validate input depth, monitor for anomalies, and regularly scan dependencies for known vulnerabilities.
The asn_coverage project's fix serves as a model for responsible dependency management: update both the dependency specification and lock file, document the security rationale, and verify that the change preserves expected behavior. By staying vigilant about dependency security and implementing multiple layers of protection, you can build resilient applications that withstand both known and emerging threats.