diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
new file mode 100644
index 0000000..5dbf5ed
--- /dev/null
+++ b/.github/workflows/main.yaml
@@ -0,0 +1,24 @@
+name: Build and push database client Docker image
+on:
+ push:
+ branches:
+ - main
+jobs:
+ build-and-push:
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout code
+ uses: actions/checkout@v4
+
+ - name: build and tag container image
+ run: make build
+
+ - name: login to GitHub container registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: push container image
+ run: make push
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..9bdbc07
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,10 @@
+FROM golang
+
+COPY go.mod main.go ./
+
+RUN go mod download && \
+ go build -o main main.go && \
+ apt-get update && \
+ apt-get install -y iputils-ping
+
+ENTRYPOINT [ "./main" ]
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..a6391ce
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,13 @@
+.PHONY: install
+
+build:
+ docker build . -t asteurer/test
+
+push:
+ docker push ghcr.io/kube-hack/command-injection
+
+install:
+ helm upgrade --install command-injection ./chart
+
+uninstall:
+ helm uninstall command-injection
\ No newline at end of file
diff --git a/README.md b/README.md
index e69de29..81aaade 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,65 @@
+# Overview
+
+This is an educational resource demonstrating a web server running in a Kubernetes cluster that has a command injection vulnerability. Here are a few suggestions for how you might use this repository:
+
+1. Practice your hacking skills by getting the contents of the `flag_to_capture` file, which is stored somewhere on the web server.
+
+2. Read the source code found in `main.go` and the solution guide found in `solution/README.md` to better-understand what command injection vulnerabilities look like, how to exploit them, and how to prevent them.
+
+3. Use this as a guide/inspiration for building your own applications with vulnerabilities.
+
+The instructions and solutions were written assuming you are using some kind of Linux distribution (sorry Windows :grimacing:), whether Ubuntu, MacOS, or another Debian-based OS.
+
+## \*\*\*\*\**DISCLAIMER*\*\*\*\*\*
+
+This is an application with a built-in security vulnerability. Please don't deploy the Helm chart into a production environment. There are also instructions showing how to exploit command injection vulnerabilities, so please don't use this to break any laws :grin:.
+
+# Usage
+
+## Requirements
+
+- Latest version of [Helm](https://helm.sh/docs/intro/install/)
+- Latest version of [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl)
+- A fully-compliant Kubernetes distribution (i.e. microk8s, k3s, k3d) that is running on Linux/amd64, and is using containerd or Docker as the runtime.
+
+## Deploying to Kubernetes
+
+Add the Helm chart repository:
+
+```sh
+helm repo add kube-hack https://kube-hack.github.io/charts
+```
+
+Update the charts in your Helm repository:
+
+```sh
+helm repo update
+```
+
+Deploy the chart to your Kubernetes cluster:
+
+```sh
+helm install command-injection kube-hack/command-injection
+```
+
+## Interacting with the application
+
+### Port-forward the application
+
+```sh
+kubectl port-forward svc/web-server-command-injection 3000:3000
+```
+
+After the application is port-forwarded (accessible via localhost), you can open your browser and enter `localhost:3000` to access the web application.
+
+### Using the application
+
+This is a web application that sends a `ping` request to a url on the internet, and prints the response on the page. Enter a website (i.e. google.com) into the field next to the `Enter a URL or Domain:` label and click the `Ping` button to see the response.
+
+### Validating the value of the flag
+
+If you have found the `flag_to_capture` file, send a `curl` request to `localhost:3000/validate` with the contents of the file as a string in the request payload:
+
+```sh
+curl --data-binary "your-string-here" localhost:3000/validate
+```
diff --git a/chart/Chart.yaml b/chart/Chart.yaml
new file mode 100644
index 0000000..bfd1cbd
--- /dev/null
+++ b/chart/Chart.yaml
@@ -0,0 +1,9 @@
+apiVersion: v2
+name: command-injection
+description: A chart that deploys a web server that has a command injection vulnerability.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
diff --git a/chart/files/templates/index.html b/chart/files/templates/index.html
new file mode 100644
index 0000000..e8cb889
--- /dev/null
+++ b/chart/files/templates/index.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+ Ping Result
+
+
+
+ Ping a Host
+
+
+
+
+
diff --git a/chart/templates/server.yaml b/chart/templates/server.yaml
new file mode 100644
index 0000000..e86d1e8
--- /dev/null
+++ b/chart/templates/server.yaml
@@ -0,0 +1,59 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: web-server-command-injection
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: web-server-command-injection
+ template:
+ metadata:
+ labels:
+ app: web-server-command-injection
+ spec:
+ containers:
+ - name: web-server-command-injection
+ image: ghcr.io/kube-hack/command-injection:latest
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 8080
+ volumeMounts:
+ - mountPath: /go/templates
+ name: templates
+ resources:
+ requests:
+ memory: "512Mi"
+ cpu: "250m"
+ limits:
+ memory: "1Gi"
+ cpu: "500m"
+ volumes:
+ - name: templates
+ configMap:
+ name: html-templates-command-injection
+
+---
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: html-templates-command-injection
+data:
+{{ (.Files.Glob "files/templates/*").AsConfig | indent 4 }}
+
+---
+
+apiVersion: v1
+kind: Service
+metadata:
+ name: web-server-command-injection
+ labels:
+ app: web-server-command-injection
+spec:
+ type: ClusterIP
+ ports:
+ - port: 3000
+ targetPort: 8080
+ selector:
+ app: web-server-command-injection
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..c8a93cd
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module command-injection
+
+go 1.22.4
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..c364ce1
--- /dev/null
+++ b/main.go
@@ -0,0 +1,132 @@
+package main
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "html/template"
+ "io"
+ "math/rand"
+ "net/http"
+ "os"
+ "os/exec"
+ "path"
+ "time"
+)
+
+var secretFilePath string
+
+func main() {
+ // The flag is created in a random location with a new value every time the container is restarted
+ path, err := createFlag()
+ if err != nil {
+ panic(err)
+ }
+
+ secretFilePath = path
+
+ http.HandleFunc("/", ping)
+ http.HandleFunc("/validate", validate)
+ http.ListenAndServe(":8080", nil)
+}
+
+// ping sends a ping request to the requested web address
+func ping(w http.ResponseWriter, r *http.Request) {
+ if r.Method == http.MethodGet {
+ tmpl, err := template.ParseFiles("templates/index.html")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ tmpl.Execute(w, nil)
+
+ } else if r.Method == http.MethodPost {
+ requestBytes, err := io.ReadAll(r.Body)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer r.Body.Close()
+
+ cmdString := fmt.Sprintf("ping -c 2 %s", string(requestBytes))
+
+ cmd := exec.Command("sh", "-c", cmdString)
+
+ outputBytes, err := cmd.CombinedOutput()
+ if err != nil {
+ errorMessage := fmt.Sprintf("unable to ping address:\n%v\n%s\n", err, outputBytes)
+ http.Error(w, errorMessage, http.StatusBadRequest)
+ return
+ }
+
+ w.Write(outputBytes)
+ }
+}
+
+// validate checks the flag file's content with the request body to see if they match
+func validate(w http.ResponseWriter, r *http.Request) {
+ if r.Method == http.MethodGet {
+ bodyBytes, err := io.ReadAll(r.Body)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer r.Body.Close()
+
+ fileBytes, err := os.ReadFile(secretFilePath)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ if string(fileBytes) == string(bodyBytes) {
+ w.Write([]byte("Success! You found the flag."))
+ } else {
+ w.Write([]byte("The string submitted doesn't match the content of the flag file."))
+ }
+ }
+}
+
+// createFlag will select a directory in root at random and place a flag file with a randomly-generated string
+func createFlag() (string, error) {
+ rootDir, err := os.ReadDir("/")
+ if err != nil {
+ return "", err
+ }
+
+ randBytes := getRandomByteArray(64)
+
+ // Integer of the random bytes
+ randNum := binary.BigEndian.Uint64(randBytes)
+
+ // Random number corresponding to the number of directories in root
+ folderPosition := int(randNum) % (len(rootDir) - 1)
+
+ // Placing the flag in a random directory in root
+ path := path.Join("/", rootDir[folderPosition].Name(), "flag_to_capture")
+
+ outfile, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
+ if err != nil {
+ return "", err
+ }
+
+ if _, err := io.Copy(outfile, bytes.NewReader(randBytes)); err != nil {
+ return "", err
+ }
+ outfile.Close()
+
+ return path, nil
+}
+
+// Adapted from https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
+func getRandomByteArray(length int) []byte {
+ charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+ seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
+ b := make([]byte, length)
+
+ for i := range b {
+ b[i] = charset[seededRand.Intn(len(charset))]
+ }
+
+ return b
+}
diff --git a/solution/README.md b/solution/README.md
new file mode 100644
index 0000000..1ca9ed4
--- /dev/null
+++ b/solution/README.md
@@ -0,0 +1,40 @@
+# What is command injection?
+
+Command injection is a vulnerability that allows the execution of unauthorized commands on the host of a web-facing application. This means that someone who exploits a command injection vulnerability has a lot of options:
+- Retrieving Kubernetes service account token, which they could use to run `kubectl` commands.
+- Viewing environment variables that could contain API keys, passwords, etc.
+- Altering the web server's static and executable files to redirect users to a malicious website.
+- Accessing other services running on the same network as the web server host.
+
+# How to identify a command injection vulnerability
+
+If application spawns a shell and passes user input into the shell command, this makes it vulnerable to command injection:
+
+```go
+cmdString := fmt.Sprintf("ping -c 2 %s", userInput)
+cmd := exec.Command("sh", "-c", cmdString)
+```
+
+In the above example, there is nothing stopping a user from passing in any command they would like to run, including `google.com; rm --no-preserve-root -rf /`, which ends the `ping` command and deletes every file on the host.
+
+# How to guard against command injection
+
+You should always use a built-in Golang library to run commands on the host. Rather than running `ping` via `exec.Command`, we might consider using this library instead: [pro-bing](https://github.com/prometheus-community/pro-bing).
+
+If there is a software running on the host that doesn't have a built-in Golang library, you should never spawn a shell to run commmands. Instead, run commands directly with the user input as arguments:
+
+```go
+cmd := exec.Command("ping", "-c", "2", userInput)
+```
+
+This will prevent the user from executing any other commands other than `ping`; however, keep in mimd that this does not prevent them from adding unauthorized arguments or flags to a command.
+
+# How to hack into the web server
+
+Our objective is to retrieve the file contents of the `flag_to_capture` file, so we need to run a `find` command at the root. In the URL field of the web application, we'll send this string to the web server to get the contents of the file:
+
+```sh
+google.com; cat $(find / -type f -name 'flag_to_capture')
+```
+
+*****Note***: Each time the Kubernetes pod hosting the web server is restarted, the flag file's contents will be different, and will be randomly placed in a different directory.
\ No newline at end of file