Dangerous Requests
The Dangerous Requests package protects applications against SSRF (Server-Side Request Forgery) vulnerabilities when using the requests
library to make HTTP requests. The package protects against SSRF by providing means to blacklist dangerous requests and whitelist safe ones, and then patching the requests
library so that the rules are adhered to.
Under the hood, Dangerous Requests leverages on the advocate
library to deliver its functionality.
Server-side request forgery, or SSRF for short, is a type of vulnerability that allows an attacker to trick a server-side application into making network requests to an unintended location. This could be an internal service within the organization's network or an external service to which the application is authenticated.
SSRF can lead to the leakage of privileged information such as authorization credentials, which can allow an attacker to escalate their privileges and gain access to more systems/data.
Installation
The Dangerous Requests package can be installed using the command below:
pip install govtech-csg-xcg-dangerousrequests
Source code for the package can be found at https://github.com/GovTech-CSG/govtech-csg-xcg-dangerousrequests.
Setup
In order to enable Dangerous Requests, you will need to add the govtech_csg_xcg.dangerousrequests
app to the INSTALLED_APPS
list in your settings.py
file.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
...,
'govtech_csg_xcg.dangerousrequests',
]
Logging
The package uses its own logger with the name dangerousrequests
. By default, this logger uses a stream handler and emits messages with level INFO
and above. The log message format looks something like this:
2023-06-22 09:50:43,348 [INFO][XCG][dangerousrequests]: This is an informational message
.
To safely override the default logging configuration, you can write code in settings.py
to import the logger from the package and modify it directly. The example below sets the logger's level to DEBUG
:
from govtech_csg_xcg.dangerousrequests import logger
import logging
logger.setLevel(logging.DEBUG)
For more information on the Python logging library, see the official documentation.
Usage
Basics
Once Dangerous Requests is set up, you can use the requests
library as you usually would (e.g. response = requests.get('https://httpbin.org/anything')
). By default, dangerousrequests
will prevent any request made to localhost and private IP addresses (or domains that resolve to a private IP address). To whitelist or blacklist any other URLs or IP addresses, see the detailed configuration guide below. Any attempts to make a request for a disallowed URL/IP address will raise advocate.exceptions.UnacceptableAddressException
.
Dangerous Requests covers the following requests
methods:
requests.get
requests.delete
requests.head
requests.post
requests.put
requests.session
requests.options
requests.patch
requests.request
requests.Session
dangerousrequests
should work regardless of whether you invoke the methods via the requests
module object, or via a custom requests.Session
object that you create in your app.
Configuration
You can customize the behaviour of dangerousrequests
by providing a Python dictionary named XCG_SECURITY
with the key "requests" in settings.py
. Both "blacklist" and "whitelist" configuration approaches are supported. A basic example is shown below:
import ipaddress
XCG_SECURITY = {
'requests': {
'ip_blacklist': {ipaddress.ip_network("37.19.221.152")},
'ip_whitelist': {ipaddress.ip_network("127.0.0.1")},
'port_whitelist': {9001, 80 ,443},
'hostname_blacklist': {'google.com'},
'request_blacklist': {('example.com', 'GET', '/foo'), 'httpbin.org'},
'request_whitelist': {'httpbin.org', 'POST', '/post'},
'allow_ipv6': True,
}
}
The above configuration:
- Blocks requests to 37.19.221.152
- Allows requests to 127.0.0.1
- Allows requests made to ports 9001, 80, and 443 only
- Blocks requests made to google.com
- Blocks requests made to example.com/foo using the HTTP GET method
- Blocks all requests made to httpbin.org except for a POST request made to httpbin.org/post
- Allows requests made to IPv6 addresses (IPv6 is blocked by default)
The full set of supported configuration options are given in the table below:
Key | Value | Default |
---|---|---|
ip_blacklist | set of ipaddress.ip_network objects | Empty set |
ip_whitelist | set of ipaddress.ip_network objects | Empty set |
port_whitelist | set of int | {80, 443, 8443, 8000} |
port_blacklist | set of int | Empty set |
hostname_blacklist | set of str | Empty set |
request_blacklist | set of str and/or tuple | Empty set |
request_whitelist | set of str and/or tuple | Empty set |
Notes on IP address white/blacklisting
ip_whitelist
has priority overip_blacklist
- this allows you to blacklist a broad IP range but whitelisting specific subsets of that range.
Notes on port white/blacklisting
- All ports (excluding default whitelisted ports) are disallowed unless explicitly whitelisted.
port_blacklist
has priority overport_whitelist
.
Notes on request white/blacklisting
- Each element in the
request_blacklist
andrequest_whitelist
sets can be either:- A
str
, which should indicate a hostname (e.g. secretsmanager.ap-southeast-1.amazonaws.com). - A
tuple
in the form(HOSTNAME, HTTP_METHOD, PATH)
(e.g.('api.cloudflare.com', 'POST', '/anything')
).
- A
request_blacklist
andrequest_whitelist
hostnames support both wildcards (*
) and regex, but thePATH
value in tuple form only supports regex.request_whitelist
has priority overrequest_blacklist
- this allows you to blacklist generic requests to specific hostnames, but whitelist specific methods/paths.
Notes on hostname white/blacklisting
hostname_blacklist
has priority overrequest_whitelist
andrequest_blacklist
- e.g. if google.com is included inhostname_blacklist
, requests to it will be blocked even ifrequest_whitelist
allows it.