专栏名称: 国舜股份
移动互联网时代的综合性网络安全解决方案供应商。专业的安全产品,专业的安全服务团队,全面的安全服务资质,安全不变,国舜同行。
目录
相关文章推荐
泗县人网上家园  ·  网络中国节·元宵 | ... ·  昨天  
泗县人网上家园  ·  网络中国节·元宵 | ... ·  昨天  
51好读  ›  专栏  ›  国舜股份

Nostromo WebServer RCE原理分析(CVE-2019-16278漏洞分析【续】)

国舜股份  · 公众号  ·  · 2020-03-10 16:13

正文


声明: 本文内容仅供安全研究之用,不恰当使用会造成危害,严禁用于非法用途,否则由使用者承担全部法律及连带责任。

0x00 前言

在文章《 利用目录遍历漏洞实现RCE(CVE-2019-16278漏洞分析,附GDB调试过程) 》分析RCE的部分,可知能够远程命令执行的原因是 execve 函数执行了指定的sh程序,但是传入的命令是怎么被执行的,POST参数中为什么要传入两个 echo\n ,为什么要使用HTTP 1.0协议这些疑问都还没解决,本着刨根问底的求知精神,下面我们一起继续分析,看看一个正常使用HTTP 1.1协议的POST请求能否实现RCE。

0x01 漏洞分析

1. 了解IPC-管道

管道是一种最基本的IPC(Inter-Process Communication,进程间通信)机制,由pipe函数创建,它是半双工的,数据只能在一个方向上流动,想要双方通信,就需要建立起两个管道。管道只能用于具有共同祖先的进程之间进行通信,通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可以应用该管道。具体应用场景见如下示例:
#include 
int main(){ int pfds[2]; char *command = "whoami"; /* * pipe创建管道,创建成功返回0 * pfds返回两个文件描述符: * pfds[0]:指向管道的读端 * pfds[1]:指向管道的写端 * pfds[1]的输出是pfds[0]的输入 */ if(pipe(pfds) == 0){ //fork返回0,子进程 if(fork() == 0){ //关闭管道的写端 close(pfds[1]); //复制文件描述符,让stdin(标准输入)指向管道的读端,相当于stdin的数据是由pfds[1]写入的 dup2(pfds[0],0); //定义传递给程序的参数,数组指针argv必须以程序(filename)开头,NULL结尾 char *argv[ ]={"sh", NULL}; //定义程序运行的环境变量,默认环境变量 char *envp[ ]={0, NULL}; //执行sh程序,显式传递给sh程序的argv参数为空 execve("/bin/sh", argv, envp); } //父进程 else{ //关闭管道的读端 close(pfds[0]); //把需要执行的命令写入到管道的写端,然后在管道的读端获取命令 write(pfds[1],command,6); } } return 0;}
简单看看代码,先使用pipe函数新建管道并fork一个子进程,在父进程中使用write函数把需要执行的命令写入管道的写端,然后在子进程中使用dup2函数复制文件描述符,让系统的标准输入(stdin)指向管道的读端,此时stdin的数据就是whoami命令,最后execve执行sh程序后,虽然没有直接给sh程序传递参数,但是sh会自己在标准输入里面找参数。编译运行程序,结果如下:

sh执行了stdin里面的whoami命令,相当于命令与命令之间使用管道符通信, command1 的标准输出会作为 command2 的标准输入使用:

2. RCE原理分析

接着上篇文章的分析,GDB动态调试定位到execve函数位置,如下所示

可以看见在execve函数中传递给sh程序的参数cgiarg为空,运行完execve函数后就自动执行了指定命令。sh程序是怎么执行id命令的呢,一起来看看源码 ./src/nhttpd/http.c ,关键代码如下:
 ......SNIP...... 462                 /* create pipes to communicate with cgi */ 463                 if (pipe(fds1) == -1) { 464                         syslog(LOG_ERR, "can't fork cgi: pipe fds1: %s", 465                             strerror(errno)); 466                         exit(1); 467                 } 468                 if (pipe(fds2) == -1) { 469                         syslog(LOG_ERR, "can't fork cgi: pipe fds2: %s", 470                             strerror(errno)); 471                         exit(1); 472                 } 473  474                 /* fork child for cgi */ 475                 if ((cpid = fork()) == -1) { 476                         syslog(LOG_ERR, "can't fork cgi: fork: %s", 477                             strerror(errno)); 478                         exit(1); 479                 } 480  481                 /* cgi */ 482                 if (cpid == 0) { 483                         /* child dont need those fds */ 484                         close(fds1[1]); 485                         close(fds2[0]); 486  487                         if (chdir(rh->rq_filep) == -1) { 488                                 syslog(LOG_ERR, "can't fork cgi: chdir: %s", 489                                     strerror(errno)); 490                                 exit(1); 491                         } 492                         dup2(fds1[0], STDIN_FILENO); 493                         dup2(fds2[1], STDOUT_FILENO); 494  495                         /* build cgi environment array */ ......SNIP...... 580  581                         execve(rh->rq_filef, cgiarg, cgienv); //here 582                         exit(0); 583                 } 584  585                 /* parent dont need those fds */ 586                 close(fds1[0]); 587                 close(fds2[1]); 588  589                 /* if post send data to cgis stdin */ 590                 if (!strcasecmp(rh->rq_method, "POST")) { 591                         rp = 0; 592                         rt = 0; 593                         size = atoi(rh->rq_fv_clt); 594  595                         if (size > 0) { 596                                 if (blen > 0) { 597                                         r = http_body_comp(body, blen, blen, 598                                             size); 599                                         if (r > 0) 600                                                 sys_write(fds1[1], body, r); 601                                         else 602                                                 sys_write(fds1[1], body, blen); 603                                 } 604  ......SNIP...... 630






请到「今天看啥」查看全文