code | #
# (C) Tenable Network Security, Inc.
#
include("compat.inc");
if (description)
{
script_id(134564);
script_version("1.1");
script_set_attribute(attribute:"plugin_modification_date", value:"2020/03/13");
script_name(english:"IBM Spectrum Protect Server and Storage Agent RCE");
script_set_attribute(attribute:"synopsis", value:
"A backup service running on the remote host is affected by a
remote code execution vulnerability.");
script_set_attribute(attribute:"description", value:
"The IBM Spectrum Protect server or storage agent running on the
remote host is affected by a remote code execution vulnerability due
to improper invalidation of user-supplied data during communication
exchanges. An unauthenticated, remote attacker can exploit this, via
a series of specially crafted messages, to execute arbitrary code on
the system with instance id privileges or cause the server or storage
agent to crash.");
script_set_attribute(attribute:"see_also", value:"https://www.ibm.com/support/pages/node/882472");
script_set_attribute(attribute:"solution", value:
"Upgrade to IBM Spectrum Protect 7.1.9.300 or 8.1.8 or later.");
script_set_cvss_base_vector("CVSS2#AV:N/AC:L/Au:N/C:C/I:C/A: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_attribute(attribute:"cvss_score_source", value:"CVE-2019-4087");
script_set_attribute(attribute:"vuln_publication_date", value:"2019/06/28");
script_set_attribute(attribute:"patch_publication_date", value:"2019/06/28");
script_set_attribute(attribute:"plugin_publication_date", value:"2020/03/13");
script_set_attribute(attribute:"plugin_type", value:"remote");
script_set_attribute(attribute:"cpe", value:"cpe:/a:ibm:tivoli_storage_manager");
script_set_attribute(attribute:"cpe", value:"x-cpe:/a:ibm:spectrum_protect");
script_end_attributes();
script_category(ACT_ATTACK);
script_family(english:"General");
script_copyright(english:"This script is Copyright (C) 2020 and is owned by Tenable, Inc. or an Affiliate thereof.");
script_dependencies("ibm_tsm_detect.nasl");
script_require_ports("tsm-agent",1500);
exit(0);
}
include('audit.inc');
include('global_settings.inc');
include('byte_func.inc');
include('misc_func.inc');
##
# Send a 'verb' request.
#
# @param socket Socket to send the verb.
# @param code Verb code.
# @param data Verb data.
#
# @return None
##
function send_verb (socket, code, data)
{
local_var header, len, req;
len = strlen(data);
if(code < 0x100)
{
header =
mkword(len+4) +
mkbyte(code) +
mkbyte(0xa5); # magic
}
else
{
header =
mkword(0) +
mkbyte(8) +
mkbyte(0xa5) +
mkdword(code) +
mkdword(len+12);
}
req = header + data;
send(socket:socket, data:req);
}
##
# Receive a 'verb' response.
#
# @param socket Socket to receive the response.
#
# @return {"code":<verb_code>, "data":<verb_data>} or NULL on error.
#
##
function recv_verb(socket)
{
local_var code, header, data, len;
header = recv(socket:socket, length:4, min:4, timeout:10);
if (isnull(header)) return NULL;
# We expect at least 4 bytes
if(strlen(header) < 4) return NULL;
# Check magic byte
if (ord(header[3]) != 0xa5) return NULL;
# Get the verb code
code = ord(header[2]);
# Got an extended verb code
if(code == 8)
{
header = recv(socket:socket, length:8, min:8, timeout:10);
if (isnull(header) || strlen(header) < 8) return NULL;
code = getdword(blob:header, pos:0);
len = getdword(blob:header, pos:4);
if (len < 12) return NULL;
len = len - 12;
}
else
{
len = getword(blob:header, pos:0);
if (len < 4) return NULL;
len = len - 4;
}
data = recv(socket:socket, length:len, min:len, timeout:10);
return {'code':code, 'data':data};
}
port = get_service(svc:'tsm-agent', default:1500, exit_on_fail:TRUE);
if(get_port_transport(port) == ENCAPS_IP)
ssl = FALSE;
else
ssl = TRUE;
soc = open_sock_tcp(port);
if(!soc) audit(AUDIT_SOCK_FAIL,port);
# Send the Identify verb message.
#
# Need to send this verb message so that the server is in the right
# state to process the subsequent Negotiate verb message.
send_verb(socket:soc, code:0x1d, data:NULL);
res = recv_verb(socket:soc);
if(isnull(res) || res.code != 0x1e)
{
close(soc);
exit(1, 'Failed to receive a IdentifyResp message.');
}
# Send the Negotiate verb message.
#
# Need to send this verb message so that the server is in the right
# state to process the subsequent TransportMethod verb message.
user = 'nessus';
data = mkbyte(1) + # version
mkword(0x1b) + # dataOffset to user from msg start
mkdword(0x40000000) + # supportedMethods
mkword(2) + # sslMode
mkword(0) + # username offset within 'data'?
mkword(strlen(user)) + # username length
mkbyte(1) + # idType
mkbyte(7) + # sessType
user; # username
send_verb(socket:soc, code:0x3E0000, data:data);
res = recv_verb(socket:soc);
if(isnull(res) || res.code != 0x3E0010)
{
close(soc);
exit(1, 'Failed to receive a NegotiateResp message.');
}
# Send the TransportMethod verb message.
#
# This verb is used to switch transport method (i.e., TCP to SSL)
#
# To detect the vulnerability, we send the verb message with:
#
# 1) The transport to switch to matches the current transport.
# For example, if the plugin currently uses SSL to communicate
# with the server, it requests to switch to SSL. Similarly, if
# it is on TCP, it requests to switch to TCP.
# 2) Total verb message length > 19 bytes.
#
# The vulnerable server does not check the verb message length, and it
# continues to process the verb message but does nothing because we
# requested to switch to the same transport we are currently on.
# As a result, it will continue to process our subsequent Authenticate
# verb message, which will result in a AuthenticateResp verb message
# from the server.
#
# The patched server checks the verb message length to ensure it
# doesn't exceed 19 bytes. If so, it shuts down the connection
# without processing any subsequent incoming verb messages.
# As a result, the patched server will not respond to our
# Authenticate verb message.
#
if (ssl)
new_transport = 2;
else
new_transport = 1;
data = mkbyte(1) + # version
mkword(0xf) + # dataOffset to transport from msg start
mkdword(new_transport) + # Transport to switch to
# 1 - TCP, 2 - SSL
'A'; # Extra data to cause length check failure on the patched
# server but no crash on the vulnerable server.
send_verb(socket:soc, code:0x3E0040, data:data);
# Send the Authenticate verb message.
pass = SCRIPT_NAME;
pass += crap(data:'\x00', length:0x40 - strlen(pass));
data = mkbyte(1) + # version
mkword(0x13) + # dataOffset to password from msg start
mkdword(0) + # password offset within 'data'?
mkword(strlen(pass)) + # password length
pass; # password
send_verb(socket:soc, code:0x3E0020, data:data);
res = recv_verb(socket:soc);
close(soc);
# Got a AuthenticateResp: vulnerable
if(!isnull(res) && res.code == 0x3E0030)
{
extra = "Nessus was able to detect the vulnerability by sending a specially crafted 'TransportMethod' message to the remote service listening on port " + port + '.';
security_report_v4(
port : port,
severity : SECURITY_HOLE,
extra : extra
);
}
else
audit(AUDIT_HOST_NOT, 'affected');
|