PHP parse_str() arbitrary variable overwrite

Discussion in 'Уязвимости' started by darky, 7 Sep 2007.

  1. darky

    darky ♠ ♦ ♣ ♥

    Joined:
    18 May 2006
    Messages:
    1,773
    Likes Received:
    825
    Reputations:
    1,418
    Удаленная перезапись переменной в ф-ии parse_str() в PHP.

    Оригинал http://www.acid-root.new.fr/advisories/14070612.txt

    . Вступление


    [II]. Руководство.

    Функция void parse_str ( string $str [, array &$arr] ) парсит строку str, которая должна иметь формат типа URL и присваивает значения переменным в текущем контексте, если не передан второй аргумент arr. В последнем случае значения будкт сохранены в этой переменной как элементы массива.

    Замечание: Поддержка необязательного второго аргумента была добавлена в PHP 4.0.3.

    Замечание: Для получения текущей строки запроса (QUERY_STRING) может быть использована переменная $_SERVER['QUERY_STRING']. Также ознакомьтесь с разделом "Переменные вне PHP".

    Замечание: Опция magic_quotes_gpc влияет на выходящее значение ф-ии, т.к. parse_str() использует тот же механизм, что пхп использует для заполнения $_GET, $_POST.

    [III]. Исходный код.

    Code:
    --- ./ext/standard/string.c ---
     4025. /*
     4025. {{{ proto void parse_str(string encoded_string [, array result])
     4026. Parses GET/POST/COOKIE data and sets global variables
     4026. */
     4027. PHP_FUNCTION(parse_str)
     4028. {
     4029.   zval **arg;
     4030.   zval **arrayArg;
     4031.   zval *sarg;
     4032.   char *res = NULL;
     4033.   int argCount;
     4034.   
     4035.   argCount = ZEND_NUM_ARGS();
     4036.   if (argCount < 1 ||
     4036.       argCount > 2 ||
     4036.       zend_get_parameters_ex(argCount,&arg,&arrayArg) == FAILURE)
     4036.   {
     ####.          /* Not enough or too many args */
     4037.   	WRONG_PARAM_COUNT;
     4038.   }
     4039. 
     4040.  convert_to_string_ex(arg);
     4041.  sarg = *arg;
     4042.  if (Z_STRVAL_P(sarg) && *Z_STRVAL_P(sarg)) {
     4043.  	res = estrndup(Z_STRVAL_P(sarg), Z_STRLEN_P(sarg));
     4043.
     ####.  /* Allocate Z_STRLEN_P(sarg)+1 bytes of memory and copy
     ####.     Z_STRLEN_P(sarg) bytes from Z_STRVAL_P(sarg) to the
     ####.     newly allocated block */
     4044.  }
     4045.
     ####.  /* parse_str(argv1) */
     4046.  if (argCount == 1)
     4046.  {
     4047.  	zval tmp;
     4048.  	Z_ARRVAL(tmp) = EG(active_symbol_table);
     4049.
     ####.  /* The problem is here, there is no conditions before setting
     ####.     the variable. If a variable already exists, it will overwrite it */
     4049.
     4050.  	sapi_module.treat_data(PARSE_STRING, res, &tmp TSRMLS_CC);
     4051.  }
     ####.  /* parse_str(argv1,argv2) */
     4051.  else
     4051.  {
     4052.  	/* Clear out the array that was passed in. */
     4053.  	zval_dtor(*arrayArg);
     4054.  	array_init(*arrayArg);
     4055.  	
     4056.  	sapi_module.treat_data(PARSE_STRING, res, *arrayArg TSRMLS_CC);
     4057.  }
     4058. }
    

    [IV]. Пояснения

    Как вы уже увидели в мануале, пользователь, использующий эту ф-ию не защищен от перезаписи переменных. Девелоперы просто забыли проверить этот момент. Простой PoC:
    PHP:
    <?php
     
     
    # ?var=new
     ###########
     
    $var   'init';                     #
     
    parse_str($_SERVER['QUERY_STRING']); #
     
    print $var;                          # new

     # ?array[]=new                       # Array
     ##############                       # (
     
    $array = array('init');              #    [0] => init
     
    parse_str($_SERVER['QUERY_STRING']); #    [1] => new
     
    print_r($array);                     # )

     # ?array=new
     ############                                # Array
     
    $array = array('init');                     # (
     
    parse_str($_SERVER['QUERY_STRING'],$array); #    [array] => new
     
    print_r($array);                            # )

     
    ?>

    Этот тип уязвимости открывает дверь к многим другим уязвимостям, поэтому в данном случае достаточно сложно обозначить уровень риска. В отлчиие от extract(), тут нет опции на подобии EXTR_SKIP, которая отвечает за защиту от перезаписи переменной (если переменная с таким именем существует, будет сохранено её прежнее значение). Поэтому если вы хотите чтобы ваш код был безопасным - не используйте эту ф-ию. Я не связывался с пхп-тимой, но возможно они зарелизят фикс на эту уязвимость.

    Избранные мысли из [V]. Комменты

    1. Даже если register_globals отключено, уязвимость есть как минимум на пхп 4.4.4

    2. Hardened-PHP и PHP с сухосин патчем тоже уязвимы.

    3. При тесте уязвимости она имело место на
     
  2. Helios

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

    Joined:
    14 Jan 2007
    Messages:
    414
    Likes Received:
    180
    Reputations:
    103
    Сколько двигов перерыл, но что-то эту функцию видел в применении всего несколько раз. Так что особой "опасности" и чего-то сверхестественного тут не вижу. Просто нужно думать, когда используешь. // и не только тут)
     
  3. Xex

    Xex Banned

    Joined:
    10 Jul 2005
    Messages:
    108
    Likes Received:
    41
    Reputations:
    7
    2blackybr:маладец, спасибо, только одно замечание - если дословный перевод получается бредом, то можно смело высказать мысль своими словами.

    RE:"Замечание: Опция magic_quotes_gpc влияет на выходящее значение ф-ии, т.к. parse_str() использует тот же механизм, что пхп использует для заполнения $_GET, $_POST."



    2Helios:
    RE:"Сколько двигов перерыл, но что-то эту функцию видел в применении всего несколько раз. Так что особой "опасности" и чего-то сверхестественного тут не вижу." - оттого что ты не нашел применение данной уязвимости, опасность и актуальность данной баги не уменьшается