Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds paid hints #374

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
18 changes: 13 additions & 5 deletions _examples/simple/beast.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
[author]
name = "fristonio"
email = "[email protected]"
name = ""
email = ""
ssh_key = "ssh-rsa AAAAB3NzaC1y"

[challenge.metadata]
name = "simple"
flag = "BACKDOOR{SAMPLE_FLAG}"
type = "bare"
hints = ["simple_hint_1", "simple_hint_2"]
points = 100

[[challenge.metadata.hints]]
text = "simple_hint_1"
percentage = 10.0

[[challenge.metadata.hints]]
text = "simple_hint_2"
percentage = 20.0

[challenge.env]
apt_deps = ["gcc", "socat"]
setup_scripts = ["setup.sh"]
run_cmd = "socat tcp-l:10001,fork,reuseaddr exec:./pwn"
ports = [10001]
run_cmd = "socat tcp-l:10005,fork,reuseaddr exec:./pwn"
ports = [10005]
136 changes: 111 additions & 25 deletions api/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
cfg "github.com/sdslabs/beastv4/core/config"
"github.com/sdslabs/beastv4/core/database"
"github.com/sdslabs/beastv4/core/utils"
coreUtils "github.com/sdslabs/beastv4/core/utils"
"github.com/sdslabs/beastv4/pkg/auth"
log "github.com/sirupsen/logrus"
)
Expand All @@ -35,6 +36,91 @@ func usedPortsInfoHandler(c *gin.Context) {
})
}

func hintHandler(c *gin.Context) {
hintIDStr := c.Param("hintID")

if hintIDStr == "" {
c.JSON(http.StatusBadRequest, HTTPErrorResp{
Error: "Hint ID cannot be empty",
})
return
}

hintID, err := strconv.Atoi(hintIDStr)

if err != nil {
c.JSON(http.StatusBadRequest, HTTPPlainResp{
Message: "Hint Id format invalid",
})
return
}

username, err := coreUtils.GetUser(c.GetHeader("Authorization"))
if err != nil {
c.JSON(http.StatusUnauthorized, HTTPErrorResp{
Error: "Unauthorized user",
})
return
}

user, err := database.QueryFirstUserEntry("username", username)
if err != nil {
c.JSON(http.StatusUnauthorized, HTTPErrorResp{
Error: "Unauthorized user",
})
return
}

if user.Status == 1 {
c.JSON(http.StatusUnauthorized, HTTPErrorResp{
Error: "Banned user",
})
return
}

// Fetch hint details
hint, err := database.GetHintByID(uint(hintID))
if err != nil {
if err.Error() == "not_found" {
c.JSON(http.StatusNotFound, HTTPErrorResp{
Error: "Hint not found",
})
} else {
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while processing the request",
})
}
return
}

// Check if the user has already taken the hint
hasTakenHint, err := database.UserHasTakenHint(user.ID, uint(hintID))
if err != nil {
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while checking hint usage",
})
return
}
if hasTakenHint {
c.JSON(http.StatusBadRequest, HTTPErrorResp{
Error: "You have already taken this hint",
})
return
}

// Save user hint
if err := database.SaveUserHint(user.ID, hint.ChallengeID, hint.HintID); err != nil {
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while saving the hint usage",
})
return
}

c.JSON(http.StatusOK, HTTPPlainResp{
Message: "Hint successfully taken",
})
}

