该漏洞是由于上传过滤器没有设置不受信任的 var 位绕过 CGI 处理程序的前缀检测,攻击者可利用该漏洞在未授权的情况下,构造恶意数据执行远程命令执行攻击,最终获取服务器最高权限。
具体成因则是GoAhead在处理CGI请求时,将用户传入的的参数作为环境变量了。这样,通过参数LD_PRELOAD
就可以劫持CGI进程的动态链接库,进而执行任意代码。并且本漏洞实际上是对CVE-2017-17562的一次绕过。
补丁对用户传入参数进行了黑名单过滤,LD_PRELOAD
这类参数不再设置为环境变量。但由于这个限制使用错了函数,导致实际上并没有生效
并且补丁还将用户传入的参数名前面增加了前缀,导致无法劫持任意环境变量。但这个限制漏掉了multipart的POST包,所以攻击者通过这个方式仍然可以注入任意环境变量。
在2021年5月份GoAhead默认将CGI相关的配置注释了,所以新版本的GoAhead默认没有开启CGI配置,并且老版本如果没有cgi-bin目录,或者里面没有cgi文件,也不受这个漏洞影响。
危害等级:高危
这个RCE的关键点在于GoAhead的一个特点,当GoAhead在遇到上传表单的时候,会先将这个上传的文件保存在一个临时目录下,待脚本程序处理完成后删掉这个临时文件,也就是函数WEBS_UPLOAD
的处理过程。
其中在文件upload.c
中,默认定义了上传文件保存路径为目录tmp
中。
原理是如果宏ME_GOAHEAD_UPLOAD_DIR没有定义,则将其定义为tmp。然后将其赋值给uploadDir,ME_GOAHEAD_UPLOAD_DIR一定不是空字符串,所以上传目录就是tmp。
要注意的是源码的上传目录配置,由于是相对路径,相对于的是当前目录,当前目录是在启动GoAhead的时候用--home参数指定的,是存放配置文件的目录,在这里是/etc/goahead。
#ifndef ME_GOAHEAD_UPLOAD_DIR
#define ME_GOAHEAD_UPLOAD_DIR "tmp"
#endif
PUBLIC void websUploadOpen(void)
{
uploadDir = ME_GOAHEAD_UPLOAD_DIR;
if (*uploadDir == '\0') {
#if ME_WIN_LIKE
uploadDir = getenv("TEMP");
#else
uploadDir = "/tmp";
#endif
}
trace(4, "Upload directory is %s", uploadDir);
websDefineHandler("upload", 0, uploadHandler, 0, 0);
}
也就是说,临时文件是存放在/etc/goahead/tmp这个目录下的,如果这个目录不存在或者不可写,那么就会出现上传时500,从而导致该漏洞无法利用。
然后当我们上传时会在函数processUploadHeader
中构造此次HTTP请求结构体的uploadTmp
值。
函数
websTempFile
默认将生成一个/tmp/tmp-0.tmp
的临时文件名称。然后打开临时文件,将文件句柄赋值给wp->upfd
。
接着当使用函数
processContentData
处理上传文件时,将通过函数writeToFile
写入文件。
最终将文件内容写入了
wp->upfd
,保存到了创建的临时文件中。
然后在WEBS_READY阶段
函数websPump
将通过函数websRunRequest
进行处理。
在这里的两个函数
websSetQueryVars
和websSetFormVars
都使用了函数addFormVars
,而函数addFormVars
处理的最后会将sp->arg
赋值为1,使文件cgi.c
中的函数cgiHandler
对请求参数进行重命名,从而修复CVE-2017-17562漏洞。
接着我们来看HTTP请求在GoAhead中的状态。
当HTTP请求出现后会使用函数
readEvent
进行处理。
查看函数
websPump
。
在
WEBS_BEGIN阶段
,使用函数parseIncoming
。
函数
parseIncoming
中会使用函数parseHeaders
会对HTTP头进行检查与解析,并根据content-type的不同类型,完成对wp->flags
的赋值。
当进入
WEBS_CONTENT阶段
,函数websPump
中使用函数processContent
进行下一步的处理。
在
WEBS_READY阶段
中,GoAhead对POST请求和GET请求提交的参数都会使用函数addFormVars
进行处理,将sp->arg
赋值为1,从而使得文件cgi.c
中的函数cgiHandler
重命名环境变量,但是我们可以看到POST请求使用函数addFormVars
的前提是函数wp-flags
取值为WEB_FORM
,回头来看WEBS_BEGIN
处理过程,当content-type
为multipart/form-data
时,wp-flags
将赋值为WEBS_UPLOAD
,也就是说,如果HTTP请求为文件上传类型,参数将不会通过函数addFormVars
处理,此时s->arg
取值仍然为0,从而文件cgi.c
中的函数cgiHandler
中将不会使用ME_GOAHEAD_CGI_VAR_PREFIX
作为前缀进行修改,而是直接进入了其他分支。
复现攻击时要注意几点
tmp
目录,导致实际上攻击者无法通过文件上传的方式向目标写入任意文件,也就无法完成攻击。在了解以上几点后构造测试漏洞的POChack.c
。
#include <unistd.h>
static void before_main(void) __attribute__((constructor));
static void before_main(void)
{
write(1, "Hello: World\r\n\r\n", 16);
write(1, "Hacked\n", 7);
}
编译文件,使用-s参数来缩小体积。
gcc -s -shared -fPIC hack.c -o hack.so
构造HTTP请求,证明存在。
原理是,在请求体中加上脏字符构造实际大小比Content-Length大的数据包,导致上传实际上只上传了一半,保存在临时文件中的是完整的payload和一些脏字符。
此时由于上传流程没有结束,所以此时文件描述符是没有关闭的,可以读取到,脏字符也不影响动态链接库的加载和运行,最后即可成功完成劫持。
建议受影响的用户及时更新升级到最新版本。
链接:https://github.com/embedthis/goahead