PicoCTF_2017: TW_GR_E1_ART

Category: Web Exploitation Points: 100 Description:

Oh, sweet, they made a spinoff game to Toaster Wars! That last room has a lot of flags in it though. I wonder which is the right one...? Check it out here.

Hint:

I think this game is running on a Node.js server. If it's configured poorly, you may be able to access the server's source. If my memory serves me correctly, Node servers have a special file that lists dependencies and a start command; maybe you can use that file to figure out where the other files are?

Write-up

All NodeJS servers generally depend on dependencies(hah!). Let's try accessing http://shell2017.picoctf.com:16929/package.json.

{
  "name": "rogue-1",
  "version": "1.0.0",
  "main": "server/serv.js",
  "dependencies": {
    "beautiful-log": "^1.3.0",
    "body-parser": "^1.16.0",
    "callsite": "^1.0.0",
    "clone": "^2.1.0",
    "colors": "^1.1.2",
    "cookie-parser": "^1.4.3",
    "deep-diff": "^0.3.4",
    "dequeue": "^1.0.5",
    "express": "^4.14.1",
    "mongodb": "^2.2.25",
    "morgan": "^1.7.0",
    "nconf": "^0.8.4",
    "promise": "^7.1.1",
    "socket.io": "^1.7.2",
    "sprintf": "^0.1.5"
  },
  "devDependencies": {},
  "scripts": {
    "prestart": "node server/init.js",
    "start": "node server/serv.js"
  }
}

Interesting. Let's try looking at http://shell2017.picoctf.com:16929/server/init.js

var mongo   = require("mongodb").MongoClient;
var nconf   = require("nconf");

nconf.argv().env();

let db;

mongo.connect(`mongodb://localhost:27017/blundertale`)
    .then((d) => {
        db = d;
        return db.authenticate(nconf.get("MONGO_USER"), nconf.get("MONGO_PASS"));
    })
    .then(() => {
        return db.createCollection("games");
    })
    .catch((err) => {
        console.error("[DB] DB connection failed", err);
    })
    .then(() => {
        db.close();
    });

Is this database I am seeing? What's this nconf? Let's take a look at http://shell2017.picoctf.com:16929/server/serv.js.

var express     = require("express");
var app         = express();

app.use(require("body-parser").json());
app.use(require("cookie-parser")());
// app.use(require("morgan")("dev"));

var http        = require("http").Server(app);
var path        = require("path");
var fs          = require("fs");
var Promise     = require("promise");
var logger      = require("./logger");
var sprintf     = require("sprintf");
var nconf       = require("nconf");
var db          = require("./db");
var io          = require("socket.io")(http);

require("./game")(app, io);

nconf.argv().env();

var PORT = nconf.get("port") || 8888;

app.get("/", function(req, res){
    res.status(200);
    res.sendFile(path.join(__dirname, "../public/html/index.html"));
});

app.use(express.static(path.join(__dirname, "..")));

http.listen(PORT, function(){
    logger.info("[server] Listening on *:" + PORT);
});

process.on("unhandledRejection", (err) => {
    logger.error(err.stack);
});

Let's try looking into game.js

            case "revealFlag":
                if (entity.items[action.item].effects[i].check == 64) {
                    outcome.flag = process.env["PICO_CTF_FLAG"];
                }
                break;

What's this? If .check is 64, we get the flag? Let's take a look at the config.js.

function createFlag(check, location) {
    return {
        name: "Flag",
        description: "Gives you the flag... maybe.",
        location: location,
        use: 0,
        id: check + 100,
        sprite: "flag",
        effects: [
            {
                type: "revealFlag",
                check: check
            },
            {
                type: "destroyItems"
            }
        ]
    };
}
...
items: Array.from(new Array(83), (_, idx) => {
    if (idx >= 2) {
        idx++
    }

    if (idx >= 77) {
        idx++;
    }

    var r = Math.floor(idx / 5) + 1;
    var c = (idx % 5) + 1;

    return createFlag(idx, { r: r, c: c });
}),

Look like a flag's .check value is only 64 when idx is 64. r and c seems to correspond with coordinates on the map. Let's try to calculate the coordinate of the flag when idx is 64.

var r = Math.floor(64 / 5) + 1;
var c = (64 % 5) + 1;

Therefore, r is 13 and c is 5. Let's try to plot this on a map.

[ -4,  -3,  -3,  -3,  -3,  -3,  -2],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1,   1,   1, flag, -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -5,   1,   1, stair, 1,   1,  -1],
[ -5,   1,   1,   1,   1,   1,  -1],
[ -6,  -7,  -7,  -7,  -7,  -7,  -8]

Navigating to the coordinate on the map gets us a flag that when used, gives us the flag.

FLAG ACTUAL FLAG