Output files -- admin passwords for vms, IP addresses, CyBOK per flag challenge and project, and simplified hints

This commit is contained in:
Z. Cliffe Schreuders
2023-04-04 11:27:52 +01:00
parent b19a20bc38
commit 79e78aa3c5
7 changed files with 103 additions and 18 deletions

View File

@@ -1,6 +1,7 @@
# datastore related global variables
$datastore = {}
$datastore_iterators = {} # keeps track of previous access to datastore elements datastorevariablename => prev_index_accessed
$cybok_coverage = [] # array of XML nodes
## FILE / DIR CONSTANTS ##
@@ -55,8 +56,12 @@ SAMP_DBS_DIR = "#{ROOT_DIR}/lib/resources/sample_databases"
LOCAL_PUPPET_DIR = "#{MODULES_DIR}build/puppet"
SECGEN_FUNCTIONS_PUPPET_DIR = "#{MODULES_DIR}build/puppet/secgen_functions"
# Filename for flags
# Filename for flags, etc
FLAGS_FILENAME = "flag_hints.xml"
CYBOK_FILENAME = "cybok.xml"
SPOILER_ADMIN_FILENAME = "spoiler_admin_pass"
IP_ADDRESSES_FILENAME = "IP_addresses.json"
## PACKER CONSTANTS ##

View File

@@ -22,6 +22,7 @@ class Module
attr_accessor :conflicts
attr_accessor :requires
attr_accessor :cybok_coverage # a list of xml doc nodes
attr_accessor :puppet_file
attr_accessor :puppet_other_path
attr_accessor :local_calc_file
@@ -34,6 +35,7 @@ class Module
self.module_type = module_type
self.conflicts = []
self.requires = []
self.cybok_coverage = []
self.attributes = {}
self.output = []
self.write_to_module_with_id = write_output_variable = ''
@@ -96,13 +98,17 @@ class Module
def attributes_for_scenario_output
attr_flattened = {}
attributes.each do |key, array|
unless "#{key}" == 'module_type' || "#{key}" == 'conflict' || "#{key}" == 'default_input' || "#{key}" == 'requires'
# creates a valid regexp that can match the original module
attr_flattened["#{key}"] = Regexp.escape(array.join('~~~')).gsub(/\n\w*/, '.*').gsub(/\\ /, ' ').gsub(/~~~/, '|')
end
end
# this alternative approach populates all the filters in the generated senario,
# but this means that changes to the metadata breaks the selection
# attributes.each do |key, array|
# unless "#{key}" == 'module_type' || "#{key}" == 'conflict' || "#{key}" == 'default_input' || "#{key}" == 'requires'
# # creates a valid regexp that can match the original module
# attr_flattened["#{key}"] = Regexp.escape(array.join('~~~')).gsub(/\n\w*/, '.*').gsub(/\\ /, ' ').gsub(/~~~/, '|')
# end
# end
# just specify modules by their path
attr_flattened["module_path"] = Regexp.escape(attributes["module_path"][0])
attr_flattened
end

View File

@@ -2,10 +2,12 @@ require 'erb'
require_relative '../helpers/constants.rb'
require_relative 'xml_scenario_generator.rb'
require_relative 'xml_marker_generator.rb'
require_relative 'xml_cybok_generator.rb'
require_relative 'ctfd_generator.rb'
require 'fileutils'
require 'librarian'
require 'zip/zip'
require 'json'
class ProjectFilesCreator
# Creates project directory, uses .erb files to create a report and the vagrant file that will be used
@@ -127,16 +129,25 @@ class ProjectFilesCreator
Print.std "Creating scenario definition file: #{xfile}"
write_data_to_file(xml, xfile)
write_data_to_file(@systems.to_s, "#{@out_dir}/systems")
write_data_to_file(@scenario.to_s, "#{@out_dir}/scenario")
# Create the marker xml file
x2file = "#{@out_dir}/#{FLAGS_FILENAME}"
xml_marker_generator = XmlMarkerGenerator.new(@systems, @scenario, @time)
xml = xml_marker_generator.output
Print.std "Creating flags and hints file: #{x2file}"
write_data_to_file(xml, x2file)
Print.std "Saving spoiler/admin records..."
# Create the CyBOK xml file
x3file = "#{@out_dir}/#{CYBOK_FILENAME}"
xml_cybok_generator = XmlCybokGenerator.new(@systems, @scenario, @time)
xml = xml_cybok_generator.output
Print.std "Creating flags and hints file: #{x3file}"
write_data_to_file(xml, x3file)
Print.std "Saving spoiler/admin records..."
jfile = "#{@out_dir}/datastores"
Print.std "Saving datastore records: #{jfile}"
json = JSON.generate($datastore)
@@ -145,14 +156,14 @@ class ProjectFilesCreator
if $datastore.has_key? "IP_addresses"
system_names = @systems.map { |system| system.name }
system_ips = Hash[system_names.zip($datastore["IP_addresses"])]
jfile = "#{@out_dir}/IP_addresses.json"
jfile = "#{@out_dir}/#{IP_ADDRESSES_FILENAME}"
Print.std "Saving IP addresses: #{jfile}"
json = JSON.generate(system_ips)
write_data_to_file(json, jfile)
end
if $datastore.has_key? "spoiler_admin_pass"
pfile = "#{@out_dir}/spoiler_admin_pass"
pfile = "#{@out_dir}/#{SPOILER_ADMIN_FILENAME}"
Print.std "Saving spoiler/admin passwords: #{pfile}"
pass_notes = $datastore["spoiler_admin_pass"].join("\n")
write_data_to_file(pass_notes, pfile)

