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

Working version of Challange Reverb #93

Open
wants to merge 1 commit 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
55 changes: 55 additions & 0 deletions others/chal-reverb/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# 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`
* build the challenge binary with `make -C challenge`
* 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.

## 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`.

### /challenge

The `challenge` directory contains a Dockerfile that describes the challenge and
any challenge files. This template comes with a Makefile to build the challenge,
which is the recommended way for pwnables if the deployed binary matters, e.g.
if you hand it out as an attachment for ROP gadgets.
If the binary layout doesn't matter, you can build it using an intermediate
container as part of the Dockerfile similar to how the chroot is created.

### /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 others/chal-reverb/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: reverb-secret-provider
spec:
provider: gke
parameters:
secrets: |
- resourceName: "projects/internet-ctf/secrets/reverb-root-flag/versions/latest"
path: "flag.txt"
33 changes: 33 additions & 0 deletions others/chal-reverb/challenge.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
apiVersion: kctf.dev/v1
kind: Challenge
metadata:
name: chal-reverb
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: reverb-secret-provider
healthcheck:
# TIP: disable the healthcheck during development
enabled: true
image: europe-west4-docker.pkg.dev/internet-ctf/challenges/healthcheck:27054f7b9c2c38bfbe153e0dbe7df4e2ab5536c5749cfde9f13997da8034e72f
image: europe-west4-docker.pkg.dev/internet-ctf/challenges/challenge:bf23a96280b24e6398b514bae83ce2587864365e7582be672a172b6d45e4ca40
83 changes: 83 additions & 0 deletions others/chal-reverb/challenge/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# 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 python:3.10-slim as chroot

RUN /usr/sbin/useradd -m -u 1000 user

RUN apt-get update \
&& apt-get install -yq --no-install-recommends \
python3 python3-pip socat nano

#Install socat
USER root
RUN echo 'deb http://archive.debian.org/debian/ rodete main' >> /etc/apt/sources.list
RUN apt-get update; exit 0
RUN apt-get install socat -y --force-yes; exit 0

RUN pip3 install numpy --upgrade
RUN pip3 install dm-tree
RUN pip3 install dm-reverb[tensorflow]
RUN pip3 install tensorrt
RUN pip3 install flask
RUN pip3 install numpy --upgrade; pip3 install dm-tree; pip3 install dm-reverb[tensorflow]

#Copy
RUN chmod +777 /home/user
ADD script.py /home/user
ADD app.py /home/user
RUN chmod +777 /home/user/script.py
RUN chmod +777 /home/user/app.py
RUN chmod +777 /dev/random
RUN chmod +777 /dev/urandom

RUN mkdir -p /logs
RUN touch /logs/log
RUN chmod -R +777 /logs
RUN chmod a=w /logs/log

ADD start.sh /home/user/start.sh
RUN chmod a+wx /home/user/start.sh

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

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

VOLUME /chroot/usr/local/lib/python3.10/dist-packages
VOLUME /chroot/dev
VOLUME /chroot/logs
COPY --from=chroot / /chroot

VOLUME /chroot/home/user

COPY nsjail.cfg /home/user/

CMD service rsyslog start; \
cron; \
kctf_setup && \
kctf_setup \
&& kctf_drop_privs nsjail --config /home/user/nsjail.cfg --port 1337 -- /home/user/start.sh \
&& tail -F /var/log/syslog
33 changes: 33 additions & 0 deletions others/chal-reverb/challenge/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from flask import Flask, request, render_template_string
import reverb

app = Flask(__name__)

html = """
<!doctype html>
<title>Execute Python Expression</title>
<h1>Enter a Python expression:</h1>
<form method=post>
<textarea name=expression rows=3 cols=40></textarea><br><br>
<input type=submit value="Evaluate">
</form>
<h2>Output:</h2>
<pre>{{ result }}</pre>
"""

@app.route('/', methods=['GET', 'POST'])
def index():
result = ""
if request.method == 'POST':
expression = request.form['expression']
try:
# define the variables and functions allowed, including the reverb lib
allowed_globals = {"__builtins__": None, "reverb": reverb}
result = eval(expression, allowed_globals, {})
except Exception as e:
result = f"Error: {str(e)}"

return render_template_string(html, result=result)

if __name__ == '__main__':
app.run(debug=True)
19 changes: 19 additions & 0 deletions others/chal-reverb/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 - 12*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 others/chal-reverb/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
17 changes: 17 additions & 0 deletions others/chal-reverb/challenge/flask.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from flask import Flask, request, jsonify
import sys

app = Flask(__name__)

@app.route('/execute', methods=['POST'])
def execute_python_code():
code = request.get_json()['code']
try:
# Execute the Python code
exec(code, globals())
return jsonify({'output': 'Code executed successfully!'})
except Exception as e:
return jsonify({'output': f'Error: {str(e)}'}), 400

if __name__ == '__main__':
app.run(debug=True)
81 changes: 81 additions & 0 deletions others/chal-reverb/challenge/nsjail.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# 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.

# See options available at https://github.com/google/nsjail/blob/master/config.proto

name: "default-nsjail-configuration"
description: "Default nsjail configuration for pwnable-style CTF task."

mode: ONCE
uidmap {inside_id: "1000"}
gidmap {inside_id: "1000"}
rlimit_as_type: HARD
rlimit_cpu_type: HARD
rlimit_nofile_type: HARD
rlimit_nproc_type: HARD

cwd: "/home/user"
hostname: "localhost"

envar: "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

mount: [
{
src: "/chroot"
dst: "/"
is_bind: true
},
{
dst: "/tmp"
fstype: "tmpfs"
rw: true
},
{
dst: "/proc"
fstype: "proc"
rw: true
},
{
src: "/dev"
dst: "/dev"
is_bind: true
},
{
src: "/dev/random"
dst: "/dev/random"
is_bind: true
},
{
dst: "/dev/urandom"
src: "/dev/urandom"
is_bind: true
},
{
src: "/chroot/home/user"
dst: "/home/user"
is_bind: true
rw: true
},
{
src: "/chroot/logs"
dst: "/logs"
is_bind: true
rw: true
},
{
src: "/etc/resolv.conf"
dst: "/etc/resolv.conf"
is_bind: true
}
]
31 changes: 31 additions & 0 deletions others/chal-reverb/challenge/script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import reverb
import tensorflow as tf
import numpy as np

OBSERVATION_SPEC = tf.TensorSpec([10, 10], tf.uint8)
ACTION_SPEC = tf.TensorSpec([2], tf.float32)

def agent_step(unused_timestep) -> tf.Tensor:
return tf.cast(tf.random.uniform(ACTION_SPEC.shape) > .5,
ACTION_SPEC.dtype)

def environment_step(unused_action) -> tf.Tensor:
return tf.cast(tf.random.uniform(OBSERVATION_SPEC.shape, maxval=256),
OBSERVATION_SPEC.dtype)


# Initialize the reverb server.
simple_server = reverb.Server(
tables=[
reverb.Table(
name='my_table',
sampler=reverb.selectors.Prioritized(priority_exponent=0.8),
remover=reverb.selectors.Fifo(),
max_size=100,
rate_limiter=reverb.rate_limiters.MinSize(1)),
],
# Sets the port to None to make the server pick one automatically.
# This can be omitted as it's the default.
port=8888)

simple_server.wait()
7 changes: 7 additions & 0 deletions others/chal-reverb/challenge/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

python3 script.py > /logs/$(date +%s).log 2>&1 &
flask --app app run > /logs/$(date +%s).log 2>&1 &

# Proxy stdin/stdout to server
socat - TCP:127.0.0.1:5000,forever
Loading