Skip to content

Commit 99873f3

Browse files
committed
Add global datastore option configuration
1 parent 3768efb commit 99873f3

File tree

11 files changed

+198
-41
lines changed

11 files changed

+198
-41
lines changed

lib/msf/base/sessions/command_shell.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ def cmd_irb(*args)
559559
end
560560
else
561561
# XXX: No vprint_status here
562-
if framework.datastore['VERBOSE'].to_s == 'true'
562+
if framework.datastore['VERBOSE']
563563
print_status("You are executing expressions in #{binding.receiver}")
564564
end
565565

lib/msf/core/data_store.rb

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,27 @@ module Msf
99
###
1010
class DataStore
1111

12-
# The global framework datastore doesn't currently import options
13-
# For now, store an ad-hoc list of keys that the shell handles
14-
#
15-
# This list could be removed if framework's bootup sequence registers
16-
# these as datastore options
17-
GLOBAL_KEYS = %w[
18-
ConsoleLogging
19-
LogLevel
20-
MinimumRank
21-
SessionLogging
22-
TimestampOutput
23-
Prompt
24-
PromptChar
25-
PromptTimeFormat
26-
MeterpreterPrompt
27-
SessionTlvLogging
28-
]
12+
# Typed option definitions for globally-scoped datastore keys.
13+
# These are registered on the framework datastore at startup so that
14+
# normalization (e.g. OptBool converting "true" to true) applies even
15+
# when no module is active.
16+
GLOBAL_OPTION_DEFINITIONS = OptionContainer.new([
17+
OptBool.new('ConsoleLogging', [false, 'Log all console input and output', false]),
18+
OptInt.new('LogLevel', [false, 'Verbosity of logs (default 0, max 3)', 0]),
19+
OptEnum.new('MinimumRank', [false, 'The minimum rank of exploits that will run without explicit confirmation', 'manual', RankingName.values]),
20+
OptBool.new('SessionLogging', [false, 'Log all input and output for sessions', false]),
21+
OptBool.new('TimestampOutput', [false, 'Prefix all console output with a timestamp', false]),
22+
OptBool.new('VERBOSE', [false, 'Enable detailed status messages - the specific behavior can differ per module', false]),
23+
OptString.new('Prompt', [false, 'The prompt string', nil]),
24+
OptString.new('PromptChar', [false, 'The prompt character', nil]),
25+
OptString.new('PromptTimeFormat', [false, 'Format for timestamp escapes in prompts', nil]),
26+
OptString.new('MeterpreterPrompt', [false, 'The meterpreter prompt string', nil]),
27+
OptSessionTlvLogging.new('SessionTlvLogging', [false, 'Log all incoming and outgoing TLV packets', nil]),
28+
])
29+
30+
# Backward-compatible list of known global key names, derived from the
31+
# typed option definitions above.
32+
GLOBAL_KEYS = GLOBAL_OPTION_DEFINITIONS.keys.freeze
2933

3034
#
3135
# Initializes the data store's internal state.
@@ -136,7 +140,7 @@ def remove_option(name)
136140

137141
#
138142
# This method is a helper method that imports the default value for
139-
# all of the supplied options
143+
# all of the supplied options.
140144
#
141145
def import_options(options, imported_by = nil, overwrite = true)
142146
options.each_option do |name, option|
@@ -350,7 +354,7 @@ def merge!(other)
350354
end
351355
else
352356
other.each do |k, v|
353-
self.store(k, v)
357+
self[k] = v
354358
end
355359
end
356360

lib/msf/core/framework.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def initialize(options={})
7070
self.events = EventDispatcher.new(self)
7171
self.modules = ModuleManager.new(self,types)
7272
self.datastore = DataStore.new
73+
self.datastore.import_options(DataStore::GLOBAL_OPTION_DEFINITIONS)
7374
self.jobs = Rex::JobContainer.new
7475
self.analyze = Analyze.new(self)
7576
self.plugins = PluginManager.new(self)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# -*- coding: binary -*-
2+
3+
module Msf
4+
###
5+
#
6+
# Session TLV Logging option.
7+
#
8+
# Valid values: 'true', 'false', 'console', or 'file:<path>'
9+
#
10+
###
11+
class OptSessionTlvLogging < OptBase
12+
VALID_KEYWORDS = %w[true false console].freeze
13+
14+
def initialize(in_name, attrs = [], **kwargs)
15+
super
16+
end
17+
18+
def type
19+
'sessiontlvlogging'
20+
end
21+
22+
def validate_on_assignment?
23+
true
24+
end
25+
26+
def valid?(value, check_empty: true, datastore: nil)
27+
return false if !super(value, check_empty: check_empty)
28+
return true if value.nil? || value.to_s.strip.empty?
29+
30+
self.class.valid_tlv_logging?(value.to_s.strip)
31+
end
32+
33+
def self.valid_tlv_logging?(value)
34+
return true if VALID_KEYWORDS.any? { |kw| value.casecmp?(kw) }
35+
return true if value.start_with?('file:') && value.split('file:', 2).last.length > 0
36+
37+
false
38+
end
39+
end
40+
end

