Lucene search

K
nessusThis script is Copyright (C) 2016-2020 and is owned by Tenable, Inc. or an Affiliate thereof.OPENSSL_AES_NI_PADDING_ORACLE.NASL
HistoryJun 13, 2016 - 12:00 a.m.

OpenSSL AES-NI Padding Oracle MitM Information Disclosure

2016-06-1300:00:00
This script is Copyright (C) 2016-2020 and is owned by Tenable, Inc. or an Affiliate thereof.
www.tenable.com
2398

The remote host is affected by a man-in-the-middle (MitM) information disclosure vulnerability due to an error in the implementation of ciphersuites that use AES in CBC mode with HMAC-SHA1 or HMAC-SHA256.
The implementation is specially written to use the AES acceleration available in x86/amd64 processors (AES-NI). The error messages returned by the server allow allow a man-in-the-middle attacker to conduct a padding oracle attack, resulting in the ability to decrypt network traffic.

#TRUSTED 76b21af400c4603cb8683db911ae129d4ddd4eb2146ad7bf8c39d1478eb4e34cb3ae7f3ddbd50f9847732819d7457b77f80ad058a55019d287c372b9a4911cc4808bf27b8a5aac7f5adecad7b80abfeb0ac2f984ac2182aad5465507290d823ec87d04699c611431273de3fda000125a5dd341b6b31871d2ebf963ebcdabd1044705af8edc36dad48d5f7eb067dba82b66e56d805d75a1f9d91550f8dbff60aa0e4c2353fc5bab080a04e153e108106f172833f774e39cc7ba6f908f30ca13009f8cd6c33bdb53c3dd1e885d1b6c0c7ee4e6f323b34e7590fd3dc8af075230308d34e64cfe52eaaa21df13e9277231598ca27fac64404e574b3e8c6b9bf6282d09e0d35624c2ceb53d0cef007f9cb99013bbb09e72def49001ad576cd4e3dbc5cecfb9b7115436f407ad82b48bc54bf720e396a3eee66fb8c362507f82fceeb6a88b334b6090703f941c99efd78ee25d1b94f77e5a1ff87998cab9a4571fa0d290943d00d6f7f995879140bd7664545e1708b89784e9941e23a9af3fb25bc4f5c4cbe0f9d0d58c336b0c653e6c1ee9a68d74dc4db2a2c1156885b11b64700899960415b62552bf354604c9ccd943dfebfbd69c5981a3e02096dd80e67840567ed582fa9fc98c8213dac5a7bda9a5eca7c6a99d3554dd8190d216e5e43f38e1397888664d5c5ac4c2f18f3d8d9b85a2855bfef1efc4b66b4131f47aa3df937ad6
#
# (C) Tenable Network Security, Inc.
#

include("compat.inc");

