CVE-2023-24497,CVE-2023-24496
Cross-site scripting (xss) vulnerabilities exist in the requestHandlers.js detail_device functionality of Milesight VPN v2.0.2. A specially-crafted HTTP request can lead to arbitrary Javascript code injection. An attacker can send an HTTP request to trigger these vulnerabilities.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Milesight VPN v2.0.2
MilesightVPN - https://www.milesight-iot.com/milesightvpn/
4.7 - CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N
6.1 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N - chain: TALOS-2023-1702
CWE-80 - Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)
The MilesightVPN is a software that make easier the setup of VPN tunnel for the Milesight products and allow to monitor the connection status with a web server interface.
The MilesightVPN exposes the /Device_Auth
API used to authenticate to the server and to get the an OpenVPN configuration file. This API is for the various Milesight devices, the API requires as data the serial number, the authentication code, the device name and the subnet of the device. Essentially the API expects four entries in the POST payload:
After been registered, the device will appear in the Device table showing the provided information. This Device table is in the landing page after the login.
Following the relevant portion of the Embedded JavaScript template related to the device table:
<table class="table table-no-bordered" data-striped="true" data-height="100%" id="tbl_content" data-toggle="table" data-url="/detail_device" data-target="/detail_device" data-id-field="sn" data-pagination="true" data-sort-stable="true" data-sort-name="connect_time" data-sort-order="asc" data-toolbar="#deviceToolbar" data-search="true" data-search-on-enter-key="true" data-cache="false">
<thead>
<tr>
<th data-field="name" data-width="15%" data-sortable="true" data-formatter="name_formatter"><%=lang.detail.device.name%></th>
<th data-field="status" data-width="10%" data-sortable="true" data-formatter="status_formatter"><%=lang.detail.device.status%></th>
<th data-field="sn" data-width="10%" data-sortable="true"><%=lang.detail.device.sn%></th>
<th data-field="virtual_ip" data-width="15%" data-sortable="true"><%=lang.detail.device.virtualip%></th>
<th data-field="real_ip" data-width="15%" data-sortable="true"><%=lang.detail.device.realip%></th>
<th data-field="remote_subnet" data-width="15%" data-sortable="true" data-formatter="subnet_formatter"><%=lang.detail.device.subnet%></th>
<th data-field="connect_time" data-width="15%" data-sortable="true" data-formatter="time_formatter"><%=lang.detail.device.time%></th>
<th data-field="history" data-width="10%" data-formatter="history_formatter"><%=lang.detail.device.history%></th>
</tr>
</thead>
The data of this table is filled using the requestHandlers.js
’s detail_device
function:
function detail_device(res,postdata,connection){
var $sql="select * from device";
$sql+=' left join ';
$sql+=' (select count(*) as total,remote_subnet from device group by remote_subnet) repeatsubnet on repeatsubnet.remote_subnet=device.remote_subnet';
var result={};
connection.query($sql).then(function(data){
if(data['error'])
{
res.write(JSON.stringify(result));
res.end();
}
else
{
if(data['result'].length>0)
{
result=JSON.stringify(data['result']);
res.writeHead(200,{'Content-Type':'application/json','Content-length':Buffer.byteLength(result, 'utf8')});
res.write(result);
res.end();
}
else
{
res.write(JSON.stringify([]));
res.end();
}
}
});
}
The device
table has the following schema:
+---------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+-------+
| name | varchar(255) | YES | | NULL | |
| sn | varchar(12) | NO | PRI | NULL | |
| virtual_ip | varchar(15) | YES | | NULL | |
| real_ip | varchar(32) | YES | | NULL | |
| remote_subnet | varchar(32) | YES | | NULL | |
| connect_time | int | YES | | NULL | |
| status | int | YES | | NULL | |
+---------------+--------------+------+-----+---------+-------+
The device
table is populated through the registered devices, so through the /Device_Auth
API.
From when a Milesight device is registered, using the /Device_Auth
API, until showing its data in the web interface, no checks about the data are performed, this can lead an XSS vulnerability. An attacker can upload malicious Javascript code through the /Device_Auth
API, registering a device. An admin of the server would execute this malicious javascript whenever they viewed the details page due to the stored XSS.
An attacker would need to know the Authorization Code of the server to actually use the /Device_Auth
API. But because TALOS-2023-1702 this information can be easily retrieved by an attacker.
An attacker can upload malicious Javascript code through the /Device_Auth
’s device_name
parameter. The device_name
value will be stored in the device
database as the name
field.
An attacker can upload malicious Javascript code through the /Device_Auth
’s subnet
parameter. The subnet
value will be stored in the device
database as the remote_subnet
field.
Since the maintainer of this software did not release a patch during the 90 day window specified in our policy, we have now decided to release the information regarding this vulnerability, to make users of the software aware of this problem. See Cisco’s Coordinated Vulnerability Disclosure Policy for more information: https://tools.cisco.com/security/center/resources/vendor_vulnerability_policy.html
2023-02-14 - Initial Vendor Contact
2023-02-21 - Vendor Disclosure
2023-07-06 - Public Release
Discovered by Francesco Benvenuto of Cisco Talos.