Загрузка по ссылке от посетителя - защита от запросов во внутреннюю сеть
Принимаем ссылку от посетителя и загружаем по ней данные так, чтобы сервер не стал разведчиком по внутренней сети проекта.
Что нужно знать заранее
Запрос по чужой ссылке уходит с сервера, а не из браузера посетителя. Значит, ему доступны адреса, закрытые снаружи: соседние службы, панель хостинга, база данных и внутренние сервисы сети.
Проверка присланного адреса обычным сравнением строк сама по себе ничего не гарантирует. Домен может указывать на внутренний адрес, а редирект - увести проверенный адрес на любой другой уже после проверки.
Опасность создаёт не сама загрузка, а доверие к присланному адресу без единой проверки. Набор мер при этом короткий: запрет частных адресов, белый список хостов и проверка содержимого полученного файла.
Шаги
- Запретить HTTP-клиенту обращения к частным адресам, доступным только изнутри сервера.
- Проверить схему и хост присланного адреса по белому списку до запроса.
- Ограничить время ожидания ответа, его размер и число переходов по редиректам.
- Определить тип загруженного файла по его содержимому, а не по расширению адреса.
- Записывать отклонённые адреса в журнал вместе с сетевым адресом отправителя запроса.
Решение
Запрещаем клиенту ходить во внутреннюю сеть:
$http = new \Bitrix\Main\Web\HttpClient();$http->setPrivateIp(false); // запрет обращений к частным адресам сети$http->setTimeout(5); // чужой сервер не должен держать наш процесс$body = $http->get($uri);Один вызов закрывает главный сценарий атаки: обращение к службам, доступным только изнутри сервера. Настройку ставят до запроса, иначе клиент успевает сходить по присланному адресу.
Проверяем схему и хост до запроса:
$parsed = new \Bitrix\Main\Web\Uri($uri);$allowed = ['images.example.com', 'cdn.example.com'];if ($parsed->getScheme() !== 'https' || !in_array($parsed->getHost(), $allowed, true)) { throw new \RuntimeException('адрес не разрешён'); // сравнение хоста целиком}if ($parsed->getPort() && $parsed->getPort() !== 443) { throw new \RuntimeException('порт не разрешён'); // службы сети висят на своих портах}Белый список хостов надёжнее любого чёрного списка адресов. Сравнивают хост целиком и точно: проверка вхождения подстроки пропускает домен вида «images.example.com.чужой-сайт.ru».
Ограничиваем переходы по редиректам:
$http->setRedirect(false); // проверенный адрес не уведут на другой$http->setStreamTimeout(10); // ограничение на медленную отдачу ответаРедирект - обычный способ обойти проверку хоста. Разрешённый адрес отвечает переходом на внутренний, и без запрета переходов клиент послушно идёт туда сам.
Отсекаем слишком большой ответ:
$size = (int)$http->getHeaders()->get('Content-Length');if ($size > 5 * 1024 * 1024) { throw new \RuntimeException('ответ слишком большой'); // память и диск не резиновые}Загружаем картинку по ссылке безопасно:
$file = \CFile::MakeFileArray($uri);if ($file && \CFile::CheckImageFile($file) === null) { // null означает: файл корректен $fileId = \CFile::SaveFile($file, 'reviews');}// проверка идёт по содержимому файла, а не по расширению в адресеПроверка содержимого отсекает скрипт, названный картинкой. Расширение в адресе ничего не значит: имя выбирает отправитель, а разбирать файл будет уже ваш сервер.
Записываем отклонённые адреса в журнал:
\Bitrix\Main\Diag\Debug::writeToFile( ['uri' => $uri, 'ip' => $_SERVER['REMOTE_ADDR']], date('H:i:s'), 'ssrf.log');// поток отклонений от одного адреса - признак подбора внутренних службЖурнал отклонений отличает случайную ошибку посетителя от целенаправленного перебора внутренних адресов сети. Поток отказов с одного адреса - достаточный повод закрыть загрузку по ссылке для неавторизованных гостей.
Типичные проблемы
Через форму загрузки по ссылке читают внутренние службы.
HTTP-клиенту не запрещены частные адреса внутренней сети. Запрет ставят отдельной настройкой клиента до первого запроса по ссылке.
Проверка хоста прошла, а запрос ушёл на внутренний адрес.
Разрешённый сервер ответил переходом на внутренний адрес, и клиент послушно пошёл дальше. Переходы запрещают целиком или проверяют хост заново после каждого из них.
Белый список обходят похожим доменом.
Хост проверен поиском подстроки внутри присланного адреса, а не точным сравнением. Сравнивают хост целиком и точно, иначе похожий домен проходит проверку.
Вместо картинки сохранён скрипт.
Тип файла определили по расширению в присланном адресе, которое выбирает отправитель. Загруженный файл проверяют по его содержимому штатными средствами платформы, а не по имени.
Загрузка по ссылке подвешивает страницу.
Ограничения времени ожидания ответа не заданы ни для соединения, ни для чтения. Чужой медленный сервер держит процесс до предела времени выполнения скрипта.
Проверку сертификата отключили ради работы.
Так соединение с чужим сервером становится доступным для подмены ответа. Доверенные сертификаты на сервере чинят настройкой системы, а проверку подлинности не отключают.
Частые вопросы
Чем опасна загрузка по ссылке?
Запрос уходит с вашего сервера и достаёт то, что закрыто снаружи: внутренние службы и панели. Поэтому адресу от посетителя не доверяют без проверки.
Достаточно ли запретить частные адреса?
Это главный шаг, но не единственный. Добавляют белый список хостов, запрет переходов и ограничения времени, иначе проверку обходят редиректом.
Как проверить, что по ссылке действительно картинка?
Загрузить файл средствами платформы и проверить его содержимое. Успешной проверкой считается пустой результат, а расширение в адресе ничего не значит.
Можно ли отключить проверку сертификата?
Нет, это открывает соединение для подмены ответа. Проблему решают установкой доверенных сертификатов на сервере.
Нужна ли эта защита при загрузке файла с компьютера?
Там нет запроса с сервера, поэтому этот риск отпадает. Проверка содержимого файла нужна в обоих случаях одинаково.
Смежное
- Защита сайта - оглавление подтемы
- Данные от посетителя: экранирование, проверка, сеанс - приём данных формы целиком
- Базовая защита сайта: обновления, доступы, проактивная защита - что закрывают в первую очередь
- Вызов внешнего сервиса из кода: таймауты, повторы, журнал - тот же клиент в обычной интеграции
- Отдача файла с проверкой прав: скачивание мимо каталога - обратная задача с файлами
- Шифрование, JWT и CAPTCHA - устройство защитных механизмов
- Защита сайта на практике - раздел безопасности целиком