Hacking an infrared panel controller

By Puck Meerburg

Note: This blog post talks about the optional web-connected controller. The panels themselves are fine and don’t do anything except heat up. This is good.

So, we moved to a new house. It’s an interesting house: We don’t have gas, or any centralized heating. Instead, we use these infrared panels, which turn 500-1000W of power into heat. These panels are really dumb; connect it to 230V to heat it up, and disconnect it to make it not heat. So, the company that makes these nice IR panels also sells these controllers. One of which is connected to a normal thermostat (that shorts two cables) and one which is… internet-connected. This is going to be fun.

Part 1: What do I even do with this.

So, there’s an internet based controller. No documentation to be found. So, how do we start hacking this? The mobile app, of course. So, I downloaded it, and it requires an account and a server IP (weird). I looked at the code, and figured out quickly that there’s a default IP (no DNS!). So, I navigated at it, and… an apache default ‘it works!’ page. Woops, added /portal after it and… it shows an interface! So, I registered, and looked at what I could do. Not much. Entered some random data, and I couldn’t really do anything. I did find a documentation file, and entered its example MAC address (yeah, it only verifies by MAC address), and got nothing. So, instead, I went back to the mobile app..

Part 2: Welp, API design

I started reversing the mobile app again, and found some fun API calls, like one that sets temperature (can’t be used to set infrared panels you don’t own), and one that requests the entire contents of a database table, filtered by the group ID you use.


Yeah, seriously. It basically builds a SQL query like this:

 SELECT * from $table WHERE group_id = $groupId

Sadly, the table name is sanitized, so no SQL injection. But sanitizing doesn’t help against logic errors… I went back to the non-mobile API, and looked at the sessions. They were authenticated php serialized blobs, so I could look, but not touch. Welp, can’t hack that either. After a bit of searching, I found the demo code this entire webapp was based off of, which was kinda weird. And then something clicked: If I change the privileges of a user while it’s logged in, the privileges only take effect after logging out and back in. It seems the privileges that you have are only dependant on the session! However, there was no group ID in there, so that had to be dynamically retrieved from the database. So, I tried some fun thing: Delete an account while it was still logged in, then use that account that was deleted to make another account. Apparently the webapp did not like that, and it resulted in that new account getting a group ID of null. Now I can’t do anything.

Part 3: Chekhov’s API call

I can’t do anything with that account. In the webapp. But wait, there’s a mobile app! I open the mobile app, enter the credentials, and… I SEE EVERYTHING. Bingo.

So, how did this happen? Well, I think the mobile API uses a SQL abstraction, which works a bit like this:

$db->table($_GET["table"])->select(["groupId" => getGroupId($_SESSION["userId"])])

But, my group ID is null. And thus, it won’t filter on this group ID. So, what can I do now? I can manipulate any infrared panel, or temperature sensor. Woo. Back to the desktop, I started inspecting the demo code. It defined some database tables for the user profile, for storing password hashes, usernames, email addresses, et cetera. So, I entered them into this nice API that allows me TO GET THE CONTENTS OF ANY TABLE I WANTED. And I get all personal info back, in nice JSON format. Hooray!

Note: I did send the company that makes these infrared panels the details of this attack, but the response I got is “I’ve sent it to the former developer”. Ouch.