From c79c9829bda8b5111174e5806fb3b12126a6f66e Mon Sep 17 00:00:00 2001 From: Carl Smith Date: Wed, 21 Feb 2024 00:46:27 +0100 Subject: [PATCH] [v8ctf] M122 release --- v8ctf/chrome-121/README.md | 55 +++++++++++++++ v8ctf/chrome-121/challenge.yaml | 27 ++++++++ v8ctf/chrome-121/challenge/Dockerfile | 56 +++++++++++++++ v8ctf/chrome-121/challenge/chal | 14 ++++ v8ctf/chrome-121/challenge/nsjail.cfg | 69 +++++++++++++++++++ v8ctf/chrome-121/healthcheck/Dockerfile | 18 +++++ v8ctf/chrome-121/healthcheck/README.md | 14 ++++ v8ctf/chrome-121/healthcheck/healthcheck.py | 50 ++++++++++++++ .../healthcheck/healthcheck_loop.sh | 32 +++++++++ .../healthcheck/healthz_webserver.py | 39 +++++++++++ v8ctf/chrome/challenge/Dockerfile | 4 +- 11 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 v8ctf/chrome-121/README.md create mode 100644 v8ctf/chrome-121/challenge.yaml create mode 100644 v8ctf/chrome-121/challenge/Dockerfile create mode 100755 v8ctf/chrome-121/challenge/chal create mode 100644 v8ctf/chrome-121/challenge/nsjail.cfg create mode 100644 v8ctf/chrome-121/healthcheck/Dockerfile create mode 100644 v8ctf/chrome-121/healthcheck/README.md create mode 100755 v8ctf/chrome-121/healthcheck/healthcheck.py create mode 100755 v8ctf/chrome-121/healthcheck/healthcheck_loop.sh create mode 100755 v8ctf/chrome-121/healthcheck/healthz_webserver.py diff --git a/v8ctf/chrome-121/README.md b/v8ctf/chrome-121/README.md new file mode 100644 index 00000000..44415f63 --- /dev/null +++ b/v8ctf/chrome-121/README.md @@ -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 ` 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. diff --git a/v8ctf/chrome-121/challenge.yaml b/v8ctf/chrome-121/challenge.yaml new file mode 100644 index 00000000..4f0a193e --- /dev/null +++ b/v8ctf/chrome-121/challenge.yaml @@ -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 diff --git a/v8ctf/chrome-121/challenge/Dockerfile b/v8ctf/chrome-121/challenge/Dockerfile new file mode 100644 index 00000000..cd010b9a --- /dev/null +++ b/v8ctf/chrome-121/challenge/Dockerfile @@ -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 diff --git a/v8ctf/chrome-121/challenge/chal b/v8ctf/chrome-121/challenge/chal new file mode 100755 index 00000000..089fa263 --- /dev/null +++ b/v8ctf/chrome-121/challenge/chal @@ -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}" diff --git a/v8ctf/chrome-121/challenge/nsjail.cfg b/v8ctf/chrome-121/challenge/nsjail.cfg new file mode 100644 index 00000000..05c1595a --- /dev/null +++ b/v8ctf/chrome-121/challenge/nsjail.cfg @@ -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 + } +] diff --git a/v8ctf/chrome-121/healthcheck/Dockerfile b/v8ctf/chrome-121/healthcheck/Dockerfile new file mode 100644 index 00000000..2df56306 --- /dev/null +++ b/v8ctf/chrome-121/healthcheck/Dockerfile @@ -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 diff --git a/v8ctf/chrome-121/healthcheck/README.md b/v8ctf/chrome-121/healthcheck/README.md new file mode 100644 index 00000000..8dbcd6a8 --- /dev/null +++ b/v8ctf/chrome-121/healthcheck/README.md @@ -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. diff --git a/v8ctf/chrome-121/healthcheck/healthcheck.py b/v8ctf/chrome-121/healthcheck/healthcheck.py new file mode 100755 index 00000000..774bf592 --- /dev/null +++ b/v8ctf/chrome-121/healthcheck/healthcheck.py @@ -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"" +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) diff --git a/v8ctf/chrome-121/healthcheck/healthcheck_loop.sh b/v8ctf/chrome-121/healthcheck/healthcheck_loop.sh new file mode 100755 index 00000000..acf69158 --- /dev/null +++ b/v8ctf/chrome-121/healthcheck/healthcheck_loop.sh @@ -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 diff --git a/v8ctf/chrome-121/healthcheck/healthz_webserver.py b/v8ctf/chrome-121/healthcheck/healthz_webserver.py new file mode 100755 index 00000000..62cf0198 --- /dev/null +++ b/v8ctf/chrome-121/healthcheck/healthz_webserver.py @@ -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() diff --git a/v8ctf/chrome/challenge/Dockerfile b/v8ctf/chrome/challenge/Dockerfile index cd010b9a..285f6858 100644 --- a/v8ctf/chrome/challenge/Dockerfile +++ b/v8ctf/chrome/challenge/Dockerfile @@ -31,8 +31,8 @@ RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key 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 +# This version is to be released on 2024-02-23 +RUN wget 'https://storage.googleapis.com/chrome-for-testing-public/122.0.6261.57/linux64/chrome-linux64.zip' -O /opt/chrome-linux.zip RUN cd /opt && unzip chrome-linux.zip && rm chrome-linux.zip COPY chal /opt/