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

Adding kCTF challenge for jetty latest #97

Open
wants to merge 3 commits into
base: main
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
90 changes: 90 additions & 0 deletions jetty/chal-jetty/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Quickstart guide to writing a challenge

The basic steps when preparing a challenge are:

* A Docker image is built from the `challenge` directory. For the simplest challenges, replacing `challenge/chal.c` is sufficient.
* Edit `challenge/Dockerfile` to change the commandline or the files you want to include.
* To try the challenge locally, you will need to
* create a a local cluster with `kctf cluster create --type kind --start $configname`
* and then deploy the challenge with `kctf chal start`
* To access the challenge, create a port forward with `kctf chal debug port-forward` and connect to it via `nc localhost PORT` using the printed port.
* Check out `kctf chal <tab>` for more commands.

## Sandboxing

Sandboxing is only necessary for challenges that give players RCE-type of access. If a challenge does not provide such access, then it is reasonable to just use a normal HTTP server out of the box listening on port 1337, without any additonal sandboxing.

For challenges that give users RCE-level access, it is then necessary to sandbox every player. In order to make that possible, kCTF provides two ways to sandbox a web server:
1. **CGI-sandbox**: You can configure PHP (or any other CGI) to be sandboxed.
2. **Proxy sandbox**: You can configure an HTTP server that sandboxes every HTTP request.

A Proxy sandbox is a bit expensive, it starts an HTTP server on every TCP connection, hence it is a bit slow. A CGI sandbox is cheaper, and it just calls the normal CGI endpoint but with nsjail.

The template challenge has an example of both (NodeJS running as a proxy, and PHP running as CGI). It is recommended that static resources are served with only Apache, as to save CPU and RAM. This can be accomplished by configuring apache to redirect certain sub-paths to the sandboxed web server, but to serve directly all other paths.

## Directory layout

The following files/directories are available:

### /challenge.yaml

`challenge.yaml` is the main configuration file. You can use it to change
settings like the name and namespace of the challenge, the exposed ports, the
proof-of-work difficulty etc.
For documentation on the available fields, you can run `kubectl explain challenge` and
`kubectl explain challenge.spec`.

If you would like to have a shared directory (for sessions, or uploads), you can mount it using:


```yaml
spec:
persistentVolumeClaims:
- $PUT_THE_NAME_OF_THE_CHALLENGE_HERE
podTemplate:
template:
spec:
containers:
- name: challenge
volumeMounts:
- name: gcsfuse
subPath: sessions # this this a folder inside volume
mountPath: /mnt/disks/sessions
- name: gcsfuse
subPath: uploads
mountPath: /mnt/disks/uploads
volumes:
- name: gcsfuse
persistentVolumeClaim:
claimName: $PUT_THE_NAME_OF_THE_CHALLENGE_HERE
```

This will mount a file across all challenges in that directory. You can test this setup on a remote cluster using the PHP/CGI sandbox.

### /challenge

The `challenge` directory contains a Dockerfile that describes the challenge and
any challenge files. You can use the Dockerfile to build your challenge as well
if required.

### /healthcheck

The `healthcheck` directory is optional. If you don't want to write a healthcheck, feel free to delete it. However, we strongly recommend that you implement a healthcheck :).

We provide a basic healthcheck skeleton that uses pwntools to implement the
healthcheck code. The only requirement is that the healthcheck replies to GET
requests to http://$host:45281/healthz with either a success or an error status
code.

In most cases, you will only have to modify `healthcheck/healthcheck.py`.

## API contract

Ensure your setup fulfills the following requirements to ensure it works with kCTF:

* Verify `kctf_setup` is used as the first command in the CMD instruction of your `challenge/Dockerfile`.
* You can do pretty much whatever you want in the `challenge` directory but:
* We strongly recommend using nsjail in all challenges. While nsjail is already installed, you need to configure it in `challenge/nsjail.cfg`. For more information on nsjail, see the [official website](https://nsjail.dev/).
* Your challenge receives connections on port 1337. The port can be changed in `challenge.yaml`.
* The healthcheck directory is optional.
* If it exists, the image should run a webserver on port 45281 and respond to `/healthz` requests.
10 changes: 10 additions & 0 deletions jetty/chal-jetty/app-secrets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: jetty-secret-provider
spec:
provider: gke
parameters:
secrets: |
- resourceName: "projects/internet-ctf/secrets/jetty-root-flag/versions/latest"
path: "flag.txt"
64 changes: 64 additions & 0 deletions jetty/chal-jetty/challenge.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
apiVersion: kctf.dev/v1
kind: Challenge
metadata:
name: chal-jetty
spec:
deployed: true
powDifficultySeconds: 0
network:
public: true
ports:
- protocol: "TCP"
targetPort: 1337
podTemplate:
template:
spec:
containers:
- name: challenge
volumeMounts:
- name: flag-volume
mountPath: "/chroot/flag"
serviceAccountName: secret-readonly-sa
volumes:
- name: flag-volume
csi:
driver: secrets-store-gke.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: jetty-secret-provider
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 75
preference:
matchExpressions:
- key: node
operator: In
values:
- three
- weight: 50
preference:
matchExpressions:
- key: node
operator: In
values:
- two
- weight: 50
preference:
matchExpressions:
- key: node
operator: In
values:
- one
healthcheck:
# TIP: disable the healthcheck during development
enabled: true
image: europe-west4-docker.pkg.dev/internet-ctf/challenges/healthcheck:d610fcf30d926dba7751f7f28d6a86eece9793d880f1a7166922364b787ba0ff
image: europe-west4-docker.pkg.dev/internet-ctf/challenges/challenge:7fbd53a514dcfeb6920aa74bc9316cabd68df1f9f80c12d09c22c8e73a8ded83
89 changes: 89 additions & 0 deletions jetty/chal-jetty/challenge/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
FROM jetty as chroot

RUN java -jar $JETTY_HOME/start.jar --add-module=ee10-deploy

ADD webapp.war $JETTY_BASE/webapps/ROOT.war

ADD start.sh /start.sh

RUN cd $JETTY_HOME

USER root
RUN apt-get update \
&& apt-get install -yq --no-install-recommends \
socat

RUN chmod a+x /start.sh
RUN chmod -R a+wrx /var/lib/jetty


RUN mkdir -p /logs
RUN chmod -R +777 /logs

FROM europe-west4-docker.pkg.dev/internet-ctf/custom-images/kctf-source

VOLUME /chroot/var/lib/jetty
VOLUME /chroot/logs

COPY --from=chroot / /chroot

ADD nsjail.cfg /home/user/nsjail.cfg

# Apache config
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends tzdata apache2 \
&& ln -fs /usr/share/zoneinfo/Europe/Berlin /etc/localtime \
&& dpkg-reconfigure --frontend noninteractive tzdata \
&& rm -rf /var/lib/apt/lists/*

# config mod security
RUN apt-get update && apt-get install libapache2-mod-security2 -y
RUN a2enmod security2

ADD modsecurity.conf /etc/modsecurity/modsecurity.conf

RUN service apache2 start

# For Proxy
RUN ln -s /etc/apache2/mods-available/proxy.load /etc/apache2/mods-enabled/
RUN ln -s /etc/apache2/mods-available/proxy_http.load /etc/apache2/mods-enabled/


COPY apache2-kctf-nsjail.conf /etc/apache2/conf-enabled/

VOLUME /var/run/apache2

RUN apt-get update \
&& apt-get install -yq --no-install-recommends cron
RUN apt-get install -y --reinstall rsyslog

ADD cleanup.sh /cleanup.sh
RUN chmod +777 /cleanup.sh

COPY cron /etc/cron.d/cron
RUN chmod 0644 /etc/cron.d/cron
RUN crontab /etc/cron.d/cron

VOLUME /var/log
VOLUME /run
VOLUME /tmp

CMD service rsyslog start; \
cron; \
kctf_setup \
&& (kctf_drop_privs nsjail --config /home/user/nsjail.cfg --port 8080 -- /start.sh &) \
&& (bash -c 'source /etc/apache2/envvars && APACHE_RUN_USER=user APACHE_RUN_GROUP=user /usr/sbin/apache2 -D FOREGROUND' &)\
&& tail -f /var/log/syslog /var/log/apache2/modsec_audit.log
9 changes: 9 additions & 0 deletions jetty/chal-jetty/challenge/apache2-kctf-nsjail.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ServerName kctf-nsjail
Listen 1337
User user

<VirtualHost *:1337>
# For proxy sandboxing use the two lines below
ProxyPreserveHost On
ProxyPass "/" "http://localhost:8080/"
</VirtualHost>
19 changes: 19 additions & 0 deletions jetty/chal-jetty/challenge/cleanup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash

logFolder="/chroot/logs"

now=$(date +%s)
cutoff=$((now - 5*60))

for file in "$logFolder"/*.log
do
filename="${file##*/}"
file_time="${filename%.log}"
if [ $file_time -lt $cutoff ]; then
cat "$file" 2>&1 | /usr/bin/logger -t cronjob
rm "$file"
fi
unset filename file_time
sleep 1
cat /dev/null > /var/log/syslog
done
2 changes: 2 additions & 0 deletions jetty/chal-jetty/challenge/cron
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* * * * * /cleanup.sh
# Don't remove the empty line at the end of this file. It is required to run the cron job
1 change: 1 addition & 0 deletions jetty/chal-jetty/challenge/flag
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CTF{TestFlag}
Loading