-
Notifications
You must be signed in to change notification settings - Fork 416
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
07244cf
commit c79c982
Showing
11 changed files
with
376 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
apiVersion: kctf.dev/v1 | ||
kind: Challenge | ||
metadata: | ||
name: chrome-121 | ||
spec: | ||
deployed: true | ||
powDifficultySeconds: 1 | ||
network: | ||
public: true | ||
healthcheck: | ||
# TIP: disable the healthcheck during development | ||
enabled: true | ||
podTemplate: | ||
template: | ||
spec: | ||
containers: | ||
- name: challenge | ||
volumeMounts: | ||
- name: flag | ||
mountPath: /chroot/flag | ||
readOnly: true | ||
volumes: | ||
- name: flag | ||
secret: | ||
defaultMode: 0555 | ||
secretName: v8ctf-flag | ||
optional: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# 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 ubuntu:22.04 as chroot | ||
|
||
RUN /usr/sbin/useradd -u 1000 user | ||
|
||
RUN apt-get update && apt-get install -y gnupg2 wget | ||
|
||
# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others) | ||
# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer installs, work. | ||
# Deps from https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#chrome-headless-doesnt-launch-on-unix | ||
# plus libxshmfence1 which seems to be missing | ||
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ | ||
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ | ||
&& apt-get update \ | ||
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \ | ||
google-chrome-stable \ | ||
&& rm -rf /var/lib/apt/lists/* | ||
|
||
RUN apt-get update && apt-get install -y unzip | ||
|
||
RUN mkdir /home/user | ||
# This version is to be released on 2024-01-26 | ||
RUN wget 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/121.0.6167.85/linux64/chrome-linux64.zip' -O /opt/chrome-linux.zip | ||
RUN cd /opt && unzip chrome-linux.zip && rm chrome-linux.zip | ||
|
||
COPY chal /opt/ | ||
|
||
FROM gcr.io/kctf-docker/challenge@sha256:0f7d757bcda470c3bbc063606335b915e03795d72ba1d8fdb6f0f9ff3757364f | ||
|
||
COPY --from=chroot / /chroot | ||
RUN mkdir /chroot/dev/shm | ||
RUN touch /chroot/dev/null | ||
RUN touch /chroot/dev/zero | ||
RUN touch /chroot/dev/urandom | ||
|
||
RUN mkdir /chroot/run/dbus | ||
|
||
COPY nsjail.cfg /home/user/ | ||
|
||
CMD kctf_setup && \ | ||
kctf_drop_privs \ | ||
socat \ | ||
TCP-LISTEN:1337,reuseaddr,fork \ | ||
EXEC:"kctf_pow nsjail --config /home/user/nsjail.cfg -- /opt/chal",stderr |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#!/usr/bin/bash | ||
|
||
CHROME=/opt/chrome-linux64/chrome | ||
|
||
echo "Version: $($CHROME --version | head -n1)" | ||
echo "Please send me a URL to open." | ||
read -r url | ||
if ! echo $url | grep -E '^https?://[A-Za-z0-9.:/?%\-_+&=]*$' -q; then | ||
echo 'url regex fail' | ||
exit 1 | ||
fi | ||
|
||
dbus-daemon --system | ||
dbus-run-session -- $CHROME --headless=new --no-sandbox --disable-crashpad --disable-breakpad --disable-crash-reporter --enable-logging=stderr "${url}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# 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"} | ||
disable_rl: true | ||
clone_newnet: false | ||
|
||
cwd: "/home/user" | ||
|
||
mount: [ | ||
{ | ||
src: "/chroot" | ||
dst: "/" | ||
is_bind: true | ||
}, | ||
{ | ||
src: "/dev" | ||
dst: "/dev" | ||
is_bind: true | ||
}, | ||
{ | ||
dst: "/tmp" | ||
fstype: "tmpfs" | ||
rw: true | ||
}, | ||
{ | ||
dst: "/home/user" | ||
fstype: "tmpfs" | ||
rw: true | ||
}, | ||
{ | ||
dst: "/run/dbus" | ||
fstype: "tmpfs" | ||
rw: true | ||
}, | ||
{ | ||
dst: "/run/user" | ||
fstype: "tmpfs" | ||
rw: true | ||
}, | ||
{ | ||
dst: "/proc" | ||
fstype: "proc" | ||
rw: true | ||
}, | ||
{ | ||
src: "/etc/resolv.conf" | ||
dst: "/etc/resolv.conf" | ||
is_bind: true | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# 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 gcr.io/kctf-docker/healthcheck@sha256:6709709a8cfd6e2d743c86d58398c00ca4eb26befd3b1a0a629ab35f91e98ef0 | ||
|
||
COPY healthcheck_loop.sh healthcheck.py healthz_webserver.py /home/user/ | ||
|
||
CMD kctf_drop_privs /home/user/healthcheck_loop.sh & /home/user/healthz_webserver.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Healthcheck | ||
|
||
kCTF checks the health of challenges by accessing the healthcheck via | ||
http://host:45281/healthz which needs to return either 200 ok or an error | ||
depending on the status of the challenge. | ||
|
||
The default healthcheck consists of: | ||
* a loop that repeatedly calls a python script and writes the status to a file | ||
* a webserver that checks the file and serves /healthz | ||
* the actual healthcheck code using pwntools for convenience | ||
|
||
To modify it, you will likely only have to change the script in healthcheck.py. | ||
You can test if the challenge replies as expected or better add a full example | ||
solution that will try to get the flag from the challenge. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
# 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. | ||
|
||
import socket | ||
from pwn import * | ||
|
||
def handle_pow(r): | ||
print(r.recvuntil(b'python3 ')) | ||
print(r.recvuntil(b' solve ')) | ||
challenge = r.recvline().decode('ascii').strip() | ||
p = process(['kctf_bypass_pow', challenge]) | ||
solution = p.readall().strip() | ||
r.sendline(solution) | ||
print(r.recvuntil(b'Correct\n')) | ||
|
||
r = remote('127.0.0.1', 1337) | ||
print(r.recvuntil(b'== proof-of-work: ')) | ||
if r.recvline().startswith(b'enabled'): | ||
handle_pow(r) | ||
|
||
l = listen() | ||
l2 = listen() | ||
|
||
r.readuntil(b'URL to open.', timeout=10) | ||
r.sendline(bytes('http://localhost:{}/ok'.format(l.lport), 'ascii')) | ||
|
||
_ = l.wait_for_connection() | ||
|
||
print(l.readuntil(b'GET /ok HTTP/1.1')) | ||
content = f"<script>fetch('http://localhost:{l2.lport}/foo')</script>" | ||
response = f'HTTP/1.1 200 OK\nContent-Length: {len(content)}\n\n{content}' | ||
l.send(response.encode()) | ||
|
||
_ = l2.wait_for_connection() | ||
print(l2.readuntil(b'GET /foo HTTP/1.1')) | ||
|
||
exit(0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#!/bin/bash | ||
# 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. | ||
set -Eeuo pipefail | ||
|
||
TIMEOUT=20 | ||
PERIOD=30 | ||
|
||
export TERM=linux | ||
export TERMINFO=/etc/terminfo | ||
|
||
while true; do | ||
echo -n "[$(date)] " | ||
if timeout "${TIMEOUT}" /home/user/healthcheck.py; then | ||
echo 'ok' | tee /tmp/healthz | ||
else | ||
echo -n "$? " | ||
echo 'err' | tee /tmp/healthz | ||
fi | ||
sleep "${PERIOD}" | ||
done |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
# 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. | ||
import http.server | ||
|
||
class HealthzHandler(http.server.BaseHTTPRequestHandler): | ||
def do_GET(self): | ||
if self.path != '/healthz': | ||
self.send_response(404) | ||
self.send_header("Content-length", "0") | ||
self.end_headers() | ||
return | ||
|
||
content = b'err' | ||
try: | ||
with open('/tmp/healthz', 'rb') as fd: | ||
content = fd.read().strip() | ||
except: | ||
pass | ||
self.send_response(200 if content == b'ok' else 400) | ||
self.send_header("Content-type", "text/plain") | ||
self.send_header("Content-length", str(len(content))) | ||
self.end_headers() | ||
self.wfile.write(content) | ||
|
||
httpd = http.server.HTTPServer(('', 45281), HealthzHandler) | ||
httpd.serve_forever() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters