原标题:GoAhead远程代码执行漏洞分析报告
0x01介绍
CVE-2017-17562是一个基于GoAhead web server < 3.6.5的远程代码执行漏洞,本文将对该漏洞的细节进行描述。
漏洞的成因是由于GoAhead允许用户通过参数,构造任意的环境变量,该环境变量将会影响所有启用了动态链接的CGI可执行文件。当CGI程序调用glibc动态链接库时,类似于LD_PRELOAD(通常用于函数钩子)的环境变量将导致远程代码执行。
GoAhead是世界上最流行的嵌入式Web服务器,它被IBM、HP、Oracle、波音、D-Link和摩托罗拉使用。我们在Shodan上可以发现超过735000个设备使用GoAhead。
本文以GoAhead作为案例进行研究,很多其他类型的软件也存在类似的问题。
0x02漏洞分析
该漏洞存在于所有的GoAhead版本(我们所能获取的最低版本是2.5.0),可以通过以下命令来获取GoAhead的源码:
daniel@makemyday:~$git clone https://github.com/embedthis/goahead.git
Cloning into 'goahead'...
remote: Counting objects: 20583, done.
remote: Total 20583 (delta 0), reused 0 (delta 0), pack-reused 20583
Receiving objects: 100% (20583/20583), 19.71 MiB | 4.76 MiB/s, done.
Resolving deltas: 100% (14843/14843), done.
daniel@makemyday:~$cdgoahead/
daniel@makemyday:~/goahead$ls
configure CONTRIBUTING.md doc installs main.me Makefile paks README.md test
configure.bat dist farm.json LICENSE.md make.bat package.json projects src
daniel@makemyday:~/goahead$git checkout tags/v3.6.4-q
daniel@makemyday:~/goahead$make >/dev/null
daniel@makemyday:~/goahead$cd test
daniel@makemyday:~/goahead/test$gcc ./cgitest.c-ocgi-bin/cgitest
daniel@makemyday:~/goahead/test$sudo../build/linux-x64-default/bin/goahead
0x03代码
漏洞存在于cgiHandler函数中,该函数先为新进程的envp参数分配一个指针数组,然后使用HTTP参数中的key-value来初始化这个数组。最后,launchCgi函数被fork和execve所执行的CGI脚本所调用。
程序会过滤REMOTE_HOST和HTTP_AUTHORIZATION,除此之外,其他的参数都是可信的,并且没有进一步的过滤。这就允许攻击者在新的CGI进程中控制环境变量。这样的行为十分危险,后面将会具体介绍。
Figure-3:goahead/src/cgi.c:cgihandler
...
PUBLICboolcgiHandler(Webs*wp)
{
Cgi*cgip;
WebsKey*s;
charcgiPrefix[ME_GOAHEAD_LIMIT_FILENAME],*stdIn,*stdOut,cwd[ME_GOAHEAD_LIMIT_FILENAME];
char*cp,*cgiName,*cgiPath,**argp,**envp,**ep,*tok,*query,*dir,*extraPath,*exe;
CgiPidpHandle;
intn,envpsize,argpsize,cid;
...
/*
Add all CGI variables to the environment strings to be passed to the spawned CGI process. This includes a few
we don't already have in the symbol table, plus all those that are in the vars symbol table. envp will point
to a walloc'd array of pointers. Each pointer will point to a walloc'd string containing the keyword value pair
in the form keyword=value. Since we don't know ahead of time how many environment strings there will be the for
loop includes logic to grow the array size via wrealloc.
*/
envpsize=64;
envp=walloc(envpsize*sizeof(char*));
for(n=0,s=hashFirst(wp->vars);s!=NULL;s=hashNext(wp->vars,s)){
if(s->content.valid&&s->content.type==string&&
strcmp(s->name.value.string,"REMOTE_HOST")!=0&&
strcmp(s->name.value.string,"HTTP_AUTHORIZATION")!=0){
envp[n++]=sfmt("%s=%s",s->name.value.string,s->content.value.string);
trace(5,"Env[%d] %s",n,envp[n-1]);
if(n>=envpsize){
envpsize*=2;
envp=wrealloc(envp,envpsize*sizeof(char*));
}
}
}
*(envp+n)=NULL;
/*
Create temporary file name(s) for the child's stdin and stdout. For POST data the stdin temp file (and name)
should already exist.
*/
if(wp->cgiStdin==NULL){
wp->cgiStdin=websGetCgiCommName();
}
stdIn=wp->cgiStdin;<