View File

@@ -0,0 +1,30 @@
require 'nokogiri'
# Convert systems objects into xml
class XmlCybokGenerator
# @param [Object] systems the list of systems
# @param [Object] scenario the scenario file used to generate
# @param [Object] time the current time as a string
def initialize(systems, scenario, time)
@systems = systems
@scenario = scenario
@time = time
end
# outputs a XML CyBOK file that can be used to track CyBOK
# even for randomised challenges, where CyBOK is defined per module
# @return [Object] xml string
def output
# $cybok_coverage starts with the cybok from the scenario, and then we also
# add all the cybok from modules that are selected
@systems.each { |system|
system.module_selections.each { |selected_module|
$cybok_coverage.push *selected_module.cybok_coverage
}
}
coverage = "<cybokmapping>" + $cybok_coverage.map { |c| "\n " + c.to_xml.gsub(/\R/, "\n ").gsub(/\t/, ' ') }.uniq.join("\n") + "\n " + "</cybokmapping>"
doc = Nokogiri.XML(coverage)
doc.to_xml
end
end

View File

@@ -1,5 +1,5 @@
require 'nokogiri'
require 'irb'
# Convert systems objects into xml
class XmlMarkerGenerator
@@ -15,6 +15,7 @@ class XmlMarkerGenerator
# outputs a XML marker file that can be used to mark flags and provide hints
# @return [Object] xml string
def output
@processed_hints = []
ns = {
'xmlns' => "http://www.github/cliffe/SecGen/marker",
'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
@@ -37,10 +38,16 @@ class XmlMarkerGenerator
# flag has to be the only thing in the parameter string (not within some text)
if output_value.match(/\Aflag{.*\z/)
xml.challenge{
xml.flag(output_value)
system.module_selections.each { |search_module|
if search_module.unique_id == selected_module.write_to_module_with_id
# special case check for flag that's fed into a parameter that isn't defined within the receiving module
if search_module.attributes["read_fact"].include? selected_module.write_output_variable
xml.flag(output_value)
else
Print.warn "Ignoring flag generated but fed into a fact that the module doesn't read: #{selected_module.write_to_module_with_id}.#{selected_module.write_output_variable} #{output_value}"
Print.warn "This likely isn't an issue, especially if fed into strings_to_pre_leak which doesn't always exist"
end
module_hints(search_module, xml, system.module_selections)
end
}
@@ -69,6 +76,10 @@ class XmlMarkerGenerator
}
end
if search_module.cybok_coverage&.size > 0
add_cybok(search_module, xml)
end
case search_module.module_type
when "vulnerability"
case search_module.attributes['access'].first
@@ -130,10 +141,22 @@ class XmlMarkerGenerator
end
def add_hint(hint_text, hint_id, hint_type, xml)
xml.hint {
xml.hint_text(hint_text)
xml.hint_type(hint_type)
xml.hint_id(hint_id)
# due to the nested structure of components a specific hint may lead to
# multiple next steps -- but we just record each hint once to simplify things
# without this condition, the same hint will appear multiple times
unless @processed_hints.include? hint_id
@processed_hints << hint_id
xml.hint {
xml.hint_text(hint_text)
xml.hint_type(hint_type)
}
end
end
def add_cybok(search_module, xml)
xml.cybok_coverage {
# quick and dirty conversion of saved nodes back to tidy xml
xml << search_module.cybok_coverage.map { |c| "\n " + c.to_xml.gsub(/\R/, "\n ").gsub(/\t/, ' ') }.join("\n") + "\n "
}
end

View File

@@ -160,6 +160,11 @@ class ModuleReader < XMLReader
new_module.requires.push(require)
end
# for each CyBOK in the module -- we just store the xml node for later
doc.xpath("/#{module_type}/CyBOK").each do |cybok_doc|
new_module.cybok_coverage.push(cybok_doc.clone)
end
# for each default input
doc.xpath("/#{module_type}/default_input").each do |inputs_doc|
inputs_doc.xpath('descendant::vulnerability | descendant::service | descendant::utility | descendant::network | descendant::base | descendant::encoder | descendant::generator').each do |module_node|
@@ -219,4 +224,4 @@ class ModuleReader < XMLReader
return modules
end
end
end

View File

@@ -16,6 +16,11 @@ class SystemReader < XMLReader
# Parse and validate the schema
doc = parse_doc(scenario_file, SCENARIO_SCHEMA_FILE, 'scenario')
# for each CyBOK in the module
doc.xpath("/scenario/CyBOK").each do |cybok_doc|
$cybok_coverage.push(cybok_doc.clone)
end
doc.xpath('/scenario/system').each_with_index do |system_node, system_index|
module_selectors = []