diff --git a/lib/helpers/constants.rb b/lib/helpers/constants.rb index 21122bfaa..0b7ca14b5 100644 --- a/lib/helpers/constants.rb +++ b/lib/helpers/constants.rb @@ -1,4 +1,4 @@ -## FILE CONSTANTS ## +## FILE / PATH CONSTANTS ## # Root directory of SecGen file structure ROOT_DIR = File.expand_path('../../../',__FILE__) @@ -12,6 +12,7 @@ VULNERABILITY_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/vulnerability_metadata_sche SERVICE_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/service_metadata_schema.xsd" UTILITY_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/utility_metadata_schema.xsd" GENERATOR_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/generator_metadata_schema.xsd" +ENCODER_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/encoder_metadata_schema.xsd" NETWORK_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/network_metadata_schema.xsd" BASE_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/base_metadata_schema.xsd" @@ -21,17 +22,16 @@ PROJECTS_DIR = "#{ROOT_DIR}/projects" # Path to environments directory ENVIRONMENTS_PATH = "#{ROOT_DIR}/modules/build/environments" - -## PATH CONSTANTS ## - # Path to modules directories MODULES_PATH = "#{ROOT_DIR}/modules/" VULNERABILITIES_PATH = "#{MODULES_PATH}vulnerabilities/" SERVICES_PATH = "#{MODULES_PATH}services/" UTILITIES_PATH = "#{MODULES_PATH}utilities/" GENERATORS_PATH = "#{MODULES_PATH}generators/" +ENCODERS_PATH = "#{MODULES_PATH}encoders/" NETWORKS_PATH = "#{MODULES_PATH}networks/" BASES_PATH = "#{MODULES_PATH}bases/" +MODULE_LOCAL_CALC_PATH = '/secgen_local/local.rb' # Path to documentation (Make sure documentation directory is already deleted with rake yard_clean before changing this) DOCUMENTATION_PATH = "#{ROOT_DIR}/documentation/yard/doc" diff --git a/lib/objects/module.rb b/lib/objects/module.rb index aedb8f7fa..f65bf0847 100644 --- a/lib/objects/module.rb +++ b/lib/objects/module.rb @@ -11,13 +11,16 @@ class Module # Module *selectors*, store filters in the attributes hash. # XML validity ensures valid and complete information. + attr_accessor :write_to_module_with_id attr_accessor :write_outputs_to + attr_accessor :output attr_accessor :unique_id attr_accessor :conflicts attr_accessor :requires attr_accessor :puppet_file attr_accessor :puppet_other_path + attr_accessor :local_calc_file # @param [Object] module_type: such as 'vulnerability', 'base', 'service', 'network' def initialize(module_type) @@ -25,6 +28,7 @@ class Module self.conflicts = [] self.requires = [] self.attributes = {} + self.output = "dynamic" # self.attributes['module_type'] = module_type # add as an attribute for filtering end @@ -33,7 +37,6 @@ class Module (<<-END) #{module_type}: #{module_path} attributes: #{attributes.inspect} - inputs: #{inputs.inspect} conflicts: #{conflicts.inspect} requires: #{requires.inspect} puppet file: #{puppet_file} @@ -46,7 +49,6 @@ class Module (<<-END) # #{module_type}: #{module_path} # attributes: #{attributes.inspect} - # inputs: #{inputs.inspect} # conflicts: #{conflicts.inspect} # requires: #{requires.inspect} END @@ -64,6 +66,13 @@ class Module module_path_name.gsub!('/','_') end + # pre-calculate any secgen_local/local.rb outputs + def local_processing + if self.local_calc_file + self.output = `#{self.local_calc_file}`.chomp + end + end + # @return [Object] a list of attributes that can be used to re-select the same modules def attributes_for_scenario_output attr_flattened = {} @@ -78,61 +87,61 @@ class Module attr_flattened end - # resolve randomisation of inputs - def select_inputs - inputs.each do |input| - # TODO TODO - Print.verbose "Input #{input["name"][0]}" - Print.verbose "Rand type: #{input["randomisation_type"][0]}" - case input["randomisation_type"][0] - when "one_from_list" - if input["value"].size == 0 - Print.err "Randomisation not possible for #{module_path} (one_from_list with no values)" - exit - end - one_value = [input["value"].shuffle![0]] - input["value"] = one_value - when "flag_value" - # if no value suppied, generate one - unless input["value"] - input["value"] = ["THE_FLAG_IS:#{SecureRandom.hex}"] - else - input["value"] = ["THE_FLAG_IS:#{input["value"][0]}"] - end - when "none" - # nothing... - - end - - # if an encoding is specified - if input["encoding"] - if input["encoding"].size > 1 - input["encoding"] = [input["encoding"].shuffle![0]] - else - enc = input["encoding"][0] - end - # - # TODO?? case enc - # when "base64_encode" - # require "base64" - # unless input["value"] - # input["value"] = [Base64.encode64(SecureRandom.hex)] - # else - # input["value"] = [Base64.encode64(input["value"][0])] - # end - # when "MD5_calc_hash" - # unless input["value"] - # input["value"] = [Digest::MD5.hexdigest(SecureRandom.hex)] - # else - # input["value"] = [Digest::MD5.hexdigest(input["value"][0])] - # end - # end - end - - end - - Print.err inputs.inspect - end + # # resolve randomisation of inputs + # def select_inputs + # inputs.each do |input| + # # TODO TODO + # Print.verbose "Input #{input["name"][0]}" + # Print.verbose "Rand type: #{input["randomisation_type"][0]}" + # case input["randomisation_type"][0] + # when "one_from_list" + # if input["value"].size == 0 + # Print.err "Randomisation not possible for #{module_path} (one_from_list with no values)" + # exit + # end + # one_value = [input["value"].shuffle![0]] + # input["value"] = one_value + # when "flag_value" + # # if no value suppied, generate one + # unless input["value"] + # input["value"] = ["THE_FLAG_IS:#{SecureRandom.hex}"] + # else + # input["value"] = ["THE_FLAG_IS:#{input["value"][0]}"] + # end + # when "none" + # # nothing... + # + # end + # + # # if an encoding is specified + # if input["encoding"] + # if input["encoding"].size > 1 + # input["encoding"] = [input["encoding"].shuffle![0]] + # else + # enc = input["encoding"][0] + # end + # # + # # TODO?? case enc + # # when "base64_encode" + # # require "base64" + # # unless input["value"] + # # input["value"] = [Base64.encode64(SecureRandom.hex)] + # # else + # # input["value"] = [Base64.encode64(input["value"][0])] + # # end + # # when "MD5_calc_hash" + # # unless input["value"] + # # input["value"] = [Digest::MD5.hexdigest(SecureRandom.hex)] + # # else + # # input["value"] = [Digest::MD5.hexdigest(input["value"][0])] + # # end + # # end + # end + # + # end + # + # Print.err inputs.inspect + # end # A one directional test for conflicts # Returns whether this module specifies it conflicts with the other_module. diff --git a/lib/objects/system.rb b/lib/objects/system.rb index 8ebd7bf54..9fafb0b4a 100644 --- a/lib/objects/system.rb +++ b/lib/objects/system.rb @@ -30,7 +30,7 @@ class System # for each module specified in the scenario module_selectors.each do |module_filter| - selected_modules += select_modules(module_filter.module_type, module_filter.attributes, available_modules, selected_modules) + selected_modules += select_modules(module_filter.module_type, module_filter.attributes, available_modules, selected_modules, module_filter.write_outputs_to, module_filter.unique_id) end selected_modules @@ -62,7 +62,7 @@ class System # returns a list containing a module (plus dependencies recursively) of the module type with the required attributes # modules are selected from the list of available modules and will be checked against previously selected modules for conflicts # raises an exception when unable to resolve and the retry limit has not been reached - def select_modules(module_type, required_attributes, available_modules, previously_selected_modules) + def select_modules(module_type, required_attributes, available_modules, previously_selected_modules, write_outputs_to, unique_id) # select based on selected type, access, cve... search_list = available_modules.clone @@ -92,7 +92,18 @@ class System Print.err 'Could not find a matching module. Please check the scenario specification' else # use from the top of the randomised list - selected = search_list[0] + selected = search_list[0].clone + + # propagate module relationships + selected.write_outputs_to = write_outputs_to + selected.unique_id = unique_id + + # pre-calculate any secgen_local/local.rb outputs + if selected.local_calc_file + Print.verbose "Module includes local calculation of output. Processing..." + selected.output = `#{selected.local_calc_file}`.chomp + Print.verbose "Output: #{selected.output}" + end # add any modules that the selected module requires dependencies = select_required_modules(selected, available_modules, previously_selected_modules + [selected]) @@ -148,7 +159,7 @@ class System Print.verbose "Dependency satisfied by previously selected module: #{existing.printable_name}" else Print.verbose 'Adding required modules...' - modules_to_add += select_modules('any', required, available_modules, modules_to_add + selected_modules) + modules_to_add += select_modules('any', required, available_modules, modules_to_add + selected_modules, '', '') end end modules_to_add diff --git a/lib/output/xml_report_generator.rb b/lib/output/xml_report_generator.rb index a33a42600..09e9e0089 100644 --- a/lib/output/xml_report_generator.rb +++ b/lib/output/xml_report_generator.rb @@ -40,6 +40,10 @@ class XMLReportGenerator xml.service(selected_module.attributes_for_scenario_output) when 'utility' xml.utility(selected_module.attributes_for_scenario_output) + when 'encoder' + xml.encoder(selected_module.attributes_for_scenario_output) + when 'generator' + xml.generator(selected_module.attributes_for_scenario_output) when 'network' xml.network(selected_module.attributes_for_scenario_output) else diff --git a/lib/readers/module_reader.rb b/lib/readers/module_reader.rb index ce03f9d75..d019179dc 100644 --- a/lib/readers/module_reader.rb +++ b/lib/readers/module_reader.rb @@ -30,6 +30,11 @@ class ModuleReader return read_modules('generator', GENERATORS_PATH, GENERATOR_SCHEMA_FILE, true) end + # reads in all utilities + def self.read_encoders + return read_modules('encoder', ENCODERS_PATH, ENCODER_SCHEMA_FILE, true) + end + # reads in all networks def self.read_networks return read_modules('network', NETWORKS_PATH, NETWORK_SCHEMA_FILE, false) @@ -87,6 +92,12 @@ class ModuleReader new_module.puppet_file = "#{ROOT_DIR}/#{module_path}/#{module_filename}.pp" new_module.puppet_other_path = "#{ROOT_DIR}/#{module_path}/manifests" + # save executable path of any pre-calculation for outputs + local = "#{module_path}#{MODULE_LOCAL_CALC_PATH}" + if File.file?(local) + new_module.local_calc_file = local + end + # check that the expected puppet files exist if require_puppet unless File.file?("#{new_module.puppet_file}") diff --git a/lib/readers/system_reader.rb b/lib/readers/system_reader.rb index 32b0e21b7..3a6c9d422 100644 --- a/lib/readers/system_reader.rb +++ b/lib/readers/system_reader.rb @@ -51,7 +51,7 @@ class SystemReader end # for each module selection - system_node.xpath('//vulnerability | //service | //utility | //network | //base | //generator').each do |module_node| + system_node.xpath('//vulnerability | //service | //utility | //network | //base | //encoder | //generator').each do |module_node| # create a selector module, which is a regular module instance used as a placeholder for matching requirements module_selector = Module.new(module_node.name) @@ -61,10 +61,7 @@ class SystemReader module_node.xpath('parent::input').each do |input| # Parent is input -- needs to send write value somewhere input.xpath('..').each do |input_parent| - # Print.verbose " -- Sends output to " + input_parent.path.gsub(/[^a-zA-Z0-9]/, '') - - #TODO propagate unique ids and writes to to selected modules - + module_selector.write_to_module_with_id = input_parent.path.gsub(/[^a-zA-Z0-9]/, '') module_selector.write_outputs_to = input_parent.path.gsub(/[^a-zA-Z0-9]/, '') + '_' + input.xpath('@into').to_s end end @@ -78,11 +75,25 @@ class SystemReader Print.verbose " - #{attr[0].to_s} ~= #{attr[1].to_s}" end end - if module_selector.write_outputs_to + + # insert into module list + # if this module feeds another... + if module_selector.write_outputs_to != nil Print.verbose " -- writes to: " + module_selector.write_outputs_to + # insert into module list before the module we are writing to + insert_pos = -1 # end of list + for i in 0..module_selectors.size-1 + if module_selector.write_to_module_with_id == module_selectors[i].unique_id + # found position of earlier module this one feeds into, so put this one first + insert_pos = i + end + end + module_selectors.insert(insert_pos, module_selector) + else + # otherwise just append module to end of list + module_selectors << module_selector end - module_selectors << module_selector end systems << System.new(system_name, system_attributes, module_selectors) end diff --git a/lib/schemas/scenario_schema.xsd b/lib/schemas/scenario_schema.xsd index 2b8000e33..f260b9e25 100644 --- a/lib/schemas/scenario_schema.xsd +++ b/lib/schemas/scenario_schema.xsd @@ -15,10 +15,11 @@ - - + + - + + @@ -36,8 +37,8 @@ - - + + @@ -97,7 +98,10 @@ - + + + + diff --git a/lib/templates/Vagrantfile.erb b/lib/templates/Vagrantfile.erb index a672df966..3f1450710 100644 --- a/lib/templates/Vagrantfile.erb +++ b/lib/templates/Vagrantfile.erb @@ -10,7 +10,6 @@ VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| <% @systems.each do |system| %> config.vm.define "<%= system.name %>" do |<%= system.name %>| - # REMOVE: < %= system.name %>.vm.synced_folder "< %= MOUNT_DIR %>", "/mount" config.vm.provider :virtualbox do |vb| vb.gui = true end @@ -28,21 +27,25 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| <% else -%> <%= system.name %>.vm.network :<%= selected_module.attributes['type'][0] %>, ip: "<%= selected_module.attributes['range'][0] %>" <% end -%> -<% when 'vulnerability', 'service', 'utility' -%> +<% when 'vulnerability', 'service', 'utility', 'encoder', 'generator' -%> <% module_name = selected_module.module_path_name -%> <%= system.name %>.vm.provision "puppet" do | <%=module_name%> | + <%=module_name%>.facter = { + "write_to" => "<%=selected_module.write_outputs_to%>", +<% if selected_module.write_outputs_to -%> + "<%=selected_module.write_outputs_to%>" => "<%=selected_module.output%>", +<% end -%> +<% if selected_module.attributes['read_fact'] != nil + selected_module.attributes['read_fact'].each do |fact| -%> + "<%=fact%>_location" => "<%=selected_module.unique_id + "_" + fact%>", +<% end + end -%> + } <%=module_name%>.module_path = "<%="puppet/#{system.name}/modules"%>" <%=module_name%>.environment_path = "<%="#{ENVIRONMENTS_PATH}"%>" <%=module_name%>.environment = "production" <%=module_name%>.manifests_path = "<%="#{ROOT_DIR}/#{selected_module.module_path}/"%>" <%=module_name%>.manifest_file = "<%="#{selected_module.module_path_end}.pp"%>" - - <%=module_name%>.facter = { -<% selected_module.inputs.each do |input| -%> - "<%="#{module_name}_#{input["name"][0]}"%>" => <%=input["value"].inspect%>, -<% end -%> - - } end <% end -%> <% end -%> diff --git a/modules/encoders/string/rot13/lib/facter/customfact.rb b/modules/encoders/string/rot13/lib/facter/customfact.rb new file mode 100644 index 000000000..358b038a9 --- /dev/null +++ b/modules/encoders/string/rot13/lib/facter/customfact.rb @@ -0,0 +1,16 @@ +Facter.add(:rot13_encoded_value) do + setcode do + # distid = Facter.value(:lsbdistid) + # case distid + # when /RedHatEnterprise|CentOS|Fedora/ + # 'redhat' + # when 'ubuntu' + # 'debian' + # else + # distid + # end + + # Facter::Core::Execution.exec('/bin/uname --hardware-platform') + "TEST".tr!("A-Za-z", "N-ZA-Mn-za-m") + end +end diff --git a/modules/encoders/string/rot13/rot13.pp b/modules/encoders/string/rot13/rot13.pp new file mode 100644 index 000000000..e69de29bb diff --git a/modules/encoders/string/rot13/secgen_metadata.xml b/modules/encoders/string/rot13/secgen_metadata.xml new file mode 100644 index 000000000..914331114 --- /dev/null +++ b/modules/encoders/string/rot13/secgen_metadata.xml @@ -0,0 +1,48 @@ + + + + ROT13 Encoder + Z. Cliffe Schreuders + MIT + Rotates each A-Z character by 13 places. Applying a second time reveals the cleartext. + + + string_encoder + linux + windows + + + + + + + + + + + + + + + + strings_to_encode + + rot13_options + + encoded_strings + + + + + + + + \ No newline at end of file diff --git a/modules/generators/hello_world/hello_world.pp b/modules/generators/hello_world/hello_world.pp new file mode 100644 index 000000000..e69de29bb diff --git a/modules/generators/hello_world/lib/facter/generated_strings.rb b/modules/generators/hello_world/lib/facter/generated_strings.rb new file mode 100644 index 000000000..a201da47a --- /dev/null +++ b/modules/generators/hello_world/lib/facter/generated_strings.rb @@ -0,0 +1,5 @@ +Facter.add(:generated_strings) do + setcode do + "Hello, world!" + end +end diff --git a/modules/generators/hello_world/secgen_local/local.rb b/modules/generators/hello_world/secgen_local/local.rb new file mode 100644 index 000000000..b67462f5f --- /dev/null +++ b/modules/generators/hello_world/secgen_local/local.rb @@ -0,0 +1,3 @@ +#!/usr/bin/ruby + +puts "Hello, world!" diff --git a/modules/generators/hello_world/secgen_metadata.xml b/modules/generators/hello_world/secgen_metadata.xml new file mode 100644 index 000000000..25a84614c --- /dev/null +++ b/modules/generators/hello_world/secgen_metadata.xml @@ -0,0 +1,19 @@ + + + + Hello, World! Generator + Z. Cliffe Schreuders + MIT + Generates a simple "Hello, world!" message. + + + string_generator + local_calculation + linux + windows + + generated_strings + + \ No newline at end of file diff --git a/modules/generators/random_base64/random_base64.pp b/modules/generators/random_base64/random_base64.pp new file mode 100644 index 000000000..e69de29bb diff --git a/modules/generators/random_base64/secgen_local/local.rb b/modules/generators/random_base64/secgen_local/local.rb new file mode 100644 index 000000000..6543e8609 --- /dev/null +++ b/modules/generators/random_base64/secgen_local/local.rb @@ -0,0 +1,3 @@ +#!/usr/bin/ruby +require 'securerandom' +puts SecureRandom.base64 diff --git a/modules/generators/random_base64/secgen_metadata.xml b/modules/generators/random_base64/secgen_metadata.xml new file mode 100644 index 000000000..a32ff38f1 --- /dev/null +++ b/modules/generators/random_base64/secgen_metadata.xml @@ -0,0 +1,20 @@ + + + + Random Base64 Generator + Z. Cliffe Schreuders + MIT + Uses Ruby's SecureRandom to generate a message made up of base64 digits (A-Z, a-z, 0-9, +, / and =). + + string_generator + local_calculation + linux + windows + + http://ruby-doc.org/stdlib-2.2.2/libdoc/securerandom/rdoc/SecureRandom.html#method-c-base64 + + generated_strings + + \ No newline at end of file diff --git a/modules/generators/random_hex/random_hex.pp b/modules/generators/random_hex/random_hex.pp new file mode 100644 index 000000000..e69de29bb diff --git a/modules/generators/random_hex/secgen_local/local.rb b/modules/generators/random_hex/secgen_local/local.rb new file mode 100644 index 000000000..bb0e850de --- /dev/null +++ b/modules/generators/random_hex/secgen_local/local.rb @@ -0,0 +1,3 @@ +#!/usr/bin/ruby +require 'securerandom' +puts SecureRandom.hex diff --git a/modules/generators/random_hex/secgen_metadata.xml b/modules/generators/random_hex/secgen_metadata.xml new file mode 100644 index 000000000..36fff87ef --- /dev/null +++ b/modules/generators/random_hex/secgen_metadata.xml @@ -0,0 +1,20 @@ + + + + Random Hex Generator + Z. Cliffe Schreuders + MIT + Uses Ruby's SecureRandom to generate a message made up of hex digits (a-f0-9). + + string_generator + local_calculation + linux + windows + + http://ruby-doc.org/stdlib-2.2.2/libdoc/securerandom/rdoc/SecureRandom.html#method-c-hex + + generated_strings + + \ No newline at end of file diff --git a/secgen.rb b/secgen.rb index 848652f81..f67349d93 100644 --- a/secgen.rb +++ b/secgen.rb @@ -58,13 +58,17 @@ def build_config(scenario, out_dir) all_available_generators = ModuleReader.read_generators Print.std "#{all_available_generators.size} generator modules loaded" + Print.info 'Reading available encoder modules...' + all_available_encoders = ModuleReader.read_encoders + Print.std "#{all_available_encoders.size} encoder modules loaded" + Print.info 'Reading available network modules...' all_available_networks = ModuleReader.read_networks Print.std "#{all_available_networks.size} network modules loaded" Print.info 'Resolving systems: randomising scenario...' # for each system, select modules - all_available_modules = all_available_bases + all_available_vulnerabilties + all_available_services + all_available_utilities + all_available_generators + all_available_networks + all_available_modules = all_available_bases + all_available_vulnerabilties + all_available_services + all_available_utilities + all_available_generators + all_available_encoders + all_available_networks # update systems with module selections systems.map! {|system| system.module_selections = system.resolve_module_selection(all_available_modules)