A basic example of a SSRF vulnerability.
- Clone the repo with
git clone https://github.com/CS4239-U6/blind-ssrf.git
. - Go into the folder with
cd basic-ssrf
. - Install the requirements with
pip3 install -r requirements.txt
. - Install
wkhtmltopdf
using instructions from https://github.com/JazzCore/python-pdfkit/wiki/Installing-wkhtmltopdf. - Run
python3 ./HiddenLocalServer/__main__.py
to run the hidden local server. - Run
python3 ./VulnerableServer/__main__.py
to run the web facing server. - Visit
http://localhost:5000
to see the website.
If you do not wish to set up the Python and wkhtmltopdf
dependencies above, an easier way to get started is using Docker.
Note: The Dockerfile may not work on a M1 Mac due to the architecture differences, which affects the
wkhtmltopdf
installation.
Make sure you have Docker installed locally before doing the following:
- Build the file with
docker build -t basic-ssrf .
. - Run the Dockerfile with
docker run -dt -p 5000:5000 basic-ssrf
. - Visit
http://localhost:5000
to see the website.
- (If running on Docker) Enter
http://localhost:5001
directly. You will see that you are unable to access the website. - Enter
http://localhost:5001
inhttp://localhost:5000
and the server will visit the webpage and fetch what is on the webpage.
An allowlist of allowed websites can be put in place in order to narrow the scope of the websites that the user can visit. This can be both in the sense of the URL schemas or even the URLs themselves. However, with many websites, the allowlist can accumulate and before very long.
Example of code that can be added:
ALLOWLIST = ('http://www.google.com',)
# ...
# Within the function that checks IP
url = request.form.get('url', '')
if url not in ALLOWLIST:
flash('URL is not in allowlist')
return redirect(url_for('index'))
# ...
A denylist of websites that are not allowed. The opposite of an allowlist. This can be both in the sense of the URL schemas or even the URLs themselves. Note that in this case it is very easy to miss out a website and this will result in a possible SSRF attack on the website itself.
It may also be avoided when the user changes the representation.
For example, localhost:3000
== 127.0.0.1:3000
== 127.1:3000
Example of code that can be added:
DENYLIST = ("127.0.0.1",)
# ...
# Within function that checks IP
ip = socket.gethostbyname(url)
if ip in DENYLIST:
flash('URL is in denylist')
return redirect(url_for('index'))
# ...
The input URLs can be filtered and check if it points to any internal service before they are are passed to the PDF downloader. This can be both in the sense of the URL schemas or even the URLs themselves.
Input sanitisation may contain loopholes it if makes use of replacements.
For example, if we replace all ../
with /
, ....//
-> ../
, the attacker is still able to do ../
.
Example of code that can be added:
from urllib.parse import quote
# Within the function that checks IP
url = request.form.get('url', '')
sanitized_url = quote(url, safe='/:?&')
# Work with sanitized_url
# ...
When the vulnerable server makes a request to the hidden local server, the vulnerable server will need to authenticate. Without the authentication details, the malicious user is unable to access any information.
This is to be implemented on all other servers including the vulnerable server.