Boost _ Shardeum_ Ancillaries 33692 - [Websites and Applications - Low] Reflected XSS in validator node endpoints leads to node shutdown via validator-gui
Submitted on Fri Jul 26 2024 14:07:57 GMT-0400 (Atlantic Standard Time) by @neplox for Boost | Shardeum: Ancillaries
Report ID: #33692
Report type: Websites and Applications
Report severity: Low
Target: https://github.com/shardeum/validator-gui/tree/dev
Impacts:
Taking down the application/website
Injection of malicious HTML or XSS through metadata
Description
Brief/Intro
Validator GUI allows users to start running a Shardeum node, which means having the external endpoints be hosted using HTTP on a different port on the same server. This opens a door for client-side vulnerabilities which can be executed Same-Site (Same Origin vs Same Site info: https://web.dev/articles/same-site-same-origin#site). The only protection validator-gui has from attacks such as CSRF is Same-Site Strict cookies. As suggested from the name, they can't protect from attacks within the same site.
As such, achieving XSS on one of the validator node (Shardeum node) endpoints allows access to validator-gui cookies and authorized endpoints. Currently, this allows an attacker to leverage an XSS in order to shut down the node via validator-gui's API.
Vulnerability Details
The shardus-core (https://github.com/shardeum/shardus-core) part of the node defines lots of HTTP endpoints that return response using the res.send()
function that by default sends data with the text/html
content type.
An example of such endpoint:
https://github.com/shardeum/shardus-core/blob/4d75f797a9d67af7a94dec8860220c4e0f9ade3c/src/p2p/SyncV2/routes.ts#L74
As we can see from the code, the function would return res.status(404).send("validator list with hash '${expectedHash}' not found")
in case an incorrect hash supplied. The value of the hash
parameter would be inserted directly to the response with text/html
content type what in turn leads to XSS.
Because validator-gui runs the Shardeum node locally, on the same site, if we use this XSS to attack an authenticated user on the validator-gui dashboard, we will be able to request all the API endpoints within the validator-gui.
Impact Details
It's possible to request validator-gui API endpoints with authentication. Using the currently available endpoints, it can be used to completely stop the node. As the functionality of the dashboard grows, new different attack vectors can be discovered (such as configuration editing).
Possible solutions
Add a more robust CSRF protection for the validator-gui. For example, CSRF tokens. In this case, we won't be able to execute API requests from the node because we can't access CSRF token as we can't read data cross-site without a properly setted up CORS.
Don't send data with text/html content-type from the node. For example, you can use
res.type('txt')
before everyres.send()
call.
Proof of concept
Set up shardeum network
Clone the Shardeum repo and switch to the last commit on the dev branch, which is c7b10c2370028f7c7cbd2a01839e50eb50faa904 as of this POC's submission.
Switch to NodeJS 18.16.1, which is the version used by Shardeum in dev.Dockerfile and its various library requirements. For example, using asdf (https://asdf-vm.com/):
or
Apply the debug-10-nodes.patch for network setup.
Install dependencies and build the project.
Launch the network with 10 nodes as specified in the patch using the shardus tool.
wait for a bit
Set up validator-cli
change config in ./build/config.json
to https://gist.github.com/Sh1Yo/0251aae4ea821fc4242b0a6978931209 so that operator-cli will use the correct archive and start a node on a correct port.
Set up validator-gui
Modify the loginHandler
function from the api/auth.ts
, replace with the content below:
I've removed the password validation with validator-cli
to simplify the PoC. In the real life we assume that a victim is logged in to the validator-gui
with his own password.
Comment out the /node/status
route from api/handlers/node.ts
. For some reasons this endpoint causes fatal errors in the web server:
change the node version
install
build
generate keys
run
Exploit
Open https://localhost:8080/login
and enter any password so that you will have a valid session.
Start node by either making a post request to https://localhost:8080/api/node/start
or running operator-cli start
.
if everything is setted up right, you will be able to access http://localhost:9000. In the validator-cli/validator-logs.txt
there will be validator logs.
If there are errors then you can skip this step and use a random node to confirm XSS (the nodes are running on localhost:9001-localhost:9010) instead.
Now, we will request this route from api/handlers/node.ts
using an XSS attack:
To stop the node we just have to execute a POST request to /api/node/stop
(with cookies). To do so we can use the following js command:
fetch("https://localhost:8080/api/node/stop", {"credentials":"include","method":"post"})
So if we trick the victim to open our site or the link with the payload directly, we will be able to stop the node.
Open http://localhost:9000/validator-list?hash=<img src=x onerror='fetch("https://localhost:8080/api/node/stop", {"credentials":"include","method":"post"}) '>
you may want to use a different port like 9001-9010 if operator-cli wasn't able to start the node correctly
If there was a running node on localhost:9000, it's now stopped.
If a node on localhost:9000 wasn't running, you can still confirm that the XSS attack worked. While we can't read the body, we still can be assured that the request pass through. In the console we can see that the request returned status code: 200 - Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://localhost:8080/api/node/stop. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200
In the proxy, for example burp, we can see that the request successfully executed as well (see screenshot)
Last updated