Ogre

The Image

We're provided with an address to pull a docker image from, so let's start by grabbing that - sudo docker pull ghcr.io/iciaran/ogre:ctf.

Once the image finishes we can run it with sudo docker run --rm ghcr.io/iciaran/ogre:ctf, the first thing we see is the output Server is listening on port 8080 so let's kill the container and run it again with the port mapped - sudo docker run --rm -p 8080:8080 ghcr.io/iciaran/ogre:ctf.

Now that the image is running we can take a look at what's running, it's a website that gives random random quotes from a film about an ogre!

Screenshot of website

The Source

From here we can easily get a shell inside the container and take a look at the webapp source with sudo docker exec -it <container name> sh.

const express = require("express");
const fs = require("fs");
const app = express();

app.set("view engine", "ejs");
app.use(express.static("public"));

const quotesRaw = fs.readFileSync("quotes.json");
const quotes = JSON.parse(quotesRaw);

app.get("/", function (req, res) {
  res.render("index", {
    quote: quotes[Math.floor(Math.random() * quotes.length)],
  });
});

app.listen(8080);
console.log("Server is listening on port 8080");

Hmm, there's really not a lot here, and browsing the other files in /app there's nothing else of interest either.

Let's take a step back, this is a forensics challenge not a web challenge after all...

The Image Returns

We can dump the contents of the docker image to a tar using the command sudo docker image save ghcr.io/iciaran/ogre:ctf > ogre.tar, and then extract with tar xvf ogre.tar.

Inside the tar we have a file called manifest.json which describes the image:

[
    {
        "Config": "0d847c76be9210105183f589735a4f515bf5cf368c63c99016c29c220df3d8cc.json",
        "RepoTags": [
            "ghcr.io/iciaran/ogre:ctf"
        ],
        "Layers": [
            "ec26cc0d3f68a58e262d2ae607ad72e2b460f739d374836e910cf6c820221f10/layer.tar",
            "ab1d13f817067475029582ba4a9a8287f67d44a4e8fb28453cda3c0d9d93fc08/layer.tar",
            "18ad72391f82b0121b2c9ea3813ec05d6d56f7a4d97f28e0e1115dcd7ad413d6/layer.tar",
            "569d094bcddab44ed9d0e3ef193036bce410d1cb3c9752968d6c2dc2a0056dc2/layer.tar",
            "a4c47b00a4323532d9c10539aaa462a1359858140c7702ab0c31a26fc610b81e/layer.tar",
            "e276065e4b2c552c18a2c83368994ff331f0b766812c09b35de873677d30cdee/layer.tar",
            "b21ab4c3c3d1788902435bc89b63ae52ca2de4e6c4a5090e784c2769261b1c7b/layer.tar",
            "1254d4ced290f6a923d57d88fa933a7b6a86e06c446c379e8821a2837c70bc7a/layer.tar",
            "3cb0ca143dee620beee4c66809258e5b7f869405b6b76a8186fea9fd5f82d3e4/layer.tar",
            "8a5c18d295804af53bb1c879af81f5f198105537cb9a7526fa7b61dff6419800/layer.tar",
            "de3e6d0b678ede7cf45541c6041d07b4407c88baf4ad3b246cce4aeb7372874d/layer.tar",
            "0a7c0c1b35f377bb0ee214abf3e9198434c0d2d2b30463c3c663fe76c19982a8/layer.tar",
            "9ff4587682f8b85407d644c67aa8e55eca41f60ec6ccb2af706860364eb10b87/layer.tar",
            "2ae1be894858f56130fc3639f7c487a2b4dfd9a6c1ed37158fab8b7c29f7ef4d/layer.tar",
            "4bacfff5efc7b2f3cb3c3c6d5cd00c9956aaff6b8c7090b29a12622e76abade4/layer.tar",
            "b5a6b55bfd7002ddf124bda89f1fc67d4ef00480c1cb5c825e24b98b3a386401/layer.tar"
        ]
    }
]

We can see a bunch of information about the image, but the most important right now is the Config property, which tells us the name of another json file: 0d847c76be9210105183f589735a4f515bf5cf368c63c99016c29c220df3d8cc.json.

Inside this next json file there is a property history which tells us exactly what was done in the Dockerfile between each layer of the image.

{
    "history": [
        {
            "created": "2022-06-17T00:01:23.98075728Z",
            "created_by": "RUN /bin/sh -c echo aWN0ZntvbmlvbnNfaGF2ZV9sYXllcnNfaW1hZ2VzX2hhdmVfbGF5ZXJzfQo= \u003e /tmp/secret # buildkit",
            "comment": "buildkit.dockerfile.v0"
        }
    ]
}

I've cut out most of the json from the file, except an object in the middle of the history property that looks interesting to us.

Converting the secret back from base64 reveals the flag: ictf{onions_have_layers_images_have_layers}.

The Shortcut

Docker has a command to do all of that manual image inspection to get to the history in one go: sudo docker history --no-trunc ghcr.io/iciaran/ogre:ctf, which makes things a lot easier!