Files
SecGen/lib/output/xml_marker_generator.rb
2025-02-24 01:42:32 +00:00

166 lines
7.4 KiB
Ruby

require 'nokogiri'
require 'irb'
# Convert systems objects into xml
class XmlMarkerGenerator
# @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 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",
'xsi:schemaLocation' => "http://www.github/cliffe/SecGen/marker"
}
builder = Nokogiri::XML::Builder.new do |xml|
xml.scenario (ns) {
xml.comment 'This file was generated by SecGen'
xml.comment "#{@time}"
xml.comment "Based on a fulfilment of scenario: #{@scenario}"
@systems.each { |system|
xml.system {
xml.system_name system.name
xml.platform system.module_selections.first.attributes['platform'].first
system.module_selections.each { |selected_module|
# start by finding a flag, and work the way back providing hints
selected_module.output.each { |output_value|
# flag has to be the only thing in the parameter string (not within some text)
if output_value.match(/\Aflag{.*\z/)
xml.challenge{
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
}
# add_hint("Remember, search for text in the format of flag{SOMETHING}, and submit it for points", "flaggyflag", "normal", xml)
}
end
}
}
}
}
}
end
builder.to_xml
end
def module_hints(search_module, xml, all_module_selections)
if search_module.write_to_module_with_id != ""
# recursion -- show hints for any parent modules
all_module_selections.each { |search_module_recursive|
if search_module_recursive.unique_id == search_module.write_to_module_with_id
module_hints(search_module_recursive, xml, all_module_selections)
end
}
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
when "remote"
add_hint("A vulnerability that can be accessed/exploited remotely. Perhaps try scanning the system/network?", "#{search_module.unique_id}remote", "normal", xml)
when "local"
add_hint("A vulnerability that can only be accessed/exploited with local access. You need to first find a way in...", "#{search_module.unique_id}local", "normal", xml)
end
type = search_module.attributes['type'].first
unless type == 'system' or type == 'misc' or type == 'ctf' or type == 'local' or type == 'ctf_challenge'
add_hint("The system is vulnerable in terms of its #{search_module.attributes['type'].first}", "#{search_module.unique_id}firsttype", "big_hint", xml)
end
add_hint("The system is vulnerable to #{search_module.attributes['name'].first}", "#{search_module.unique_id}name", "big_hint", xml)
if search_module.attributes['hint']
search_module.attributes['hint'].each_with_index { |hint, i|
add_hint(clean_hint(hint), "#{search_module.unique_id}hint#{i}", "big_hint", xml) # .gsub(/\s+/, ' ')
}
end
if search_module.attributes['solution']
solution = search_module.attributes['solution'].first
add_hint(clean_hint(solution), "#{search_module.unique_id}solution", "big_hint", xml)
end
if search_module.attributes['msf_module']
add_hint("Can be exploited using the Metasploit module: #{search_module.attributes['msf_module'].first}", "#{search_module.unique_id}msf_module", "big_hint", xml)
end
when "service"
add_hint("The flag is hosted using #{search_module.attributes['type'].first}", "#{search_module.unique_id}type", "normal", xml)
when "encoder"
add_hint("The flag is encoded/hidden somewhere", "#{search_module.unique_id}itsanencoder", "normal", xml)
if search_module.attributes['type'].include? 'string_encoder'
add_hint("There is a layer of encoding using a standard encoding method, look for an unusual string of text and try to figure out how it was encoded, and decode it", "#{search_module.unique_id}stringencoder", "normal", xml)
end
if search_module.attributes['solution'] == nil
add_hint("The flag is encoded using a #{search_module.attributes['name'].first}", "#{search_module.unique_id}name", "big_hint", xml)
end
if search_module.attributes['hint']
search_module.attributes['hint'].each_with_index { |hint, i|
add_hint(clean_hint(hint), "#{search_module.unique_id}hint#{i}", "big_hint", xml)
}
end
if search_module.attributes['solution']
solution = search_module.attributes['solution'].first
add_hint(clean_hint(solution), "#{search_module.unique_id}solution", "big_hint", xml)
end
when "generator"
if search_module.attributes['hint']
search_module.attributes['hint'].each_with_index { |hint, i|
add_hint(clean_hint(hint), "#{search_module.unique_id}hint#{i}", "big_hint", xml)
}
end
if search_module.attributes['solution']
solution = search_module.attributes['solution'].first
add_hint(clean_hint(solution), "#{search_module.unique_id}solution", "big_hint", xml)
end
end
end
end
def add_hint(hint_text, hint_id, hint_type, xml)
# 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
def clean_hint str
str.tr("\n",'').gsub(/\s+/, ' ')
end