The 'query-certificate' endpoint calls queryCertificateHandler which is a massive function, which verifies everything first, then queries the certificate and then gathers signatures from the nearest peers. This endpoint can be used for performing a DOS and increasing load on the node by continously querying certificate using the same request.
There is a second weakpoint also. The request can be sent by anybody, he just needs to sign it, as in the verification it only checks if the signature matches the payload and not if owner==nominee (Although this can be a design feature by shardeum so I am not heavily focusing on this point).
Recommendation
Use trySpendServicePoints function to implement rate limiting in this endpoint.
Impact Details
-> Increased strain on the node
-> Endpoint can be used for DOS/DDOS
The Proof of concept has the following flow -
-> Spin up a node
-> Stake for the node (on bb-testnet)
-> Call the 'query-certificate' endpoint continously on any of the active node on the testnet
So first we have to spin up a node on the bb-testnet. We need to modify config.json in the shardeum repo first
Now let us spin up our node, compile the project and run node ./dist/src/index.js. This will spin up the node and will produce a secrets.json which contains our node's public and private key. Now let us stake some SHM.
import { ethers } from "ethers";
import axios from "axios";
// Define the main asynchronous function
const main = async () => {
try {
// Initialize the provider
const provider = new ethers.JsonRpcProvider(
"http://34.132.212.252:8000",
8082,
{ batchMaxCount: 1 }
);
// Initialize the wallet with the provider
const walletWithProvider = new ethers.Wallet(
"YOUR PRIVATE KEY HERE", //ADD YOUR PRIVATE KEY HERE
provider
);
// Define the required stake as a BigInt
const stakeRequired = BigInt("0x8ac7230489e80000");
// Sign the transaction
const raw = await walletWithProvider.signTransaction({
to: "0x0000000000000000000000000000000000010000",
gasPrice: "30000",
gasLimit: 30000000,
value: stakeRequired,
chainId: 8082,
data: ethers.hexlify(
ethers.toUtf8Bytes(
JSON.stringify({
isInternalTx: true,
internalTXType: 6, //InternalTXType.Stake
nominator: walletWithProvider.address.toLowerCase(),
timestamp: Date.now(),
nominee: "YOUR NODE PUBLIC KEY HERE", //CHANGE TO YOUR NODE'S PUBLIC KEY
stake: { dataType: "bi", value: stakeRequired.toString(16) },
})
)
),
nonce: await walletWithProvider.getNonce(),
});
// Inject the transaction
const response = await axios.post("http://34.23.142.144:9001/inject", { // random active node's ip
raw,
timestamp: Date.now(),
});
console.log("Transaction injected successfully:", response.data);
} catch (error) {
console.error("An error occurred:", error);
}
};
// Execute the main function
main();
ok, so now staking done, now let us sign the payload.
Signing the payload and sending requests can be done in one step but I did these separately, you can do it all at one time.