Craig Heffner, a Vulnerability Analyst with Tactical Network Solutions, presented at Black Hat to cover common security issues in network surveillance cameras.
Hi, I’m Craig Heffner; this talk is, obviously, “Exploiting Surveillance Cameras Like a Hollywood Hacker”. As some of you may or may not know, when my talk was first announced it got a little bit of press, which as a speaker is really cool.
The problem is that in order to hype their articles, all of the news stories that covered my talk decided to emphasize the fact that I used to work for a particular three-letter agency who has been in the press quite a bit themselves lately (see right-hand image). And although they couldn’t quite seem to agree what those three letters actually stood for, some of them did go as far as to claim that what I presented at Black Hat was actually work I had done for said agency.
Now, with current events being where they are, this resulted in some very interesting phone calls from my ex-employer (see leftmost image below). Luckily we have people who handle the phones for me and they got yelled at instead of me. But while my initial attempts to kind of assuage their fears didn’t work (see middle image below), I was eventually able to convince them that yes, sometimes people on the Internet are wrong (see rightmost image below).
So, just to be very clear, this talk is not about any work that I’ve ever done for any ex-employer. What this talk is about is work that I do for my current employer. I work as an embedded vulnerability analyst for Tactical Network Solutions. I also teach our Embedded Device Exploitation courses and dabble with wireless hacking from time to time as well.
What I’m going to be talking about today, obviously, is the security in surveillance cameras, or lack thereof as it may be. Back in early 2011 I started taking a look at the security that’s in the firmware actually running on these network-connected surveillance cameras. And since I’m up here talking about it, as you might surmise, I found a lot of interesting things. So I’ll be dropping some 0-days as well as some not so 0-days – and we’ll talk about that when we get to it – but also demonstrating how these vulnerabilities ultimately can be leveraged in true Hollywood style fashion.
When I started looking at surveillance cameras I said, well, I’ve looked at a lot of embedded devices before but I haven’t looked at surveillance cameras. So I kind of wanted to start off for something easy, something that would be almost guaranteed to get me a win, so I picked D-Link because they never fail to disappoint. Specifically, I looked at the DCS-7410 (see right-hand image) which at around $900 is one of the more expensive business IP cameras. And like all the cameras that I’m going to be talking about today, they provide an administrative interface as well as access to the video feed through a web server running on the camera, which makes the web server a very attractive target for an attacker.
Specifically, this camera uses Lighttpd, which is an open source web server that you find used quite a bit in embedded devices. And in the Lighttpd configuration they set up some very sane and restrictive access rules as to who can get to what through the web server (see right-hand image). You can see here that if you wanted to get to anything in the CGI admin directory, you have to be logged in as an admin. If you want to get to anything in the Video directory, you can be any user but you do have to be authenticated, you do have to be a valid user.
So they had entries for every single directory in their web interface. Except one. They did not have an entry for cgi-bin. As it turns out, there’s not much in the cgi-bin directory (see right-hand image). Almost all of the CGI scripts are actually in the CGI directory which is protected. In fact, cgi-bin only has one file in it, and that was rtpd.cgi (see leftmost image below), which is a shell script that can be used to start and stop the Real-time Transport Protocol Daemon. So, for example, if you wanted to stop the RTP Daemon, you would simply send a request to rtpd.cgi, and in your query string you specify ‘action=stop’ to stop the service (see middle image below).
The problem is, the way they handle this query string that you provide is they replace all ampersands with spaces and then eval the result, so they’re literally executing in a shell whatever you put in your query string, and not only that – it runs as root. So you can do something like this (see rightmost image above) and it reboots the device. And I actually had a hard time categorizing this because it’s not even command injection, we’re not injecting anything, it’s just running whatever we give it. So I’ve dubbed this the ‘Ron Burgundy’ because it will literally execute whatever you put in your query string (see right-hand image).
Not only will it execute what you put in your query string but it will send you the response back to your browser. So you can do something like this (see leftmost image below), this particular command will echo out the admin password, and you get that sent back to your browser. So you now have the admin password and access to the video feed; you’re not only root, you’re also admin (see middle image below).
As it turns out, D-Link, like many vendors, really likes to reuse code, and so this popped up in a lot of their products (see rightmost image above). But it didn’t just affect D-Link, because it was also used by TRENDnet and several other off-brand devices as well. And due to the fact that there’s so much reuse of this code within vendors and throughout different vendors, it turns out there’re quite a few of them already publicly accessible and indexed by Shodan for you (see right-hand image).
This vulnerability (see right-hand image) might sound familiar to some people and that’s because it probably is. Of course after my talk, I’d accept it, someone put a CVE out for this bug, so it’s technically not an 0-day anymore. However the CVE only addressed D-Link devices, it did not mention any of the other vendors affected; and the truth is, even if every single vendor put out a firmware update fixing this today, everyone would still be vulnerable, like, three years from now because no one updates firmware or even knows what it is half the time. So I expect this bug, even though it’s technically not an 0-day, to be quite useful for some time to come.
So I said, okay, D-Link is an easy target, as I mentioned – that’s why I picked them. Let’s move on to perhaps a more reputable vendor, like Cisco. The Cisco PVC2300 (see right-hand image) is kind of a mid-range business IP camera costing about $500. It, too, has a web server and it enforces authentication by using .htpasswd files which most people are probably already familiar with. So, basically, you put an .htpasswd file, or more specifically a symlink to a centralized .htpasswd file, in every directory that you want to be password protected (see leftmost image below). Looking through the firmware, every single directory in the web interface had a .htpasswd file. Except one.
The oamp directory did not have any .htpasswd file (see middle image above). What it had was a bunch of .xml files that were actually symlinks to this oamp.cgi binary. So I said, okay, let’s look at what this oamp.cgi thing does. As it turns out, it implements kind of its own little mini API that’s totally separate from the rest of everything else running in the web interface. So it expects you, when you make a request to it, to specify an action (see rightmost image above). And this action you can specify can be one of many different things including downloadConfigurationFile, updateFirmware and many others (see leftmost image below).
But they weren’t completely stupid – we’ll get to the stupid stuff later – because what they do is before executing an action they check to make sure that you’ve also provided a valid SessionID (see middle image above). If you have not provided a valid SessionID, the only action it lets you run is the login action (see rightmost image above). I said, okay, well, this in itself is interesting because they’re implementing authentication that’s totally separate from the authentication used everywhere else in the interface. So I started looking at how they actually handle this login action.
They expect you to specify a username (see leftmost image above) and a password (see middle image above), no surprises there. They then make two calls to this PRO_GetStr function. At this point, I have no idea what PRO_GetStr does, presumably it gets a string of some sort. But I do know that on the first call to this function they pass it two strings – Oamp and L1_usr (see rightmost image above). And on the second call, they pass Oamp and L1_pwd (see leftmost image below). The value returned for L1_usr is then compared against the username that you provided (see middle image below), and the value returned for L1_pwd is compared against the password you provided (see rightmost image below). So, presumably, this L1_usr and L1_pwd, whatever their values are, are the correct login for this oamp interface.
Now, the only other place I could find in the firmware that actually referenced L1_usr and L1_pwd was in the configuration file (see right-hand image). These values are hard-coded in the device’s running config under the oamp section of the configuration file. You can see that l1_usr is set to string L1_admin, and l1_pwd is set to the string L1_51. And this is a real problem because this whole oamp interface and these hard-coded accounts are completely undocumented, so no one knows they’re there except for people who bothered to look at the firmware, which of course an admin is never going to do that. And even if an admin knew that these were here, there’s no way for the admin to change this, there’s no interface for the admin to go in and change these values.
And the problem with having hard-coded secret passwords in your system and backdoors is that they don’t stay secrets for long. So we can use these backdoor accounts to exercise the login action and, sure enough, we get back a session ID (see leftmost image below). Now, as long as we send the session ID along with all of our other requests, we can invoke any of the other actions supported by oamp.cgi., including downloadConfigurationFile (see middle image below). And this gets us back – what appears to be base64 encoded data.
The problem is, if you try to base64 decode this – not working. You just get a bunch of junk (see rightmost image above). The reason for that becomes readily apparent when you look at the actual encode 64 function in the binary itself. They all are doing base64 encoding but they’re using a non-standard base64 key string. Luckily, it’s very easy in Python to substitute the standard base64 key string in Python’s base64 module with a custom key string like this (see rightmost image below).
And so, with a couple lines of Python we can easily decode the config, which gives us plaintext admin creds (see leftmost image above), which lets me see your server room (see middle image above). Now, the problem with viewing server rooms is it’s really exciting for, like, 10 seconds, and then you’re like: “Holy shit, this is boring!” So I went back and started looking at some more code, because that’s more exciting.
Now, the loadFirmware action is one of the other actions you can invoke, and it’s actually very interesting because instead of uploading a firmware file to the device you specify a URL, and then the device goes to that URL, downloads, presumably, the firmware, and then flashes it (see right-hand image). The problem is, that URL that you specify is shoved into a command line string that’s ultimately passed to libc system, and hopefully everyone here knows that taking unfiltered user-supplied content and passing it to system is bad (see leftmost image below). You don’t do it.
So we can easily do command injection through this parameter: just putting ;reboot; somewhere in your URL reboots the machine (see middle image above). And of course we can run whatever command we want at this point. This also affects the WVC2300 (see rightmost image above), it’s basically the same camera but with antennas. And there are hundreds of these cameras already out there online (see right-hand image) in hotels, obviously server rooms, and engineering companies who design things for the International Space Station (untold).
So I said, okay, clearly, D-Link and Cisco are doing it wrong in their defense, though, you know, they’re not really camera companies; they don’t do cameras; cameras aren’t really their focus. So let’s look at someone who is a camera company, it’s their bread & butter, hopefully they know how to do it right. So I picked on IQinVision, partially because they make some really expensive high-definition cameras; the IQ832N, for example (see right-hand image), will run you over $1000 apiece, so it’s certainly not cheap. The main reason, though, is that these are the guys who make the cameras that are used in the business complex where I work, so it’s a little more personal.
What you get for $1000 per a camera is a high-definition video feed unauthenticated (see right-hand image), open to the world by default. Now, admittedly, this is a configurable option, you can require authentication. This is the default setting but you can go in as an admin and change that. Now, guess how many admins connect to their cameras through the Internet without changing that default setting? Almost all of them. So this is really interesting, but it’s not really that interesting from a security standpoint. It’s like, okay, they don’t know how to secure their stuff, big deal… But I wanted some actual vulnerabilities.
So I said, well, let’s look at getting into the admin area, which is password protected (see right-hand image) and which, thankfully, most admins have the presence of mind to change the default settings for. To do this I started looking at what else I can get to without authentication. And one of the few pages you can get to without authentication is oidtable.cgi (see leftmost image below). The output from the oidtable, really, isn’t that interesting; it’s a bunch of technical camera settings like focus and all the other stuff. There’s nothing really sensitive in here like usernames and passwords.
What’s more interesting is the code behind oidtable (see middle image above), because if you disassemble this CGI binary you’ll see that it looks to see if you specified a ‘grep’ parameter in your QUERY_STRING when you sent your GET request to it. If you have, it checks to make sure the value you provided is less than 32 bytes long (see rightmost image above), and as long as you meet that requirement it will take that string, shove it into a ‘grep’ command (see leftmost image below) and pass it to ‘popen’ (see middle image below), at which case I facepalmed, did my best Kim Jong-il impression (rightmost image below), and did some command injection.
Again, just like with the D-Link, you see I can run a ‘ps’ command and I get the output sent back to my browser (see right-hand image). So I have a built-in unauthenticated web root shell already on the device. It’s also worth noting that while process listings are interesting, these cameras already have Netcat installed on them, with the ‘-e’ option enabled, so I’m sure most people here can think of some more interesting commands to run than a process listing.
But my main goal in all this was, really, to get to the admin area, recall. So I said, well, how can I do that? We can also use this bug to retrieve the contents of arbitrary files (see leftmost image below). The etc/privpasswd file is what contains the actual admin credentials – it’s not etc/passwd, it’s etc/privpasswd.
So we can pull back the contents of that file (see middle image above). As you can see here, it has the username ‘root’ and an encrypted password. This is where we have two options: you can try and decrypt the password, or if that doesn’t work – screw it, we’re root, we’ll just overwrite the file with whatever we want (see rightmost image above). In either case we get access to the admin area (see leftmost image below), and again, we’re both root and admin on these cameras.
These bugs affect most of IQinVision’s product line (see middle image above), including their 3 series cameras, the 7 series, the Sentinel series, the Alliance-pro, Alliance-mx, and Alliance-mini series. And there are plenty of these out online (see rightmost image above), which is a little distressing considering that they are known to be deployed by schools, police, banks, governments, prisons, casinos, utilities companies, financial consulting firms, to name a few. So have fun with that…
By far, the most expensive camera I looked at, though, was the N5072 from 3S Vision (see right-hand image). This one has a list price of “Contact Us”, which is how I know I can afford it. And I ran into a bit of a problem with this particular camera, little hiccup at first, because for all the other cameras I had been able to just go to the vendor’s website, pull down a firmware update whatever the latest firmware update was, and start analyzing the code in the firmware for vulnerabilities. So I really didn’t have to buy the device in order to at least do initial testing and things like that.
And I said, well gee, since I’m literate and all, I bet I can do that myself, and sure enough I get the download page (see rightmost image above). So this does not bode well for the security of their systems, nor does the fact that they’re using a custom web server (see right-hand image). It’s rather innocuously named ‘httpd’, but if you just look at the strings in this binary, it’s very clear that this is very custom to their firmware. It’s either something that they wrote themselves from scratch or something that has been very heavily modified by them.
So I said, well, this looks really custom. I really need to start looking at how their web server handles authentication. I know that the cameras use HTTP Basic authentication, so I know that they’re going to be doing some base64 decoding; because if you’re not familiar with HTTP Basic auth, your username and password are basically concatenated and then base64 encoded.
So I started looking through the code for cross-references to b64_decode (see right-hand image). So, what they do when they decode your password is they pass it to b64_decode – alright, that’s fine, they’re decoding your stuff. They then do two string comparisons against a hard-coded string “3sadmin” and another hard-coded string “27988303”. I saw this and I thought: “There is no way you were dumb enough to hard-code stuff into your HTTP server” (see leftmost image below). These can’t possibly be creds.
But they were, and they worked great (see middle image above). So you can access any 3S Vision camera, become admin with these backdoor creds, and that gives you access to video feeds of cash machines, Taiwanese checkpoints (see rightmost image above), and Russian industrial basements, at least that’s what I assume that is (see right-hand image). Now, again, looking at video feeds is really boring, so I wanted root. Luckily, their code is littered – especially once you’re logged in as admin your attack surface is wide open – and their code is just littered with unsafe function calls, it’s absolutely horrible.
Probably the best example of this is their records.cgi handler (see right-hand image). Not all of their cameras, but many of their cameras support local storage, so you can plug in, say, an SD card to the camera and it will save files off to the SD card for you. They also provide a way to do some basic file management from the admin interface, and this is done through the records.cgi page. Now, records.cgi is not a physical CGI page sitting on disk. What happens is when the web server sees that you requested records.cgi it invokes the do_records function handler.
The do-records function handler checks to see what action you’ve provided. So for example if you want it to delete a file, you can tell it “action=remove” (see right-hand image). Now, if you’re deleting a file, you also have to tell it which file you want deleted. So it checks to make sure that you’ve specified the filename as well. That filename is then shoved into an “rm” command that is passed to system (see leftmost image below). And I think everyone knows where this is going, yeah (see middle image below). Setting filename=’reboot’ makes it not respond to pings anymore (see rightmost image below).
This affects almost all of 3S Vision’s products line (see right-hand image), not only the cameras but also their video servers because they use the same web server as well. And after a bit of research I found that another company named ALinking used the same code in their cameras as well. Now, ALinking went through and changed the hard-coded creds to something else, so all the ALinking cameras have the same hard-coded creds, they’re just different from 3S Vision’s hard-coded creds. They’re there and they’re easy to find.
These cameras are particularly interesting due to their cost, there’s not a whole lot of them when compared to some of the other devices (see right-hand image). But considering that these cameras are known to be deployed in foreign military, energy and industrial facilities, they are particularly interesting. So all of you who are already looking these up on Shodan – be careful; don’t blame me when the Chinese military shows up and gets pissed at you.
What this all boils down to is: I’m in your network, I can see you, and I’m root; which is not a bad position for any attacker could be in. Most of these cameras, the way that they’re deployed, they’re actually connected to the internal network, so if you can remotely access them and break into them, get root on them – you now have a Linux-based ARM machine sitting inside their network that you can then use to go after anything else in that network.
But I wanted to kind of take a step back from that and say, okay, that’s great and all, but what can I do to the camera itself? I’ve got root on the camera, this is awesome. But if you go up to an average admin and say: “I got root on your camera,” – he’s like: “I don’t even know what that means, what the hell are you talking about?”
So I wanted to do something a little more interesting, something that would actually demonstrate what you can do. I wanted to take, say, a video stream that looks like this (see leftmost image below), and instead make it look like this (see middle image below). And this is kind of the classic Hollywood hack, right? You’ve got to get into the facility that’s guarded and it’s got security cameras, so the token hacker of the group has to break into this camera system and make it look like no one’s there, when really they are.
To demonstrate this and just do a little proof of concept, I picked TRENDnet TV-IP410WN (see rightmost image above). Now, I picked this camera for a couple of reasons. First of all, I can afford it, which is a big plus. But secondly, it has a backdoor account (see right-hand image) that can access certain restricted files (see leftmost image below), which have very obvious command injection bugs (see middle image below), which can be trivially exploited by anyone who can send packets to the camera (see rightmost image below). In other words, it’s the same stuff I’ve been up here talking about the whole time, just on a slightly less expensive camera.
As it turns out, this particular bug is also not an 0-day (see right-hand image). It was actually first published in 2011. The problem is I didn’t know about it, and neither did anyone else, because when they published this they didn’t mention any specific devices that were affected, they didn’t mention any specific firmware versions that were affected. So if you went and googled for, you know, TV-IP410WN vulnerabilities or exploits, you didn’t find anything.
And the problem with not providing this information when you do things like vulnerability reports is it’s difficult for other people in the security community to validate your claims, first of all. But it also makes it impossible for vendors and customers to determine what models need to be fixed and whether I’m actually vulnerable or not. So the easiest solution here is to just ignore it and hope it goes away, which is what everyone did. These devices, even though this is a known bug since 2011, have been shipping with this bug ever since.
Let’s say, hypothetically, this is our admin’s video feed (see leftmost image to the right). We want to make sure it stays that way. For purposes of demonstration, we’ll assume that the admin is browsing the video feed through the web interface rather than maybe through a custom service or RTP or something like that. On this particular camera, when you’re viewing the video feed through your browser, the process responsible for streaming images to your browser is mjpg.cgi (see image above).
So, with command injection and the ability to see the output from our commands that we’re injecting, we can run process lists and see what processes are running, and then we can just kill off all the mjpg.cgi processes (see right-hand image). And this actually has the effect of temporarily freezing the admin’s video feed, because his browser is only going to show him whatever the last image it received was and he is no longer getting any more images.
But we don’t want to stop there because if the admin refreshes his browser or navigates away from the page and comes back, he starts up a new stream and then he’s going to see the live video feed. So, what we also want to do is replace mjpg.cgi, and this does not have to be difficult (see right-hand image). A two-line Bash script will suffice, particularly in a pinch. All you have to do is replace the CGI page on disk with a Bash script that echoes out some basic headers to make the browser happy and then cats out the contents of its static JPG image. Presumably, this JPG is a picture of the empty elevator but, you know, it can be Goatse or whatever you want.
… So the admin will now always see the empty elevator no matter what is actually going on in there. This is actually a lot more fun to see in a live demo. So, if the demo gods will work with me today…
Alright, so I have my camera guarding my precious beer here. You can see if I try and take it, whoever is watching will know. However, I have a little exploit script written up, and this exploit script does a couple of things. First of all, it’s going to kill the admin’s video feed, it’s going to replace the mjpg.cgi just like we showed, it’s also going to give me the administrative credentials to the camera and set up a secret URL so that I can still see the live video feed even though the admin’s is frozen permanently.
So, sending exploit…Okay, you can see it gave me back the user credentials: the login is admin and securecam1234. It also tells me what URL it set up so that I can view the real video feed. If I go over to my hacker’s browser here, I can see what’s going on. So if I try and take this, I know that but the admin doesn’t. So, that’s the demo.
A couple of closing thoughts I’d like to leave you with (see right-hand image). First of all, this clearly is not an all-encompassing list of bugs in security cameras, not by a long shot, so there’re lots more of these to be found if you want to go look for them yourself. And as you see, most of them are epically trivial to exploit. Another thing I’d like to point out is that almost all of these cameras will reveal their model name even if you’re not authenticated, either on the login page or the login prompt, depending on how they’re doing authentication. It will tell you what its model number is.
So, if I as an attacker know the model number, even if I know nothing else about this camera, I can go on Google, I can google the model number, go to the vendor’s web page, download the firmware, maybe have to add “tab=4”, and start analyzing it for vulnerabilities without ever even having to buy the device. And this is exactly what I did with all of these cameras. I was able to find the vulnerabilities and write working exploits without ever having to buy a single camera. It was all done with firmware analysis, basically, using Binwalk to do firmware analysis and extraction, and then using IDA and Qemu for disassembly and emulation if necessary.
I know if I open up a Q&A, like half the room always leaves. So before you head out please fill out the surveys or swipe you badge for surveys, or however that works in the back. But with that, I’m ready for any questions you guys might have.
Question: Have you worked with any actually secure cameras?
Craig: No… There were a lot more cameras that I looked at. I simply didn’t need, like, a two-hour slot to talk about all of them. I did not run into one that was actually well done and actually secure. With that said, there are a lot of larger vendors that I simply didn’t have the resources to look at. You know, they don’t put their firmware up for free and I don’t have the money to buy their cameras. So I’m not saying there aren’t secure cameras out there, but the ones I’ve looked at are certainly not.
Question: Is there a way to make the admin see some motion rather than a static image?
Craig: The easiest way to do that in this case would be, like, an animated GIF, which is a pretty hacky workaround. Certainly this can be taken a lot farther. I mean, you can easily write your own CGI that really does feed a live video of whatever you want. I’m lazy though, and that was my demo. But the concept is the same: you’re root on these devices, you can do whatever you want. Yeah, you can certainly do that.