c++ 结构体遍历_利用目录遍历漏洞实现RCE(CVE201916278漏洞分析,附GDB调试过程)

本文分析了 Nostromo Web 服务器中的 CVE-2019-16278 漏洞,该漏洞允许通过跨目录遍历实现远程命令执行。文章详细介绍了漏洞原理、环境搭建、调试过程及复现步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

993c9130c78a3c880067c4995f4c854c.png

0x00 前言

前几天有大佬放出了CVE-2019-16278和CVE-2019-16279的payload,16278可以利用跨目录遍历漏洞造成RCE(Remote Command Execute),16279可以实现DOS攻击。漏洞影响Nostromo webserver 1.9.6及以下所有版本,因为这是一个小众Web服务器,网络上的资料较少,笔者在搭建环境的过程中踩了不少坑,导致分析漏洞的时间周期较长,下面我们一起来看看这个利用跨目录遍历实现RCE的"神奇漏洞"。

0x01 影响版本

nhttpd:version <= 1.9.6,最近一次更新是16年,目测已流产。

0x02 漏洞分析

1. 环境搭建

在官方下载nostromo-1.9.6.tar.gz,使用tar命令解压:
root@Memory:~$ tar xvzf nostromo-1.9.6.tar.gznostromo-1.9.6nostromo-1.9.6/confnostromo-1.9.6/conf/mimesnostromo-1.9.6/conf/nhttpd.conf-dist......SNIP......
修改Makefile文件,添加-g参数并删除strip命令,编译debug版本支持GDB调试:

83e9cdfd989b4abe6833e4a405a69ab6.png

make命令编译

root@Memory:~/nostromo-1.9.6# make......SNIP......make[2]: Entering directory `/root/nostromo-1.9.6/src/nhttpd'cc -O2 -pipe -Wall -Wstrict-prototypes -c -g main.c......SNIP......make[2]: Leaving directory `/root/nostromo-1.9.6/src/tools'make[1]: Leaving directory `/root/nostromo-1.9.6/src'

make install安装

root@Memory:~/nostromo-1.9.6# make installinstall -c -o root -g bin -m 555 src/nhttpd/nhttpd \        /usr/local/sbin/nhttpd......SNIP......install -c -o root -g bin -m 644 icons/file.gif \        /var/nostromo/icons/file.gif
readelf查看nhttpd可执行文件的section header,存在debug节说明可以GDB调试  

30e0110f0388102c5078ba9dab61d802.png

配置nhttpd.conf,注意user不能是root

3ce11f6a3332fd909e20cfc4f657a737.png

修改logs目录权限

3a38e96ec807fcf3ef6b1b781000b525.png

启动nhttpd,8080端口开启说明服务器配置启动成功

447d83858fed9c8f523b479726859e4b.png

2. GDB动态调试

从公布的漏洞详情可知,造成漏洞的原因是http_verify函数对路径校验不严导致的。先make clean清理一下编译过程中产生的目标文件,然后grep搜索http_verify函数所在的源码文件,从搜索结果可知,main函数里面调用了http_verify,代码实现在http.c文件中:

31ee6892f420eca68a496ad100eaa877.png

打开http.c文件,http_verify函数描述及部分关键代码如下

/* * http_verify() *  verify if incoming header is valid * Return: *  0 = invalid header, 1 = valid header */inthttp_verify(char *header, const int header_size, const char *cip, const int sfd,    const int hr){    int     r, proto;    char        *h, *b, line[1024], protocol[16];    time_t      tnow;    struct tm   *t;    r = proto = 0;    /* check if header URI needs to be decoded */    if (http_decode_header_uri(header, header_size) == -1) {        h = http_head(http_s_400, "-", cip, 0);        b = http_body(http_s_400, "", h, 0);        ......SNIP......        return (0);    }    ......SNIP......    /* check for valid uri */    if (strstr(header, "/../") != NULL) {        h = http_head(http_s_400, line, cip, 0);        b = http_body(http_s_400, "", h, 0);        ......SNIP......        return (0);    }    ......SNIP......}
分析代码,首先调用http_decode_header_uri函数对请求中URL编码的字符进行解码,然后使用strstr函数检测hader字符串中是否包含/../字符串,如果包含就直接返回400错误。从代码逻辑可知,这里strstr检测方法有点暴力,它不允许header中存在/../字符串,即使是"合法"的目录回溯也不允许,比如存在以下形式的工程目录:

ec46be604b174f007dfe7df716043628.png

先进入test目录,然后回溯访问首页index.html,这类合法的请求也会报400错误,如下:e8670da61966f35e24114c0a8b4370a1.png具体GDB动态调试看一看,先使用命令start-stop-daemon --stop -x /usr/local/sbin/nhttpd停止已经运行的nhttpd,然后再启动gdb,因为需要调试的代码在子进程中,所以需要set follow-fork-mode child,具体如下:c01c6d03584eda3036264ef394c01b19.png重放数据包,程序自动停止在http_verify函数,调试过程如下,可以看见被url编码的..符号先被解码,然后被strstr函数检测到并返回400错误:60baee3e9d7c7fef0dd6f3d45e43ae65.png从调试结果可知,header变量代表的是整个http请求头,假如请求头中的其他属性携带有/../字符串一样会被strstr函数检测,这种一刀切的暴力检测方法明显存在不合理之处。
到这里,我们知道http_verify函数只是对请求头做了些解码检测的操作,如何绕过检测,看样子还得继续分析后续的代码逻辑。从开始grep搜索结果可知,http_verify函数是在main.c中被调用的,代码如下:

