Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions _examples/web-php/beast.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ name = "web-php"
flag = "BACKDOOR{SAMPLE_WEB_FLAG}"
type = "web:php:7.1:cli"
hints = ["web_hint_1", "web_hint_2"]
failSolveLimit = 3

[challenge.env]
ports = [10002]
Expand Down
2 changes: 2 additions & 0 deletions _examples/xinetd-service/beast.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ name = "xinetd-service"
flag = "CTF{sample_flag}"
type = "service"
hints = ["xinetd_hint_1", "xinetd_hint_2"]
preReqs = ["simple", "web-php"]
failSolveLimit=2

[challenge.env]
apt_deps = ["gcc", "socat"]
Expand Down
5 changes: 3 additions & 2 deletions api/manage.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,9 +458,9 @@ func manageUploadHandler(c *gin.Context) {

// Extract and show from zip and return response
tempStageDir, err := manager.UnzipChallengeFolder(zipContextPath, core.BEAST_TEMP_DIR)

// log.Debug("The dir is ",tempStageDir)

// The file cannot be successfully un-zipped or the resultant was a malformed directory
if err != nil {
c.JSON(http.StatusBadRequest, HTTPErrorResp{
Expand Down Expand Up @@ -510,6 +510,7 @@ func manageUploadHandler(c *gin.Context) {
AdditionalLinks: config.Challenge.Metadata.AdditionalLinks,
Ports: config.Challenge.Env.Ports,
Hints: config.Challenge.Metadata.Hints,
PreReqs: config.Challenge.Metadata.PreReqs,
Desc: config.Challenge.Metadata.Description,
Points: config.Challenge.Metadata.Points,
})
Expand Down
3 changes: 3 additions & 0 deletions api/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ type ChallengeInfoResp struct {
AdditionalLinks []string `json:"additionalLinks" example:"['http://link1.abc:8080','http://link2.abc:8081']"`
CreatedAt time.Time `json:"createdAt"`
Status string `json:"status" example:"deployed"`
FailSolveLimit int `json:"failSolveLimit" example:"5"`
PreReqs []string `json:"preReqs" example:"['web-php','simple']"`
Ports []uint32 `json:"ports" example:[3001, 3002]`
Hints string `json:"hints" example:"Try robots"`
Desc string `json:"description" example:"A simple web challenge"`
Expand All @@ -122,6 +124,7 @@ type ChallengePreviewResp struct {
Tags []string `json:"tags" example:"['pwn','misc']"`
Assets []string `json:"assets" example:"['image1.png', 'zippy.zip']"`
AdditionalLinks []string `json:"additionalLinks" example:"['http://link1.abc:8080','http://link2.abc:8081']"`
PreReqs []string `json:"preReqs" example:"['web-php','simple']"`
Ports []uint32 `json:"ports" example:[3001, 3002]`
Hints []string `json:"hints" example:"Try robots"`
Desc string `json:"description" example:"A simple web challenge"`
Expand Down
49 changes: 47 additions & 2 deletions api/submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,19 @@ func submitFlagHandler(c *gin.Context) {
})
return
}
if challenge.Flag != flag {

preReqsStatus, err := database.CheckPreReqsStatus(challenge, user.ID)

if err != nil {
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while processing the request.",
})
return
}

if !preReqsStatus {
c.JSON(http.StatusOK, FlagSubmitResp{
Message: "Your flag is incorrect",
Message: "You have not solved the prerequisites of this challenge.",
Success: false,
})
return
Expand All @@ -136,6 +146,40 @@ func submitFlagHandler(c *gin.Context) {
return
}

tryStatus, err := database.GetUserPreviousTriesStatus(user.ID, challenge.ID, challenge.FailSolveLimit)

if err != nil {
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while processing the request."})
return
}

if !tryStatus {
c.JSON(http.StatusOK, FlagSubmitResp{
Message: "You have reached the maximum number of tries for this challenge.",
Success: false,
})
return
}

// Increase user tries by 1
err = database.UpdateUserChallengeTries(user.ID, challenge.ID)

if err != nil {
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while processing the request.",
})
return
}

if challenge.Flag != flag {
c.JSON(http.StatusOK, FlagSubmitResp{
Message: "Your flag is incorrect",
Success: false,
})
return
}

challengePoints := challenge.Points
log.Debugf("Dynamic scoring is set to %t", config.Cfg.CompetitionInfo.DynamicScore)
if config.Cfg.CompetitionInfo.DynamicScore {
Expand Down Expand Up @@ -172,6 +216,7 @@ func submitFlagHandler(c *gin.Context) {
CreatedAt: time.Time{},
UserID: user.ID,
ChallengeID: challenge.ID,
Solved: true,
}

err = database.SaveFlagSubmission(&UserChallengesEntry)
Expand Down
2 changes: 1 addition & 1 deletion core/auth/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func ParseAuthorizedKeysFile(filePath string) (map[string][]string, error) {
return authorizedKeysMap, eMsg
}

//This function parses ssh Private Key
// This function parses ssh Private Key
func ParsePrivateKey(keyFile string) (*rsa.PrivateKey, error) {
keyString, err := ioutil.ReadFile(keyFile)
if err != nil {
Expand Down
33 changes: 16 additions & 17 deletions core/config/challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ type ChallengeMetadata struct {
Sidecar string `toml:"sidecar"`
Description string `toml:"description"`
Hints []string `toml:"hints"`
FailSolveLimit int `toml:"failSolveLimit"`
PreReqs []string `toml:"preReqs"`
Points uint `toml:"points"`
MaxPoints uint `toml:"maxPoints"`
MinPoints uint `toml:"minPoints"`
Expand All @@ -150,6 +152,10 @@ func (config *ChallengeMetadata) ValidateRequiredFields() (error, bool) {
return fmt.Errorf("Name and Flag required for the challenge"), false
}

if config.FailSolveLimit <= 0 {
return fmt.Errorf("fail Solve Limit must be greater than 0"), false
}

if !(utils.StringInSlice(config.Sidecar, Cfg.AvailableSidecars) || config.Sidecar == "") {
return fmt.Errorf("Sidecar provided is not an available sidecar."), false
}
Expand Down Expand Up @@ -188,59 +194,52 @@ func (config *ChallengeMetadata) ValidateRequiredFields() (error, bool) {
// # Dependencies required by challenge, installed using default package manager of base image apt for most cases.
// apt_deps = ["", ""]
//
//
// # A list of setup scripts to run for building challenge enviroment.
// # Keep in mind that these are only for building the challenge environment and are executed
// # in the iamge building step of the deployment pipeline.
// setup_scripts = ["", ""]
//
//
// # A directory containing any of the static assets for the challenge, exposed by beast static endpoint.
// static_dir = ""
//
//
// # Command to execute inside the container, if a predefined type is being used try to
// # use an existing field to let beast automatically calculate what command to run.
// # If you want to host a binary using xinetd use type service and specify absolute path
// # of the service using service_path field.
// run_cmd = ""
//
//
// # Similar to run_cmd but in this case you have the entire container to yourself
// # and everything you are doing is done using root permissions inside the container
// # When using this keep in mind you are root inside the container.
// entrypoint = ""
//
//
// # Relative path to binary which needs to be executed when the specified
// # Type for the challenge is service.
// # This can be anything which can be exeucted, a python file, a binary etc.
// service_path = ""
//
//
// # Relative directory corresponding to root of the challenge where the root
// # of the web application lies.
// web_root = ""
//
//
// # Any custom base image you might want to use for your particular challenge.
// # Exists for flexibility reasons try to use existing base iamges wherever possible.
// base_image = ""
//
//
// # Docker file name for specific type challenge - `docker`.
// # Helps to build flexible images for specific user-custom challenges
// docket_context = ""
//
//
// # Environment variables that can be used in the application code.
// [[var]]
// key = ""
// value = ""
//
// key = ""
// value = ""
//
// [[var]]
// key = ""
// value = ""
//
// key = ""
// value = ""
//
// Type of traffic to expose through the port mapping provided.
// traffic = "udp" / "tcp"
Expand Down Expand Up @@ -501,10 +500,10 @@ func (config *ChallengeEnv) ValidateRequiredFields(challType string, challdir st

// Metadata related to author of the challenge, this structure includes
//
// * Name - Name of the author of the challenge
// * Email - Email of the author
// * SSHKey - Public SSH key for the challenge author, to give the access
// to the challenge container.
// - Name - Name of the author of the challenge
// - Email - Email of the author
// - SSHKey - Public SSH key for the challenge author, to give the access
// to the challenge container.
//
// ```toml
// # Optional fields
Expand Down
11 changes: 1 addition & 10 deletions core/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,58 +18,49 @@ import (

// This is the global beast configuration structure
//
// An example of a config file
// # An example of a config file
//
// ```toml
// # Authorized key file used by ssh daemon running on the host
// # This is used for forwarding ssh connection to docker containers, the
// # access to a container is only given to the author, maintainers of challenge and admin.
// authorized_keys_file = "/home/fristonio/.beast/beast_authorized_keys"
//
//
// # Directory which will contain all the autogenerated scripts by beast
// # These scripts are the heart to above authorized keys file. Each entry in authorized
// # keys file as a corresponding script which is executed during an SSH attempt.
// scripts_dir = "/home/fristonio/.beast/scripts"
//
//
// # Base OS image that beast allows the challenges to use.
// allowed_base_images = ["ubuntu:18.04", "ubuntu:16.04", "debian:jessie"]
//
//
// # Beast static URL refers to the host used by beast for serving static content
// # of the challenges, whenever required. It follows the URL pattern like
// # [beast_static_url]/static/[chall_dir]/[file_name]
// beast_static_url = "http://hack.sdslabs.co:8034"
//
//
// # For authentication purposes beast uses JWT based authentication, this is the
// # key used for encrypting the claims of a user. Keep this strong.
// jwt_secret = "beast_jwt_secret_SUPER_STRONG_0x100010000100"
//
//
// # To allow beast to send notification to a notification channel povide this webhook URL
// # We are also working on implmeneting notification using Discord and IRC.
// slack_webhook = ""
//
//
// # The sidecar that we support with beast, currently we only support two MySQL and
// # MongoDB.
// available_sidecars = ["mysql", "mongodb"]
//
//
// # The frequency for any periodic event in beast, the value is provided in seconds.
// # This is currently only used for health check periodic duration.s
// ticker_frequency = 3000
//
//
// # Container default resource limits for each challenge, this can be
// # Overridden by challenge configuration beast.toml file.
// default_cpu_shares = 1024
// default_memory_limit = 1024
// default_pids_limit = 100
//
//
// # Configuration corresponding to the remote repository used by beast
// # We use ssh authentication mechanism for interacting with git repository.
// [remote]
Expand Down
Loading