Атаки на Web-скрипты

e740edde64428a3a3befb6e129c[1]Публикацию статьи о технологии атаки через reverse-ip (lookup-ip) за авторством NSD можно считать началом открытого противостояния администраторов хостингов – и тех, кто желает залезть на смежный сайт соседа. Многие начинающие хостинги вообще не занимаются разделением прав доступа. Затем сисадмин (видимо, после пары тысяч жалоб\дефейсов) прикручивает suphp либо suexec, и какое-то время все работает. Инициатива переходит от одной противоборствующей стороны к другой в ритме выхода нового паблик эксплойта под PHP и выхода патчей. Прошло достаточно времени, чтобы всем, наконец, стало ясно – PHP дыряв, как дуршлаг, и доверять ему нельзя (что, кто-то еще сомневается?). Не сегодня, завтра выйдет адвизори с новым мегабагом, и опять все будут ахать и патчиться – в который уже раз… и уж точно не в последний.

Если говорить о тенденциях, то наблюдается упор на разделение прав через стандартную файловую систему вкупе с suexec и ftp-доступом разделения прав. Это, можно сказать, бронебойный прием, поскольку попасть в директорию, тебе не принадлежащую, может разве что root. Для надежности дополнительно чмодят все системные директории на -rwx--x--x.

Действительно, кому еще (и, главное, зачем) может потребоваться листинг корневой директории, /etc, /var, /usr помимо root'a? Полезные бинарники вроде id, cwd, uname, find тоже порой имеют права root:wheel -r-xr-x--- , так что и команды не выполнишь!

Но у чмода есть одна маленькая тонкость. Наличие байта выполнения --x на директории позволяет пройти в нее! Прочитать ее мы не сможем, но, к примеру, вложенные директории и файлы могут иметь уже более демократичные права доступа. Ниже приведу пример.

id
uid=80 (www) gid=80 (www) groups=80 (www)
ls -liaR /home
— rwx--x--x root:root /home/
— rwx--x--x user:user /home/user/
— rwxr-xr-x user:user /home/user/htdocs/
— rwxrwxrwx user:user /home/user/htdocs/tmp/

Смотри: обратившись по абсолютному пути «/home/user/htdocs/tmp/», мы окажемся в папке, доступной на запись и видимой из WWW. Если отсутствует листинг папок, ничего не мешает тупо, брутфорсом, подобрать вероятное имя по словарю (мою реализацию скрипта ищи на диске).

Перебор возможных путей через скрипт брутфорса [a-z] на путь длиной в пять символов займет не более минуты на .php и еще меньше – через .sh-скрипт.
Даже в случае нечитабельных папок не составляет труда прочесть /index.php и уже из них вытягивать логины/пароли/инклуды на конфиги и т.п.
Определив существование директории/файла, мы, скорее всего, получим доступ на чтение и возможный бонус «листать/писать» во вложенные директории и файлы. Ну, а где же достать самый полный путь?

Для этого идеально подходят конфиги апача httpd.conf, vhosts.conf, – но практически всегда они защищены от любопытных глаз уже правильным чмодом.
К счастью, «/etc/passwd» всегда готов рассказать нам о домашнем каталоге юзеров. А уж наличие соответствующих папок можно определить, исходя из аналогии имеющегося домашнего каталога (либо незатейливым брутфорсом). Даже если включен safe_mode или open_basedir, мешающий получить passwd, и никто не делится с тобой приватными эксплоитами для обхода этого фашистского изобретения, – все равно можно вывернуться через фичу в posix_getpwuid () (подробнее на packetstormsecurity.org/0712-exploits/php525-bypass.txt).

Защита проста, как два байта, – достаточно всего одной директории в пути быть чмоднутой на «-rwx--x---» или даже «-rwx------м», и все хакеры пойдут лесом. Хотя, вспоминая прошлый номер ][ с описанием багов файловых систем от Криса Касперски, я начинаю задумываться: а что, если... :). Впрочем, наша история совсем не о том!..

