18 июля была опубликована информация о наборе уязвимостей, получившем название HTTPoxy. Используя его, злоумышленники могут подменять переменную окружения HTTP_PROXY, что позволяет им перенаправлять http-запросы к веб-приложениям на свои ресурсы.
Уязвимость была выявлена при участии разработчика компании Vend Доменика Шайрлинка (Dominic Scheirlinck), который в своем блоге на Medium рассказал о том, как она была обнаружена его коллегами в ходе разбора одного из тикетов, поступившем в службу поддержки.
Как это работает
Шайрлинк подробно объясняет принцип работы HTTPoxy. Типичная атака с использованием этого набора уязвимостей выглядит так:
- Атакующий создает специально составленный HTTP-запрос, в котором содержится заголовок Proxy;
- CGI получает запрос и сохраняет значение заголовка в переменную среды HTTP_PROXY;
- CGI приложение запускает собственный веб-клиент, использующий переменную среды HTTP_PROXY в качестве настроек прокси;
- Клиент отправляет запрос, который вместо адреса назначения проксируется через сервер атакующего.
К примеру, так может выглядеть код эксплуатации на нескольких популярных языках:
PHP:
$client = new GuzzleHttp\Client();
$client->get('http://api.internal/?secret=foo')
Python:
from wsgiref.handlers import CGIHandler
def application(environ, start_response):
requests.get("http://api.internal/?secret=foo")
CGIHandler().run(application)
Go:
cgi.Serve(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
res, _ := http.Get("http://api.internal/?secret=foo")
// [...]
Более подробные PoC можно найти на GitHub в специальном репозитории HTTPoxy.
Интересный момент заключается в том, что сам баг, который используется в атаке HTTPoxy, был обнаружен пятнадцать лет назад. На сайте, посвященном HTTPoxy, представлено подробное описание истории уязвимости.
Согласно ему, в марте 2001 была обнаружена и исправлена ошибка некорректной обработки заголовков HTTP_PROXY в libwww-perl. В апреле того же года проблема была обнаружена в curl (и также исправлена, хотя и не для Windows). В 2012 году команда проекта Ruby разработала HTTP_PROXY для Net::HTTP — в их системе уязвимости не было.
В ноябре 2013 года она была упомянута в листе рассылки NGINX — пользователь Джонатан Мэттьюс описал ошибку, хотя и не был полностью уверен в своей правоте. В феврале 2015 года уязвимость также была упомянута в списке рассылке Apache httpd-dev. И уже в июле 2016 года сотрудник Vend Скот Джири (Scott Geary) обнаружил баг в реальной системе.
Какие системы уязвимы
Как выяснила команда безопасности Vend, уязвимость содержится во многих современных языках и библиотеках.
- CVE-2016-5386 Go
- CVE-2016-5387 Apache HTTPD
- CVE-2016-5388 Tomcat
- CVE-2016-1000104 mod_fcgi
- CVE-2016-1000107 Erlang HTTP Server
- CVE-2016-1000108 YAWS
- CVE-2016-1000109 HHVM FastCGI
- CVE-2016-1000110 Python CGIHandler
- CVE-2016-1000111 Python twisted
Как обнаружить уязвимость в своем софте
Специалисты компании RedHat разработали небольшой скрипт, позволяющий определить, уязвима ли конкретная система к HTTPoxy.
Для этого администратору сервера необходимо установить следующий CGI-скрипт и сделать его исполняемым:
test.cgi:
#!/bin/sh
echo "Content-Type:text/plain"
?echo ""
echo "HTTP_PROXY='$HTTP_PROXY'"
После этого следует вызвать CGI-скрипт с заголовком Proxy:
curl -H ‘Proxy: AFFECTED’ http://my-server-name/cgi-bin/test.cgi
Если вывод команды выглядит следующим образом — сервер не подвержен уязвимости:
HTTP_PROXY="
Если же вывод выглядит не так, к примеру отображается надпись ниже, то система уязвима:
HTTP_PROXY='AFFECTED'
Как защититься
Конечные пользователи веб-приложений в данной ситуации никак не могут повысить уровень своей безопасности, однако разработчики этого программного обеспечения имеют все возможности по его защите.
Для этого им нужно заблокировать заголовки запросов Proxy — сделать это можно очень быстро. Это не должно повредить функциональности приложения, поскольку такие заголовки являются нестандартными и обычно не используются.
К примеру, вот так можно отключить заголовки Proxy в Apache c помощью файла .htaccess:
<IfModule mod_headers.c>
RequestHeader unset Proxy
</IfModule>
Многие ИТ-компании и разработчики софта уже заявили о том, приняли меры к защите своих пользователей от уязвимости HTTPoxy — в их числе Akamai, CloudFlare, исправления выпустила и RedHat.
Эксперты Positive Technologies разработали сигнатуру для Suricata IDS, позволяющую обнаружить использование заголовка “Proxy” в HTTP запросах и предотвратить возможные последствия. Сигнатуру и пример эксплуатации можно найти в официальном репозитории twitter.com/AttackDetection/status/756142402268639232.
Комментарии (26)
Evengard
21.07.2016 19:40А для чего вообще нужен заголовок Proxy? Какое его истинное предназначение?
Evengard
21.07.2016 19:49А, всё, почитал источник и разобрался в чём суть уязвимости. Дело не в том, что именно Proxy превращается в HTTP_PROXY, а вообще любые заголовки превращаются в переменные окружения с префиксом «HTTP_».
G-M-A-X
21.07.2016 20:531. Дефолтный конфиг для fastcgi от nginx 1.8.1 не передает этот заголовок
2. В php 7.0.9 пофиксили: www.php.net/ChangeLog-7.php#7.0.9
amarao
21.07.2016 21:03+6Вся проблема в модели CGI.
CGI подразумевает, что мы получаем хидеры от запросов пользователей как переменные среды окружения. В то же самое время весь остальной софт считает, что переменные среды окружения — это довренная информация, которая используется администраторами (и локальными пользователями запускающими софт).
Давайте я вам сейча ещё более страшную уязвимость назову такого типа:
LD_PRELOAD.
И я не шучу. Добиваемся, чтобы наша so-шка оказалась в указанном месте на сервере, выставляем LD_PRELOAD в заголовках запроса, после чего наша so'шка выполняется с правами администратора.
(Интересно, апач и остальные LD_PRELOAD фильтруют или я только что новую CVE'шку с remote code execution описал?)evnuh
21.07.2016 21:04не баг, а фича
На сам деле вроде бы правильно заметили, что добавляется префикс HTTPamarao
21.07.2016 21:09Это плохая фича. Точнее, плохой дизайн. Путать источники информации — это всё равно, что читать конфиги для софта из ../users/upload, а потом защищать Важные Конфиги от подмены.
Насчёт HTTP-префикса да, забавно. Получается, что эта «защита» ломается выставлением _PROXY. Плохо прочитал.merlin-vrn
22.07.2016 11:44"_" (простите за нечаянный смайл ^_^') уже содержится в добавляемом префиксе, поэтому http-заголовок должен быть просто «Proxy».
И пожалуй да, проблема в архитектуре CGI
selivanov_pavel
22.07.2016 13:15Нет. Вот если бы переменная называлась HTTP_LD_PRELOAD, то была бы новая CVE-шка.
saboteur_kiev
22.07.2016 14:00С правами администратора или веб-сервера?
amarao
22.07.2016 21:29Кто исполняемый файл запускает, тот его переменные и контролирует. Именно потому у всякого рода sudo так строго с фильтрацией переменных окружения — чтобы не было эскалации привилегий.
saboteur_kiev
25.07.2016 00:35Я к тому, что CGI выполняется же через web, следовательно права будут веб сервера, а не администратора?
merlin-vrn
25.07.2016 09:06Но этого достаточно например для того, чтобы стырить или испортить базу данных, к которой это CGI-приложение обращается.
Barafu
21.07.2016 22:19Отлично же! Вот именно по этому я и шифрую свой трафик. А не потому, что…
merlin-vrn
22.07.2016 11:52+3И каким образом это вам помогает?
Скажем, ваш сайт использует API какого-то сервиса, который существует только по HTTP. Ну обратятся к вашему сервису по HTTPS, выставят заголовок Proxy, всё равно при обращении к HTTP API ваш сервис пойдёт по указанному в заголовке адресу.
Даже если API по HTTPS: как правило, для обращения к API используют что-то вроде curl, а он использует переменную HTTP_PROXY и по умолчанию на верификацию сертификата HTTPS-сервера кладёт болт, так что по адресу Proxy спокойно сможет сидеть MitM и ваш сайт это проглотит. Это браузеры показывают красное окошко с пугалками «идентификация сайта не удалась, открывать его может быть опасно», а тут-то это некому показывать.
Если эту верификацию затребовать, то да, при обращении к API через фейковый прокси получим ошибку вроде «API-сервер не отвечает, попробуйте позже», что хорошо.
Decker
21.07.2016 23:54Поясните в двух словах, если не сложно… может ли быть данная уязвимость опасна для Apache + PHP подключенного через php5_module?
Тестовый скрипт, выглядящий как:
<?php var_dump($_SERVER["HTTP_PROXY"]); ?>
Показывает что переменная HTTP_PROXY все-таки устанавливается при наличии заголовка Proxy в HTTP запросе от пользователя. Ради интереса попробовал поднять тестовый proxy сервер и передать его в заголовке Proxy в HTTP запросе скрипту, в котором осуществлялся GET-запрос через file_get_contents и с помощью curl_exec в PHP. В обоих случаях несмотря на то что в $_SERVER[«HTTP_PROXY»] содержался IP: порт Proxy запрос не пошел через него. Т.е. получается что $_SERVER[«HTTP_PROXY»] PHP при формировании исходящих запросов игнорировал.
В итоге получили, что любой пользователь извне может изменить значение $_SERVER[«HTTP_PROXY»], но исходящие запросы все равно не используют Proxy прописанный там. Стоит ли последовать рекомендациям, подключив mod_headers, чтобы вырезать заголовок Proxy или можно считать что система не подвержена данной уязвимости?
p.s. Или же речь идет только о CGI?Evengard
22.07.2016 07:17Дело не в $_SERVER, а в $_ENV. Если значение попадёт в $_ENV, значит теоретически уязвимость имеется.
zelenin
22.07.2016 13:34через прокси будет работать библиотеки, берущие прокси из HTTP_PROXY, а не сам php.
Decker
22.07.2016 20:04Спасибо, уже разобрался… в результате получается что getenv('http_proxy') = $_SERVER[«HTTP_PROXY»]… ну а далее действительно все что использует http_proxy из переменных окружения начинает работать через Proxy. Примером уязвимого кода может быть, например, такой:
function autoDetectProxySettings() { if (($httpProxy = getenv('http_proxy')) || ($httpProxy = getenv('HTTP_PROXY'))) { return $httpProxy; } } ... // ну а далее ... $httpProxy = autoDetectProxySettings(); $ch = curl_init("http://server.url/secret_api"); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); if (isset($httpProxy)) { curl_setopt($ch, CURLOPT_PROXY, $httpProxy); } $result = curl_exec($ch);
Аналогично и использующие переменную среды HTTP_PROXY библиотеки. Вообще интересно было бы краткий список наиболее часто используемых уязвимых библиотек.zelenin
23.07.2016 02:50так точно.
Список есть в принципе. Самые известные это наверное guzzle для php, и requests для питона.
YuriM1983
22.07.2016 16:22Для python-requests уже давно придумали обход:
import requests
session = requests.Session()
session.trust_env = False
response = session.get('http://www.stackoverflow.com')
Но вообще, конечно, подстава.
sshikov
Откровенно говоря, после shellshock я ожидал пачки подобных уязвимостей. Странно что так долго копались.
amarao
В shell-shock'е немного другое было. То есть в целом это «копаемся в старых программах и старых протоколах в поисках garbage in garbage out», но векторы атаки разные.
Плюс shell-shock глубже вошёл (благодаря dhcp).
sshikov
Разумется другое, но суть ровно такая же: никто не проверяет данные, которые приходят снаружи. Такие же ошибки в SQL (попытка подставить параметры в код вместо того, чтобы использовать ?) — на каждом шагу.
amarao
Да. Смешивают источники — доверенные строки в коде с недоверенными строками со входа от пользователя.