吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 854|回复: 1
上一主题 下一主题
收起左侧

[漏洞分析] faqia浅浅分析CVE-2023-33012

[复制链接]
跳转到指定楼层
楼主
faqiadegege 发表于 2024-8-21 16:04 回帖奖励
本帖最后由 faqiadegege 于 2024-8-21 19:19 编辑

faqia 这里浅浅分析 Zyxel ATP 系列命令注入漏洞
参考:https://vulncheck.com/blog/zyxel-cve-2023-33012#you-get-one-shot
首先,
在其httpd.conf中显示路径
...
LoadModule auth_zyxel_module    modules/mod_auth_zyxel.so
...
AuthZyxelSkipPattern
/images/
/lib/
/mobile/
/weblogin.cgi
/admin.cgi
/login.cgi
/error.cgi
/redirect.cgi
/I18N.js/language
/logo/
/ext-js/web-pages/login/no_granted.html
/ssltun.jar/sslapp.jar
/VncViewer.jar
/Forwarder.jar
/eps.jar
/css/
/sdwan_intro.html/sdwan_intro_video.html
/videos/
/webauth_error.cgi
/webauth_relogin.cgi
/SSHTermApplet-jdk1.3.1-dependencies-signed.jar
/SSHTermApplet-jdkbug-workaround-signed.jar
/SSHTermApplet-signed.jar
/commons-logging.properties
/org.apache.commons.logging.LogFactory/fetch_ap_info.cgi
/agree.cgi
/walled_garden.cgi
/payment_transaction.cgi
/paypal_pdt.cgi
/redirect_pdt.cgi
/securepay.cgi
/authorize_dot_net.cgi
/payment_failed.cgi
/customize/
/multi-portal/
/free_time.cgi
/free_time_redirect.cgi
/free_time_transaction.cgi
/free_time_failed.cgi
/js/
/terms_of_service.html
/dynamic_script.cgi
/ext-js/ext/ext-all.js
/ext-js/ext/adapter/ext/ext-base.js
/ext-js/ext/resources/css/ext-all.css
/ext-js/app/common/zyFunction.js
/ext-js/app/common/zld_product_spec.js
/cf_hdf_blockpage.cgi
/2FA-access.cgi
/webauth_ga.cgi
/fbwifi_error.cgi
/fbwifi/
/ztp/cgi-bin/ztp_reg.py
/ztp/cgi-bin/checkdata.py
/ztp/cgi-bin/parse_config.py
/ztp/cgi-bin/checkconn.py
/ztp/cgi-bin/ztppolling.py
/ztp/cgi-bin/activate.py
/ztp/cgi-bin/conn_fail_checking.py
/ztp/cgi-bin/changeLEDst.py
/ztp/cgi-bin/postcertificate.py
/ztp/cgi-bin/serverinit.py
/ztp/cgi-bin/twoFApincode.py
/ztp/cgi-bin/twoFApolling.py
/ztp/cgi-bin/vpn_certificate.py
/ztp/cgi-bin/ztp_bg.py
/ztp/cgi-bin/dumpztplog.py
/ztp/activation_success.html
/ztp/activation_fail.html
/ztp/activationfail.html
/ztp/apply_fail.html
/ztp/twoFAapps.html
/ztp/twoFAsms.html
/ztp/verification_fail.html
/ztp/zld_enabled.html
/ztp/ztp_enabled.html
/ztp/ztp_reg.html
/ztp/css/ztp/images
/ztp/fonts
...
sdwan_interface sdwan_iface_ipc
进程间通信时,
可执行代码如下,向V31中组包了大量数据,其中的offset584 的数据被使用了 0x14长度
  

紧接着,使用pic_sdwan_send_config将组包数据 v31作为参数 发送给接口 sdwan_interface
操作 /var/log/ztplog ,追加数据
将 【IPC】写入到 ztplog 中
将 pic_sdwan_send_config (&v31) 写入到 ztplog中




sdwan_interface 接口中,offset_584被添加到system command 执行