Кстати, уже после написания подтемы chmod, вынашиваемой и проверяемой мною в течение многих месяцев, я, руководствуясь исключительно лишь своим насыщенным опытом, решил в давно заведенном порядке посерфить секурити-блоги в надежде разжиться мало-мальски свежей инфой и новыми идеями.
Как выяснилось, ни одного меня затронула вышеописанная бага. И это нормально, история знает немало примеров, когда одинаковые идеи посещают двух людей одновременно – вспомним законы в физике, математике и других науках, названные двойными фамилиями соавторов, порой живших в разных точках земного шара и понятия не имевших друг о друге.

Коммунальный /tmp

Казалось бы, что такого примечательного в общей временной папке? Самое главное, это то, что она – общая! Как правило, каждому виртуалхосту прописывают индивидуальные настройки вроде сэйфмода, опенбаздира. Но временную папку ставят одну на всех вместо создания индивидуальной в домашней директории клиента. Так, в php.ini это параметр «session.save_path». По факту, «темп» – это мусорка. Проходит месяц-другой, и в одно недоброе утро администратору приходит пара десятков жалоб на многочисленные ошибки с варнигами. «Темп» оказывается переполненным, ведь созданные сессии удаляются только при вызове session_destroy(), о которой часто забывают, не жмут на Exit или просто не догружают страницу.

Админ, недолго думая, в cron.month ставит новую задачу, призванную периодически чистить «/tmp». Разумеется, написать sh-скрипт очистки одной статичной папки проще, чем N юзверских папок (хотя можно возложить почетную задачу уборки на юзера, особенно если квоты дискового пространства уже прикручены). В общем, так или иначе, но индивидуальный «session.save_path» необходим для секурности. Как, впрочем, и... «upload_tmp_dir»! Ведь сбои при загрузке файлов происходят довольно часто, ибо аплоад файла – это, прежде всего, POST-пакет нехилых размеров. По моему опыту, далеко не каждый провайдер корректно его переваривает, особенно в часы пик. Да и PHP не всегда удаляет недогруженные куски, подвисая балластом в ожидании конечной части. Это дает вероятный бонус найти в темпе что-либо интересное, например, кусок недогруженного конфига или дампа БД, логи, а иногда – следы творческих экспериментов администрации в программировании с наличием чувствительных данных.

Более того, любой php-скрипт при «file_uploads=ON» автоматически принимает файл в POST-пакете. Это позволяет загрузить на целевой сервер большое количество файлов. Например, в PHP 5.1.x количество файлов, которые можно загрузить зараз, не ограничено! А в свежем PHP – «не более [post_max_size]». Все это повлечет за собой исчерпание свободного дискового пространства в папке временных файлов.

Примерная схема атаки выглядит так:

1. Атакующий посылает к любому php-скрипту на сервере специально сформированный POST-запрос («multipart/form-data»), содержащий файл (или файлы). Размер одного отсылаемого файла должен быть не больше максимально установленного значения директивы PHP [upload_max_filesize] (Default=32Мб) – и не более [post_max_size] (Default=16Мб) по минимальному значению любого из параметров. Размер пакета (http-хидер) «Content-Length» указывается заведомо больше истинного (как минимум, на 5 байт).

2. Сервер принимает POST-пакет с файлом. В незаконченной передаче мы держим коннект и заставляем сервер слушать соединение сколь возможно долго. Все это время загруженный файл будет занимать место на диске сервера, реализуя атаку на исчерпание дискового пространства.

Варианты защиты от атаки:

Запретить [file_uploads]
Ограничить время установленного соединения «клиент – сервер» в настройках файрвола или веб-демоне (apache) –http://sysoev.ru/web/upload.html.

Что интересно, мне встречались системы, где загружаемые временные файлы имеют не псевдослучайное имя, а именуются строго последовательно. От версии PHP это не зависит. А от чего зависит? Пока мне это неизвестно. Проверить конкретный сервер на предмет предсказуемых имен временных файлов легко – достаточно найти вывод phpinfo (). Посылаем пакет с файлом и смотрим в ответе значение [tmp_name]. Посылаем еще раз и сравниваем. Если имя вполне последовательно прибавило пару чисел – налицо вышеописанный баг!

