-
Notifications
You must be signed in to change notification settings - Fork 10
feat: add support for ssh challenge type along with docs #416
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
base: bl4ze/dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| FROM ubuntu:16.04 | ||
|
|
||
| # Install SSH server and other required packages | ||
| RUN apt-get update && apt-get install -y openssh-server sudo vim \ | ||
| && apt-get clean \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| # Configure SSH server | ||
| RUN mkdir /var/run/sshd | ||
| RUN echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config | ||
| RUN echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config | ||
|
|
||
| # Create a CTF user with a default password | ||
| RUN useradd -m -s /bin/bash ctf-user \ | ||
| && echo "ctf-user:ctf-password" | chpasswd \ | ||
| && echo "ctf-user ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/ctf-user | ||
|
|
||
| # Add the challenge flag in a hidden location | ||
| RUN echo "FLAG{SSH_CHALLENGE_FLAG}" > /home/ctf-user/.hidden_flag \ | ||
| && chmod 600 /home/ctf-user/.hidden_flag | ||
|
|
||
| # Add a welcome message | ||
| RUN echo "Welcome to the SSH Challenge! Find the hidden flag." > /etc/motd | ||
|
|
||
| # Expose SSH port | ||
| EXPOSE 22 | ||
|
|
||
| # Start SSH service | ||
| CMD ["/usr/sbin/sshd", "-D"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| [author] | ||
| name = "ph03nix" | ||
| email = "author@example.com" | ||
| ssh_key = "ssh-ed25519 AAAAC3NzaC1lZD" | ||
|
|
||
| [challenge.metadata] | ||
| name = "simple-ssh" | ||
| flag = "FLAG{SSH_CHALLENGE_FLAG}" | ||
| type = "ssh" | ||
| points = 150 | ||
| description = "SSH into the server and find the flag. Here are the creds: ctf-user:ctf-password" | ||
|
|
||
| [[challenge.metadata.hints]] | ||
| text = "Check for hidden files in the home directory" | ||
| points = 10 | ||
|
|
||
| [challenge.env] | ||
| docker_context = "Dockerfile" | ||
| port_mappings = ["14442:22"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -230,9 +230,9 @@ func (config *ChallengeMetadata) ValidateRequiredFields() (error, bool) { | |
| // # Exists for flexibility reasons try to use existing base iamges wherever possible. | ||
| // base_image = "" | ||
| // | ||
| // # Docker file name for specific type challenge - `docker`. | ||
| // # Docker file name for specific type challenge - `docker` or `ssh`. | ||
| // # Helps to build flexible images for specific user-custom challenges | ||
| // docket_context = "" | ||
| // docker_context = "" | ||
| // | ||
| // # Environment variables that can be used in the application code. | ||
| // [[var]] | ||
|
|
@@ -390,6 +390,23 @@ func (config *ChallengeEnv) ValidateRequiredFields(challType string, challdir st | |
| return fmt.Errorf("max ports allowed for challenge : %d given : %d", core.MAX_PORT_PER_CHALL, len(config.Ports)) | ||
| } | ||
|
|
||
| // For SSH challenges, ensure there's an explicit host->container mapping to SSH port. | ||
| // If user didn't specify a mapping like "host:22", map the first port in `Ports` | ||
| // to container SSH port so downstream code doesn't need the challenge type. | ||
| if challType == core.SSH_CHALLENGE_TYPE_NAME { | ||
| sshMapped := false | ||
| for _, pm := range config.PortMappings { | ||
| _, cp, err := utils.ParsePortMapping(pm) | ||
| if err == nil && cp == core.SSH_PORT { | ||
| sshMapped = true | ||
| break | ||
| } | ||
| } | ||
| if !sshMapped && len(config.Ports) > 0 { | ||
| config.PortMappings = append(config.PortMappings, fmt.Sprintf("%d:%d", config.Ports[0], core.SSH_PORT)) | ||
| } | ||
| } | ||
|
|
||
| portMappings, err := config.GetPortMappings() | ||
| if err != nil { | ||
| return fmt.Errorf("error while parsing port mapping: %s", err) | ||
|
|
@@ -495,6 +512,21 @@ func (config *ChallengeEnv) ValidateRequiredFields(challType string, challdir st | |
| return fmt.Errorf("web Root directory does not exist") | ||
| } | ||
| } | ||
| } else if challType == core.SSH_CHALLENGE_TYPE_NAME { | ||
| // Challenge type is SSH which requires docker_context | ||
| if config.DockerCtx == "" { | ||
| return fmt.Errorf("docker_context can not be empty for SSH challenges") | ||
| } else if config.DockerCtx != "" { | ||
| if filepath.IsAbs(config.DockerCtx) { | ||
| return fmt.Errorf("docker_context path should be relative to challenge directory root") | ||
| } else if err := utils.ValidateFileExists(filepath.Join(challdir, config.DockerCtx)); err != nil { | ||
| return fmt.Errorf("docker_context file does not exist") | ||
| } | ||
| } | ||
|
|
||
| if !checkIfPortExistInMapping(portMappings, core.SSH_PORT) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe the port mapping being appended GetPortMappings could be handled here. |
||
| log.Warnf("No SSH port (22) found in port mappings for SSH challenge, it might not be accessible via SSH") | ||
| } | ||
| } | ||
|
|
||
| for _, script := range config.SetupScripts { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -4,7 +4,7 @@ | |||||
|
|
||||||
| Any service whether it is a binary file, or a shell script, which needs to be instantiated on every connection can be easily hosted using `service` type challenge. **Xinetd** is for hosting these type of challenges inside a docker container. | ||||||
|
|
||||||
| ###Primary Requirements | ||||||
| ### Primary Requirements | ||||||
|
|
||||||
| ```toml | ||||||
| # Relative path to binary or script which needs to be executed when the specified | ||||||
|
|
@@ -21,7 +21,7 @@ Web challenges are hosted using the corresponding images from Dockerhub. Current | |||||
| * Python : Django and Flask | ||||||
| * Php | ||||||
|
|
||||||
| ###Primary Requirements | ||||||
| ### Primary Requirements | ||||||
|
|
||||||
| ```toml | ||||||
| # Relative directory corresponding to root of the challenge where the root | ||||||
|
|
@@ -57,10 +57,24 @@ entrypoint = "" | |||||
|
|
||||||
| Authors might have tested the challenges in a isolated docker environment and might not want to port the challenge to one of these types. So they can use `docker` type challenge in which you can provide your own docker context file and ports. | ||||||
|
|
||||||
| ###Primary Requirements | ||||||
| ### Primary Requirements | ||||||
|
|
||||||
| ```toml | ||||||
| # Docker file name for specific type challenge - `docker`. | ||||||
| # Helps to build flexible images for specific user-custom challenges | ||||||
| docket_context = "" | ||||||
|
||||||
| docket_context = "" | |
| docker_context = "" |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -184,3 +184,35 @@ The type of challenge consist of the following format - `web:php:<PHP Version>:< | |||||
|
|
||||||
| Make sure that you are installing all the mysql related dependencies in the `apt_deps` configuration parameter. As in the | ||||||
| above case `php*-mysql` | ||||||
|
|
||||||
| ## SSH Challenges | ||||||
|
|
||||||
| SSH challenges allow participants to connect directly to a container using SSH and interact with a shell environment to solve the challenge. These challenges are ideal for scenarios where participants need to explore a system, find hidden files, or exploit vulnerabilities in a controlled environment. | ||||||
|
|
||||||
| ```toml | ||||||
| [author] | ||||||
| name = "ph03n1x" | ||||||
| email = "author@example.com" | ||||||
| ssh_key = "ssh-rsa AAAAB3NzaC1y..." | ||||||
|
|
||||||
| [challenge.metadata] | ||||||
| name = "simple-ssh" | ||||||
| flag = "FLAG{SSH_CHALLENGE_FLAG}" | ||||||
| type = "ssh" | ||||||
| points = 150 | ||||||
| description = "SSH into the server and find the flag. The creds are ctf-user:ctf-password" | ||||||
|
|
||||||
| [[challenge.metadata.hints]] | ||||||
| text = "Check for hidden files in the home directory" | ||||||
| points = 10 | ||||||
|
|
||||||
| [challenge.env] | ||||||
| docker_context = "Dockerfile" | ||||||
| port_mappings = ["14442:22"] # Map external port 14442 to container's SSH port 22 | ||||||
| ``` | ||||||
|
|
||||||
| Participants will be able to connect to this challenge using SSH: | ||||||
| ``` | ||||||
| ssh ctf-user@challenge-host -p 2222 | ||||||
|
||||||
| ssh ctf-user@challenge-host -p 2222 | |
| ssh ctf-user@challenge-host -p 14442 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In SSH challenges, this logic appends a new
port_mappingsentry but leaves the originalPortsentry intact. That causesGetPortMappings()to also add ahostPort:hostPortmapping for the same host port, which can lead to duplicate host port bindings (e.g.,14442:22and14442:14442) and deployment failure. Consider removing the selected host port fromconfig.Ports(or otherwise excluding it fromGetPortMappings) when you synthesize the SSH mapping, and ensure the max-port validation still reflects the final mapping set.