JSON-RPC API security: Preventing Batching Attacks and Request Smuggling
JSON-RPC API security requires defending against batching attacks. Learn how to prevent resource exhaustion and request smuggling in your Node.js and PHP apps.
We once had a microservice handling internal state synchronization that started spiking CPU usage every time a specific client performed a "bulk" sync. It turned out the client was sending thousands of JSON-RPC requests in a single HTTP POST body. The server, trying to be helpful, parsed the entire array and attempted to process every call sequentially, locking up the event loop for around 280ms per request.
That experience taught me that JSON-RPC batching—intended to reduce network round trips—is a double-edged sword. If you don't constrain it, you're essentially handing an attacker a tool to trigger resource exhaustion with a single, well-crafted payload.
Understanding the JSON-RPC Batching Attack Vector
The JSON-RPC 2.0 specification allows a client to send an array of request objects instead of a single object. When your server receives this, it naturally iterates through the array and executes each method.
The danger arises when you lack a "cost" model for these operations. An attacker can send a batch containing 500 requests to a computationally expensive method, such as a cryptographic hash or a complex database query. While the server tries to process these, the thread (or event loop) becomes blocked. In PHP, this might hit your max_execution_time and cause a 500 error; in Node.js, it stops the event loop, effectively deadlocking the entire service for all other users.
Furthermore, if your middleware parses the entire body before validating the batch size, you’re already vulnerable to memory exhaustion attacks.
Mitigating Resource Exhaustion
To secure your endpoints, you need to enforce strict boundaries on what constitutes an acceptable batch.
1. Limit Batch Size
Never allow an arbitrary number of operations. A hard cap is the simplest defense. In most production scenarios, a batch size of 10 to 20 is more than sufficient.
2. Implement Timeouts and Concurrency Limits
If you are using Node.js, use a semaphore or a queue to limit concurrent processing of batch items. Don't just map() over the array and execute; use a library like p-limit to ensure you aren't overwhelming your database connections.
JAVASCRIPT// A simple Node.js pattern to limit concurrent RPC calls const pLimit = require(CE9178">'p-limit'); const limit = pLimit(5); // Only 5 concurrent operations async function handleBatch(requests) { const tasks = requests.map(req => limit(() => executeRpc(req))); return Promise.all(tasks); }
Preventing Request Smuggling and Injection
Beyond exhaustion, you have to worry about how your parser handles malformed or nested payloads. When the input isn't strictly validated, you risk request smuggling if your proxy server (like Nginx) and your backend interpret the Content-Length or Transfer-Encoding headers differently.
Always ensure your JSON-RPC parser is isolated. If you are using PHP, avoid using json_decode on raw input without first verifying the Content-Type header is strictly application/json. Similar to the lessons we learned in Preventing HTTP Parameter Pollution: Secure Request Parsing Guide, consistency is your best friend.
Comparison of Mitigation Strategies
| Strategy | Complexity | Effectiveness | Best For |
|---|---|---|---|
| Batch Size Cap | Low | High | Preventing immediate DoS |
| Rate Limiting | Medium | High | Preventing brute-force/exhaustion |
| Schema Validation | Medium | Medium | Preventing malformed injection |
| Request Queuing | High | High | Handling high-load environments |
Schema Validation is Non-Negotiable
Just as we emphasize in JSON-Patch API security: Stop Injection and Data Corruption, you must validate the structure of every single RPC call within the batch. An attacker might try to "smuggle" unauthorized method calls by embedding them inside a batch that contains one legitimate request.
Use a validation library like ajv in Node.js or justinrainbow/json-schema in PHP to ensure each request object matches your expected schema before it touches your business logic.
JSON// Example of a schema that enforces strict method names { "type": "object", "properties": { "jsonrpc": { "const": "2.0" }, "method": { "enum": ["get_user", "get_status"] }, "params": { "type": "object" } }, "required": ["jsonrpc", "method"] }
What I'd Do Differently Next Time
Looking back, we spent too much time trying to optimize the database queries instead of just capping the batch size. We also initially ignored the potential for nested arrays if the parser wasn't strict enough—always ensure your parser doesn't recursively handle arrays in ways you didn't intend.
If you are dealing with complex API requirements, consider if JSON-RPC is the right tool. Sometimes, moving to a more structured approach like GraphQL security: Preventing Batching Attacks and Resource Exhaustion gives you better native tooling to handle these exact problems.
Are you still relying on basic input sanitization? Start by implementing a hard batch limit today. It’s a low-effort change that prevents the most common JSON-RPC API security failures.
FAQ
Q: Should I disable batching entirely? A: If your client doesn't explicitly need it, yes. Disabling batching removes the attack vector entirely.
Q: How do I handle partial failures in a batch? A: The JSON-RPC 2.0 spec allows for individual error responses within a batch. Ensure your error handler doesn't leak system stack traces when one item fails.
Q: Does this protect against all DoS? A: No. It only protects against batch-specific resource exhaustion. You still need global rate limiting at your load balancer or gateway level.