zyxel路由器CVE-2024-9200漏洞调用链分析

admin 2025年5月5日23:06:21评论15 views字数 7745阅读25分49秒阅读模式

本文主要是针对于zyxel厂商的路由器的zhttpd程序进行漏洞分析,作者在挖掘zyxel设备漏洞时,翻到一篇较新的漏洞公告:

Zyxel security advisory for buffer overflow and post-authentication command injection vulnerabilities in some 4G LTE/5G NR CPE, DSL/Ethernet CPE, fiber ONTs, and WiFi extenders | Zyxel Networks

其中我关注到CVE-2024-9200这个漏洞,比较经典的ping功能产生命令注入,日期比较新,就决定简单地看看这个漏洞。然而网上搜不到该漏洞的详细信息,便自己去他们官网下载了一个新版固件来分析(型号vmg4005)。然而在自己分析的过程中,发现该漏洞调用链还是有一点点的复杂度的。

本文就是分享我分析该漏洞时遇到的一些问题和知识点,或许可以给挖掘zyxel设备或遇到zhttpd这个框架的师傅一些帮助。

1

固件仿真

可以用FirmAE直接进行模拟,启动后可能不知道管理员默认密码,FirmAE模拟时使用-d参数开启终端,使用passwd admin修改密码就可以登录web页面了。

这里值得一说的是,要分析的程序zhttpd,打开后函数是没有命名的,影响了分析,不过我在data段找到了一张表,容易看出里面存储着的就是url页面和对应的处理函数。

zyxel路由器CVE-2024-9200漏洞调用链分析

那么函数名这里就可以恢复了,使用ida python的脚本就行。

import idc
import idaapi
import idautils
 
def fix_function_names(start_addr, end_addr):
    """
    从指定地址开始解析符号表,修复函数名。
    """

    print(f"开始修复函数名,符号表起始地址: {hex(start_addr)},截止地址: {hex(end_addr)}")
    offset = 0
 
    while True:
        # 计算当前符号表条目的地址
        entry_addr = start_addr + offset
 
        # 如果当前地址超出截止地址,停止解析
        if entry_addr >= end_addr:
            print("已到达符号表截止地址,停止解析。")
            break
 
        # 读取函数名字符串地址和函数地址
        name_addr = idc.get_wide_dword(entry_addr)  # 前4字节是函数名地址
        func_addr = idc.get_wide_dword(entry_addr + 4)  # 后4字节是函数地址
 
        # 如果函数名地址或函数地址无效,跳过当前条目
        if name_addr == 0 or func_addr == 0:
            print(f"无效的符号表条目,地址: {hex(entry_addr)},跳过...")
            offset += 8
            continue
 
        # 获取函数名字符串
        func_name = idc.get_strlit_contents(name_addr, -1, idc.STRTYPE_C)
        if func_name is None:
            print(f"无法读取函数名字符串,地址: {hex(name_addr)},跳过...")
            offset += 8
            continue
 
        # 将函数名解码为字符串,并替换非法字符
        try:
            func_name = func_name.decode('utf-8', errors='ignore')
            # 替换非法字符为 _
            illegal_chars = ['/''-''?''=']
            for char in illegal_chars:
                func_name = func_name.replace(char, '_')
        except Exception as e:
            print(f"解码函数名失败,地址: {hex(name_addr)},错误: {e}")
            offset += 8
            continue
 
        # 重命名函数
        if idc.set_name(func_addr, func_name, idc.SN_NOWARN):
            print(f"函数名已修复: {hex(func_addr)} -> {func_name}")
        else:
            print(f"无法重命名函数: {hex(func_addr)} -> {func_name}")
 
        # 移动到下一个符号表条目
        offset += 8
 
# 调用函数,指定符号表起始地址和截止地址
fix_function_names(0x573B80x575E8)

2

漏洞分析

以下是从公告中获取的漏洞信息,知道了触发页面在诊断(diagnostic)中,参数就是host。

zyxel路由器CVE-2024-9200漏洞调用链分析

对应页面内容

zyxel路由器CVE-2024-9200漏洞调用链分析

点击按钮后抓包,发了三个包,第一个是put,但是参数被加密了,后面两个包是get类型,应该是获取结果。

zyxel路由器CVE-2024-9200漏洞调用链分析

上面的抓包获取了/cgi-bin/DAL?oid=PINGTEST这个关键信息,那么直接关键词在zhttpd中搜索即可,来到了cgi-bin_DAL这个函数。

