Merge branch 'ctfd_output'

This commit is contained in:
Z. Cliffe Schreuders
2018-06-22 17:02:36 +01:00
7 changed files with 772 additions and 2 deletions

View File

@@ -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

View 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

View File

@@ -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

View 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

View 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%;
}

View 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>

View 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>