#39910 [W&A-Medium] Numerous replay attacks (with arbitrary data) to protected endpoints are possible
Submitted on Feb 10th 2025 at 15:10:03 UTC by @anton_quantish for Audit Comp | Shardeum: Ancillaries III
Report ID: #39910
Report Type: Websites and Applications
Report severity: Medium
Target: https://github.com/shardeum/json-rpc-server/tree/itn4
Impacts:
Taking and/modifying authenticated actions (with or without blockchain state interaction) on behalf of other users without any interaction by that user, such as:
Changing registration information
Commenting
Voting
Making trades
Withdrawals, etc.
Description
Brief/Intro
An attacker has the opportunity to, being able to intercept signed '/api/subscribe' (or any other protected endpoint) request once, execute it on multiple other JSON-RPC servers with arbitrary request body.
Vulnerability Details
Some of the API endpoints are protected with the debugMiddleware
, for instance:
/api/subscribe
which changes the main validator node the JSON-RPC server communicates to;counters debug endpoints;
logs debug endpoints.
This protection allows them to be used only either if the JSON-RPC server is in debug mode, or if the request is signed with the developer key.
Below is the corresponding check code: https://github.com/shardeum/json-rpc-server/blob/616fe1007568db801b0433ece9ef822a0e39d5f6/src/middlewares/debugMiddleware.ts#L47-L81
If the request to a protected endpoint received, and the server is not in the debug mode:
The presence of
sig
andsig_counter
query params is checkedIt's checked that the request
sig_counter
param is greater than thelastCounter
global variableThe signature of request
route path
andcount
is checked to be valid for any of the developer authorized public keys.
There are two vulnerabilities:
The
lastCounter
is set to 0 when the server starts, which means that the attacker can re-send the signed request to:
the same server after it has been restarted – the signature will be valid, and the count will be greater than 0;
any other JSON-RPC server which has not received any protected calls yet (it's a default state of any server) or has received them earlier than the intercepted request;
The
request data
is not included in the signed object, which means that the attacker can arbitrary alter the request params.
Moreover, the /api/subscribe
request is HTTP-based (without encryption) and uses the GET method. All the data, including the signature, is contained in the request URL. This means that it could easily be intercepted or even logged by various network devices, such as routers (and then intercepted from there).
Impact Details
Thus, being able to once intercept the signed request to the /api/subscribe
endpoint of any JSON-RPC server, the attacker can then arbitrary change the validator node ip and/or port inside the request, and successfully re-send it to other JSON-RPC servers having the same dev public keys configured that, in turn, can paralyze or disrupt the whole network.
The other debug endpoints are also vulnerable so the requests to them could also be forged.
Despite the impact falls under critical, the initial request intercepting is needed, so I downgraded the severity to High
.
Proof of Concept
First, let's imitate the authorized /api/subscribe
request.
With node REPL:
You will see the request signature.
Send it with curl:
You should see the request is successfully processed (authorization is passed).
Now, as an attacker, arbitrary change the ip
and/or port
variables within the request and send it to any other JSON-RPC server. You will see the request is successfully processed again and the JSON-RPC server's validator node is changed. You can send it to as many servers as you want if they hasn't received any signed requests yet (because it bumps the internal replay-protection counter) or if they has received them earlier than the counter in your intercepted request.
Mitigation
Initialize the
lastCounter
to be equal to the current time on server start, it will prevent the same-server replay attackInclude something instance-specific in the request signature (server IP maybe or something similar), it will prevent the cross-server replay attacks
It's better to also sign the request data itself, not only the route
Was this helpful?