PyRunner
Description
CTF: STACK the Flags 2022
Can you help us test our internal Python job runner prototype?
web_pyrunner.zip
Solution
Pwned by @skytect (opens in a new tab)
Opening the webpage, we see fields for a template and arguments, hinting at template injection.
We appear to be working with some form of Jinja template.
import uvicorn
from fastapi import FastAPI, Request, Response, Form, File
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.responses import JSONResponse
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
print("Webserver: <title>")
@app.get("/")
def index(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
if __name__ == "__main__":
print("Hello!")
#uvicorn.run("app:app", host="<host>", port=<port>, reload=True) #TODO: add support for files that don't exit
Since we're given the source code, let's dig through it.
Looking closer, we find a filter mechanism.
# /app/app.py L13
disallowed = ["import", ";", "\n", "eval", "exec", "os"]
# /app/app.py L29–33
def textfilter(text):
for i in disallowed:
if i in text:
text = text.replace(i, "")
return text
# /app/app.py L77–80
for argument in template["arguments"]:
contents = contents.replace(
f"<{argument}>", textfilter(data["arguments"][argument])
)
It appears that it just replaces text directly unless it's in the disallowed
blacklist.
Let's try to inject something. Using the payload >"+__file__+"<
, we can sanity
check that this actually works.
Now, let's try to get the flag. I dug around and found the flag's location in
the Dockerfile
.
# /Dockerfile L5
COPY ./src/flag /root/flag
The blacklist is actually quite easy to circumvent, since we just need to read a file.
I used this payload: >"+open("/root/flag", "r").read()+"<
.
Yay flag!
STF22{4ut0m4t3d_c0mm4nd_1nj3ct10n}