(注:下文后面我贴的所有ida中看到的程序的代码,我都做了删改和注释,帮助快速看懂关键代码)

int __fastcall cgi_bin_DAL(int a1, unsigned __int8 a2)
{
  RdmObjHandler = 0;
  v34 = 0;
  oid = 0;
  method = 0;
  oid = (char *)cg_http_vallist_getvalue((a1 + 672), (int)"oid");//获取oid的值,也就是抓包中看到的pingtest
  method = (char *)cg_http_request_getmethod(a1);

  v23 = json_object_new_object(v2);
  if ( !strcmp(method, "POST") || !strcmp(method, "PUT") )
  {
    v24 = cg_string_getvalue((a1 + 4));//获取POST数据
    v3 = cg_http_packet_getheaderlonglong(a1, "Content-Length");
    
    v34 = (char *)json_tokener_parse(v24, HIDWORD(v3));
   
    insertLoginUserInfo((int)v34, a1 + 680, a1 + 712);
    RdmObjHandler = zcfgFeDalHandler((int)oid, (int)method, (int)v34, 0, (int)s);//根据oid值处理请求,具体实现在libzcfg_fe_dal.so中
   
  }
}

zcfgFeDalHandler这个函数在libzcfg_fe_dal.so中,继续跟进代码。这里的全局变量dalHandler,跟进后可确认就是handler表,保存oid值和对应处理函数。

int __fastcall zcfgFeDalHandler(char *oid, const char *method, int a3, int a4, char *dest)
{

  if ( !oid )
    return 0;
  v9 = 0;
  v10 = 24;
  while ( 1 )
  {
    v11 = *(&dalHandler + v10 * v9);
    if ( !v11 )
    {
      v12 = 0;
      goto LABEL_7;
    }
    v15 = v10;
    if ( !strcmp(oid, v11) )
      break;
    v10 = v15;
    ++v9;
  }
  v12 = 1;
LABEL_7:
  printf("handlerName=%s method=%s i=%dn", oid, method, v9);

  v14 = 24 * v9;
  if ( !j_parseValidateParameter(a3, method, *(&dalHandler + v14), *(&dalHandler + v14 + 4), dest) )
    return -17;
  if ( !*(&dalHandler + v14 + 8) )
    return 0;
  return (*(&dalHandler + v14 + 8))(method, a3, a4, dest);//根据oid的值在dalHandler表中匹配到的函数指针,调用对应的函数
}

表中pingtest对应如下,这里很容易判断出zcfgFeDalPingTest就是处理函数。

zyxel路由器CVE-2024-9200漏洞调用链分析

跟进zcfgFeDalPingTest,其中还有多处调用,这里就忽略了,直接来到最后一层关键函数zcfgFeDalIpDiagIPPingEdit。

int __fastcall zcfgFeDalIpDiagIPPingEdit(int a1)
{

  v24 = 0;
 
  string = json_object_object_get(a1, "ProtocolVersion");
 
  v4 = string;
  if ( json_object_object_get(a1, "Host") )
  {
    v7 = json_object_object_get(a1, "Host");
    v6 = json_object_get_string(v7);
    j_invalidCharFilter(v6, ')');
    v5 = j_DomainFilter(v6) != 1 && j_IPFilter(v6) != 1 && j_IPv6Filter(v6) != 1;
  }

  if ( v5 )
  {
    puts("ninvalid input...");
    return -5;
  }
 
  *v25 = 0;
  *&v25[3] = 0;
  if ( zcfgFeObjJsonGet(68592, v25, &v24) )
  {
    //...
  }
  v14 = v24;
  if ( v24 )
  {
    v15 = json_object_new_string(v4);
    json_object_object_add(v14, "ProtocolVersion", v15);
    v16 = v24;
    v17 = json_object_new_string(v6);
    json_object_object_add(v16, "Host", v17);
    v18 = v24;
    v19 = json_object_new_string(v10);
    json_object_object_add(v18, "DiagnosticsState", v19);
    v20 = v24;
    v21 = json_object_new_int(v13, 0);
    json_object_object_add(v20, "NumberOfRepetitions", v21);
    v22 = json_object_to_json_string(v24);
    v5 = zcfgFeObjWrite(68592, v25, v22, 0x200000030);//将数据写入68592标识的对象中
 
    if ( v24 )
      json_object_put(v24);
  }
  return v5;
}

其实到这里后,一开始,就会比较困惑,因为这就是最后一层调用,但是并没有发现ping命令执行的逻辑,仅仅做了把数据保存到json对象中,然后就啥也没干了。经过确认,其实关键函数在于这两行,他们的实现分别在libzcfg_fe_rdm_access.so和libzcfg_fe_rdm_string.so

