#39910 [W&A-Medium] Numerous replay attacks (with arbitrary data) to protected endpoints are possible
Was this helpful?
Was this helpful?
Submitted on Feb 10th 2025 at 15:10:03 UTC by @anton_quantish for
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.
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.
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
and sig_counter
query params is checked
It's checked that the request sig_counter
param is greater than the lastCounter
global variable
The signature of request route path
and count
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).
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
.
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.
Initialize the lastCounter
to be equal to the current time on server start, it will prevent the same-server replay attack
Include 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