forked from google/google-ctf
-
Notifications
You must be signed in to change notification settings - Fork 0
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
Showing
23 changed files
with
619 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
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,29 @@ | ||
FROM kctf-nsjail | ||
|
||
RUN apt-get -y update | ||
RUN apt-get -y upgrade | ||
|
||
RUN mkdir /home/user | ||
|
||
RUN apt-get install -y nodejs npm | ||
RUN cd /home/user && npm install puppeteer | ||
|
||
# See https://crbug.com/795759 | ||
RUN apt-get install -yq libgconf-2-4 | ||
|
||
# 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. | ||
RUN apt-get update && apt-get install -y wget --no-install-recommends \ | ||
&& 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 \ | ||
&& apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst \ | ||
--no-install-recommends | ||
|
||
RUN cd /home/user && npm install proof-of-work | ||
|
||
COPY files /home/user | ||
|
||
# Since we don't need nsjail here, we could also run as user in the k8s config | ||
CMD exec setpriv --init-groups --reset-env --reuid user --regid user --inh-caps=-all -- /usr/bin/bash -c 'while true; do /usr/bin/node /home/user/bot.js; 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,227 @@ | ||
.PHONY: start stop docker ip status logs ssh clean .deploy .maybe-expose .cluster-config .deployment .autoscaling .secrets .config .healthcheck-secrets .healthcheck-exploit-key-secret .healthcheck-config .FORCE | ||
|
||
SHELL := bash | ||
.ONESHELL: | ||
.SHELLFLAGS = -e -c | ||
|
||
PROJECT:=CONFIGMISSING | ||
CLUSTER_NAME:=CONFIGMISSING | ||
ZONE:=CONFIGMISSING | ||
-include $(HOME)/.config/kctf/cluster.conf | ||
KUBECONFIG=$(HOME)/.config/kctf/kube.conf | ||
export KUBECONFIG | ||
|
||
CHALLENGE_NAME:=$(shell basename ${CURDIR}) | ||
CLUSTER_GEN=gen/${PROJECT}_${ZONE}_${CLUSTER_NAME} | ||
DEPLOYMENT_CONF_DIR=${CLUSTER_GEN}/deployment-conf | ||
|
||
docker: gen/docker-image | ||
|
||
start: | ||
source chal.conf | ||
if [ $${DEPLOY} = "true" ]; then | ||
$(MAKE) .deploy | ||
else | ||
echo "skipping deployment: DEPLOY=\"$${DEPLOY}\"" | ||
fi | ||
|
||
stop: .cluster-config | ||
for resource_type in deployment service configMap hpa; do | ||
kubectl get "$${resource_type}/${CHALLENGE_NAME}" >/dev/null 2>&1 && kubectl delete "$${resource_type}/${CHALLENGE_NAME}" || true | ||
done | ||
kubectl get "secret/${CHALLENGE_NAME}-secrets" >/dev/null 2>&1 && kubectl delete "secret/${CHALLENGE_NAME}-secrets" || true | ||
for resource in "secret/${CHALLENGE_NAME}-healthcheck-exploit-key" "secret/${CHALLENGE_NAME}-healthcheck-secrets" "configMap/${CHALLENGE_NAME}-healthcheck-config"; do | ||
kubectl get "$${resource}" >/dev/null 2>&1 && kubectl delete "$${resource}" || true | ||
done | ||
|
||
ip: .cluster-config | ||
LB_IP="" | ||
while [ -z "$${LB_IP}" ]; do | ||
LB_IP=$$(kubectl get "service/${CHALLENGE_NAME}" -o=jsonpath='{.status.loadBalancer.ingress[0].ip}') | ||
sleep 3 | ||
done | ||
echo "$${LB_IP}" | ||
|
||
status: .cluster-config | ||
@echo "= INSTANCES / PODs =" | ||
@echo | ||
@echo "Challenge execution status" | ||
@echo "This shows you how many instances of the challenges are running." | ||
@echo | ||
@kubectl get pods -l "app=${CHALLENGE_NAME}" -o wide | ||
@echo | ||
@echo | ||
@echo "= DEPLOYMENTS =" | ||
@echo | ||
@echo "Challenge deployment status" | ||
@echo "This shows you if the challenge was deployed to the cluster." | ||
@echo | ||
@kubectl get deployments -l "app=${CHALLENGE_NAME}" -o wide | ||
@echo | ||
@echo "= EXTERNAL SERVICES =" | ||
@echo | ||
@echo "Challenge external status" | ||
@echo "This shows you if the challenge is exposed externally." | ||
@echo | ||
@kubectl get services -l "app=${CHALLENGE_NAME}" -o wide | ||
@echo | ||
|
||
logs: .cluster-config | ||
kubectl logs -l "app=${CHALLENGE_NAME}" -c challenge | ||
|
||
ssh: .cluster-config | ||
kubectl exec deployment/${CHALLENGE_NAME} -c challenge -it /bin/bash | ||
|
||
clean: | ||
rm -R gen/* || true | ||
rm -R healthcheck/gen/* || true | ||
|
||
.deploy: .autoscaling .deployment .secrets .config .healthcheck-secrets .healthcheck-exploit-key-secret .healthcheck-config .cluster-config .maybe-expose | ||
|
||
.maybe-expose: | .cluster-config | ||
source chal.conf | ||
if [ "$${PUBLIC}" = "true" ]; then | ||
kubectl apply -f k8s/network.yaml | ||
else | ||
kubectl get "service/${CHALLENGE_NAME}" >/dev/null 2>&1 && kubectl delete "service/${CHALLENGE_NAME}" || true | ||
fi | ||
|
||
.autoscaling: | .cluster-config | ||
kubectl apply -f k8s/autoscaling.yaml | ||
|
||
.deployment: ${DEPLOYMENT_CONF_DIR} ${CLUSTER_GEN}/image-pushed ${CLUSTER_GEN}/remote-image ${CLUSTER_GEN}/healthcheck-image-pushed ${CLUSTER_GEN}/remote-healthcheck-image | .cluster-config | ||
kubectl apply -k ${DEPLOYMENT_CONF_DIR} | ||
# update the challenge container if the image changed | ||
PUSHED_IMAGE="$$(cat ${CLUSTER_GEN}/image-tagged)" | ||
CHAL_IMAGE="$$(cat ${CLUSTER_GEN}/remote-image)" | ||
if [ $${CHAL_IMAGE} != $${PUSHED_IMAGE} ]; then | ||
kubectl set image "deployment/${CHALLENGE_NAME}" "challenge=$${PUSHED_IMAGE}" | ||
fi | ||
# update the healthcheck container if the image changed | ||
PUSHED_HEALTHCHECK_IMAGE="$$(cat ${CLUSTER_GEN}/healthcheck-image-tagged)" | ||
HEALTHCHECK_IMAGE="$$(cat ${CLUSTER_GEN}/remote-healthcheck-image)" | ||
if [ $${HEALTHCHECK_IMAGE} != $${PUSHED_HEALTHCHECK_IMAGE} ]; then | ||
kubectl set image "deployment/${CHALLENGE_NAME}" "healthcheck=$${HEALTHCHECK_IMAGE}" | ||
fi | ||
|
||
.secrets: $(shell find secrets) | .cluster-config | ||
kubectl apply -k secrets | ||
|
||
.config: $(shell find config) | .cluster-config | ||
kubectl apply -k config | ||
|
||
gen/docker-image: Dockerfile files gen/src $(shell find files) ../kctf-conf/base/nsjail-docker/gen/docker-image | ||
docker build -t "kctf-chal-${CHALLENGE_NAME}" . | ||
echo $$(docker image ls "kctf-chal-${CHALLENGE_NAME}" -q) > $@ | ||
|
||
gen/src: .FORCE | ||
$(MAKE) -C src ../gen/src | ||
|
||
${CLUSTER_GEN}/image-tagged: gen/docker-image | .cluster-config | ||
IMAGE_ID="$$(cat gen/docker-image)" | ||
IMAGE_TAG="eu.gcr.io/${PROJECT}/${CHALLENGE_NAME}:$${IMAGE_ID}" | ||
docker tag "kctf-chal-${CHALLENGE_NAME}" "$${IMAGE_TAG}" | ||
echo -n "$${IMAGE_TAG}" > $@ | ||
|
||
${CLUSTER_GEN}/image-pushed: ${CLUSTER_GEN}/image-tagged | ||
IMAGE_TAG="$$(cat ${CLUSTER_GEN}/image-tagged)" | ||
docker push "$${IMAGE_TAG}" | ||
touch $@ | ||
|
||
gen/healthcheck-docker-image: healthcheck/Dockerfile healthcheck/gen/exploit.cpio.enc $(shell find healthcheck/files) | ||
docker build -t "kctf-healthcheck-${CHALLENGE_NAME}" healthcheck | ||
echo $$(docker image ls "kctf-healthcheck-${CHALLENGE_NAME}" -q) > $@ | ||
|
||
healthcheck/gen/exploit.cpio.enc: healthcheck/gen/exploit.cpio healthcheck/gen/exploit.key | ||
openssl aes-256-cbc -e -in healthcheck/gen/exploit.cpio -out $@ -K "$$(cat healthcheck/gen/exploit.key)" -nosalt -iv 00000000000000000000000000000000 | ||
|
||
healthcheck/gen/exploit.cpio: $(shell find healthcheck/exploit) | ||
pushd ${@D} | ||
rm -R exploit 2>/dev/null || true | ||
cp -R ../exploit . | ||
for f in $$(find exploit); do | ||
TZ="UTC" touch -a -m -t 198001010000.00 $$f | ||
done | ||
rm exploit.cpio || true | ||
find exploit -print0 | sort -z | cpio -0 --reproducible -R 0:0 -o > exploit.cpio | ||
rm -R exploit | ||
popd | ||
|
||
healthcheck/gen/exploit.key: healthcheck/gen/exploit.cpio | ||
sha256sum healthcheck/gen/exploit.cpio | awk '{print $$1}' > $@ | ||
|
||
${CLUSTER_GEN}/healthcheck-image-tagged: gen/healthcheck-docker-image | .cluster-config | ||
IMAGE_ID="$$(cat gen/healthcheck-docker-image)" | ||
IMAGE_TAG="eu.gcr.io/${PROJECT}/${CHALLENGE_NAME}-healthcheck" | ||
docker tag "kctf-healthcheck-${CHALLENGE_NAME}" "$${IMAGE_TAG}" | ||
echo -n "$${IMAGE_TAG}" > $@ | ||
|
||
${CLUSTER_GEN}/healthcheck-image-pushed: ${CLUSTER_GEN}/healthcheck-image-tagged | ||
IMAGE_TAG="$$(cat ${CLUSTER_GEN}/healthcheck-image-tagged)" | ||
docker push "$${IMAGE_TAG}" | ||
touch $@ | ||
|
||
healthcheck/gen/exploit-key.yaml: healthcheck/gen/exploit.key | .cluster-config | ||
kubectl create secret generic "${CHALLENGE_NAME}-healthcheck-exploit-key" --from-file=exploit.key=healthcheck/gen/exploit.key --dry-run -o yaml > $@ | ||
|
||
.healthcheck-exploit-key-secret: healthcheck/gen/exploit-key.yaml | .cluster-config | ||
kubectl apply -f healthcheck/gen/exploit-key.yaml | ||
|
||
.healthcheck-secrets: $(shell find healthcheck/secrets) | .cluster-config | ||
kubectl apply -k healthcheck/secrets | ||
|
||
.healthcheck-config: $(shell find healthcheck/config) | .cluster-config | ||
kubectl apply -k healthcheck/config | ||
|
||
../kctf-conf/base/nsjail-docker/gen/docker-image: .FORCE | ||
$(MAKE) -C ${@D}/.. gen/docker-image | ||
|
||
define CLUSTER_KUSTOMIZATION | ||
bases: | ||
- ../../../k8s | ||
patchesStrategicMerge: | ||
- update_image_name.yaml | ||
endef | ||
|
||
define UPDATE_IMAGE_NAME | ||
apiVersion: apps/v1 | ||
kind: Deployment | ||
metadata: | ||
name: ${CHALLENGE_NAME} | ||
spec: | ||
template: | ||
spec: | ||
containers: | ||
- name: challenge | ||
image: CHAL_IMAGE_PLACEHOLDER | ||
- name: healthcheck | ||
image: HEALTHCHECK_IMAGE_PLACEHOLDER | ||
endef | ||
|
||
export CLUSTER_KUSTOMIZATION | ||
export UPDATE_IMAGE_NAME | ||
${DEPLOYMENT_CONF_DIR}: ${CLUSTER_GEN}/remote-image ${CLUSTER_GEN}/remote-healthcheck-image | .cluster-config | ||
mkdir -p $@ | ||
echo "$${CLUSTER_KUSTOMIZATION}" > "${DEPLOYMENT_CONF_DIR}/kustomization.yaml" | ||
CHAL_IMAGE="$$(cat ${CLUSTER_GEN}/remote-image)" | ||
HEALTHCHECK_IMAGE="$$(cat ${CLUSTER_GEN}/remote-healthcheck-image)" | ||
echo "$${UPDATE_IMAGE_NAME}" | sed "s#CHAL_IMAGE_PLACEHOLDER#$${CHAL_IMAGE}#" | sed "s#HEALTHCHECK_IMAGE_PLACEHOLDER#$${HEALTHCHECK_IMAGE}#" > "${DEPLOYMENT_CONF_DIR}/update_image_name.yaml" | ||
touch $@ | ||
|
||
${CLUSTER_GEN}/remote-image: ${CLUSTER_GEN}/image-tagged .FORCE | ||
PUSHED_IMAGE="$$(cat ${CLUSTER_GEN}/image-tagged)" | ||
(kubectl get deployment/${CHALLENGE_NAME} -o jsonpath='{.spec.template.spec.containers[?(@.name == "challenge")].image}' 2>/dev/null || echo -n "$${PUSHED_IMAGE}") > $@ | ||
|
||
${CLUSTER_GEN}/remote-healthcheck-image: ${CLUSTER_GEN}/healthcheck-image-tagged .FORCE | ||
PUSHED_IMAGE="$$(cat ${CLUSTER_GEN}/healthcheck-image-tagged)" | ||
(kubectl get deployment/${CHALLENGE_NAME} -o jsonpath='{.spec.template.spec.containers[?(@.name == "healthcheck")].image}' 2>/dev/null || echo -n "$${PUSHED_IMAGE}") > $@ | ||
|
||
.cluster-config: | ||
@if [ "${PROJECT}" = "CONFIGMISSING" ]; then | ||
@ echo 'error: config not loaded' | ||
@ exit 1 | ||
@fi | ||
kubectl config use-context "kctf_${PROJECT}_${ZONE}_${CLUSTER_NAME}" | ||
mkdir -p ${CLUSTER_GEN} | ||
|
||
.FORCE: |
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,3 @@ | ||
# this file will be sourced by the deployment scripts | ||
DEPLOY="true" | ||
PUBLIC="false" |
10 changes: 10 additions & 0 deletions
10
infrastructure/kctf/samples/xss-bot/config/kustomization.yaml
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,10 @@ | ||
configMapGenerator: | ||
- name: xss-bot-config | ||
files: | ||
- pow | ||
generatorOptions: | ||
disableNameSuffixHash: true | ||
labels: | ||
type: generated | ||
annotations: | ||
note: generated |
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 @@ | ||
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,100 @@ | ||
const puppeteer = require('puppeteer'); | ||
const process = require('process'); | ||
const fs = require('fs'); | ||
const net = require('net'); | ||
const pow = require('proof-of-work'); | ||
const crypto = require("crypto"); | ||
|
||
(async function(){ | ||
const browser = await puppeteer.launch(); | ||
|
||
function ask_for_url(socket) { | ||
socket.state = 'URL'; | ||
socket.write('Please send me a URL to open.\n'); | ||
} | ||
|
||
function ask_for_pow(socket) { | ||
let pow_difficulty = Number.parseInt(fs.readFileSync('/config/pow')); | ||
if (pow_difficulty) { | ||
socket.write('Proof-of-work enabled.\n'); | ||
verifier = new pow.Verifier({ | ||
size: 1024, | ||
n: 16, | ||
complexity: pow_difficulty, | ||
prefix: crypto.randomBytes(2), | ||
validity: 180000 | ||
}); | ||
socket.state = 'POW'; | ||
socket.write(`Please solve a proof-of work with difficulty ${pow_difficulty} and prefix ${verifier.prefix.toString('hex')} using https://www.npmjs.com/package/proof-of-work\n`); | ||
} else { | ||
socket.write('Proof-of-work disabled.\n'); | ||
ask_for_url(socket); | ||
} | ||
} | ||
|
||
function validate_pow(socket, data) { | ||
if (verifier.check(Buffer.from(data.toString().trim(), 'hex'))) { | ||
socket.write('Proof-of-work verified.\n'); | ||
ask_for_url(socket); | ||
} else { | ||
socket.state = 'ERROR'; | ||
socket.write('Proof-of-work invalid.\n'); | ||
socket.destroy(); | ||
} | ||
} | ||
|
||
async function load_url(socket, data) { | ||
let url = data.toString().trim(); | ||
console.log(`checking url: ${url}`); | ||
if (!url.startsWith('http://') && !url.startsWith('https://')) { | ||
socket.state = 'ERROR'; | ||
socket.write('Invalid scheme (http/https only).\n'); | ||
socket.destroy(); | ||
return; | ||
} | ||
socket.state = 'LOADED'; | ||
let cookie = JSON.parse(fs.readFileSync('/secrets/cookie')); | ||
|
||
const context = await browser.createIncognitoBrowserContext(); | ||
const page = await context.newPage(); | ||
await page.setCookie(cookie); | ||
socket.write(`Loading page ${url}.\n`); | ||
await page.goto(url); | ||
setTimeout(()=>{ | ||
try { | ||
page.close(); | ||
socket.write('timeout\n'); | ||
socket.destroy(); | ||
} catch (err) { | ||
console.log(`err: ${err}`); | ||
} | ||
}, 60000); | ||
} | ||
|
||
var server = net.createServer(); | ||
server.listen(1337); | ||
console.log('listening on port 1337'); | ||
|
||
server.on('connection', socket=>{ | ||
socket.captcha = 'foo'; | ||
|
||
socket.on('data', data=>{ | ||
try { | ||
if (socket.state == 'POW') { | ||
validate_pow(socket, data); | ||
} else if (socket.state == 'URL') { | ||
load_url(socket, data); | ||
} | ||
} catch (err) { | ||
console.log(`err: ${err}`); | ||
} | ||
}); | ||
|
||
try { | ||
ask_for_pow(socket); | ||
} catch (err) { | ||
console.log(`err: ${err}`); | ||
} | ||
}); | ||
})(); | ||
|
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,3 @@ | ||
# this directory is for Makefile generated outputs | ||
* | ||
!.gitignore |
17 changes: 17 additions & 0 deletions
17
infrastructure/kctf/samples/xss-bot/healthcheck/Dockerfile
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,17 @@ | ||
FROM ubuntu:19.10 | ||
|
||
RUN apt-get -y update | ||
RUN apt-get -y upgrade | ||
|
||
RUN apt-get -y install python2.7 python-pip | ||
RUN pip install pwntools | ||
|
||
RUN apt-get -y install cpio | ||
|
||
COPY gen/exploit.cpio.enc / | ||
|
||
RUN /usr/sbin/useradd --no-create-home -u 1000 user | ||
COPY files /home/user | ||
RUN mkdir /home/user/.pwntools-cache && echo never > /home/user/.pwntools-cache/update | ||
|
||
CMD openssl aes-256-cbc -d -in exploit.cpio.enc -out exploit.cpio -K "$(cat /keys/exploit.key)" -nosalt -iv 00000000000000000000000000000000 && < exploit.cpio cpio -i && rm /exploit.cpio.enc && rm /exploit.cpio && setpriv --init-groups --reuid user --regid user --inh-caps=-all -- /exploit/run.sh & /home/user/healthz.py |
Oops, something went wrong.