zcfgFeObjJsonGet(68592, v25, &v24)
zcfgFeObjWrite(68592, v25, v22, 0x20000003, 0);

其实这里具体去追究他们的实现,是比较麻烦而且困难的,因为不仅抽象难懂,还反反复复涉及到了多个外部函数。可以直接猜到这里涉及到的事情。

观察他们的命名,反复出现的zcfg(Z-config)应该是个配置标识,而FE(Front End)往往指前端,那么可以推测,这里肯定是有对应的后端 (BE)的,前端把数据发送给后端,后端再进行处理。而这里的参数68592,很可能就是标识后端的某个数据对象。可能存在某种监控机制,当前端写入数据后,后端监控到该数据对象发生了改变,就进行相应的处理,比如执行ping命令。

而这两个函数也可以猜测出功能:

 zcfgFeObjJsonGet(68592, ...):从后端获取与 `68592` 对应的对象数据。

  zcfgFeObjWrite(68592, ...):将数据写入与 `68592` 对应的对象。

最后确实找到了进行后端处理的文件libzcfg_be.so,也找到了对应的执行ping命令的函数。

int __fastcall beIpPingDiagSet(int a1, int a2, int a3, int a4)
{

  
  PartialObj = zcfgBeApplyObjRetrievePartialObj(a1);
  if ( PartialObj )
  {
    v9 = json_object_object_get(PartialObj, "DiagnosticsState");
    string = (const char *)json_object_get_string(v9);
    v11 = strcmp(string, "Requested");
    if ( v11 == 0 )
      zcfgBeSaveParamValue(68592, a3, (int)"X_ZYXEL_Creator", (int)"ACS");
  }
 
LABEL_19:
    if ( !strcmp((const char *)a1, "Requested") )
    {
        system("killall ping 2>/dev/null");
        zcfgBeSaveParamValue(68592, a3, (int)"DiagnosticsState", a1);
        sprintf(s, "echo "" >%s""/var/diagResult");
        system(s);
        memset(s, 00x140u);
       
        strcpy(s, "ping ");
        v16 = &s[strlen(s)];
       
        if ( strcmp((const char *)(a1 + 286), "IPv4") )
        {
    LABEL_33:
            v19 = v16 + 2;
           
            sprintf(
            v19,
            "-s %d -c %d -W %d %s >%s&",
            (a1 + 568),
            (a1 + 560),
            v14,
            (const char *)(a1 + 303),   //host的值,即用户输入
            "/var/diagResult");

            system(s);
            syslog(6"Command %sn", s);
            return 0;
            }
           
        }         
    return 0;
}

跟进到这,也就完成了漏洞分析了,这里分析的是新版固件,可以看到过程中,zcfgFeDalIpDiagIPPingEdit做了对host的过滤,修复了漏洞。

3

补充

zyxel的这种类型的洞,网上应该是搜不到什么的,所以这里分享出来希望能帮到后面看到文章的师傅。

处理数据的后端文件就是libzcfg_be.so,漏洞也基本都发生在这,要挖的话可以在这找到疑似危险函数,再往上回溯到对应接口验证。

前端不仅做了参数数据加密,还做了对危险字符的过滤,需要打开浏览器开发者模式,在element中的event listeners那一栏找到对应的事件,然后删掉,才能绕开前端过滤。

在逆向分析的时候,由于代码混乱,涉及到大量json数据的处理,而且根据不同的请求方法还有多个分支,所以实际分析的时候还是有很多干扰项的,明确put/post/delete/get四种请求方法的作用,抓包分析,还有一些审计伪代码的经验,都是必要的。

对于zhttpd本身,做了一下未授权接口的分析,核心逻辑在下面,可以看出,只要url包括/cgi-bin/,就需要验证身份,那么未授权接口只要不包含/cgi-bin/就行。

zyxel路由器CVE-2024-9200漏洞调用链分析

zyxel路由器CVE-2024-9200漏洞调用链分析

看雪ID:CLan_nad

https://bbs.kanxue.com/user-home-1014504.htm

*本文为看雪论坛优秀文章,由 CLan_nad 原创,转载请注明来自看雪社区

原文始发于微信公众号(看雪学苑):zyxel路由器CVE-2024-9200漏洞调用链分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月5日23:06:21
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   zyxel路由器CVE-2024-9200漏洞调用链分析https://cn-sec.com/archives/4028900.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息