Используя алгоритм задержки загруженных файлов на сервере, можно попытаться загрузить веб-шелл. И теперь, если где-то на сервере имеется php-include, его легко заюзать!

Помимо описанного, на мой взгляд, самым интересным здесь является угон чужой сессии (session hijacking). Но об этом чуть ниже.

Коварные сессии

Немалая часть веб-приложений юзает сессии PHP для разграничения прав доступа между юзерами. Напомню забывчивым, что вызов <?php;session_start ();?> создает файл сессии с id из случайных alpha-numeric – случайных или определенных в скрипте посредством вызова session_id ('<alpha-numeric>') – перед session_start (). Пример alpha-numeric: /tmp/sess_50d7ed4b37f95f991679fdd92ba894e2. После чего это имя дублируется в браузер пользователю как значение кукис с дефолтным именем «PHPSESSID» (можно изменить глобально из php.ini или локально в скрипте, вызвав session_name ('<MY_SESSID>') перед session_start ())

Set-: PHPSESSID=50d7ed4b37f95f991679fdd92ba894e2

Атака на сессии считается одним из эффективнейших тестов на раскрытие полного пути – она в десятке лидеров, таких, как поиск и обращение к левым скриптам, передача массива вместо строки в переменной, DDoS на БД, переполнение мусором логфайла и т.п. Достаточно, скажем, передать в GET или COOKIE «PHPSESSID=123!@#$%^» и получим уровня Warning, если error_reporting (E_WARNING) разрешено.

Warning: session_start () [function.session-start]: The session id contains illegal characters, valid characters are a-z, A-Z, 0-9 and '- ,' in...

Либо передадим «PHPSESSID» длиной более 256 символов. Файл с таким именем не может быть создан, и ответом сервака будет:

Warning: Unknown: Failed to write session data (files). Please verify that the current setting of session.save_path is correct

Сам факт создания файла лишь на основании PHPSESSID в GET или COOKIE, с именем аналогичным id, наводит на мысль о... DDoS-атаке. Точнее, об эффекте ее усиления.

Ресурсы системы, затраченные на создание файла сессии, ничтожны. Сам файл сессии зачастую и вовсе пуст (зависит от того, были ли внесены значения в $_SESSION в процессе выполнения скрипта). Но генерируя мусорный трафик с рэндомным PHPSESSID, можно создать количество файлов сессий, равное числу http-пакетов при DDoS – то есть, очень много. MFT никто не отменял! Совсем недавно Крис писал, что даже совершенно пустой файл занимает 1 кластер.
Да что там раскрытие пути! К PHP написаны более десяти эксплоитов с атакой BoF на функции сессий. Это уже кое о чем да говорит. И те, кто с презреньем утверждают, что BoF для PHP – полная лажа, лишь оправдывают свое незнание.

Между прочим, порой это единственный шанс выбраться из клетки опен_баздира с сэйф-модом, когда exec-эксплоиты кончились, а выполнения системной команды так и нет.

Ну а теперь подробно рассмотрим перехват сессий. Примечательной особенностью пользовательской сессии является уникальность ее id, знание которого, как ни странно, будет достаточным для успешного прохождения авторизации юзверя. Хранятся сессии либо в базе данных приложения (и это правильно, ибо максимально безопасно), либо в созданных средствами PHP файлах в папке «/tmp».

Ну а поскольку «/tmp», как мы уже выяснили, часто доступен всем желающим на чтение/запись, то нетрудно догадаться о выводах. Здесь, в зависимости от условий, возможны различные варианты атаки.

Фактически, можно обойтись листингом темпа, ведь, как правило, достаточно знать id сессии. Подменив id своей сессии на id чужой в кукисе, мы успешно авторизуемся под другим юзверем. Зачастую, если возможен лишь листинг темпа, то сессии каждого сайта имеют uid/guid, принадлежащий конкретному сайту. Это очень удобно, так как вместо перебора всех сессий из «/tmp» легко отделить нужные.