这两项数据,尝试下载v37版本,但是解压时需要密码,并尝试使用二进制查找该两部分线索,没有搜到,这里无法做出判断
/ztp/cgi-bin/parse_config.py
可访问在其代码中有如下逻辑

[Python] 纯文本查看 复制代码
def main():
    form = cgi.FieldStorage()
    conf_str = form.getvalue("config")
    #### skip ####
    if conf_str is None:
        conf_str = ""
    else:
        #### skip ####
        if not os.path.exists(ztpinclude.SERVER_SOCK_FILE):
            logging.error(
                "Cannot find sdwan_interface socket [%s]!" % ztpinclude.SERVER_SOCK_FILE
            )
            print("ParseError: 0xC0DE0005")
        else:
            conf_str = urllib.unquote(conf_str)
            try:
                decoded_config = base64.b64decode(conf_str)
            except:
                logging.error("invalid base64 str %s" % conf_str)
                print("ParseError: 0xC0DE0004")
                return
            #### skip ####
            try:
                fout = open(ztpinclude.ZTPFILEPATH + "ztpconf.conf", "w+")
                if fout is not None:
                    fout.write(decoded_config)
                    ok = True
                    fout.close()
            except Exception as e:
                logging.debug("e=%s" % e)
                print("ParseError: 0xC0DE0002")
                return
            #### skip ####
            if ok:
                ztp_soc.ztp_led_start()
                (parse_result, ou, org, cn) = network_parse.parse_result(
                    ztpinclude.ZTPFILEPATH + "ztpconf.conf"
                )
                if parse_result == ztpinclude.APPLYSUCC:
                    csrmgr.new_csrcfg(ou, org, cn)
                    print(
                        "ou=%s,org=%s,cn=%s"
                        % (urllib.quote(ou), urllib.quote(org), urllib.quote(cn))
                    )
                else:
                    print("ParseError")
            else:
                print("ParseError: 0xC0DE0006")



如上,未认证的用户可以直接提交config数据并写入文件ztpconf.conf中在上面代码中,最后被写入的config文件,调用parse_result解析逻辑如下
[Python] 纯文本查看 复制代码
def parse_result(filepath):
  #### skip ####
  if os.path.isfile(filepath):
    parser = Parser()
    config = parser.parse(filepath)
    if check_model_id(config) != 0:
      logging.info("Check model id with config fail!!")
      return (ztpinclude.MODELIDERR, parm_ou, parm_o, parm_cn) 
    save_config_data(config)
    with open(ztpinclude.ZTPFILEPATH + 'parsed_config', 'w+') as fout:
      for configlist in config:
        try:
          if configlist['proto'] == "cellular":
            #### skip ####
          elif configlist['proto'] == "static":
            #### skip ####
          elif configlist['proto'] == "pppoe":
            #### skip ####
          elif configlist['proto'] == "deviceha":
            #### skip ####
          elif configlist['proto'] == "certificate":
            #### skip ####
          elif configlist['proto'] == "vti":
            if not handle_vti(configlist, vti_cnt):
                break
            vti_cnt += 1
          elif configlist['proto'] == "gre":
            if not handle_gre(configlist, gre_cnt):
                break
            gre_cnt += 1
        except Exception as e:
            #### skip ####
      return (applyresult, parm_ou, parm_o, parm_cn) 
  else:
      #### skip ####




重点关注上面的解析的两个分支
configlist['proto'] == "vti":时

