Обход дросселирования DRF
У меня есть приложение Django, работающее на сервере Nginx + Gunicorn, где я использую DRF-дросселирование. Всякий раз, когда я делаю API запросы к моему серверу и изменяю значение заголовка X-Forwarded-For в клиенте, я могу обойти дросселирование для неаутентифицированных пользователей и тем самым получить неограниченный доступ к API. Это, конечно, нежелательно.
Я думаю, что способ справиться с этим заключается в том, чтобы Nginx добавлял реальный IP в конец заголовка запроса X-Forwarded-For до того, как он достигнет сервера, используя прокси-параметры. Это просто не изменяет заголовок, когда я проверяю Postman / RapidApi клиент. Я предполагаю, что именно это и вызывает ошибку, но в конечном итоге я не знаю.
Nginx conf:
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
Файл proxy_params от Nginx включает установку заголовка запроса X-Forwarded-For следующим образом:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Может кто-нибудь подсказать, что я делаю не так и как это исправить, чтобы нельзя было делать неограниченное количество запросов к API? Если вам нужна дополнительная информация или разъяснения, пожалуйста, дайте мне знать.
Дросселирование DRF не является надежным решением для смягчения последствий DDOS-атак. Существует несколько известных уязвимостей для обхода дросселирования DRF в природе:
- уязвимость безопасности: обход дросселирования
- Обход дросселирования на основе ip-адреса источника
- Внедрение дросселирования API Django-rest и обхода без аутентификации
Настоятельно рекомендуется использовать другие сторонние решения для защиты от DDOS и грубой силы.
Вы можете настроить дросселирование DRF для устранения упомянутой уязвимости. Но имейте в виду, что это не безопасное решение!
DRF throttling использует X-Forwarded-For
HTTP-заголовок для генерации ключа для ограничения доступа. Как описано в официальных документах, настройка осуществляется путем наследования throttling.BaseThrottle
:
class RandomRateThrottle(throttling.BaseThrottle):
def allow_request(self, request, view) -> bool:
# Your custom logic here...
Заголовок запроса доступен в allow_request()
и вы можете использовать другие поля (например, User-Agent) для проверки уникальности создателя запроса. Вы также можете добавить немного случайности.
Посмотрите здесь список полей заголовков HTTP.
Примечание: allow_request()
должен возвращать булево значение.
Примечание: Проверка других полей исправляет только упомянутую вами уязвимость, и это просто лучше, чем значения по умолчанию.
Я смог правильно получить IP-адрес клиента, установив параметр NUM_PROXIES в конфигурации DRF. Этот параметр определяет, скольким прокси-серверам DRF должен доверять в заголовке XFF и, следовательно, сможет выбрать правильный IP-адрес. Поскольку у меня был только один прокси-сервер, я установил NUM_PROXIES равным 1:
REST_FRAMEWORK = {
…
'NUM_PROXIES': 1
}
В дополнение к этому я изменил формат журнала Gunicorn, чтобы иметь возможность наблюдать заголовок XFF в журналах доступа. Затем я понял, что nginx действительно правильно добавляет IP в заголовок XFF.