Защитой может служить хранимый в сессии IP-адрес или User-Agent пользователя, срок действия, и соответствующая их проверка при авторизации.
Когда есть возможность читать файлы чужих сессий, – мы уже будем иметь хотя бы представление о ее структуре и содержании. Это важно, если используется какой-либо самописный\неизвестный движок, структура сессий которого неизвестна заранее. Или если стоит секурити-проверка на ip, browser, срок действия.
Пусть тогда мы и не сможем использовать существующие сессии, но, исходя из примеров читабельных сессий, становится возможным сгенерировать свою собственную! Закинуть ее в темп, чмоднуть на 0777, чтобы другой пользователь имел к ней доступ (правда, с safe_mode=on это не прокатит) – после чего использовать. Защитой здесь может служить хранение данных в зашифрованном виде: либо обратимым алгоритмом (AES,blufish,twufish) с ключом, хранящимся в конфиге или БД, либо необратимым алгоритмом (md5,sha1) с рэндомной солью, хранящейся опять же в конфиге, БД или другом недосягаемом месте. Даже если хакеру будет известен механизм генерации хеша – он не сможет сгенерировать собственную подложную сессию!

Самый редкий, но благоприятный вариант: если Апач работает на правах www-data/nobody для всех. Это значит, что все файлы (в том числе, сессионные) создаются с одинаковыми правами и доступны любому желающему не только на чтение, но и на запись.

Права на запись хороши тем, что становится возможным обойти простую (не зашифрованную) привязку к пользователю, изменив параметры в существующей сессии под себя. Алгоритмы защиты аналогичны предыдущему пункту.

Заметно прибавит проблем взломщику вызов session_regenerate_id (), меняющий session_id ().

Говоря о сессиях, следует упомянуть еще об одном баге PHP от Stefan Esser www.php-security.org/MOPB/PMOPB-46-2007.html – «EXT/Session HTTP Response Header Injection Vulnerability PHP4<=4.4.7,PHP5<=5.2.3».

Так и быть, я сэкономлю несколько часов твоего времени (ибо время для хакеров бесценно).

Попробовав обратиться к сайту так: «/session.php?PHPSESSID=123» – мы просто заюзаем сессию sess_123, если она есть, или создадим ее, если таковой нет. Но так будет только в случае, когда в браузере еще нет куки «PHPSESSID» (поскольку она имеет более высокий приоритет в сравнении с GET). Дело обстоит совсем по-другому при обращении к линку вида «/session.php/PHPSESSID=123456;INJECTED=ATTRIBUTE;/». Браузеру в ответ отправится пакет с хидером «Set-cookie: PHPSESSID=123456;INJECTED=ATTRIBUTE;;path=/». Для успешной эксплуатации уязвимости необходим error_reporting (0).

К сожалению, внедрить какой-либо символ в urlencode вроде %0D,%0A нельзя – urlencode просто не обрабатывается. То есть, ни «Location», ни дополнительный «Set-cookie» добавить не выйдет: только expired, domain и path для текущей куки. Если заблаговременно создать свой файл сессии, чмоднуть его на 0777 и заманить админа по линку (фактически, вынудить его браузер юзать сессию с нашим id) – тогда мы получим его файл сессии с правами на запись.

Здесь же хочу напомнить про актуальность перехвата cессии через REFERER. С одной стороны, звучит неправдоподобно, но это только так кажется. Атака становится возможной, если приложение добавляет сессию пользователя в GET-запросе. Причем, это может быть отнюдь не глюком, а документированной поддержкой.

Зачем это нужно? Подобный механизм авторизации через GET активируется в двигах в случаях отключенных куков в браузере или отключенной явы. В итоге, вместо повышения безопасности мы получаем бонусную течь.

Как же это можно использовать? Тебе необходима лишь поддержка чатом/форумом/блогом тэга <img> (или bb-тэга [img]). Ну, или поддержка удаленного аватара. Вот тут и дырка: мы можем сослаться на изображение на своем сервере и снифать поступающие рефереры его просмотра через access_log или через PHP, подобно капча-скриптам. В реферере помимо URL предыдущей страницы будет содержаться сессия юзера. Ярким примером служат практически все WAP-порталы. Я не специалист по такому софту, но некоторые телефоны (PDA/КПК) не держат cookie. Это компенсируется передачей сессии в GET-запросе.