// Returns information about a challenge
// @Summary Returns all information about the challenges.
// @Description Returns all information about the challenges by the challenge name.
Expand Down Expand Up @@ -118,14 +204,14 @@ func challengeInfoHandler(c *gin.Context) {

if autherr != nil {
c.JSON(http.StatusOK, ChallengeInfoResp{
Name: name,
ChallId: challenge.ID,
Category: challenge.Type,
CreatedAt: challenge.CreatedAt,
Tags: challengeTags,
Status: challenge.Status,
Ports: challengePorts,
Hints: challenge.Hints,
Name: name,
ChallId: challenge.ID,
Category: challenge.Type,
CreatedAt: challenge.CreatedAt,
Tags: challengeTags,
Status: challenge.Status,
Ports: challengePorts,
// Hints: challenge.Hints,
Desc: challenge.Description,
Assets: strings.Split(challenge.Assets, core.DELIMITER),
AdditionalLinks: strings.Split(challenge.AdditionalLinks, core.DELIMITER),
Expand All @@ -137,15 +223,15 @@ func challengeInfoHandler(c *gin.Context) {
}

c.JSON(http.StatusOK, ChallengeInfoResp{
Name: name,
ChallId: challenge.ID,
Category: challenge.Type,
Flag: challenge.Flag,
CreatedAt: challenge.CreatedAt,
Tags: challengeTags,
Status: challenge.Status,
Ports: challengePorts,
Hints: challenge.Hints,
Name: name,
ChallId: challenge.ID,
Category: challenge.Type,
Flag: challenge.Flag,
CreatedAt: challenge.CreatedAt,
Tags: challengeTags,
Status: challenge.Status,
Ports: challengePorts,
// Hints: challenge.Hints,
Desc: challenge.Description,
Assets: strings.Split(challenge.Assets, core.DELIMITER),
AdditionalLinks: strings.Split(challenge.AdditionalLinks, core.DELIMITER),
Expand Down Expand Up @@ -297,14 +383,14 @@ func challengesInfoHandler(c *gin.Context) {
}

availableChallenges[index] = ChallengeInfoResp{
Name: challenge.Name,
ChallId: challenge.ID,
Category: challenge.Type,
Tags: challengeTags,
CreatedAt: challenge.CreatedAt,
Status: challenge.Status,
Ports: challengePorts,
Hints: challenge.Hints,
Name: challenge.Name,
ChallId: challenge.ID,
Category: challenge.Type,
Tags: challengeTags,
CreatedAt: challenge.CreatedAt,
Status: challenge.Status,
Ports: challengePorts,
//Hints: challenge.Hints,
Desc: challenge.Description,
Points: challenge.Points,
Assets: strings.Split(challenge.Assets, core.DELIMITER),
Expand Down
5 changes: 2 additions & 3 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 @@ -509,7 +509,6 @@ func manageUploadHandler(c *gin.Context) {
Assets: config.Challenge.Metadata.Assets,
AdditionalLinks: config.Challenge.Metadata.AdditionalLinks,
Ports: config.Challenge.Env.Ports,
Hints: config.Challenge.Metadata.Hints,
Desc: config.Challenge.Metadata.Description,
Points: config.Challenge.Metadata.Points,
})
Expand Down
2 changes: 0 additions & 2 deletions api/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ type ChallengeInfoResp struct {
CreatedAt time.Time `json:"createdAt"`
Status string `json:"status" example:"deployed"`
Ports []uint32 `json:"ports" example:[3001, 3002]`
Hints string `json:"hints" example:"Try robots"`
Desc string `json:"description" example:"A simple web challenge"`
Points uint `json:"points" example:"50"`
SolvesNumber int `json:"solvesNumber" example:"100"`
Expand All @@ -123,7 +122,6 @@ type ChallengePreviewResp struct {
Assets []string `json:"assets" example:"['image1.png', 'zippy.zip']"`
AdditionalLinks []string `json:"additionalLinks" example:"['http://link1.abc:8080','http://link2.abc:8081']"`
Ports []uint32 `json:"ports" example:[3001, 3002]`
Hints []string `json:"hints" example:"Try robots"`
Desc string `json:"description" example:"A simple web challenge"`
Points uint `json:"points" example:"50"`
}
Expand Down
1 change: 1 addition & 0 deletions api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ func initGinRouter() *gin.Engine {
infoGroup.GET("/users", getAllUsersInfoHandler)
infoGroup.GET("/submissions", submissionsHandler)
infoGroup.GET("/tags", tagHandler)
infoGroup.GET("/hint/:hintID", hintHandler)
}

// Notification route group
Expand Down
24 changes: 23 additions & 1 deletion api/submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,29 @@ func submitFlagHandler(c *gin.Context) {
}
}

err = database.UpdateUser(&user, map[string]interface{}{"Score": user.Score + challengePoints})
// Fetch hints taken by the user for this challenge
hints, err := database.QueryHintsTaken(user.ID, challenge.ID)
if err != nil {
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while processing the request.",
})
return
}

// Calculate total deduction percentage
totalDeduction := 0.0
for _, hint := range hints {
totalDeduction += hint.DeductionPercentage
}
if totalDeduction > 100 {
totalDeduction = 100 // Cap deduction at 100%
}

// Adjust points based on hints taken
deductedPoints := float64(challengePoints) * (1 - totalDeduction/100)
finalPoints := uint(math.Round(deductedPoints))

err = database.UpdateUser(&user, map[string]interface{}{"Score": user.Score + finalPoints})
if err != nil {
c.JSON(http.StatusInternalServerError, HTTPErrorResp{
Error: "DATABASE ERROR while processing the request.",
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
44 changes: 20 additions & 24 deletions core/config/challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,16 @@ func (config *Challenge) ValidateRequiredFields(challdir string) error {
// sidecar = "" # Name of the sidecar if any used by the challenge.
// ```
type ChallengeMetadata struct {
Flag string `toml:"flag"`
Name string `toml:"name"`
Type string `toml:"type"`
Tags []string `toml:"tags"`
Sidecar string `toml:"sidecar"`
Description string `toml:"description"`
Hints []string `toml:"hints"`
Flag string `toml:"flag"`
Name string `toml:"name"`
Type string `toml:"type"`
Tags []string `toml:"tags"`
Sidecar string `toml:"sidecar"`
Description string `toml:"description"`
Hints []struct {
Text string `toml:"text"`
Percentage float64 `toml:"percentage"`
} `toml:"hints"`
Points uint `toml:"points"`
MaxPoints uint `toml:"maxPoints"`
MinPoints uint `toml:"minPoints"`
Expand Down Expand Up @@ -188,59 +191,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 +497,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
Loading