[Python] 纯文本查看 复制代码
def handle_vti(configlist, idx):
    ok = False
    qsrname = "/tmp/%s.qsr" % configlist["name"]//qsr文件
    logging.info("setting up vti interface")
    logging.info("; ".join(["=".join(_) for _ in configlist.items()]))
    out = open(qsrname, "w+")
    if out:
        for k in configlist:
            out.write("%s %sn" % (k, configlist[k]))//向qsr中写入
        out.flush()
        out.close()
    else:
        return False
    # it's time to create vti interface
    # sdwan_iface_ipc 7   vti0  192.168.100.1 24  192.168.100.2 qsr /tmp/qsr0.txt
    params = [
        "/usr/sbin/sdwan_iface_ipc",
        "7",
        configlist["name"],
        configlist["ipaddr"],
        configlist["netmask"],
    ]
    if "gateway" in configlist:
        params.append(configlist["gateway"])
    else:
        params.append("-")
    params.append("qsr")
    params.append(qsrname)
    response = subprocess.call(params)//启动执行命令,包含了qsr内容
    if response != (256 >> 8):
        logging.info("Apply fail : %d %s" % (response, "".join(params)))
        applyresult = ztpinclude.APPLYFAIL
        ok = False
    else:
        f = open("/tmp/ignore-nccubs-vpn-reset", "a")
        if f is not None:
            f.write("%s," % configlist["name"])
            f.close()
        ok = True
    return ok


configlist['proto'] == "gre" 时:
      
[Python] 纯文本查看 复制代码
def handle_gre(configlist, idx):
    ok = False
    logging.info("setting up gre interface")
    logging.info("; ".join(["=".join(_) for _ in configlist.items()]))
    # it's time to create gre interface
    # sdwan_iface_ipc 8   gre1  192.168.100.1 24  192.168.100.2 if:eth0         61.220.240.159  key 190815111 nhrp  nhrppsk ciscozyxel  nhs 192.168.100.2
    # sdwan_iface_ipc 8   gre1  192.168.100.1 24  192.168.100.2 61.220.240.160  61.220.240.159  key 190815111 nhrp  nhrppsk ciscozyxel  nhs 192.168.100.2
    params = [
        "/usr/sbin/sdwan_iface_ipc",
        "8",
        configlist["name"],
        configlist["ipaddr"],
        configlist["netmask"],
    ]
    if "gateway" in configlist:
        params.append(configlist["gateway"])
    else:
        params.append("-")
    if "base" in configlist:
        params.append("if:%s" % configlist["base"])
    elif "localip" in configlist:
        params.append(configlist["localip"])
    else:
        logging.info("Apply fail: neither base or localip is specificied")
        return False
    params.append(configlist["remoteip"])
    if "key" in configlist://将配置内容追加到params中
        params.append("key")
        params.append(configlist["key"])
    if "nhrp" in configlist and configlist["nhrp"] != "0":
        params.append("nhrp")
        if "nhrpsecret" in configlist:
            params.append("nhrppsk")
            params.append(configlist["nhrpsecret"])
        if "nhs" in configlist:
            params.append("nhs")
            params.append(configlist["nhs"])
    response = subprocess.call(params)//执行命令,内容为配置信息
    if response != (256 >> 8):
        logging.info("Apply fail: %d %s" % (response, " ".join(params)))
        applyresult = ztpinclude.APPLYFAIL
        ok = False
    else:
        ok = True
    return ok



综上,漏洞过程为:使用 gre 分支 向config 传输base64编码的数据
或者
向 /tmp/*.qsr中写入数据

最后sdwan_interface 和 sdwan_iface_ipc 进程间通信中可执行代码
传入参数到 offset_584中
之后pic_sdwan_send_config 将这部分数据组包发给 sdwan_interface
在sdwan_interface 中,offset_584 被发送给 system 执行
其执行结果 + IPC 写入到 /var/log/ztplog 中
此时,只需要触发 IPC 即可
前面说到/ztp/cgi-bin/parse_config.py 可访问
而在其代码中 获取config数据
对数据解析后两个分支 vti和gre中
都使用了/usr/sbin/sdwan_iface_ipc而该服务的参数正好来自config
并且在vti分支,还可以写文件,传入命令


!!!此笔记仅作学习研究








免费评分

参与人数 1吾爱币 +7 热心值 +1 收起 理由
willJ + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
jsncy 发表于 2024-11-20 11:36
感谢分享,有学到东西。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-21 13:06

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表