Пример уязвимого движка – всеми любимый: phpBB 2.0.23 Session Hijacking Vulnerability(securityfocus.com/archive/1/489815). Утечка сессии происходит при закрытии темы модератором.

Multy-Byte

Впервые об этой уязвимости в PHP заговорили в 2006 году. Шло обсуждение недостаточной фильтрации в функции addslashes (), прославившейся как простая защита от -инъекций. Общественность, как всегда, очень вяло отреагировала на появление уязвимости – за что и была наказана. Многие достаточно крупные и очень крупные серверы подвержены этой уязвимости. Баг присутствует как в версиях MySQL 4.1.х->4.1.20 , 5.0.x->5.0.22, так и в самом PHP <= 5.2.5
Addslashes () некорректно обрабатывает три различные кодировки – SJIS, BIG5 (более известную, как CP950) и GBK (CP936). Все они основаны на двоичной системе. Кодировки являются расширенными и используются преимущественно в азиатских странах, где требования языка не удовлетворяют стандартному 256-символьному ограничению. Таким образом, вероятная уязвимость азиатского сайта к атаке на мультибайт несоизмеримо выше по сравнению со всеми остальными ресурсами.

Первый адвизори вышел для GBK, поэтому подробно рассмотрим особенность именно этой кодировки.

В GBK символы интерпретируются по одному. 0xbf27 состоит из двух символов в 2-ной системе – именно этот символ будет некорректно отфильтрован. Также некорректно отфильтруется 0xbf5c. Это вызовет ошибку, так как будет недопустимый символ (\). Рассмотрим теперь эти данные, обработанные функцией addslashes ().

