mirror of
https://github.com/cliffe/SecGen.git
synced 2026-02-21 11:18:06 +00:00
Merge branch 'ctfd_output'
This commit is contained in:
3
Gemfile
3
Gemfile
@@ -17,6 +17,7 @@ gem 'credy'
|
||||
gem 'pg'
|
||||
gem 'cinch'
|
||||
gem 'nori'
|
||||
gem 'bcrypt'
|
||||
gem 'programr', :git => "http://github.com/robertjwhitney/programr.git"
|
||||
gem 'process_helper'
|
||||
gem 'ovirt-engine-sdk'
|
||||
@@ -27,4 +28,4 @@ group :test, :development do
|
||||
gem 'rake'
|
||||
gem 'rdoc'
|
||||
gem 'yard'
|
||||
end
|
||||
end
|
||||
|
||||
477
lib/output/ctfd_generator.rb
Normal file
477
lib/output/ctfd_generator.rb
Normal file
@@ -0,0 +1,477 @@
|
||||
require 'bcrypt'
|
||||
|
||||
# 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 = []
|
||||
keys = []
|
||||
|
||||
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",
|
||||
"hidden"=>0}
|
||||
keys << {
|
||||
"id"=>1,
|
||||
"chal"=>1,
|
||||
"type"=>"static",
|
||||
"flag"=>"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"=>"",
|
||||
"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",
|
||||
"hidden"=>0}
|
||||
key_id = keys.length + 1
|
||||
keys << {
|
||||
"id"=>key_id,
|
||||
"chal"=>challenge_id,
|
||||
"type"=>"static",
|
||||
"flag"=>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"=>0,
|
||||
"chal"=>challenge_id,
|
||||
"hint"=>collected_hint["hint_text"],
|
||||
"cost"=>cost
|
||||
}
|
||||
}
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output_hash = {
|
||||
"alembic_version.json" => "",
|
||||
"awards.json" => "",
|
||||
"challenges.json" => challenges_json(challenges),
|
||||
"config.json" => config_json(),
|
||||
"files.json" => files_json(),
|
||||
"hints.json" => hints_json(hints),
|
||||
"keys.json" => keys_json(keys),
|
||||
"pages.json" => pages_json(),
|
||||
"solves.json" => "",
|
||||
"tags.json" => "",
|
||||
"teams.json" => teams_json(),
|
||||
"tracking.json" => "",
|
||||
"unlocks.json" => "",
|
||||
"wrong_keys.json" => "",
|
||||
}
|
||||
|
||||
output_hash
|
||||
|
||||
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" => 31,
|
||||
"results" => [
|
||||
{
|
||||
"id"=>1,
|
||||
"key"=>"next_update_check",
|
||||
"value"=>"1529096764"
|
||||
},
|
||||
{
|
||||
"id"=>2,
|
||||
"key"=>"ctf_version",
|
||||
"value"=>"1.2.0"
|
||||
},
|
||||
{
|
||||
"id"=>3,
|
||||
"key"=>"ctf_theme",
|
||||
"value"=>"core"
|
||||
},
|
||||
{
|
||||
"id"=>4,
|
||||
"key"=>"ctf_name",
|
||||
"value"=>"SecGenCTF"
|
||||
},
|
||||
{
|
||||
"id"=>5,
|
||||
"key"=>"ctf_logo",
|
||||
"value"=>nil #"fca9b07e1f3699e07870b86061815b1c/logo.svg"
|
||||
},
|
||||
{
|
||||
"id"=>6,
|
||||
"key"=>"workshop_mode",
|
||||
"value"=>"0"
|
||||
},
|
||||
{
|
||||
"id"=>7,
|
||||
"key"=>"hide_scores",
|
||||
"value"=>"0"
|
||||
},
|
||||
{
|
||||
"id"=>8,
|
||||
"key"=>"prevent_registration",
|
||||
"value"=>"0"
|
||||
},
|
||||
{
|
||||
"id"=>9,
|
||||
"key"=>"start",
|
||||
"value"=>nil
|
||||
},
|
||||
{
|
||||
"id"=>10,
|
||||
"key"=>"max_tries",
|
||||
"value"=>"0"
|
||||
},
|
||||
{
|
||||
"id"=>11,
|
||||
"key"=>"end",
|
||||
"value"=>nil
|
||||
},
|
||||
{
|
||||
"id"=>12,
|
||||
"key"=>"freeze",
|
||||
"value"=>nil
|
||||
},
|
||||
{
|
||||
"id"=>13,
|
||||
"key"=>"view_challenges_unregistered",
|
||||
"value"=>"0"
|
||||
},
|
||||
{
|
||||
"id"=>14,
|
||||
"key"=>"verify_emails",
|
||||
"value"=>"0"
|
||||
},
|
||||
{
|
||||
"id"=>15,
|
||||
"key"=>"mail_server",
|
||||
"value"=>nil
|
||||
},
|
||||
{
|
||||
"id"=>16,
|
||||
"key"=>"mail_port",
|
||||
"value"=>nil
|
||||
},
|
||||
{
|
||||
"id"=>17,
|
||||
"key"=>"mail_tls",
|
||||
"value"=>"0"
|
||||
},
|
||||
{
|
||||
"id"=>18,
|
||||
"key"=>"mail_ssl",
|
||||
"value"=>"0"
|
||||
},
|
||||
{
|
||||
"id"=>19,
|
||||
"key"=>"mail_username",
|
||||
"value"=>nil
|
||||
},
|
||||
{
|
||||
"id"=>20,
|
||||
"key"=>"mail_password",
|
||||
"value"=>nil
|
||||
},
|
||||
{
|
||||
"id"=>21,
|
||||
"key"=>"mail_useauth",
|
||||
"value"=>"0"
|
||||
},
|
||||
{
|
||||
"id"=>22,
|
||||
"key"=>"setup",
|
||||
"value"=>"1"
|
||||
},
|
||||
{
|
||||
"id"=>23,
|
||||
"key"=>"css",
|
||||
"value"=>File.read(ROOT_DIR + '/lib/templates/CTFd/css.css')
|
||||
},
|
||||
{
|
||||
"id"=>24,
|
||||
"key"=>"view_scoreboard_if_authed",
|
||||
"value"=>"0"
|
||||
},
|
||||
{
|
||||
"id"=>25,
|
||||
"key"=>"prevent_name_change",
|
||||
"value"=>"1"
|
||||
},
|
||||
{
|
||||
"id"=>26,
|
||||
"key"=>"version_latest",
|
||||
"value"=>nil
|
||||
},
|
||||
{
|
||||
"id"=>27,
|
||||
"key"=>"mailfrom_addr",
|
||||
"value"=>nil
|
||||
},
|
||||
{
|
||||
"id"=>28,
|
||||
"key"=>"mg_api_key",
|
||||
"value"=>nil
|
||||
},
|
||||
{
|
||||
"id"=>29,
|
||||
"key"=>"mg_base_url",
|
||||
"value"=>nil
|
||||
},
|
||||
{
|
||||
"id"=>30,
|
||||
"key"=>"view_after_ctf",
|
||||
"value"=>"1"
|
||||
},
|
||||
{
|
||||
"id"=>31,
|
||||
"key"=>"paused",
|
||||
"value"=>"0"
|
||||
}
|
||||
],
|
||||
"meta"=>{}
|
||||
}
|
||||
|
||||
config_json_hash.to_json
|
||||
end
|
||||
|
||||
def hints_json(hints)
|
||||
{"count"=>hints.length,
|
||||
"results"=>hints,
|
||||
"meta"=>{}
|
||||
}.to_json
|
||||
end
|
||||
|
||||
def keys_json(keys)
|
||||
{"count"=>keys.length,
|
||||
"results"=>keys,
|
||||
"meta"=>{}
|
||||
}.to_json
|
||||
end
|
||||
|
||||
def pages_json
|
||||
pages_json_hash = {
|
||||
"count" => 2,
|
||||
"results" => [
|
||||
{
|
||||
"id"=>1,
|
||||
"route"=>"index",
|
||||
"html"=>File.read(ROOT_DIR + '/lib/templates/CTFd/index.html'),
|
||||
"auth_required"=>0,
|
||||
"draft"=>0,
|
||||
"title"=>"Welcome"
|
||||
},
|
||||
{
|
||||
"id"=>2,
|
||||
"route"=>"submit",
|
||||
"html"=>File.read(ROOT_DIR + '/lib/templates/CTFd/submit.html'),
|
||||
"auth_required"=>0,
|
||||
"draft"=>0,
|
||||
"title"=>"Flag submission"
|
||||
}
|
||||
],
|
||||
"meta"=>{}
|
||||
}
|
||||
|
||||
pages_json_hash.to_json
|
||||
end
|
||||
|
||||
def teams_json
|
||||
|
||||
teams_json_hash = {
|
||||
"count" => 2,
|
||||
"results" => [
|
||||
{
|
||||
"id"=>1,
|
||||
"name"=>"adminusername",
|
||||
"email"=>"admin@email.com",
|
||||
"password"=>password_hash_string("adminpassword"),
|
||||
"website"=>nil,
|
||||
"affiliation"=>nil,
|
||||
"country"=>nil,
|
||||
"bracket"=>nil,
|
||||
"banned"=>0,
|
||||
"verified"=>1,
|
||||
"admin"=>1,
|
||||
"joined"=>"2018-06-22T10:46:26"
|
||||
},
|
||||
{
|
||||
"id"=>2,
|
||||
"name"=>"Me",
|
||||
"email"=>"email@email.com",
|
||||
"password"=>password_hash_string("mypassword"),
|
||||
"website"=>nil,
|
||||
"affiliation"=>nil,
|
||||
"country"=>nil,
|
||||
"bracket"=>nil,
|
||||
"banned"=>0,
|
||||
"verified"=>1,
|
||||
"admin"=>1,
|
||||
"joined"=>"2018-06-22T10:46:26"
|
||||
}
|
||||
],
|
||||
"meta"=>{}
|
||||
}
|
||||
|
||||
teams_json_hash.to_json
|
||||
|
||||
end
|
||||
|
||||
# fix difference between ruby and python bcrypt formats used by libraries
|
||||
# $bcrypt-sha256$variant,rounds$salt$checksum
|
||||
# python lib used by CTFd expects , between variant and rounds, the ruby lib puts a $ there...
|
||||
def password_hash_string(pass)
|
||||
hash_string = "$bcrypt-sha256" + BCrypt::Password.create(pass)
|
||||
hash_string[17]= ","
|
||||
hash_string
|
||||
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
|
||||
@@ -2,8 +2,10 @@ require 'erb'
|
||||
require_relative '../helpers/constants.rb'
|
||||
require_relative 'xml_scenario_generator.rb'
|
||||
require_relative 'xml_marker_generator.rb'
|
||||
require_relative 'ctfd_generator.rb'
|
||||
require 'fileutils'
|
||||
require 'librarian'
|
||||
require 'zip/zip'
|
||||
|
||||
class ProjectFilesCreator
|
||||
# Creates project directory, uses .erb files to create a report and the vagrant file that will be used
|
||||
@@ -83,7 +85,7 @@ class ProjectFilesCreator
|
||||
|
||||
if File.file? packerfile_path
|
||||
Print.info "Would you like to use the packerfile to create the packerfile from the given url (y/n)"
|
||||
# TODO: remove user interaction, this should be an config option
|
||||
# TODO: remove user interaction, this should be set via a config option
|
||||
(Print.info "Exiting as vagrant needs the basebox to continue"; abort) unless ['y','yes'].include?(STDIN.gets.chomp.downcase)
|
||||
|
||||
Print.std "Packerfile #{packerfile_path.split('/').last} found, building basebox #{url.split('/').last} via packer"
|
||||
@@ -140,6 +142,37 @@ class ProjectFilesCreator
|
||||
Print.err "Error writing file: #{e.message}"
|
||||
abort
|
||||
end
|
||||
|
||||
# Create the CTFd zip file for import
|
||||
ctfdfile = "#{@out_dir}/CTFd_importable.zip"
|
||||
Print.std "Creating CTFd configuration: #{ctfdfile}"
|
||||
|
||||
ctfd_generator = CTFdGenerator.new(@systems, @scenario, @time)
|
||||
ctfd_files = ctfd_generator.ctfd_files
|
||||
|
||||
# zip up the CTFd export
|
||||
begin
|
||||
Zip::ZipFile.open(ctfdfile, Zip::ZipFile::CREATE) { |zipfile|
|
||||
zipfile.mkdir("db")
|
||||
ctfd_files.each do |ctfd_file_name, ctfd_file_content|
|
||||
zipfile.get_output_stream("db/#{ctfd_file_name}") { |f|
|
||||
f.print ctfd_file_content
|
||||
}
|
||||
end
|
||||
zipfile.mkdir("uploads")
|
||||
# TODO: could add a logo image
|
||||
# zipfile.mkdir("uploads/uploads") # empty as in examples
|
||||
# zipfile.mkdir("uploads/fca9b07e1f3699e07870b86061815b1c")
|
||||
# zipfile.get_output_stream("uploads/fca9b07e1f3699e07870b86061815b1c/logo.svg") { |f|
|
||||
# f.print File.readlines(ROOT_DIR + '/lib/resources/images/svg_icons/flag.svg')
|
||||
# }
|
||||
}
|
||||
rescue StandardError => e
|
||||
Print.err "Error writing zip file: #{e.message}"
|
||||
abort
|
||||
end
|
||||
|
||||
|
||||
Print.std "VM(s) can be built using 'vagrant up' in #{@out_dir}"
|
||||
|
||||
end
|
||||
|
||||
61
lib/resources/images/svg_icons/flag.svg
Normal file
61
lib/resources/images/svg_icons/flag.svg
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="2400"
|
||||
height="2304"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="Racing_Flag_Black.svg">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="1021"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.20486111"
|
||||
inkscape:cx="2539.4912"
|
||||
inkscape:cy="1151.8449"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="424"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="imagebot_1" />
|
||||
<!-- Created with ImageBot - http://www.imagebot.com/ -->
|
||||
<g
|
||||
id="imagebot_1">
|
||||
<title
|
||||
id="title5">Layer 1</title>
|
||||
<path
|
||||
style="fill:#000000"
|
||||
d="M 336.01871,1345.9118 C 151.58734,818.96333 0.49867228,386.82831 0.26612228,385.61179 c -0.39016,-2.04097 3.69719002,-4.04295 52.90552972,-25.91308 29.33059,-13.03568 53.815188,-23.70066 54.410218,-23.69997 0.96168,10e-4 743.15307,1889.51856 744.15847,1894.52526 0.3662,1.8236 -8.4385,5.5616 -88.91939,37.75 -49.12919,19.6493 -89.80881,35.726 -90.39914,35.726 -0.59033,0 -151.97172,-431.1397 -336.4031,-958.0882 z M 479.50938,1092.3805 C 257.96814,529.293 191.89301,360.38549 192.66841,359.13055 194.10774,356.8011 221.6079,333.15768 241,317.57719 436.81575,160.24995 682.93467,68.097249 959.50004,48.553919 c 45.23886,-3.19678 72.80616,-3.83212 134.99996,-3.11128 33.55,0.38884 65.7142,1.01082 71.4761,1.38218 l 10.476,0.67518 237.4405,534.250111 c 130.5923,293.83752 237.5874,534.10329 237.7668,533.92379 0.1795,-0.1795 -52.5812,-196.04197 -117.2459,-435.25007 C 1469.7487,441.21571 1416.609,244.13574 1416.3252,242.46832 l -0.516,-3.03168 17.3454,-3.78686 c 23.4212,-5.11333 81.8996,-19.49383 104.6962,-25.74598 165.4811,-45.38453 283.651,-104.27268 384.9332,-191.825521 7.5438,-6.52121 15.0377,-13.2565904 16.6531,-14.9675104 1.6154,-1.71092 3.4529,-3.1107699693067 4.0834,-3.1107699693067 1.0301,0 455.1917,1145.4257013693068 456.2503,1150.6934013693069 0.5225,2.6003 -3.1355,9.3046 -13.8076,25.3066 -102.2638,153.3369 -401.9154,336.4283 -654.4632,399.8864 -93.5851,23.5153 -179.3483,30.178 -245,19.0333 -61.1827,-10.386 -108.5139,-35.5095 -139.8339,-74.224 -11.5414,-14.2663 -26.6661,-39.457 -26.6661,-44.4134 0,-2.6349 23.5806,-25.5849 43.0722,-41.9203 64.7325,-54.2507 147.9934,-100.2232 201.4431,-111.2268 21.6123,-4.4493 37.2773,-3.8343 49.173,1.9304 11.3433,5.4972 16.4014,14.4909 17.0993,30.404 1.349,30.7594 -16.3489,73.3974 -58.8851,141.8667 l -6.9354,11.1637 5.7665,-6.1213 c 63.0542,-66.9349 102.4232,-119.2542 126.3248,-167.8787 18.9414,-38.5338 23.5295,-66.1308 14.1758,-85.2657 -2.888,-5.9079 -10.8774,-13.9941 -16.8076,-17.0112 -11.6097,-5.9067 -28.7304,-8.3664 -49.1951,-7.0676 -86.998,5.5211 -257.5107,85.8797 -435.7315,205.3499 -179.4036,120.2631 -335.74396,258.38 -430.41521,380.2446 -5.66134,7.2875 -10.61591,13.25 -11.01015,13.25 -0.39424,0 -130.24861,-329.2288 -288.56526,-731.6195 z"
|
||||
id="path4140"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
57
lib/templates/CTFd/css.css
Normal file
57
lib/templates/CTFd/css.css
Normal file
@@ -0,0 +1,57 @@
|
||||
img.ctf_logo {
|
||||
/* from black icon to light grey */
|
||||
filter=> invert(0.8) sepia(1) saturate(0) hue-rotate(0deg);
|
||||
}
|
||||
|
||||
.challenge-button{
|
||||
|
||||
}
|
||||
.corner-button-check{
|
||||
margin-right: 3px !important;
|
||||
}
|
||||
.challenges-row{
|
||||
display: inline-block;
|
||||
}
|
||||
.category-challenges {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
max-width: 450px;
|
||||
border: 5px;
|
||||
border-style: solid;
|
||||
border-color: #ddd;
|
||||
border-radius: 7px;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
background: linear-gradient(whitesmoke, white);
|
||||
}
|
||||
.category-challenges:hover {
|
||||
border-color: #aaa;
|
||||
}
|
||||
|
||||
.category-challenges::after{
|
||||
height: 5px;
|
||||
width: 40px;
|
||||
background-color: #ddd;
|
||||
content: " ";
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: calc(50% - 20px);
|
||||
margin-left: -5px;
|
||||
}
|
||||
.pt-5{
|
||||
display: inline-block;
|
||||
}
|
||||
.col-md-3{
|
||||
width: auto;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
max-width: 450px !important;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
h3{
|
||||
font-size: 1rem;
|
||||
}
|
||||
.jumbotron {
|
||||
margin-left: -100%;
|
||||
margin-right: -100%;
|
||||
}
|
||||
13
lib/templates/CTFd/index.html
Normal file
13
lib/templates/CTFd/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3">
|
||||
<br>
|
||||
<h1 class="text-center">Welcome to SecGenCTF</h1>
|
||||
<br>
|
||||
<h4 class="text-center">
|
||||
Here you can <a href="submit">submit flags you discover</a>, and <a href="challenges">review the challenges, and (if available) purchase hints</a>.
|
||||
</h4>
|
||||
<h4 class="text-center">
|
||||
Good luck!
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
128
lib/templates/CTFd/submit.html
Normal file
128
lib/templates/CTFd/submit.html
Normal file
@@ -0,0 +1,128 @@
|
||||
<div class="jumbotron">
|
||||
<div class="container">
|
||||
<h1>Flag submission</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content" style='border:none;'>
|
||||
<div class="modal-body">
|
||||
<div role="tabpanel">
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane fade show active" id="challenge">
|
||||
<div class="chal-tags text-center"></div>
|
||||
<p>This will submit your flag against a set of challenges (such as a VM). Navigate to <a href="challenges">Challenges</a> for hints for specific flags.</p>
|
||||
</div>
|
||||
<div class="row submit-row">
|
||||
<div class="col-md-12 form-group">
|
||||
<input class="form-control" type="text" name="answer" id="answer-input" placeholder="Flag" />
|
||||
</div>
|
||||
<div class="col-md-12 form-group">
|
||||
<select id="challenge_set" class="form-control" style="height:60px;">
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-12 form-group key-submit">
|
||||
<button type="submit" onclick="submitflags()" id="submit-key" tabindex="5" class="btn btn-md btn-outline-secondary float-right">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row notification-row">
|
||||
<div class="col-md-12">
|
||||
<div id="result-notification" class="alert alert-dismissable text-center w-100" role="alert">
|
||||
<strong id="result-message"></strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// pause submitting, due to rate limits?
|
||||
var cont = true;
|
||||
// stop submitting
|
||||
var abort = false;
|
||||
|
||||
var challenge_list;
|
||||
|
||||
// retrieve the list of challenges and add VMs options to selection box on load
|
||||
function loadoptions() {
|
||||
var chal_list = $.get(window.location.origin + "/chals", {}, function (json) {
|
||||
// get the challenge list from the server, and store it for later
|
||||
challenge_list = json;
|
||||
// for each challenge, add the category to the selection box, if it's not been added already
|
||||
$.each(json['game'], function (i, item) {
|
||||
if (!$("#challenge_set option[value='" + item['category'] + "']").length) {
|
||||
$('#challenge_set').append($('<option>', {
|
||||
value: item['category'],
|
||||
text : item['category']
|
||||
}));
|
||||
}
|
||||
});
|
||||
$('#challenge_set').append($('<option>', {
|
||||
value: 'All',
|
||||
text : 'All'
|
||||
}));
|
||||
}, 'json');
|
||||
|
||||
}
|
||||
window.onload = loadoptions;
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// Spams the challenge submission with flags.
|
||||
// This is necessary since with SecGen flags it's not always known which # challenge they are completing.
|
||||
// Since this is all done client-side (all easily importable into CTFd), we have to pause when we hit rate limits.
|
||||
// This involves some timing between asynchronous threads.
|
||||
async function submitflags() {
|
||||
var answer = $('#answer-input').val();
|
||||
var nonce = csrf_nonce;
|
||||
var url = window.location.origin + "/chal/";
|
||||
|
||||
$( "#result-message" ).empty();
|
||||
|
||||
cont = true;
|
||||
abort = false;
|
||||
var process_count = 0;
|
||||
$.each(challenge_list['game'], async function (i, item) {
|
||||
var selected = $( "#challenge_set option:selected" ).text();
|
||||
if (selected == "All" || selected == item["category"]) {
|
||||
process_count++;
|
||||
await sleep(process_count * 500); // asynchronous spacing of queries
|
||||
var chal_id = item["id"];
|
||||
if (abort == true) {
|
||||
return;
|
||||
} else if (cont == false) {
|
||||
await sleep(60000);
|
||||
cont = true; // on waking this currently clobbers any subsequent pause
|
||||
}
|
||||
$( "#result-message" ).append('#' + chal_id + ': ');
|
||||
|
||||
var retval = $.post(url + chal_id, {
|
||||
key: answer,
|
||||
nonce: nonce
|
||||
}, function (json) {
|
||||
// success post
|
||||
$( "#result-message" ).append(json['message'] + '<br/>');
|
||||
if(json['status'] == 3) {
|
||||
$( "#result-message" ).append( 'Waiting 60 seconds...<br/>' );
|
||||
cont = false;
|
||||
// currently doesn't retry the challenges that trigger a wait
|
||||
} else if(json['status'] == 1) {
|
||||
$( "#result-message" ).append( '<p class="text-success">Success! (Stopping)</p>' );
|
||||
abort = true;
|
||||
}
|
||||
}, 'json');
|
||||
|
||||
retval.fail(function() {
|
||||
alert( "error" );
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
Reference in New Issue
Block a user