if (description)
{
  script_id(91572);
  script_version("1.19");
  script_set_attribute(attribute:"plugin_modification_date", value:"2020/08/17");

  script_cve_id("CVE-2016-2107");
  script_bugtraq_id(89760);
  script_xref(name:"EDB-ID", value:"39768");

  script_name(english:"OpenSSL AES-NI Padding Oracle MitM Information Disclosure");
  script_summary(english:"Checks if the server sends a RECORD_OVERFLOW alert to a crafted TLS handshake.");

  script_set_attribute(attribute:"synopsis", value:
"It was possible to obtain sensitive information from the remote host
with TLS-enabled services.");
  script_set_attribute(attribute:"description", value:
"The remote host is affected by a man-in-the-middle (MitM) information
disclosure vulnerability due to an error in the implementation of
ciphersuites that use AES in CBC mode with HMAC-SHA1 or HMAC-SHA256.
The implementation is specially written to use the AES acceleration
available in x86/amd64 processors (AES-NI). The error messages
returned by the server allow allow a man-in-the-middle attacker to
conduct a padding oracle attack, resulting in the ability to decrypt
network traffic.");
  script_set_attribute(attribute:"see_also", value:"https://blog.filippo.io/luckyminus20/");
  # https://web-in-security.blogspot.com/2016/05/curious-padding-oracle-in-openssl-cve.html
  script_set_attribute(attribute:"see_also", value:"http://www.nessus.org/u?7647e9f0");
  script_set_attribute(attribute:"see_also", value:"https://www.openssl.org/news/secadv/20160503.txt");
  script_set_attribute(attribute:"solution", value:
"Upgrade to OpenSSL version 1.0.1t / 1.0.2h or later.");
  script_set_cvss_base_vector("CVSS2#AV:N/AC:H/Au:N/C:P/I:N/A:N");
  script_set_cvss_temporal_vector("CVSS2#E:POC/RL:OF/RC:C");
  script_set_attribute(attribute:"cvss_score_source", value:"CVE-2016-2107");
  script_set_cvss3_base_vector("CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N");
  script_set_cvss3_temporal_vector("CVSS:3.0/E:P/RL:O/RC:C");
  script_set_attribute(attribute:"exploitability_ease", value:"Exploits are available");
  script_set_attribute(attribute:"exploit_available", value:"true");

  script_set_attribute(attribute:"vuln_publication_date", value:"2016/05/03");
  script_set_attribute(attribute:"patch_publication_date", value:"2016/05/03");
  script_set_attribute(attribute:"plugin_publication_date", value:"2016/06/13");

  script_set_attribute(attribute:"plugin_type", value:"remote");
  script_set_attribute(attribute:"cpe", value:"cpe:/a:openssl:openssl");
  script_set_attribute(attribute:"in_the_news", value:"true");
  script_end_attributes();

  script_category(ACT_GATHER_INFO);
  script_family(english:"General");

  script_copyright(english:"This script is Copyright (C) 2016-2020 and is owned by Tenable, Inc. or an Affiliate thereof.");

  script_dependencies("ssl_supported_versions.nasl");
  script_require_ports("SSL/Supported");

  exit(0);
}

include("audit.inc");
include("global_settings.inc");
include("x509_func.inc");
include("rsync.inc");
include("ftp_func.inc");
include("ldap_func.inc");
include("nntp_func.inc");
include("smtp_func.inc");
include("telnet2_func.inc");
include("ssl_funcs.inc");
include("string.inc");

##
# Checks whether a cipher is in a list of cipher suites.
#
# @anonparam cipher Cipher in question.
# @anonparam ciphers List of cipher suites.
#
# @return TRUE for success, FALSE otherwise.
##
function tls_cipher_in_list()
{
  local_var cipher, ciphers, i, id, len;

  cipher = _FCT_ANON_ARGS[0];
  ciphers = _FCT_ANON_ARGS[1];

  len = strlen(ciphers);
  for (i = 0; i < len; i += 2)
  {
    id = substr(ciphers, i, i + 2 - 1);
    if (cipher == id) return TRUE;
  }

  return FALSE;
}

##
# Split the key block into IVs, cipher keys, and MAC keys.
#
# @anonparam keyblk Key block derived from the master secret.
#
# @return TRUE for success, FALSE otherwise.
##
function tls_set_keys(cipher_desc, keyblk)
{
  local_var mac_size, iv_size, key_size, pos, tls, mac, encryption;

  # Determine the size of the key block's fields.
  mac = cipher_field(name:cipher_desc, field:"mac");
  if ('SHA1' >< mac)        mac_size = 20;
  else if ('SHA256' >< mac) mac_size = 32;
  else return FALSE;

  encryption = cipher_field(name:cipher_desc, field:"encrypt");
  if ('AES-CBC(128)' >< encryption)      { key_size = 16; iv_size = 16; }
  else if ('AES-CBC(256)' >< encryption) { key_size = 32; iv_size = 16; }
  else return FALSE;

  # Ensure the block is big enough.
  if (strlen(keyblk) < 2 * (mac_size + key_size + iv_size))
    return FALSE;

  # Extract the data from the key block.
  pos = 0;
  tls['enc_mac_key'] = substr(keyblk, pos, pos + mac_size - 1); pos += mac_size;
  tls['dec_mac_key'] = substr(keyblk, pos, pos + mac_size - 1); pos += mac_size;
  tls['enc_key']     = substr(keyblk, pos, pos + key_size - 1); pos += key_size;
  tls['dec_key']     = substr(keyblk, pos, pos + key_size - 1); pos += key_size;
  tls['enc_iv']      = substr(keyblk, pos, pos + iv_size  - 1); pos += iv_size;
  tls['dec_iv']      = substr(keyblk, pos, pos + iv_size  - 1);

  return tls;
}

##
##
# Tries to make a TLS connection to the server.
#
# @return TRUE for success, FALSE otherwise.
##
function attack(port, ciphers)
{
  local_var soc, data, rec, srv_random, clt_random, version, cipher_desc;
  local_var cert, clt_cert_requested, skex, premaster, n, e, dh_privkey;
  local_var ckex, keyblk, tls_keys, tls_ciphertext, pubkey;

  # Get a socket to perform a handshake.
  soc = open_sock_ssl(port);
  if (!soc)
    # XXX-ALW Fix this error message
    return [FALSE, "open_sock_ssl", "Couldn't begin SSL handshake"];

  data = client_hello(
    v2hello:FALSE,
    version:mkword(TLS_10), # Record-layer version (RFC5246 Appendix E)
    maxver:mkword(TLS_12),  # Handshake version; maximum we support
    cipherspec:ciphers,
    extensions:tls_ext_ec(keys(curve_nid.tls))
  );
  send(socket:soc, data:data);
  rec = ssl_parse(blob:data);
  # Hang onto the Client Random; we need it to derive keys later.
  clt_random = mkdword(rec['time']) + rec['random'];

  # Read records one at a time. Expect to see at a minimum:
  # ServerHello, Certificate, and ServerHelloDone.
  while (TRUE)
  {
    # Receive a record from the server.
    data = recv_ssl(socket:soc);
    if (isnull(data))
    {
      close(soc);
      return [FALSE, "recv_ssl", "Did not receive expected ServerHello, ServerCertificate, etc."];
    }

    # ServerHello: Extract the random data for computation of keys.
    rec = ssl_find(
      blob:data,
      'content_type', SSL3_CONTENT_TYPE_HANDSHAKE,
      'handshake_type', SSL3_HANDSHAKE_TYPE_SERVER_HELLO
    );

    if (!isnull(rec))
    {
      # If server asks for version less than TLS 1.0 or higher than TLS 1.2, fail.
      if (rec['handshake_version'] < TLS_10 || rec['handshake_version'] > TLS_12)
        return [FALSE, "handshake_version", "Server does not support TLS 1.0, 1.1, or 1.2"];

      # Use the TLS version the server wants
      version = rec['handshake_version'];

      srv_random = mkdword(rec['time']) + rec['random'];

      # Wacko SSL servers might return a cipher suite not in the
      # client's request list.
      if (!tls_cipher_in_list(mkword(rec['cipher_spec']), ciphers))
      {
        close(soc);
        return [FALSE, "cipher_spec", "Server ignored our list of supported ciphers"];
      }

      # Store the negotiated cipher suite.
      cipher_desc = ciphers_desc[cipher_name(id:rec['cipher_spec'])];

      if (isnull(cipher_desc))
      {
        close(soc);
        return [FALSE, "cipher_spec", "Assertion failure"];
      }
    }

    # Certificate: Extract the server's public key.
    rec = ssl_find(
      blob:data,
      'content_type', SSL3_CONTENT_TYPE_HANDSHAKE,
      'handshake_type', SSL3_HANDSHAKE_TYPE_CERTIFICATE
    );

    if (!isnull(rec) && max_index(rec['certificates']) > 0)
    {
      # First cert in the chain should be the server cert.
      cert = parse_der_cert(cert:rec['certificates'][0]);
      if (isnull(cert))
      {
        close(soc);
        return [FALSE, "parse_der_cert", "Failed to parse server's certificate"];
      }
      cert = cert['tbsCertificate'];
    }

    # Server Key Exchange.
    rec = ssl_find(
      blob:data,
      'content_type', SSL3_CONTENT_TYPE_HANDSHAKE,
      'handshake_type', SSL3_HANDSHAKE_TYPE_SERVER_KEY_EXCHANGE
    );

    if (!isnull(rec['data']))
      skex = ssl_parse_srv_kex(blob:rec['data'], cipher:cipher_desc, version:version);

    # Certificate Request.
    rec = ssl_find(
      blob:data,
      'content_type', SSL3_CONTENT_TYPE_HANDSHAKE,
      'handshake_type', SSL3_HANDSHAKE_TYPE_CERTIFICATE_REQUEST
    );

    if (!isnull(rec['data']))
      clt_cert_requested = TRUE;

    # Server Hello Done.
    rec = ssl_find(
      blob:data,
      'content_type', SSL3_CONTENT_TYPE_HANDSHAKE,
      'handshake_type', SSL3_HANDSHAKE_TYPE_SERVER_HELLO_DONE
    );

    # When we get a ServerHelloDone, it's our turn to send again.
    if (!isnull(rec))
      break;

    # Is it an alert?
    rec = ssl_find(
      blob:data,
      encrypted:FALSE,
      'content_type', SSL3_CONTENT_TYPE_ALERT
    );

    if (!isnull(rec))
    {
      close(soc);
        return [FALSE, "handshake_failure", "Server sent alert to ClientHello. Level: " + rec['level'] + ", description: " + rec['description']];
    }
  }

  # Will contain an empty ClientCertificate (if requested), ClientKeyExchange,
  data = '';

  # Create an empty client certificate if one is requested.
  if (clt_cert_requested)
  {
    # Send an empty certificate for now. TLSv1.0 says the client can
    # send an empty certificate.
    data += ssl_mk_record(
      type:SSL3_CONTENT_TYPE_HANDSHAKE,
      version:version,
      data:ssl_mk_handshake_msg(
        type : SSL3_HANDSHAKE_TYPE_CERTIFICATE,
        data : ssl_vldata_put(data:NULL,len:3)
      )
    );
  }

  # Process ServerCertificate and ServerKeyExchange messages.
  var cipher_kex = cipher_field(name:cipher_desc, field:"kex");
  if (cipher_kex =~ "RSA($|\()")
  {
    if (isnull(cert))
    {
      close(soc);
      return [FALSE, "rsa_kx", "Server selected RSA key exchange but didn't provide a certificate"];
    }

    if (isnull(cert['subjectPublicKeyInfo']) || isnull(cert['subjectPublicKeyInfo'][1]))
    {
      close(soc);
      return [FALSE, "rsa_kx", "A server certificate with an unsupported algorithm was found."];
    }

    n = cert['subjectPublicKeyInfo'][1][0];
    e = cert['subjectPublicKeyInfo'][1][1];

    if (isnull(n) || isnull(e))
    {
      close(soc);
      return [FALSE, "rsa_kx", "Failed to extract public key from server certificate."];
    }

    premaster = mkword(TLS_12) + rand_str(length:46);

    # Encrypt the premaster secret with server's RSA public key.
    ckex = rsa_public_encrypt(data:premaster, n:n, e:e);

    # It looks like TLS 1.0 and up prepend a two-byte length, but the
    # RFC is vague.
    if (version >= TLS_10)
      ckex = ssl_vldata_put(data:ckex, len:2);
  }
  else if (cipher_kex =~ "ECDH($|\()" && ecc_functions_available())
  {
    if (isnull(skex))
    {
      close(soc);
      return [FALSE, "ecdh_kx", "Server selected ECDHE key exchange but didn't provide a ServerKeyExchange"];
    }

    # Generate the client private key
    dh_privkey = rand_str(length:16);

    # Compute the premaster secret
    premaster = ecc_scalar_multiply(
      curve_nid:curve_nid.tls[skex['named_curve']],
      scalar:dh_privkey,
      x:substr(skex['pubkey'], 1, (strlen(skex['pubkey'])) / 2),
      y:substr(skex['pubkey'], (strlen(skex['pubkey']) / 2) + 1)
    );
    # Just the X coordinate of the curve point is used
    premaster = ecc_fe2osp(element:premaster.x, curve_nid:curve_nid.tls[skex['named_curve']]);

    pubkey = ecc_scalar_multiply(
      curve_nid:curve_nid.tls[skex['named_curve']],
      scalar:dh_privkey
    );

    pubkey.x = ecc_fe2osp(element:pubkey.x, curve_nid:curve_nid.tls[skex['named_curve']]);
    pubkey.y = ecc_fe2osp(element:pubkey.y, curve_nid:curve_nid.tls[skex['named_curve']]);

    ckex = ssl_vldata_put(
      # Uncompressed curve point encoding
      data:'\x04' + pubkey.x + pubkey.y,
      len:1
    );
  }
  else if (cipher_kex =~ "DH($|\()")
  {
    if (isnull(skex))
    {
      close(soc);
      return [FALSE, "dh_kx", "Server selected DH key exchange but didn't provide a ServerKeyExchange"];
    }

    # Generate the client private key,
    dh_privkey = rand_str(length:16);

    # Compute the premaster secret.
    premaster = bn_mod_exp(skex['dh_y'], dh_privkey, skex['dh_p']);

    # Encode the client's DH public key
    ckex = ssl_vldata_put(
      data:bn_mod_exp(skex['dh_g'], dh_privkey, skex['dh_p']),
      len:2
    );
  }
  else
  {
    close(soc);
    return [FALSE, "kx", "Unsupported key exchange method"];
  }

  # Create a ClientKeyExchange record
  data += ssl_mk_record(
    type:SSL3_CONTENT_TYPE_HANDSHAKE,
    version:version,
    data:ssl_mk_handshake_msg(
      type:SSL3_HANDSHAKE_TYPE_CLIENT_KEY_EXCHANGE,
      data:ckex
    )
  );

  tls_keys = tls_set_keys(
    cipher_desc:cipher_desc,
    keyblk:ssl_derive_keyblk(
      c_random:clt_random,
      s_random:srv_random,
      version:version,
      cipher_desc:cipher_desc,
      master:ssl_calc_master(
        c_random:clt_random,
        s_random:srv_random,
        version:version,
        premaster:premaster,
        cipher_desc:cipher_desc
      )
    )
  );

  if (tls_keys == FALSE)
  {
    close(soc);
    return [FALSE, "kx", "Failed to make TLS keys from key exchange"];
  }

  data += tls_mk_record(
    type:SSL3_CONTENT_TYPE_CHANGECIPHERSPEC,
    data:mkbyte(1),
    version:version
  );

  # Use a random IV, as it's included explicitly in TLS 1.1
  if (version >= TLS_11)
    tls_keys['enc_iv'] = rand_str(length:strlen(tls_keys['enc_iv']));

  # Finished message.
  # We make a record of just bad padding to trigger a RECORD_OVERFLOW alert.
  # 48 bytes of padding because:
  # o Must be a multiple of AES block size (16 bytes).
  # o Must be at least one byte bigger than the MAC size.
  # o SHA1 is 20 bytes, SHA256 is 32 bytes, so we round up to 48.
  # o SHA384 ciphersuites are not vulnerable.
  tls_ciphertext = aes_cbc_encrypt(
    data:crap(data:'\xff', length:48),
    iv:tls_keys['enc_iv'],
    key:tls_keys['enc_key']
  );
  # aes_cbc_encrypt() returns an array, [0] is ciphertext, [1] is CBC
  # residue (for TLS 1.0 IV). We don't retain the residue because we
  # don't intent to send any more records.
  tls_ciphertext = tls_ciphertext[0];

  # TLS 1.1 explicitly includes the IV in each record
  if (version >= TLS_11)
    tls_ciphertext = tls_keys['enc_iv'] + tls_ciphertext;

  data += tls_mk_record(
    type:SSL3_CONTENT_TYPE_HANDSHAKE,
    data:tls_ciphertext,
    version:version
  );

  # Send the ChangeCipherSpec and tampered Finished message
  send(socket:soc, data:data);

  while (TRUE)
  {
    # Receive a record from the server.
    data = recv_ssl(socket:soc);
    if (isnull(data))
    {
      close(soc);
      return [FALSE, "post_attack", "Server did not send an alert when sent a crafted Finished message"];
    }

    # Is it an alert?
    rec = ssl_find(
      blob:data,
      encrypted:FALSE,
      'content_type', SSL3_CONTENT_TYPE_ALERT
    );

    if (!isnull(rec))
    {
      close(soc);
      if (rec['level'] == 2 && rec['description'] == SSL3_ALERT_TYPE_RECORD_OVERFLOW)
        return [TRUE, "post_attack", "Server sent RECORD_OVERFLOW alert"];
      else
        return [FALSE, "post_attack", "Server sent alert to tampered Finished. Level: " + rec['level'] + ", description: " + rec['description']];
    }
  }
}

get_kb_item_or_exit('SSL/Supported');

# Get a port that uses SSL.
port = get_ssl_ports(fork:TRUE);

if (isnull(port))
  exit(1, 'The host does not appear to have any SSL-based services.');

# Find out if the port is open.
if (!get_port_state(port))
  audit(AUDIT_PORT_CLOSED, port, "TCP");

# Ciphersuites should basically be the "Cartesian product" of:
# * DHE and RSA key exchanges
# * AES-CBC with 128- and 256-bit keys
# * SHA1 and SHA256 HMACs (SHA384 ciphersuites are not vulnerable)
# TODO: should support ECDHE and ECDSA, once we can do that from NASL.

# We test SHA1 separately from SHA256 and check if *either* was
# vulnerable, because vulnerable 1.0.1 servers support SHA256 but are
# only vulnerable on SHA1 ciphersuites. If we offered SHA1 and SHA256
# at the same time and the server preferred SHA256, it'd be a false
# negative.

cipher_list_sha1 =
  ciphers['TLS1_CK_RSA_WITH_AES_128_CBC_SHA'] + # <- Required by all TLS 1.2 impls.
  ciphers['TLS1_CK_RSA_WITH_AES_256_CBC_SHA'] +
  ciphers['TLS1_CK_DHE_RSA_WITH_AES_128_CBC_SHA'] +
  ciphers['TLS1_CK_DHE_RSA_WITH_AES_256_CBC_SHA'];

cipher_list_sha256 =
    ciphers['TLS1_RSA_WITH_AES_128_CBC_SHA256'] +
    ciphers['TLS1_RSA_WITH_AES_256_CBC_SHA256'] +
    ciphers['TLS1_DHE_RSA_WITH_AES_128_CBC_SHA256'] +
    ciphers['TLS1_DHE_RSA_WITH_AES_256_CBC_SHA256'];

if (ecc_functions_available())
{
  cipher_list_sha1 +=
    ciphers["TLS1_CK_ECDHE_RSA_WITH_AES_128_CBC_SHA"] +
    ciphers["TLS1_CK_ECDHE_RSA_WITH_AES_256_CBC_SHA"];

  cipher_list_sha256 +=
    ciphers["TLS1_ECDHE_RSA_WITH_AES_128_CBC_SHA256"] +
    ciphers["TLS1_ECDHE_RSA_WITH_AES_256_CBC_SHA256"];
}

sha1_result = attack(port:port, ciphers:cipher_list_sha1);

# Only do SHA256 test if we didn't find a vuln with SHA1.
if (sha1_result[0] == FALSE)
  sha256_result = attack(port:port, ciphers:cipher_list_sha256);

if (sha1_result[0] == TRUE || sha256_result[0] == TRUE)
{
  security_report_v4(
    port:port,
    severity:SECURITY_NOTE,
    extra:
      'Nessus was able to trigger a RECORD_OVERFLOW alert in the\n' +
      'remote service by sending a crafted SSL "Finished" message.'
  );
}
else
{
  exit(0,
    "[Port " + port + "] " +
    "SHA1 test: " + sha1_result[1] + ": " + sha1_result[2] + ". " +
    "SHA256 test: " + sha256_result[1] + ": " + sha256_result[2]);
}
VendorProductVersionCPE
opensslopensslcpe:/a:openssl:openssl