lib/msf/core/option_container.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module Msf
1919
autoload :OptPort, 'msf/core/opt_port'
2020
autoload :OptRaw, 'msf/core/opt_raw'
2121
autoload :OptRegexp, 'msf/core/opt_regexp'
22+
autoload :OptSessionTlvLogging, 'msf/core/opt_session_tlv_logging'
2223
autoload :OptString, 'msf/core/opt_string'
2324

2425
#

lib/msf/ui/console/command_dispatcher/developer.rb

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ def cmd_irb(*args)
151151
driver.input.reset_tab_completion
152152
end
153153
else
154-
# XXX: No vprint_status here either
155-
if framework.datastore['VERBOSE'].to_s == 'true'
154+
# XXX: No vprint_status in this context
155+
if framework.datastore['VERBOSE']
156156
print_status("You are executing expressions in #{binding.receiver}")
157157
end
158158

@@ -242,9 +242,8 @@ def cmd_edit(*args)
242242
print_warning("LocalEditor or $VISUAL/$EDITOR should be set. Falling back on #{editor}.")
243243
end
244244

245-
# XXX: No vprint_status in this context?
246-
# XXX: VERBOSE is a string instead of Bool??
247-
print_status("Launching #{editor} #{path}") if framework.datastore['VERBOSE'].to_s == 'true'
245+
# XXX: No vprint_status in this context
246+
print_status("Launching #{editor} #{path}") if framework.datastore['VERBOSE']
248247

249248
unless system(*editor.split, path)
250249
print_error("Could not execute #{editor} #{path}")
@@ -333,9 +332,8 @@ def cmd_log(*args)
333332
print_warning("LocalPager or $PAGER/$MANPAGER should be set. Falling back on #{pager}.")
334333
end
335334

336-
# XXX: No vprint_status in this context?
337-
# XXX: VERBOSE is a string instead of Bool??
338-
print_status("Launching #{pager} #{path}") if framework.datastore['VERBOSE'].to_s == 'true'
335+
# XXX: No vprint_status in this context
336+
print_status("Launching #{pager} #{path}") if framework.datastore['VERBOSE']
339337

340338
unless system(*pager.split, path)
341339
print_error("Could not execute #{pager} #{path}")

lib/msf/ui/console/command_dispatcher/modules.rb

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1619,18 +1619,21 @@ def show_global_options
16191619
'Postfix' => "\n",
16201620
'Columns' => columns
16211621
)
1622-
[
1623-
[ 'ConsoleLogging', framework.datastore['ConsoleLogging'] || "false", 'Log all console input and output' ],
1624-
[ 'LogLevel', framework.datastore['LogLevel'] || "0", 'Verbosity of logs (default 0, max 3)' ],
1625-
[ 'MinimumRank', framework.datastore['MinimumRank'] || "0", 'The minimum rank of exploits that will run without explicit confirmation' ],
1626-
[ 'SessionLogging', framework.datastore['SessionLogging'] || "false", 'Log all input and output for sessions' ],
1627-
[ 'SessionTlvLogging', framework.datastore['SessionTlvLogging'] || "false", 'Log all incoming and outgoing TLV packets' ],
1628-
[ 'TimestampOutput', framework.datastore['TimestampOutput'] || "false", 'Prefix all console output with a timestamp' ],
1629-
[ 'Prompt', framework.datastore['Prompt'] || Msf::Ui::Console::Driver::DefaultPrompt.to_s.gsub(/%.../,"") , "The prompt string" ],
1630-
[ 'PromptChar', framework.datastore['PromptChar'] || Msf::Ui::Console::Driver::DefaultPromptChar.to_s.gsub(/%.../,""), "The prompt character" ],
1631-
[ 'PromptTimeFormat', framework.datastore['PromptTimeFormat'] || Time::DATE_FORMATS[:db].to_s, 'Format for timestamp escapes in prompts' ],
1632-
[ 'MeterpreterPrompt', framework.datastore['MeterpreterPrompt'] || '%undmeterpreter%clr', 'The meterpreter prompt string' ],
1633-
].each { |r| tbl << r }
1622+
1623+
# Display-only defaults for options whose actual defaults are managed
1624+
# by the UI layer (driver prompt constants, time format, etc.)
1625+
computed_defaults = {
1626+
'Prompt' => Msf::Ui::Console::Driver::DefaultPrompt.to_s.gsub(/%.../, ''),
1627+
'PromptChar' => Msf::Ui::Console::Driver::DefaultPromptChar.to_s.gsub(/%.../, ''),
1628+
'PromptTimeFormat' => Time::DATE_FORMATS[:db].to_s,
1629+
'MeterpreterPrompt' => '%undmeterpreter%clr',
1630+
}
1631+
1632+
Msf::DataStore::GLOBAL_OPTION_DEFINITIONS.each_option do |name, opt|
1633+
val = framework.datastore[name]
1634+
display_value = val.nil? ? (opt.default.nil? ? '' : opt.default.to_s) : val.to_s
1635+
tbl << [ name, display_value, opt.desc ]
1636+
end
16341637

