mirror of
https://github.com/cliffe/SecGen.git
synced 2026-02-21 11:18:06 +00:00
443 lines
14 KiB
Ruby
443 lines
14 KiB
Ruby
# Convert systems objects into a format that can be imported into CTFd
|
|
class CTFdGenerator
|
|
|
|
POINTS_PER_FLAG = 100
|
|
FREE_POINTS = 200
|
|
|
|
# How much of the total reward is offset by the cost of all the hints for that flag
|
|
# Since CTFd doesn't force hints to be taken in order, we penalise bigger hints much more, to the point that
|
|
# they need to think before taking a hint as they can't afford to take all of them
|
|
PERCENTAGE_COST_FOR_ALL_HINTS = 0.8 # 80 / number of hints (normal nudge hints are cheap)
|
|
PERCENTAGE_COST_FOR_BIG_HINTS = 0.5 # 50% cost for a big hint (bigger hints are less so)
|
|
PERCENTAGE_COST_FOR_REALLY_BIG_HINTS = 0.7 # 50% cost for a really big hint (the name of the SecGen module)
|
|
PERCENTAGE_COST_FOR_SOLUTION_HINTS = 0.8 # 80% cost for a solution (msf exploit, etc)
|
|
|
|
|
|
# @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 hash of filenames with JSON contents
|
|
# @return [Object] hash of files
|
|
def ctfd_files
|
|
|
|
challenges = []
|
|
hints = []
|
|
flags = []
|
|
|
|
challenges << {
|
|
"id"=> 1,
|
|
"name"=>"Free points",
|
|
"description"=>"Some free points to get you started (and for purchasing hints)!\n Enter flag{FREEPOINTS}",
|
|
"max_attempts"=>0,
|
|
"value"=>FREE_POINTS,
|
|
"category"=>"Freebie",
|
|
"type"=>"standard",
|
|
"state"=>"visible",
|
|
"requirements"=>"null"}
|
|
flags << {
|
|
"id"=>1,
|
|
"challenge_id"=>1,
|
|
"type"=>"static",
|
|
"content"=>"flag{FREEPOINTS}",
|
|
"data"=>nil}
|
|
|
|
@systems.each { |system|
|
|
system.module_selections.each { |selected_module|
|
|
# start by finding a flag, and work the way back providing hints
|
|
selected_module.output.each { |output_value|
|
|
if output_value.match(/^flag{.*$/)
|
|
challenge_id = challenges.length + 1
|
|
challenges << {
|
|
"id"=> challenge_id,
|
|
"name"=>"Challenge ##{challenge_id}",
|
|
"description"=>"Remember, search for text in the format of flag{SOMETHING}, and submit it for points. If you are stuck a hint may help!",
|
|
"max_attempts"=>0,
|
|
"value"=>POINTS_PER_FLAG,
|
|
"category"=>"#{system.name} VM (#{system.module_selections.first.attributes['platform'].first})",
|
|
"type"=>"standard",
|
|
"state"=>"visible",
|
|
"requirements"=>"null"}
|
|
flag_id = flags.length + 1
|
|
flags << {
|
|
"id"=>flag_id,
|
|
"challenge_id"=>challenge_id,
|
|
"type"=>"static",
|
|
"content"=>output_value,
|
|
"data"=>nil}
|
|
|
|
collected_hints = []
|
|
system.module_selections.each { |search_module_for_hints|
|
|
if search_module_for_hints.unique_id == selected_module.write_to_module_with_id
|
|
collected_hints = get_module_hints(search_module_for_hints, collected_hints, system.module_selections)
|
|
end
|
|
}
|
|
|
|
collected_hints.each { |collected_hint|
|
|
hint_id = hints.length + 1
|
|
# weight hints for big_hint
|
|
if collected_hint["hint_type"] == "solution"
|
|
cost=(POINTS_PER_FLAG * PERCENTAGE_COST_FOR_SOLUTION_HINTS).round
|
|
elsif collected_hint["hint_type"] == "really_big_hint"
|
|
cost=(POINTS_PER_FLAG * PERCENTAGE_COST_FOR_REALLY_BIG_HINTS).round
|
|
elsif collected_hint["hint_type"] == "big_hint"
|
|
cost=(POINTS_PER_FLAG * PERCENTAGE_COST_FOR_BIG_HINTS).round
|
|
else
|
|
cost=(POINTS_PER_FLAG * PERCENTAGE_COST_FOR_ALL_HINTS / collected_hints.length).round
|
|
end
|
|
hints << {
|
|
"id"=> hint_id,
|
|
"type"=>"standard",
|
|
"challenge_id"=>challenge_id,
|
|
"content"=>collected_hint["hint_text"],
|
|
"cost"=>cost,
|
|
"requirements"=>nil
|
|
}
|
|
}
|
|
end
|
|
}
|
|
}
|
|
}
|
|
|
|
output_hash = {
|
|
"alembic_version.json" => alembic_version_json(),
|
|
"awards.json" => "",
|
|
"challenges.json" => challenges_json(challenges),
|
|
"config.json" => config_json(),
|
|
"dynamic_challenge.json" => "",
|
|
"files.json" => files_json(),
|
|
"flags.json" => flags_json(flags),
|
|
"hints.json" => hints_json(hints),
|
|
"notifications.json" => "",
|
|
"pages.json" => pages_json(),
|
|
"solves.json" => "",
|
|
"submissions.json" => "",
|
|
"tags.json" => "",
|
|
"teams.json" => "",
|
|
"tracking.json" => "",
|
|
"unlocks.json" => "",
|
|
"users.json" => users_json(),
|
|
}
|
|
|
|
output_hash
|
|
|
|
end
|
|
|
|
def alembic_version_json
|
|
alembic_version_json_hash = {
|
|
"count" => 1,
|
|
"results" => [
|
|
{
|
|
"version_num"=>"8369118943a1"
|
|
}
|
|
],
|
|
"meta"=>{}
|
|
}
|
|
|
|
alembic_version_json_hash.to_json
|
|
end
|
|
|
|
def files_json
|
|
return ''
|
|
end
|
|
|
|
def challenges_json(challenges)
|
|
{"count"=>challenges.length,
|
|
"results"=>challenges,
|
|
"meta"=>{}
|
|
}.to_json
|
|
end
|
|
|
|
def config_json
|
|
config_json_hash = {
|
|
"count" => 23,
|
|
"results" => [
|
|
{
|
|
"id"=>1,
|
|
"key"=>"ctf_version",
|
|
"value"=>"2.0.2"
|
|
},
|
|
{
|
|
"id"=>2,
|
|
"key"=>"ctf_theme",
|
|
"value"=>"core"
|
|
},
|
|
{
|
|
"id"=>3,
|
|
"key"=>"ctf_name",
|
|
"value"=>"SecGenCTF"
|
|
},
|
|
{
|
|
"id"=>4,
|
|
"key"=>"user_mode",
|
|
"value"=>"users"
|
|
},
|
|
{
|
|
"id"=>5,
|
|
"key"=>"challenge_visibility",
|
|
"value"=>"private"
|
|
},
|
|
{
|
|
"id"=>6,
|
|
"key"=>"score_visibility",
|
|
"value"=>"public"
|
|
},
|
|
{
|
|
"id"=>7,
|
|
"key"=>"account_visibility",
|
|
"value"=>"public"
|
|
},
|
|
{
|
|
"id"=>8,
|
|
"key"=>"registration_visibility",
|
|
"value"=>"public"
|
|
},
|
|
{
|
|
"id"=>9,
|
|
"key"=>"start",
|
|
"value"=>nil
|
|
},
|
|
{
|
|
"id"=>10,
|
|
"key"=>"end",
|
|
"value"=>nil
|
|
},
|
|
{
|
|
"id"=>11,
|
|
"key"=>"freeze",
|
|
"value"=>nil
|
|
},
|
|
{
|
|
"id"=>12,
|
|
"key"=>"verify_emails",
|
|
"value"=>nil
|
|
},
|
|
{
|
|
"id"=>13,
|
|
"key"=>"mail_server",
|
|
"value"=>nil
|
|
},
|
|
{
|
|
"id"=>14,
|
|
"key"=>"mail_port",
|
|
"value"=>nil
|
|
},
|
|
{
|
|
"id"=>15,
|
|
"key"=>"mail_tls",
|
|
"value"=>nil
|
|
},
|
|
{
|
|
"id"=>16,
|
|
"key"=>"mail_ssl",
|
|
"value"=>nil
|
|
},
|
|
{
|
|
"id"=>17,
|
|
"key"=>"mail_username",
|
|
"value"=>nil
|
|
},
|
|
{
|
|
"id"=>18,
|
|
"key"=>"mail_password",
|
|
"value"=>nil
|
|
},
|
|
{
|
|
"id"=>19,
|
|
"key"=>"mail_useauth",
|
|
"value"=>nil
|
|
},
|
|
{
|
|
"id"=>20,
|
|
"key"=>"setup",
|
|
"value"=>"true"
|
|
},
|
|
{
|
|
"id"=>21,
|
|
"key"=>"paused",
|
|
"value"=>"false"
|
|
},
|
|
{
|
|
"id"=>22,
|
|
"key"=>"css",
|
|
"value"=>File.read(ROOT_DIR + '/lib/templates/CTFd/css.css')
|
|
},
|
|
{
|
|
"id"=>23,
|
|
"key"=>"ctf_logo",
|
|
"value"=>nil #"fca9b07e1f3699e07870b86061815b1c/logo.svg"
|
|
}
|
|
],
|
|
"meta"=>{}
|
|
}
|
|
|
|
config_json_hash.to_json
|
|
end
|
|
|
|
def hints_json(hints)
|
|
{"count"=>hints.length,
|
|
"results"=>hints,
|
|
"meta"=>{}
|
|
}.to_json
|
|
end
|
|
|
|
def flags_json(flags)
|
|
{"count"=>flags.length,
|
|
"results"=>flags,
|
|
"meta"=>{}
|
|
}.to_json
|
|
end
|
|
|
|
def pages_json
|
|
pages_json_hash = {
|
|
"count" => 2,
|
|
"results" => [
|
|
{
|
|
"id"=>1,
|
|
"title"=>"Welcome",
|
|
"route"=>"index",
|
|
"content"=>File.read(ROOT_DIR + '/lib/templates/CTFd/index.html'),
|
|
"draft"=>false,
|
|
"hidden"=>false,
|
|
"auth_required"=>false
|
|
},
|
|
{
|
|
"id"=>2,
|
|
"title"=>"Flag Submission",
|
|
"route"=>"submit",
|
|
"content"=>File.read(ROOT_DIR + '/lib/templates/CTFd/submit.html'),
|
|
"draft"=>false,
|
|
"hidden"=>false,
|
|
"auth_required"=>true
|
|
}
|
|
],
|
|
"meta"=>{}
|
|
}
|
|
|
|
pages_json_hash.to_json
|
|
end
|
|
|
|
def users_json
|
|
# Default admin username: adminusername
|
|
# Default admin password: adminpassword
|
|
# To use an alternate password, utilize the lib/output/sha256_password.py script.
|
|
# This ensures compatibility with CTFd v2.0.2+
|
|
|
|
users_json_hash = {
|
|
"count" => 1,
|
|
"results" => [
|
|
{
|
|
"id"=>1,
|
|
"oauth_id"=>nil,
|
|
"name"=>"adminusername",
|
|
"password"=>"$bcrypt-sha256$2b,12$Fh9KaueZuSEK5YzSdTbcI.$cbJCW5wGDNBX0/C/xDvMhnv8X3vqI92",
|
|
"email"=>"admin@email.com",
|
|
"type"=>"admin",
|
|
"secret"=>nil,
|
|
"website"=>nil,
|
|
"affiliation"=>nil,
|
|
"country"=>nil,
|
|
"bracket"=>nil,
|
|
"hidden"=>true,
|
|
"banned"=>false,
|
|
"verified"=>true,
|
|
"team_id"=>nil,
|
|
"created"=>"2019-02-01T20:13:03.80374"
|
|
},
|
|
],
|
|
"meta"=>{}
|
|
}
|
|
|
|
users_json_hash.to_json
|
|
|
|
end
|
|
|
|
def get_module_hints(search_module_for_hints, collected_hints, all_module_selections)
|
|
|
|
if search_module_for_hints.write_to_module_with_id != ""
|
|
# recursion -- show hints for any parent modules
|
|
all_module_selections.each { |search_module_for_hints_recursive|
|
|
if search_module_for_hints_recursive.unique_id == search_module_for_hints.write_to_module_with_id
|
|
get_module_hints(search_module_for_hints_recursive, collected_hints, all_module_selections)
|
|
end
|
|
}
|
|
end
|
|
|
|
case search_module_for_hints.module_type
|
|
when "vulnerability"
|
|
case search_module_for_hints.attributes['access'].first
|
|
when "remote"
|
|
collected_hints = collect_hint("A vulnerability that can be accessed/exploited remotely. Perhaps try scanning the system/network?", "#{search_module_for_hints.unique_id}remote", "normal", collected_hints)
|
|
when "local"
|
|
collected_hints = collect_hint("A vulnerability that can only be accessed/exploited with local access. You need to first find a way in...", "#{search_module_for_hints.unique_id}local", "normal", collected_hints)
|
|
end
|
|
type = search_module_for_hints.attributes['type'].first
|
|
unless type == 'system' or type == 'misc' or type == 'ctf' or type == 'local' or type == 'ctf_challenge'
|
|
collected_hints = collect_hint("The system is vulnerable in terms of its #{search_module_for_hints.attributes['type'].first}", "#{search_module_for_hints.unique_id}firsttype", "big_hint", collected_hints)
|
|
end
|
|
collected_hints = collect_hint("The system is vulnerable to #{search_module_for_hints.attributes['name'].first}", "#{search_module_for_hints.unique_id}name", "really_big_hint", collected_hints)
|
|
if search_module_for_hints.attributes['hint']
|
|
search_module_for_hints.attributes['hint'].each_with_index { |hint, i|
|
|
collected_hints = collect_hint(clean_hint(hint), "#{search_module_for_hints.unique_id}hint#{i}", "big_hint", collected_hints) # .gsub(/\s+/, ' ')
|
|
}
|
|
end
|
|
if search_module_for_hints.attributes['solution']
|
|
solution = search_module_for_hints.attributes['solution'].first
|
|
collected_hints = collect_hint(clean_hint(solution), "#{search_module_for_hints.unique_id}solution", "solution", collected_hints)
|
|
end
|
|
if search_module_for_hints.attributes['msf_module']
|
|
collected_hints = collect_hint("Can be exploited using the Metasploit module: #{search_module_for_hints.attributes['msf_module'].first}", "#{search_module_for_hints.unique_id}msf_module", "big_hint", collected_hints)
|
|
end
|
|
|
|
when "service"
|
|
collected_hints = collect_hint("The flag is hosted using #{search_module_for_hints.attributes['type'].first}", "#{search_module_for_hints.unique_id}type", "normal", collected_hints)
|
|
when "encoder"
|
|
collected_hints = collect_hint("The flag is encoded/hidden somewhere", "#{search_module_for_hints.unique_id}itsanencoder", "normal", collected_hints)
|
|
if search_module_for_hints.attributes['type'].include? 'string_encoder'
|
|
collected_hints = collect_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_for_hints.unique_id}stringencoder", "normal", collected_hints)
|
|
end
|
|
if search_module_for_hints.attributes['solution'] == nil
|
|
collected_hints = collect_hint("The flag is encoded using a #{search_module_for_hints.attributes['name'].first}", "#{search_module_for_hints.unique_id}name", "really_big_hint", collected_hints)
|
|
end
|
|
if search_module_for_hints.attributes['hint']
|
|
search_module_for_hints.attributes['hint'].each_with_index { |hint, i|
|
|
collected_hints = collect_hint(clean_hint(hint), "#{search_module_for_hints.unique_id}hint#{i}", "big_hint", collected_hints)
|
|
}
|
|
end
|
|
if search_module_for_hints.attributes['solution']
|
|
solution = search_module_for_hints.attributes['solution'].first
|
|
collected_hints = collect_hint(clean_hint(solution), "#{search_module_for_hints.unique_id}solution", "solution", collected_hints)
|
|
end
|
|
when "generator"
|
|
if search_module_for_hints.attributes['hint']
|
|
search_module_for_hints.attributes['hint'].each_with_index { |hint, i|
|
|
collected_hints = collect_hint(clean_hint(hint), "#{search_module_for_hints.unique_id}hint#{i}", "big_hint", collected_hints)
|
|
}
|
|
end
|
|
if search_module_for_hints.attributes['solution']
|
|
solution = search_module_for_hints.attributes['solution'].first
|
|
collected_hints = collect_hint(clean_hint(solution), "#{search_module_for_hints.unique_id}solution", "solution", collected_hints)
|
|
end
|
|
end
|
|
|
|
collected_hints
|
|
end
|
|
end
|
|
|
|
def collect_hint(hint_text, hint_id, hint_type, collected_hints)
|
|
collected_hints << {
|
|
"hint_text"=>hint_text,
|
|
"hint_type"=>hint_type,
|
|
"hint_id"=>hint_id
|
|
}
|
|
end
|
|
|
|
def clean_hint str
|
|
str.tr("\n",'').gsub(/\s+/, ' ')
|
|
end
|