The security of home gadgets is a burning topic. Botnet attacks such as Mirai affect millions of devices and inflict huge damages. Ethical hackers continue discovering vulnerabilities in popular gadgets, which manufacturers don’t rush fixing. In this article, I will tell how to check your hardware for security issues using a popular IP camera as an example. Spoiler: it is plagued by tons of vulnerabilities.
This material is based on the report “Oldschool Way of Hacking MicroDigital IP Cameras” I presented at ZeroNights 2019. Many people had asked me for the slides; I decided that an article addressing all the identified vulnerabilities would be even more useful.
When selecting a piece of hardware, I wanted to pick a popular vendor who has not yet attracted much attention in the cybersecurity community. That’s how I picked MicroDigital Co., Ltd., a well-known Korean producer of IP cameras.
The company’s website offers a broad selection of more than 30 digital video recorders and over 150 video cameras. Terrific!
The company has been on the market for more than 20 years, so its products are widespread. For instance, some 30,000 buses in Russia are equipped with MicroDigital cameras.
I was primarily interested in N-series cameras that are advanced enough but have not been tested by renowned security researchers yet; particularly, the MDC-N4090W camera designed for indoor use. More information on the device can be found on the vendor’s website.
Examining the camera
A hardware research starts with the review of available documentation.
The manual is available on the MicroDigital website. It states that the camera has a web interface with two pre-defined users: root (password: root) and anonymous.
A firmware update can also be downloaded from the vendor’s site.
However, there’s no guarantee that the firmware image has everything required for the testing, so I’ll put it aside as a last resort in case I cannot obtain admin access to the device’s console.
Preparing for testing
To examine the hardware, I opened the device by undoing the four screws on the edges and removed the mainboard.
The mainboard contains:
- Memory S34ML01G100TF100
- Digital Media System-on-Chip (DMSoC) DM368ZCE
- Four interface pinouts (UART, USB, microSD, and Ethernet).
I’ll disregard pins marked as BLE; most probably, these are contacts for a Bluetooth module.
The S34ML01G100TF100 module contains NAND flash memory in a TSOP-48 case. According to the datasheet, the case type is NAND08, while the memory size is 128 MB.
Before I carry on the research, I’ll back up the data to restore the device in case it ‘bricks’. A ProMan TL86 or TL866 programmer equipped with a NAND08 → DIP48 adapter will help with that.
The content of the flash memory chip is successfully saved to the work directory. I will only examine it if I cannot get access to the admin console.
The documentation for DM368ZCE is also available. It turned out that this chip is based on the ARM architecture.
Now to the interfaces. According to the documentation, USB and microSD slots are mostly used to attach external data storage devices to the camera. To get the full picture, I’ll connect a USB fuzzer facedancer21 and obtain the list of supported devices with umap2scan.
Unfortunately, the camera does not support any known devices.
What about UART? I have to identify the purpose of each pin and figure out the data transmission rate. A Saleae Logic logical analyzer will assist in this. I used the wire that links the mainboard with infrared lamps.
I’ll number the pins to avoid confusion.
Prior to tuning the logical analyzer on, I’ll connect the ground to the GDN pin on the BLE interface.
So I turn on the logical analyzer and watch.
Pin #3 (the program displays it as #2 because it starts from 0) is used to transmit binary data. Accordingly, this the TX pin. Checking the time it takes to transmit that one bit, I can figure out the baud rate, which is 115 200 bps. After playing with the settings for some time, I can even see a portion of the text.
Pin #1 carries 3V; therefore, it is responsible for power supply. Pin #4 is linked with the GDN pin of the BLE interface. Accordingly, this is a ground pin, too. The last pin (#2) is the receiver (RX). Now I have all information required to communicate with the camera via UART. I am going to use an Arduino Uno microcontroller board in the TTL adapter mode.
Monitoring the UART port, I can see the following information.
The Universal Boot Loader (U-Boot) is loaded first at the startup. Unfortunately, the TX pin is disabled in the camera settings at the boot-up level; therefore, I can only monitor the debugging output. Some time later, the main system is loaded; it enables entering the login and password to get access to the admin console. The root/root combination (similar to the one used for web administration and provided in the manual) has worked well.
After gaining access to the console, I can review the active services. Note, however, that the camera supports one more interface: Ethernet. To research it, I’ll set up a traffic monitoring system. It is important to monitor the very first connection to the network.
The traffic must be intercepted from the very beginning because some devices start downloading updates at the first connection. There’s no guarantee this will work the next time.
I will use a LAN Tap Pro Ethernet traffic monitoring device.
No activities related to updates can be detected. Exploration completed, time to look for vulnerabilities!
Nmap will scan the ports and return the list of open ports.
Let’s examine the available services.
This service requests a login and password. Anonymous logon is disabled. However, the root/root combination has worked well again!
Now I can open any folder and conveniently transfer the files to remote hosts.
To connect via Telnet, the system prompts for the login and password; however, the root/root combination helps again. The UART console is no longer needed as subsequent operations can be performed remotely via Telnet.
To connect via RTSP, log in using the root/root combination. The connection link looks as follows:
Upon examining the camera’s web server, I drew the following scheme.
The PHP scripts and CGI applications stored on the server communicate with executable files located in the
/usr/local/ipsca/ folder (mostly with
MainProc). A SQLite 3 database stores the settings.
The search for vulnerabilities begins. The database is located in
/usr/local/ipsca/mipsca.db. It contains everything from system logs to settings controlling the automatic upload of camera’s records on a remote server. The database structure is shown on the screenshot below.
A table named User has attracted my attention. It contains user data: login, password, and privileges.
The user password is stored in the Password column in plain view, completely unencrypted. Anyone who can gain access to the database can also obtain the admin password and test it on other available services.
Time to examine the PHP scripts. The Web folder
/root/httpd/htdocs/Web contains three scripts:
login.php is of no interest because PHP is used here only to configure the ActiveX component required for browser add-ons that stream the video online.
download.php receives the name of a file to be downloaded, checks its extension, and if such a file exists in the folder
updownload, sends its contents in response.
The script does not check the file name; so if anyone puts an executable PHP script into this folder, its contents would be downloaded upon request (the
$file_type variable would be empty if the extension is unknown).
The last file,
upload.php, is not free of bugs, too: it can send files not only with whitelisted extensions (.dat and .DAT), but with blank extensions as well.
The following string sets the extensions whitelist.
If the file extension is not blank, it is checked against the data massive received from
$allowExt. A comma is used as a delimiter.
However, if the extension is blank, the script execution won’t advance to this condition and no checks will be performed. Too bad, this bug is useless from the exploitation perspective.
The next bug, however, is a solid vulnerability: the file name length is not checked. You may not view this problem as severe, but the point is that a Bash script is launched in the beginning of the program.
It clears the
updownload directory from earlier downloads; however, the Bash interpreter included in BusyBox has a limitation on the length of a file name: this parameter may not exceed 255 symbols. As a result, the script cannot delete files whose names are longer.
upload.php does not require authorization, any user can upload as many files whose names are longer than 255 symbols as necessary to fill the entire device memory, thus, performing a Denial of Service Attack.
File upload example.
The list of files stored in the
/updownload/ directory can be viewed in the Bash console.
The examination of PHP scripts is completed; time to move to CGI applications, the main part of our study.
CGI applications are responsible for nearly all actions in the admin panel of an IP camera, from authorization to updates.
I will break further work into two stages: (1) ‘naked eye testing’ (vulnerabilities whose identification does not require reverse engineering of executable files); and (2) reverse engineering of these binary files.
The naked eye testing identified two vulnerabilities. The first one allows performing CSRF (Cross-Site Request Forgery) attacks. Using social engineering, one can trick an administrator into clicking on a malicious link. This enables the attacker to execute nearly any command in the admin interface. For instance, the following link can be created:
It creates a user named “tester” whose password is “password”.
While researching the traffic in Burp Suite, I spent a long time trying to find the server’s response – a cookie containing authentication data (username, auth, and password) sent to the browser. It turned out that I was searching in vain: these data are set on the client’s side in the file
In other words, initially, the browser makes a request to check the login and password, and if the result is positive, saves the login, password, and privileges in the respective cookie. Then these cookies are used in command requests, for instance, to create a new user.
Here the second vulnerability comes: even if the cookie does not include the
password variable, the server will successfully process the request anyway.
So, the admin login (by default, root) is sufficient to bypass authentication and make any requests available to the administrator in the camera’s admin console. Note that I didn’t even need to reverse-engineer anything.
Researching binary apps
Prior to researching the executable files, some preparations are necessary.
- Install the statistically compiled GDB debugger from public repositories on GitHub; and
- Install a microSD card formatted with VFAT file system for extra space.
The research of the binaries includes the following steps:
- Examining with IDA Pro.
- If necessary, debugging via Telnet with GDB. By the way, because the application is multithreaded, I had to check the required process id every time to deal with a certain thread (the thread is created prior to the request processing).
- Writing a proof of concept to demonstrate the vulnerability.
Almost all requests are sent to
/webparams. After reviewing httpd settings in
/usr/local/httpd/conf/httpd.conf, I discovered that all requests made to
/webparams are redirected to an executable FCGI file located at
Accordingly, I decided to focus my efforts on this ARMv7 executable.
Execution of arbitrary FTP commands
The camera can upload videos onto an FTP server. A special web form is used to configure the connection
Press the Test button to check the connection. A function at
0xaeb0 is called. I’ll use Hex-Rays Decompiler to analyze the call.
Establishing a connection
Authorizing on the FTP server.
Changing the current directory to one specified in the received argument.
Creating a temporary file.
A security issue popped up as early as at the third step. The function
ftp_CWD located at
0xA9F0 does not check the path variable for incorrect symbols, including line breaks.
This enables the attacker to send arbitrary FTP commands; all you have to do is add the
\r\n characters to the name of the directory that stores the downloaded files. Congrats! We have found a SSRF (Server-Side Request Forgery) vulnerability.
For instance, you can now make a request to the camera’s FTP server and add to it a command creating the directory
/tmp/123 (the GET variable
uploadpath sets the path to that directory).
/tmp/ directory on the camera to access the newly created folder
Path traversal and checking for files
The next ability of the web server that is of interest to us is clock synchronization via NTP protocol.
A function at the address
0x12564 changes these parameters. I won’t examine it in detail, but the variable
TZ (Time Zone) is worth attention.
The first 32 bites of the GET parameter
TZare stored in the variable
The value is concatenated with the path to the directory storing time zone settings, and the file system of the device is checked for the presence of this directory (or file).
If successful, various actions are performed, including requests sent to the database.
Overall, not only can I manipulate the full address of the directory (path traversal), but also check the presence of files in the file system based on server’s responses. To make sure it’s true, I’ll send a request checking if
/etc/passwd is there.
Let’s see what happens if the file does not exist.
Time to deal with more severe vulnerabilities. The camera configs are stored in a SQLite 3 database, and almost all actions performed on the web server interact with that database. It turned out that nearly all requests with string parameters can be successfully sent to the database with an incorrectly formatted input. As you are aware, this means an SQL Injection Attack! Take, for example, one of the vulnerable forms, the DNS Settings Panel.
When these parameters are edited, two requests are sent to the server: a request to modify information and a request for the current settings.
Below is an example of an information modification request.
A function at the address
0x18374 processes such requests. First, it reads the GET parameters of the request (up to 32 bytes each) and checks for null values.
strip command is called; it removes space and tab symbols in the beginning and end of the lines.
The resultant lines are sent to the function that makes a SQL request
Update to the SQLite 3 database.
The problem is that the strings are transmitted with
%s instead of
%q (the safe variant); accordingly, one can go beyond the string limits and add an arbitrary set of SQL instructions to the request. (By the way, if an integer-valued parameter is sent, it is better to use
Below is an exploitation example
During the processing of this request, the following SQL command is created:
Update Network set DDNSUsage=1, DDNSHostname='', DDNSName=(select/*', DDNSName='*/Password from/*', DDNSUserName='*/User limit 1) -- ', DDNSPassword='***'
This request will write the password to the first account from the User table in the field DDNSName. The only thing an attacker has to do now is request the current DDNS settings.
As a result, the attacker received the password of the first user listed in the User table; in this particular case, it is root/root. Taking that earlier I have discovered a way to bypass authentication, any unauthorized user can obtain the admin password.
Twenty-five other GET parameters of the web server are plagued by the same problem (some of these parameters have to be encoded to Base64 first).
While I was reviewing parameters susceptible to SQL Injection Attacks, a function processing the
action variable located at
0x155D0 attracted my attention. The beginning of the function pseudocode is shown on the screenshot below.
String 78 calls the
GET_val function. It takes the string – name of the GET variable – as an argument and returns another string: value of this variable.
Then the function
strcat is called; it takes two string pointers as inputs and writes the concatenated result of these two strings at the address specified in the first pointer. The problem is that the function
strcat can cause a buffer overflow error. This occurs if the stack memory allocated for the first variable is insufficient to store the concatenation result for the two strings, thus, causing a stack overflow.
The first argument of the function was declared in string 53.
Four bytes are allocated for this string, and then a zero byte marking the end of the string is placed in the first cell.
In other words, to cause a stack overflow, one has to send two strings as arguments for the function
strcat. As a result, the length of the second string will exceed three bytes (the first byte is zero; it is set automatically).
So, let’s exploit the identified vulnerability. First, I’ll check the protection of the executable file.
The NX flag is disabled, making it is possible to execute a piece of code located in any memory area, including the code I am about to inject.
In addition, I’ll check whether the address space layout randomization is enabled in this system.
1 means that the stack address will be random every time the program is started. However, the second argument of the function
strcat (i.e. an argument of the GET variable
action) is initially written to the heap memory so that I can use it.
While debugging, I discovered the return address of the function that calls
strcat: it is stored 52 bytes below from the top of the stack.
To check this, I send the following request.
While debugging the executable file
webparam.fcgi, I received an error as the program is trying to go to the address
Now I’ll add executable code (i.e. shell code) after the return address and overwrite the return address with the address of payload stored in the heap memory. In this case, I will use executable code that opens the port
10240 and grants access to the command shell without authentication (Bind Shell).
Checking networking activities of the device.
The process 1263 of
webparam.fcgi started listening all interfaces for port
10240. I’ll use
netcat to connect.
I successfully gained access to the shell with privileges of the user
A similar buffer overflow problem affects the
params variable. The exploitation technique does not differ significantly from the one described above; so, I won’t waste time on it.
One of the most common problems shared by IoT devices is lack of signature verification for firmware updates. My camera is also affected by it. As a result, I can modify the firmware by adding arbitrary code. In theory, the device functionality can be restored using a memory dump; however, very few (if any) camera owners have this dump or skills required to make one.
The device administrator has access to the firmware upgrade interface (at the bottom of the page).
As you remember, I had earlier downloaded the latest firmware from the official website.
This .tar archive contains two files:
UpdatePackage_6400.0.8.5.bin. The latter one is an archive as well.
After unpacking, I can observe the following file hierarchy.
These directories contain files with the same names as in the camera’s file system. So, I can replace one of them, repack the firmware, and send it to the device as an update. Before I do it, let me check
Line 8 specifies the checksum of the .bin file. While not a replacement for a proper digital signature, the checksum will prevent the update of a file with an incorrect value of this parameter; therefore, it must be edited prior to uploading the customized firmware. This vulnerability falls under the Remote Code Evaluation/Execution (RCE) category.
Finally, I will examine one more vulnerability of the same type that involves root privilege escalation. If a microSD card is inserted into the camera, one can delete files through the web interface.
When a file is deleted, the browser sends the following HTTP request:
webparam.fcgi processes requests on the server side, but in this case, it forwards the request to another binary application called
MainProc. Therefore, it is possible to execute arbitrary code on behalf of the user who has launched
MainProc (in this case, the root user).
Proof of concept: I’ll create a file named
/tmp/test.txt containing the
Taking that the authentication can be bypassed, this bug enables a malefactor to gain control over any camera with an open web interface and potentially use it for attacks.
When researching the MicroDigital IP camera, I identified more than ten vulnerabilities including some critical ones. The full list containing twelve common vulnerabilities and exposures (CVE) is available here.
Important: the firmware file available on the vendor’s website is compatible with the entire range of six N-series IP cameras produced by MicroDigital. Most probably, some of the identified vulnerabilities exist in other MicroDigital devices whose assortment exceeds 150 camera models.
It is also worth mentioning that the conference Positive Hack Days 8 included a camera hacking contest CAMBreaker. The model described in this article was among the devices tested at this contest. One of its winners was Ivan Anisenya who has discovered the SQL Injection vulnerability as early as last year and used it to bypass authentication on this camera.
So how can you secure a camera such as this? A few simple steps can drastically raise the security level:
- Install the camera in a physically secure location;
- Review the documentation thoroughly;
- Disable all services you’re not going to use (e.g. FTP);
- Change all passwords and user names (if this feature is supported); and (probably the most important)
- Disable port forwarding in your router so that the camera is not accessible from outside of your home network.
The above recommendations are fully applicable to any other IoT device.