Curl - это мощный и многофункциональный инструмент командной строки и библиотека для передачи данных с URL-адресами с поддержкой нескольких протоколов, включая HTTP и HTTPS, а также десятки других. Он поддерживает современные функции, такие как QUIC и TLS 1.3, и имеет множество параметров настройки.
Расширение PHP curl объединяет libcurl, базовое ядро Curl с PHP. Расширение curl поддерживает большинство функций, предлагаемых libcurl, и такой высокий уровень кастомизации требует внимательного подхода к настройке и безопасности.
Дэниел Стенберг и сотни других ярких контрибьютеров тратят много часов на разработку новых функций и устранение проблем с безопасностью, но не на всех серверах может быть установлена последняя версия Curl, а это означает, что необходимо активно защищаться от потенциальных уязвимостей безопасности.
Curl поддерживает более 25 протоколов, а PHP, в свою очередь, поддерживает многие из протоколов, которые поддерживает Curl. Сюда входят не только широко используемые протоколы HTTP и HTTPS, но также FTP, file, SCP, LDAP и многие другие.
Curl с радостью примет любой поддерживаемый протокол, и в этом заключается первая потенциальная уязвимость в системе безопасности.
При использовании Curl с указанным пользователем URL-адресом крайне необходимо проверить предоставленный URL-адрес, поскольку не все URL-адреса являются URL-адресами, относящимся к HTTP / HTTPS.
Приложение, которое принимает предоставленный пользователем URL-адрес, а затем возвращает или делает его доступным для пользователя в противном случае, делает возможным подделку запросов со стороны сервера (SSRF).
$url = $_POST['feed_url'];
$ch = curl_init($url);
curl_exec($ch);
Приведенный выше фрагмент может быть безопасным, если предоставленный URL-адрес является, к примеру, безопасным URL-адресом https://. Однако, поскольку для него отсутствует валидация, злоумышленник, предоставляющий созданный URL-адрес, может выполнить SSRF атаку.
$url = 'file:///etc/passwd'
$ch = curl_init($url);
curl_exec($ch);
Поскольку запрос Curl выполняется на веб-сервере, он может обходить брандмауэры и использовать внутренние сети, к которым не должны иметь доступ посторонние.
Например, FTP-сервер, не подключенный к Интернету, но соединенный внутренней сетью с веб-сервером, может принимать соединения, если пользователь предоставляет URL-адрес, например ftps://192.168.1.15/secrets.txt.
Curl поддерживает опцию CURLOPT_PROTOCOLS, которая принимает битовую маску протоколов, которые Curl должен принимать.
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
По умолчанию CURLOPT_PROTOCOLS настроен на прием любого URL, включая file://. Важно ограничить протоколы, которые принимает Curl, в качестве последней линии защиты. Все константы CURLPROTO_* описаны в документации curl_setopt.
Curl обладает функцией автоматического следования HTTP-перенаправлениям. Это открывает вектор атаки, когда URL, предоставленный пользователем, выглядит как безобидный URL, но удаленный сервер выдает ответ HTTP-перенаправления, который Curl автоматически выполняет.
Например, пользователь может предоставить правильный URL https://, но удаленный сервер отвечает HTTP-перенаправлением, которое ведет на file://, ftp:// или другую схему URL, которую приложение может не ожидать.
Начиная с libcurl 7.19.4, он больше не разрешает URL ftp:// и scp:// для перенаправления, но по-прежнему разрешает другие протоколы, что может открыть уязвимость в безопасности.
По умолчанию Curl не следит за перенаправлениями, и для этого необходимо включить опцию CURLOPT_FOLLOWLOCATION. Однако довольно часто можно встретить пользовательский PHP-код, который включает опцию CURLOPT_FOLLOWLOCATION.
По умолчанию Curl не следит за перенаправлениями. Если это не является абсолютно необходимым, не включайте опцию CURLOPT_FOLLOWLOCATION.
Как упоминалось в разделе выше, установка CURLOPT_PROTOCOLS является хорошей мерой против вредоносных протоколов; Curl откажется перенаправлять на определенное расположение (location), если оно не разрешено опцией CURLOPT_PROTOCOLS.
Кроме того, Curl поддерживает опцию CURLOPT_REDIR_PROTOCOLS для дальнейшего ограничения разрешенных протоколов URL перенаправления. Она принимает битовую маску значений CURLPROTO_*. Если эта опция не установлена, она наследуется от CURLOPT_PROTOCOLS.
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
В приведенном выше примере Curl примет URL http:// и https:// в качестве начального URL. Однако он откажется следовать дальше, если удаленный сервер ответит перенаправлением на URL http://.
Ни при каких обстоятельствах не используйте CURLPROTO_ALL, CURLPROTO_FILE или любые нежелательные протоколы для опций CURLOPT_PROTOCOLS и CURLOPT_REDIR_PROTOCOLS, так как это позволит злоумышленнику получить доступ к произвольным файлам, внутренним сетям или нежелательным схемам URL.
Другой вектор атаки с автоматическими перенаправлениями Curl заключается в том, что целевой сервер может ответить длинной последовательностью перенаправлений или, что еще хуже, циклом перенаправлений.
PHP внутренне обеспечивает защиту от этого, устанавливая значение по умолчанию для опции CURLOPT_MAXREDIRS равным 20.
Документация PHP для CURLOPT_FOLLOWLOCATION в настоящее время предупреждает, что PHP будет следовать неограниченному количеству перенаправлений. Эта информация неточна, и PHP на самом деле имеет значение CURLOPT_MAXREDIRS по умолчанию, установленное на 20.
Если автоматические перенаправления включены, и если ограничение max-redirects снято, это фактически остановит приложение на неопределенный срок из-за синхронной природы стандартных запросов Curl.
PHP внутренне устанавливает значение по умолчанию 20, чтобы предотвратить бесконечные циклы перенаправления. Однако это ограничение является чрезмерным для большинства веб-приложений. CURLOPT_MAXREDIRS принимает целое число, большее или равное -1.
-1 позволяет бесконечное число перенаправлений; 0 полностью запрещает перенаправления. Идеальным вариантом является установка разумного значения по умолчанию, например, 3 или 5.
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
По умолчанию Curl не устанавливает тайм-аут для запроса. Параметр INI PHP default_socket_timeout также не влияет на тайм-аут Curl.
Если не исчерпано время max_execution_time в PHP, или сетевой стек операционной системы не принял решение о завершении запроса, вредоносный или неисправный удаленный сервер может заставить запрос Curl ждать неопределенное время, просто не отправляя никаких данных после получения запроса.
Опция CURLOPT_TIMEOUT принимает целочисленное значение количества секунд, которое Curl может ждать каждый запрос.
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
Обратите внимание, что этот тайм-аут применяется к общему времени, даже если Curl настроен на следование перенаправлениям. Это не значение таймаута для каждого перенаправления, если сервер отвечает перенаправлениями.
Curl делает отличную работу, устанавливая нормальные значения по умолчанию для проверки сертификата TLS (HTTP). Однако в большинстве PHP-приложений есть несколько плохих примеров и быстрых исправлений, которые заставляют Curl не проверять сертификат (путем проверки доменных имен, для которых выпущен сертификат) и его аналогов.
По умолчанию Curl настроен на проверку хоста и пиров (peers). Лучшая практика - не изменять значения по умолчанию.
Однако, чтобы убедиться, что параметры установлены правильно, можно явно установить CURLOPT_SSL_VERIFYPEER и CURLOPT_SSL_VERIFYHOST.
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
CURLOPT_SSL_VERIFYHOST заставляет Curl проверять доменные имена, для которых был выпущен сертификат, на наличие (значение 1), и включает имя соединяющего домена (значение 2). Распространенной ошибкой при использовании этой опции является то, что значение установлено в 1. Для эффективной проверки сертификата оно должно быть равно 2. В новых версиях Curl, чтобы преодолеть это заблуждение, опцию 1 заменяют на 2.
CURLOPT_SSL_VERIFYPEER включает проверку эмитента сертификата. Установка этой опции в true означает, что Curl будет криптографически проверять, что сертификат был выдан центром сертификации (ЦС), которому Curl доверяет. Если установить значение "false", Curl будет принимать самоподписанные сертификаты, что практически не обеспечивает никакой защиты.
Curl поддерживает SSL v2, v3 и TLS версий от 1.0 до 1.3. Сегодня безопасными считаются только TLS 1.2 и TLS 1.3.
Фактически, большинство браузеров больше не принимают соединения с SSL, а также TLS 1.0 и TLS 1.1. Даже те браузеры, которые продолжают поддерживать эти устаревшие версии TLS 1.0 и TLS 1.1, делают это со строгими предупреждениями.
Curl будет выбирать наиболее безопасную версию TLS при установлении защищенного соединения, однако Curl все еще поддерживает небезопасные версии TLS 1.0 и 1.1 по причинам совместимости.
Можно отказаться от поддержки небезопасных версий TLS и поддерживать только TLS 1.2 и выше (при этом TLS 1.3 является последней версией), чтобы Curl не использовал потенциально небезопасные схемы шифрования и протоколы.
Опция CURLOPT_SSLVERSION Curl управляет границами версий SSL и TLS, которые должен поддерживать Curl.
curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
CURLOPT_SSLVERSION принимает одно из *`CURLSSLVERSION** [значений] (https://www.php.net/manual/function.curl-setopt.php). Настоятельно рекомендуется *не* задавать одно из значений *CURL_SSLVERSIONMAX*`, так как они будут эффективно препятствовать использованию расширением Curl современных протоколов.
CURL_SSLVERSION_TLSv1_2 - идеальное значение, поскольку оно отключает все небезопасные протоколы (а именно, SSL v2, v3, и TLS 1.0 и 1.1), и включает все современные версии TLS (TLS 1.2 и более поздние).
Обратите внимание, что поддержка версий TLS зависит от версии OpenSSL, на основе которой был создан Curl. Настоятельно рекомендуется обновить операционную систему сервера, если система не поддерживает TLS 1.2.
Ниже перечислены некоторые экстра варианты по усилению безопасности. Однако для их работы необходимо, чтобы удаленный сервер был правильно настроен, а PHP был создан с использованием более новых версий Curl. Используйте их с осторожностью!
Curl поддерживает такие механизмы, как OCSP, которые могут проверить у эмитента сертификата, действителен ли еще сертификат.
Усовершенствованием запросов Curl может быть запрос OCSP статуса сертификата сервера, чтобы убедиться, что сертификат не отозван.
Обратите внимание, что Curl не включает проверку OCSP по умолчанию. Если сервер не сможет предоставить действительный OCSP-ответ, Curl будет работать с ошибками, и это может привести к поломке многих легитимных веб-сайтов.
Когда опция CURLOPT_SSL_VERIFYSTATUS установлена в true, Curl проверяет, что сервер скрепляет OCSP-ответы во время TLS рукопожатия. Есть веб-сайты, которые, имеют эту функцию, но не все серверы ее имеют.
curl_setopt($ch, CURLOPT_SSL_VERIFYSTATUS, true);
Обратите внимание, что если CURLOPT_SSL_VERIFYSTATUS включен, и если сервер не сшивает действительный OCSP-ответ, Curl откажет в этом соединении. Отсутствие сшивки ответа OCSP не означает, что соединение TLS небезопасно.
Когда Curl подключается к удаленному URL, он преобразует доменное имя в IP-адрес с помощью системного DNS-резольвера. По своей природе DNS-запросы и ответы могут быть подслушаны или подделаны, если злоумышленнику удастся перехватить DNS-сервер или маршрут к нему.
Хотя системный DNS-резольвер может подтвердить подлинность DNS-ответа с помощью DNSSEC, он не может предотвратить цензуру или помешать наблюдению за ними.
DNS-over-HTTPS - это новый стандарт, согласно которому HTTP-клиент взаимодействует с DNS-сервером, используя зашифрованное HTTPS-соединение. Это предотвращает фальсификацию запросов DNS и подслушивание запросов.
В Curl версии 7.62 и более поздних Curl поддерживает DNS-over-HTTPS, который использует HTTPS-соединение с выбранным DNS-сервером.
PHP 8.1 добавляет DNS over HTTPS в Curl, и он может использовать поддержку DoH, если базовый libcurl поддерживает ее.
if (defined('CURLOPT_DOH_URL')) {
curl_setopt($ch, CURLOPT_DOH_URL, 'https://cloudflare-dns.com/dns-query');
}
Приведенный выше пример настраивает Curl на использование службы DNS CloudFlare 1.1.1.1, если Curl поддерживает DoH.
Если сделать шаг вперед, то специализированные DNS-серверы, например, блокирующие домены вредоносного ПО, также могут представлять интерес.
Последняя версия Curl добавляет поддержку HTTP Strict Transport Security. Она пока не поддерживается в расширении PHP curl, но может появиться в будущей версии.
HSTS позволяет серверу отправлять HTTP-заголовок, который просит клиента запомнить, чтобы в будущем он всегда использовал HTTPS и никогда не использовал HTTP в течение определенного периода времени. Если сделать шаг вперед, то Curl будет поддерживать функцию HSTS Preload, которая позволяет Curl ссылаться на статический файл со списком всех доменов, с которыми он должен всегда соединяться по HTTPS.
Например, devrating.org посылает HSTS-заголовок и даже поддерживает HSTS Preload, включая devrating.org в списки HSTS Preload почти всех браузеров, чтобы всегда использовать HTTPS. Поддержка HSTS и HSTS Preload в Curl означает, что Curl может ссылаться на подобный список доменов и быть уверенным, что он всегда будет использовать HTTPS при подключении к этому домену. Это работает, даже если исходный URL или URL перенаправления - http://.
1. Ограничение протоколов Curl
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
2. Не включайте автоматические перенаправления, если в этом нет крайней необходимости
3.Если перенаправления включены, ограничьте разрешенные протоколы (если они отличаются от приведенного выше №1).
curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
4. Если перенаправления включены, установите строгое ограничение
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
5. Установите строгий тайм-аут
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
6. Не отключайте проверку сертификации
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
7. Отключите небезопасные версии SSL и TLS
curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
Ниже приведены некоторые дополнительные меры по усилению безопасности, которые, возможно, подойдут не всем.
1. Требовать сшитые ответы OCSP
curl_setopt($ch, CURLOPT_SSL_VERIFYSTATUS, true);
2. Используйте DNS over HTTPS (PHP >= 8.1)
if (defined('CURLOPT_DOH_URL')) {
curl_setopt($ch, CURLOPT_DOH_URL, 'https://cloudflare-dns.com/dns-query');
}
3. HSTS и предварительная загрузка HSTS (в новых версиях PHP Curl)