16351638
print(tbl.to_s)
16361639
end

lib/msf/ui/console/command_dispatcher/session.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def cmd_irb(*args)
101101
end
102102
else
103103
# XXX: No vprint_status here
104-
if framework.datastore['VERBOSE'].to_s == 'true'
104+
if framework.datastore['VERBOSE']
105105
print_status("You are executing expressions in #{binding.receiver}")
106106
end
107107

spec/lib/msf/core/data_store_spec.rb

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,42 @@
3333

3434
RSpec.describe Msf::DataStore do
3535

36+
describe ".GLOBAL_OPTION_DEFINITIONS" do
37+
it "contains only OptBase subclass instances" do
38+
Msf::DataStore::GLOBAL_OPTION_DEFINITIONS.each_option do |_name, opt|
39+
expect(opt).to be_a(Msf::OptBase)
40+
end
41+
end
42+
43+
it "defines the expected option types" do
44+
defs = Msf::DataStore::GLOBAL_OPTION_DEFINITIONS
45+
expect(defs['ConsoleLogging']).to be_a(Msf::OptBool)
46+
expect(defs['LogLevel']).to be_a(Msf::OptInt)
47+
expect(defs['MinimumRank']).to be_a(Msf::OptEnum)
48+
expect(defs['SessionLogging']).to be_a(Msf::OptBool)
49+
expect(defs['TimestampOutput']).to be_a(Msf::OptBool)
50+
expect(defs['VERBOSE']).to be_a(Msf::OptBool)
51+
expect(defs['SessionTlvLogging']).to be_a(Msf::OptSessionTlvLogging)
52+
end
53+
54+
it "derives GLOBAL_KEYS with the same key names" do
55+
expected_keys = %w[
56+
ConsoleLogging
57+
LogLevel
58+
MinimumRank
59+
SessionLogging
60+
TimestampOutput
61+
VERBOSE
62+
Prompt
63+
PromptChar
64+
PromptTimeFormat
65+
MeterpreterPrompt
66+
SessionTlvLogging
67+
]
68+
expect(Msf::DataStore::GLOBAL_KEYS).to match_array(expected_keys)
69+
end
70+
end
71+
3672
describe "#import_option" do
3773
subject do
3874
s = described_class.new
@@ -102,7 +138,57 @@
102138
end
103139

104140
it_behaves_like "datastore"
141+
142+
context "normalizes values loaded from an INI config file" do
143+
subject do
144+
ds = described_class.new
145+
ds.import_options(Msf::DataStore::GLOBAL_OPTION_DEFINITIONS)
146+
ds
147+
end
148+
149+
it "normalizes boolean and integer strings" do
150+
ini_instance = double group?: true,
151+
:[] => {
152+
"VERBOSE" => "true",
153+
"ConsoleLogging" => "false",
154+
"LogLevel" => "3"
155+
}
156+
ini_class = double from_file: ini_instance
157+
stub_const("Rex::Parser::Ini", ini_class)
158+
159+
subject.from_file("path")
160+
161+
expect(subject['VERBOSE']).to eq true
162+
expect(subject['ConsoleLogging']).to eq false
163+
expect(subject['LogLevel']).to eq 3
164+
end
165+
end
105166
end
167+
it "registers each option by name" do
168+
options = Msf::OptionContainer.new([
169+
Msf::OptBool.new('TestBool', [false, 'A bool', false]),
170+
Msf::OptInt.new('TestInt', [false, 'An int', 0]),
171+
])
172+
subject.import_options(options)
173+
expect(subject.options['TestBool']).to be_a(Msf::OptBool)
174+
expect(subject.options['TestInt']).to be_a(Msf::OptInt)
175+
end
106176

177+
it "normalizes values through registered options" do
178+
options = Msf::OptionContainer.new([Msf::OptBool.new('MyFlag', [false, 'A flag', false])])
179+
subject.import_options(options)
180+
subject['MyFlag'] = 'true'
181+
expect(subject['MyFlag']).to eq true
182+
end
183+
184+
it "registers options from a standalone OptionContainer" do
185+
options = Msf::OptionContainer.new
186+
options.add_options([
187+
Msf::OptString.new('Foo', [false, 'A string', nil])
188+
])
189+
subject.import_options(options)
190+
expect(subject.options['Foo']).to be_a(Msf::OptString)
191+
end
192+
end
107193

108194
end

spec/lib/msf/core/framework_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@
1313
framework
1414
}.not_to change { Thread.list.count }
1515
end
16+
17+
it 'registers global option definitions on the datastore' do
18+
Msf::DataStore::GLOBAL_KEYS.each do |key|
19+
expect(framework.datastore.options).to have_key(key)
20+
end
21+
22+
framework.datastore['VERBOSE'] = 'true'
23+
expect(framework.datastore['VERBOSE']).to eq true
24+
25+
framework.datastore['VERBOSE'] = 'false'
26+
expect(framework.datastore['VERBOSE']).to eq false
27+
end
1628
end
1729

1830
describe "#version" do

0 commit comments

Comments
 (0)