ssrf.md 4.4 KB

[ssrf] Server Side Request Forgery

Server Side Request Forgery - уязвимость, позволяющая выполнять различного рода запросы от имени веб-приложения (в нашем случае от имени Nginx). Возникает, когда атакующий может контролировать адрес проксируемого сервера (второй аргумент директивы proxy_pass).

Как самостоятельно обнаружить?

Наиболее распространенно два класса ошибок конфигурации, которые приводят к этой проблеме:

  • отсутствие директивы internal. Её смысл заключается в указании того, что определенный location может использоваться только для внутренних запросов;
  • небезопасное внутреннее перенаправление.

Отсутствие директивы internal

Классический пример уязвимости типа SSRF в виду отсутствия директивы internal выглядит следующим образом:

location ~ /proxy/(.*)/(.*)/(.*)$ {
    proxy_pass $1://$2/$3;
}

Злоумышленник, полностью контролируя адрес проксируемого сервера, может выполнять произвольные запросы от имени Nginx.

Небезопасное внутреннее перенаправление

Подразумевается, что в вашей конфигурации есть internal location, которые использует какие-либо данные из запроса в качестве адреса проксируемого сервера.

Например:

location ~* ^/internal-proxy/(?<proxy_proto>https?)/(?<proxy_host>.*?)/(?<proxy_path>.*)$ {
    internal;

    proxy_pass $proxy_proto://$proxy_host/$proxy_path ;
    proxy_set_header Host $proxy_host;
}

Согласно документации Nginx внутренними запросами являются:

  • запросы, перенаправленные директивами error_page, index, random_index и try_files;
  • запросы, перенаправленные с помощью поля “X-Accel-Redirect” заголовка ответа вышестоящего сервера;
  • подзапросы, формируемые командой “include virtual” модуля ngx_http_ssi_module и директивами модуля ngx_http_addition_module;
  • запросы, изменённые директивой rewrite.]>

Соответственно, любой "неосторожный" реврайт позволит злоумышленнику сделать внутренний запрос и контролировать адрес проксируемого сервера.

Пример плохой конфигурации:

rewrite ^/(.*)/some$ /$1/ last;

location ~* ^/internal-proxy/(?<proxy_proto>https?)/(?<proxy_host>.*?)/(?<proxy_path>.*)$ {
    internal;

    proxy_pass $proxy_proto://$proxy_host/$proxy_path ;
    proxy_set_header Host $proxy_host;
}

Что делать?

Есть несколько правил, которых стоит придерживаться в подобного рода конфигурациях:

  • использовать только internal location для проксирования;
  • по возможности запретить передачу пользовательских данных;
  • обезопасить адрес проксируемого сервера:
    • если количество проксируемых хостов ограниченно (например, у вас S3), то лучше их захардкодить и выбирать при помощи map или иным удобным для вас образом;
    • если по какой-то причине нет возможности перечислить все возможные хосты для проксирования, его стоит подписать.