I have a Django application running on an Nginx + Gunicorn server where I use DRF throttling. Whenever I make API requests to my server and change the X-Forwarded-For header value in the client I’m then able to bypass the throttling for unauthenticated users and thereby have unlimited access to the API. This is of course not desired.
I think a way to deal with this is to have Nginx append the real IP to the end of the X-Forwarded-For request header before it reaches the server by using proxy params. It just doesn’t seem to change the header when I inspect Postman / RapidApi client. I assume that’s what causes the error but ultimately I don’t know.
Nginx conf:
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
The proxy_params file from Nginx includes setting the X-Forwarded-For request header like so:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Can somebody tell me what I’m doing wrong and how to fix it so you can’t make unlimited API requests? If you need more information or clarification please let me know.
2
Answers
I was able to properly retrieve the client's IP address by setting the NUM_PROXIES setting in the DRF configuration. This setting determines how many proxy servers the DRF should trust in the XFF header and is therefore able to pick the right IP address. Since I only had one proxy server, I set NUM_PROXIES to 1:
In addition to this, I changed the Gunicorn log format to be able to observe the XFF header in the access logs. I then realized that nginx did in fact correctly append the IP to the XFF header.
DRF throttling is not a reliable solution to mitigate DDOS attacks. There are some known vulnerabilities to bypass DRF throttling in the wild:
It is highly recommended to use other 3rd party solutions for DDOS and brute force mitigation.
You can customize DRF throttling to patch the mentioned vulnerability. But keep in mind it is not the secure solution!
DRF throttling uses
X-Forwarded-For
HTTP header to generate a key to limit access. As described in the official docs, customization is implemented by inheritingthrottling.BaseThrottle
:Request header is available in
allow_request()
and you can use other fields (like User-Agent)to check the uniqueness of the request originator. You can also add a little randomness.Check here for a list of HTTP header fields.
Note:
allow_request()
should return a boolean.Note: Checking other fields patches only your mentioned vulnerability, and it is just better than the defaults.