Skip to content

Commit

Permalink
v8ctf setup and rules (#57)
Browse files Browse the repository at this point in the history
* v8ctf setup and rules
  • Loading branch information
sroettger authored Oct 6, 2023
1 parent d98a553 commit dc4333a
Show file tree
Hide file tree
Showing 63 changed files with 16,158 additions and 0 deletions.
13 changes: 13 additions & 0 deletions v8ctf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# v8CTF challenge

This challenge is part of the v8CTF, an exploit VRP for the v8 JavaScript engine.

See RULES.md for how to participate.

You can reach it at `nc v8.ctfcompetition.com 1337`.

It runs a `chrome --headless=new` on a user-provided URL. You can find the command line in chrome/challenge/chal and the Chrome version in chrome/challenge/Dockerfile.

The flag is at /flag/flag and is in the format `v8CTF{.*}`.

If you want to recreate the environment locally, check out https://google.github.io/kctf/ for tips on how to use the kCTF infrastructure.
41 changes: 41 additions & 0 deletions v8ctf/RULES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# v8CTF Rules

The v8CTF is a part of the [Google VRP](https://g.co/vrp) in which we reward successful exploitation attempts against a V8 version running on our infrastructure.
This program is orthogonal to the [Chrome VRP](https://g.co/chrome/vrp), if you find a bug and exploit it, you can submit the bug to the Chrome VRP and use the exploit for the v8CTF.

In the following, we will differentiate between 0-day and n-day exploits.
If the bug that led to the initial memory corruption was found by you, i.e. reported from the same email address as used in the v8CTF submission, we will consider the exploit a 0-day submission.
All other exploits are considered n-day submissions.

## Rules

The following rules apply to the eligibility of exploits:
* Your exploit needs to exfiltrate the flag from our v8CTF infrastructure.
* Only the first submission for a given bug that leads to the initial memory corruption is eligible.
* Only the first submission per deployed V8 version in v8CTF is eligible based on the timestamp of the form submission.
* 0-day submissions are exempt from this limit.
* Exploits need to be reasonably fast and stable. We accept submissions with an average runtime of less than 5 minutes and at least 80% success rate.
* Valid submissions get a reward of $10,000.

## Submission Process

1. If your exploit targets a 0-day vulnerability, make sure to report it first to the [Chrome VRP](https://g.co/chrome/vrp).
1. Check [this sheet](https://docs.google.com/spreadsheets/d/e/2PACX-1vTWvO0tFNl8fJbOmTV1nwGJi4fAy5pDg-6DsHARRubj8I6c7_11RQ36Jv735zj9EQggz6AWjAOaebJh/pubhtml?gid=0&single=true) if there’s already a submission for the currently deployed V8 version.
1. Exploit the bug and capture the flag from our v8CTF environment.
1. Create a .tar.gz archive of your exploit and calculate its sha256, e.g. with `sha256sum exploit.tar.gz`.
1. Please double check that the exploit doesn’t have any external dependencies.
1. Fill out [this form](https://docs.google.com/forms/d/e/1FAIpQLScoWE5-XoF85dXMjWKTIrJGTEfCybFaktsYZMCZ86iFPrW8Ew/viewform?usp=header_link) with the flag and the exploit sha256 sum.
1. For 0-day submissions, please use the same email address you reported the bug from.
1. A bug in the Google Issue Tracker will be filed on your behalf. Attach the exploit matching the sha256 sum and a short write up to the bug.
1. Give us a few days to validate your submission.

## Setup

You can find a description of our v8CTF infrastructure in the [README](https://github.com/google/security-research/blob/master/v8ctf/readme.md).

## Communication

We have two discord channels set up on the [Capture The Flag](https://discord.gg/hqcSdTk6vm) server:

* #v8ctf-announcements: will be used for announcements such as changes to the rules.
* #v8ctf: is open to all. If you have any questions, please ask here.
55 changes: 55 additions & 0 deletions v8ctf/chrome/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.
27 changes: 27 additions & 0 deletions v8ctf/chrome/challenge.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
apiVersion: kctf.dev/v1
kind: Challenge
metadata:
name: chrome
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
55 changes: 55 additions & 0 deletions v8ctf/chrome/challenge/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# 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 --no-create-home -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
RUN wget 'https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F1181205%2Fchrome-linux.zip?generation=1691535836009137&alt=media' -O /home/user/chrome-linux.zip
RUN cd /home/user && unzip chrome-linux.zip && rm chrome-linux.zip

COPY chal /home/user/

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 -- /home/user/chal",stderr
15 changes: 15 additions & 0 deletions v8ctf/chrome/challenge/chal
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/bash

CHROME=/home/user/chrome-linux/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

export HOME=/tmp
dbus-daemon --system
dbus-run-session -- $CHROME --headless=new --no-sandbox --disable-crashpad --disable-breakpad --disable-crash-reporter --user-data-dir=/tmp/chrome-userdata --enable-logging=stderr "${url}"
79 changes: 79 additions & 0 deletions v8ctf/chrome/challenge/nsjail.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# 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/null"
dst: "/dev/null"
is_bind: true
},
{
src: "/dev/zero"
dst: "/dev/zero"
is_bind: true
},
{
src: "/dev/urandom"
dst: "/dev/urandom"
is_bind: true
},
{
dst: "/tmp"
fstype: "tmpfs"
rw: true
},
{
dst: "/run/dbus"
fstype: "tmpfs"
rw: true
},
{
dst: "/run/user"
fstype: "tmpfs"
rw: true
},
{
dst: "/dev/shm"
fstype: "tmpfs"
rw: true
},
{
dst: "/proc"
fstype: "proc"
rw: true
},
{
src: "/etc/resolv.conf"
dst: "/etc/resolv.conf"
is_bind: true
}
]
18 changes: 18 additions & 0 deletions v8ctf/chrome/healthcheck/Dockerfile
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
14 changes: 14 additions & 0 deletions v8ctf/chrome/healthcheck/README.md
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.
31 changes: 31 additions & 0 deletions v8ctf/chrome/healthcheck/healthcheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/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 *

r = remote('127.0.0.1', 1337)
l = listen()

r.readuntil(b'URL to open.', timeout=10)
r.sendline(bytes('http://localhost:{}/ok'.format(l.lport), 'ascii'))

_ = l.wait_for_connection()

l.readuntil(b'GET /ok HTTP/1.1')
l.send(b'HTTP/1.1 200 OK\nContent-Length: 0\n\n')

exit (0)
32 changes: 32 additions & 0 deletions v8ctf/chrome/healthcheck/healthcheck_loop.sh
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
Loading

0 comments on commit dc4333a

Please sign in to comment.