metasploit-framework/modules/auxiliary/admin/atg/atg_client.rb

255 lines
7.8 KiB
Ruby

##
# encoding: utf-8
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Scanner
def initialize
super(
'Name' => 'Veeder-Root Automatic Tank Gauge (ATG) Administrative Client',
'Description' => %q{
This module acts as a simplistic administrative client for interfacing
with Veeder-Root Automatic Tank Gauges (ATGs) or other devices speaking
the TLS-250 and TLS-350 protocols. This has been tested against
GasPot, a honeypot meant to simulate ATGs; it has not been tested
against anything else, so use at your own risk.
},
'Author' =>
[
'Jon Hart <jon_hart[at]rapid7.com>' # original metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['URL', 'https://community.rapid7.com/community/infosec/blog/2015/01/22/the-internet-of-gas-station-tank-gauges'],
['URL', 'http://www.trendmicro.com/vinfo/us/security/news/cybercrime-and-digital-threats/the-gaspot-experiment'],
['URL', 'https://github.com/sjhilt/GasPot'],
['URL', 'http://www.veeder.com/us/automatic-tank-gauge-atg-consoles'],
['URL', 'http://www.chipkin.com/files/liz/576013-635.pdf'],
['URL', 'http://www.veeder.com/gold/download.cfm?doc_id=6227']
],
'DefaultAction' => 'INVENTORY',
'Actions' =>
[
[ 'ALARM',
{
'Description' => 'I30200 Sensor alarm history (untested)',
'TLS-350_CMD' => "\x01I30200"
}
],
[ 'ALARM_RESET',
{
'Description' => 'IS00300 Remote alarm reset (untested)',
'TLS-350_CMD' => "\x01IS00300"
}
],
[ 'DELIVERY',
{
'Description' => 'I20200 Delivery report',
'TLS-350_CMD' => "\x01I20200"
}
],
[ 'INVENTORY',
{
'Description' => '200/I20100 In-tank inventory report',
'TLS-250_CMD' => "\x01200",
'TLS-350_CMD' => "\x01I20100"
}
],
[ 'LEAK',
{
'Description' => 'I20300 Leak report',
'TLS-350_CMD' => "\x01I20300"
}
],
[ 'RELAY',
{
'Description' => 'I40600 Relay status (untested)',
'TLS-350_CMD' => "\x01I40600"
}
],
[ 'RESET',
{
'Description' => 'IS00100 Reset (untested)',
'TLS-350_CMD' => "\x01IS00100"
}
],
[ 'CLEAR_RESET',
{
'Description' => 'IS00200 Clear Reset Flag (untested)',
'TLS-350_CMD' => "\x01IS00200"
}
],
[ 'SENSOR',
{
'Description' => 'I30100 Sensor status (untested)',
'TLS-350_CMD' => "\x01I30100"
}
],
[ 'SENSOR_DIAG',
{
'Description' => 'IB0100 Sensor diagnostics (untested)',
'TLS-350_CMD' => "\x01IB0100"
}
],
[ 'SHIFT',
{
'Description' => 'I20400 Shift report',
'TLS-350_CMD' => "\x01I20400"
}
],
[ 'SET_TANK_NAME',
{
'Description' => 'S602 set tank name (use TANK_NUMBER and TANK_NAME options)',
'TLS-350_CMD' => "\x01S602"
}
],
# [ 'SET_TIME',
# {
# 'Description' => 'S50100 Set time of day (use TIME option) (untested)',
# 'TLS-350_CMD' => "\x01S50100"
# }
# ],
[ 'STATUS',
{
'Description' => 'I20500 In-tank status report',
'TLS-350_CMD' => "\x01I20500"
}
],
[ 'SYSTEM_STATUS',
{
'Description' => 'I10100 System status report (untested)',
'TLS-350_CMD' => "\x01I10100"
}
],
[ 'TANK_ALARM',
{
'Description' => 'I20600 Tank alarm history (untested)',
'TLS-350_CMD' => "\x01I20600"
}
],
[ 'TANK_DIAG',
{
'Description' => 'IA0100 Tank diagnostics (untested)',
'TLS-350_CMD' => "\x01IA0100"
}
],
[ 'VERSION',
{
'Description' => 'Version information',
'TLS-250_CMD' => "\x01980",
'TLS-350_CMD' => "\x01I90200"
}
]
]
)
register_options(
[
Opt::RPORT(10001),
OptInt.new('TANK_NUMBER', [false, 'The tank number to operate on (use with SET_TANK_NAME, 0 to change all)', 1]),
OptString.new('TANK_NAME', [false, 'The tank name to set (use with SET_TANK_NAME, defaults to random)'])
]
)
deregister_options('SSL', 'SSLCipher', 'SSLVerifyMode', 'SSLVersion')
register_advanced_options(
[
OptEnum.new('PROTOCOL', [true, 'The Veeder-Root TLS protocol to speak', 'TLS-350', %w(TLS-350 TLS-250)]),
OptInt.new('TIMEOUT', [true, 'Time in seconds to wait for responses to our probes', 5])
]
)
end
def setup
# ensure that the specified command is implemented for the desired version of the TLS protocol
unless action.opts.keys.include?(protocol_opt_name)
fail_with(Failure::BadConfig, "#{action.name} not defined for #{protocol}")
end
# ensure that the tank number is set for the commands that need it
if action.name == 'SET_TANK_NAME' && (tank_number < 0 || tank_number > 99)
fail_with(Failure::BadConfig, "TANK_NUMBER #{tank_number} is invalid")
end
unless timeout > 0
fail_with(Failure::BadConfig, "Invalid timeout #{timeout} -- must be > 0")
end
end
def get_response(request)
sock.put(request)
response = sock.get_once(-1, timeout)
response
end
def protocol
datastore['PROTOCOL']
end
def protocol_opt_name
protocol + '_CMD'
end
def tank_name
@tank_name ||= (datastore['TANK_NAME'] ? datastore['TANK_NAME'] : Rex::Text.rand_text_alpha(16))
end
def tank_number
datastore['TANK_NUMBER']
end
def time
if datastore['TIME']
Time.parse(datastore['TIME']).to_i
else
Time.now.to_i
end
end
def timeout
datastore['TIMEOUT']
end
def run_host(_host)
begin
connect
case action.name
when 'SET_TANK_NAME'
# send the set tank name command to change the tank name(s)
if tank_number == 0
vprint_status("Setting all tank names to #{tank_name}")
else
vprint_status("Setting tank ##{tank_number}'s name to #{tank_name}")
end
request = "#{action.opts[protocol_opt_name]}#{format('%02d', tank_number)}#{tank_name}\n"
sock.put(request)
# reconnect
disconnect
connect
# send an inventory probe to show that it succeeded
inventory_probe = "#{actions.find { |a| a.name == 'INVENTORY' }.opts[protocol_opt_name]}\n"
inventory_response = get_response(inventory_probe)
message = "#{protocol} #{action.opts['Description']}:\n#{inventory_response}"
if inventory_response.include?(tank_name)
print_good message
else
print_warning message
end
else
response = get_response("#{action.opts[protocol_opt_name]}\n")
print_good("#{protocol} #{action.opts['Description']}:\n#{response}")
end
ensure
disconnect
end
end
end