e6933aaadbffb796ba120639aee17e97.png

如果发送的是一个正常http请求,根据http_verify函数检测逻辑可知,如果验证全部通过,则返回1,所以1570行的if判断结果为true,接着进入http_proc函数,该函数定义也在http.c文件中,部分代码如下:

1377bfd096b1e9f35ed72cc2cdcab323.png

分析代码,关键代码是290行if语句中的http_header函数,根据注释可知该函数主要是对请求头进行解析,从296行代码开始就是处理response了。
跟进http_header函数,函数定义同样在http.c文件中,该函数处理完成之后返回一个结构体指针:

561d20c2cbc3005bd2b1661d2aa810b1.png

搜索结构体定义,在extern.h文件中,有兴趣的朋友可以自己看看,这里就不贴出来了:2b5b2812be39838a99f4c31dfa3a42bb.png继续分析http_header函数,先初始化header结构体,初始化完成以后开始解析request,具体代码如下:838b7e3b8ad7c1c524b546c8e21f9aaf.png

下个断点,调试看看变量的具体值:

ab289947bf8fa7d7dee3f2129f8850df.png

根据调试结果可知,1500行的strcutl函数先把请求头中的接口协议等信息解析出来放在line变量中,然后在1502行把接口赋值给header结构体的rq_uri成员。接口解析的主要逻辑在strcutl函数里面,搜索下函数定义,结果如下:6311f9c65c0732e8081b0dc53ddb9764.png

strcutl函数代码详情如下:

a89e9d69470d8b0e5daf0ec3d5af0985.png

简单分析下代码,可知该函数主要是通过换行符(LF,ASCII转义字符\n)为特征来截取request中的第一行数据(接口、协议)信息。核心代码是56和57行,第56行先判断字符是不是回车符号,如果不是就执行57行代码,把符合规则的字符赋值给dst指针。这里重点看56行代码,它会检测\r符号,结合上下文可知,如果检测到了回车符,会自动把回车符号删掉,而不影响后续字符的拼接。

所以,如果在/../中插入回车符号,不就可以绕过http.c中http_verify函数的检测而不影响语义吗?

0x03 漏洞复现

1. POC构造

根据上述的代码分析可知,可以在/../字符串中插入回车符号绕过http_verify函数的检测,然后程序会在strcutl函数里面自动把回车符号删除恢复原本语义,构造poc如下:
GET /..%0d/..%0d/..%0d/..%0d/..%0d/..%0d/etc/passwd HTTP/1.1

测试结果如下,成功读取etc/passwd文件内容

b78d7b1c3b58bb67b6b4de1820741705.png

2. RCE POC分析

该漏洞最初公布的poc可以达到远程命令执行的效果,如下所示

0c6b9cc5c4f8015ed56812390c3985cf.png

对于孤陋寡闻,只知道利用目录遍历读取etc/passwd的菜鸟笔者来说,能利用目录遍历实现RCE还是一件非常新奇的事情,所以决定好好学习下这个脚本,先看看大佬公布的poc:

886beb09242f262b31ec35ff11c30da9.png

脚本还是比较简单,就是利用nc发送了一个post请求包,post参数是两个echo字符串加需要执行的命令。运行poc脚本,用HTTP Analyzer抓下post请求包,结果只要开启抓包工具就返回500错误,最后发现用Wireshark能记录到数据包:bec77d067efe050f27f82d2a95a6a9c6.png把post请求复制到burp里面,重放数据包,结果又报500错误...两个字,讲究!!用Wireshark抓包对比sh脚本和burp发包的二进制流,发现问题出在burp发送的数据包在echo参数换行时多了回车符(CR,URL编码%0d),在Hex编辑器里面删除回车符,重放数据包,成功执行命令:b4ddcb1f65657602499c4b8d0acfb0dd.png分析request可知,就是请求了/bin/sh这个shell,然后shell执行了post参数中的命令。回到代码中,结合前面的分析过程,先把断点下在http.c的290行,使用burp重放数据包,程序自动停止在断点处:f56854ca230c0afe85605b288a8d8c6f.png从调试结果可以看见,header参数只包含头信息,所以可以先不用继续分析http_header函数。直接next命令,然后一直回车,最后发现代码运行到如下位置:  ed0995e762aebf47b6cb52c01c679099.png这里可以看见就是利用execve函数执行了sh可执行文件,next运行execve函数,结果如下,最终执行了指定的命令:651d2b5275b9e8bc3cb85fa8554c9ef6.png

0x04 结语

本想把这个漏洞分析透彻的,结果RCE部分应该是利用到了溢出漏洞,奈何笔者功力不够,暂不能进一步深入分析,搞的文章有点虎头蛇尾的感觉,安全的道路真是路漫漫其修远兮,待学习下相关知识,找机会再写篇文章单独分析下RCE部分详情。笔者水平有限,文章如有理解错误的地方,还请不吝赐教。References:https://git.sp0re.sh/sp0re/Nhttpd-exploits

发现 | 发掘

为随时发生的网安动态发声

91ee814d2154050ef660b135c8f1856a.png

资讯|干货|案例|威胁|动态

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值