Первый вариант, 0xbf27, будет интерпретирован, как одиночные символы. 0xbf при переводе будет (?), а затем – 0×27 ('). И второй вариант – 0xbf5c. 0xbf при переводе будет (?), а затем 0x5c (\).

Addslashes возвращает сроку str, в которой перед каждым спецсимволом добавлен обратный слэш (\), например, для последующего использования этой строки в запросе к базе данных. Экранируются одиночная кавычка ('), двойная кавычка («), обратный слэш (\) и NUL (байт NULL). Этот фильтр может пропустить некорректные символы к базе данных, а именно ('). После чего станет возможно составления запроса к базе данных.

При следующем синтаксисе БД:

CREATE TABLE tables (
id VARCHAR (32) CHARACTER SET GBK,
data VARCHAR (8000) CHARACTER SET GBK,
PRIMARY KEY (data)
);

– можно составить запрос:

mysql_query („SELECT data FROM tables WHERE id='“.addslashes ($id).»'");

Чтобы составить корректный запрос, тут обязательно надо закрыть параметр «id='» и привести его к виду «id='1'». Но передать одинарную кавычку мы не сможем, так как она экранируется. Если версия нашей базы данных совпадает с одной из указанных выше, мы можем эксплуатировать уязвимость, передав следующий пакет:

/?id=%BF%27' union select 1,2/*';

Метод будет работать только при magic_quotes_gpc = OFF (так как при включенном magic_quotes_gpc по умолчанию все GET/POST/COOKIE будут обрабатываться функцией addslashes () – что приведет к двойной фильтрации).

Примером паблик эксплоитов могут служить SQL-inj в WordPress <= 2.3.2 с БД в GBK-кодировке (securityvulns.ru/Sdocument635.html).

Особого внимания заслуживает паблик эксплоит под SMF<=1.1.4 (milw0rm.com/exploits/5826). Эксплоит, основанный на принудительной смене локали базы данных на уязвимую BIG5, уже не зависит от локали кодировки сервера. Не исключено существование аналогичных багов и в других приложениях.

Эти виды кодировок мало распространены, поэтому самая простая защита – не использовать их в базах данных. Ну, а по-хорошему, – обновить программное обеспечение!

Фрагментированная SQL-inj

Теперь обратимся к такому малоизвестному виду уязвимости, как фрагментированная SQL-inj. По сути, та же самая SQL-inj – с той лишь разницей, что ее обнаружение и эксплуатация на порядок сложнее. Классический пример этой инъекции можно встретить, когда данные извлекаются из первого SQL-запроса – и без должной фильтрации подставляются во второй SQL-запрос. То есть, налицо дефект обработки данных (либо полное отсутствие фильтрации данных на одной из стадий манипуляции ими). Рассмотрим уязвимость на практическом примере для Coppermine Photo Gallery <= 1.4.19.

Предварительно я опишу детали бага:

  1. В GET,POST,REQUEST опасные символы заменяются на html-аналоги, но '\' не затрагивается. Также они обрабатываются функцией stripslashes (). Это дает возможность использовать NULL-byte.
  2. При регистрации нового пользователя для email используется регулярное выражение, основанное на ereg (), который, в свою очередь, уязвим к NULL-byte (обработка строки при встрече с ним прекращается). Так что, email вида «test@blah.com%00\» успешно проходит проверку.

Что нам дает, если в базу данных email попадает со слешем на конце?

Если где-либо в скрипте первый query получит e-mail и передаст его во второй query без фильтрации, то слеш заэкранирует закрывающую кавычку, синтаксис запроса нарушится и возникет SQL-Error.

SQL-error:
INSERT INTO cpg1418_ecards (sender_name, sender_email, recipient_name, recipient_email, link, date, sender_ip) VALUES ('123', 'test@blah.com\', 'SomeUserName', 'SomeUser@email.com', '5OntzOjI6InJuIjtzOjk', '', '127.0.0.1')

Ошибка, как таковая, бесполезна, но если сразу после параметра email будет идти еще один параметр, который мы можем произвольно менять, то становится возможным проведение фрагментированной SQL-injection.

SQL-inj more1row:
INSERT INTO cpg1418_ecards (sender_name, sender_email, recipient_name, recipient_email, link, date, sender_ip) VALUES ('123', 'test@blah.com\', '[SQL-inject more1row]', 'SomeUser@email.com', '5OntzOjI6InJuIjtzOjk', '', '127.0.0.1')

Как видно, открывающаяся кавычка поля recipient_name превращается в закрывающуюся кавычку sender_email. Мы получаем шанс провести SQL-inj в поле recipient_name.

Для эксплуатации уязвимости потребуется:

  1. Возможность регистрации новых пользователей;
  2. Отсутствие подтверждения регистрации через email;
  3. Возможность лога открыток.

Алгоритм действий при соблюдении трех условий очень прост:

  1. Отснифать POST-пакет регистрации, заменить в нем еmail на «test@blah.com%00\» и отослать серверу.
  2. Авторизовавшись, выбрать любое загруженное изображение в галерее и нажать на значок письма (отправить этот файл как открытку).
  3. Корректно заполнить все поля и отослать. Если сервер сообщает об ошибке базы данных, значит, лог открыток включен и возможна SQL-inj.
  4. В поле «Имя получателя» проводим SQL-more1row, используя автоматизированный скрипт.

FALSE — «Имя получателя»:
or if (ascii (substring ((select concat (user_id,0x3a,user_name,0x3a,user_password,0x3a,user_email) from cpg14x_users where user_group=1 limit 1),1,1))=254,1,(select 1 union select 2))=1, 0x6861636b6572 , 0x6861636b6572406d61696c2e7275 , 0x6861636b , 0×31323039333931343430 , 0x3230372e34362e3233322e313832 )/*

TRUE — «Имя получателя»:
or if (ascii (substring ((select concat (user_id,0x3a,user_name,0x3a,user_password,0x3a,user_email) from cpg14x_users where user_group=1 limit 1),1,1))=49,1,(select 1 union select 2))=1, 0x6861636b6572 , 0x6861636b6572406d61696c2e7275 , 0x6861636b , 0×31323039333931343430 , 0x3230372e34362e3233322e313832 )/*

Из-за фильтрации '<' '>' можно использовать только знак '='. Но это уже, как говорится, мелочи и дело техники. Необходима надежная фильтрация данных, заносимых в БД, либо дополнительная проверка при извлечении.

Запись навигация

Top