CTF, Wargame,

29C3 CTF: Node writeup

LittleSnapper

This 29C3 from Chaos Computer Club hackers. We participate as dcua team, awesome people trying the best effort for the challenges.  Nice job!

Node

Points: 200
Solves: 18

Description

Node.js is smart, fast, easy and secure… Don’t you think so too?

Hint: google and other sites always look at one file before they access a website by themself, you might want to have a look that file.

So, a Node app that presents a login screen. We follow the hint that refers view the robots.txt. In this file we see a directive disallow:pages.js. Let’s see it:

var fs = exports.fs = module.parent.exports.fs,
    path = require("path"),
    auth = require("./auth.js"),
    less = require("less"),
    uglify = require("uglify-js"),
    logRequest = function (e) {};
exports.all = function (e, t, n) {
    if (e.path.match(/\.\./)) {
        n(e.who + " relative path " + e.path);
        return
    }
    var r = {
        ua: e.get("user-agent"),
        ip: e.ip
    };
    e.body.user && (r = [e.body.user, e.body.password, r]), auth.check("auth", "app/stats", r, function (t) {
        e.data = t, t && (e.who = e.who.replace(/^guest/, t.type)), n()
    })
}, exports.start = function (e, t, n) {
    logRequest(e);
    if (!e.data) {
        t.type("html");
        if (e.xhr) {
            t.setHeader("Connection", "close"), t.setHeader("Content-Length", 0), t.send(404, "");
            return
        }
        t.render("login", {
            xhr: e.xhr
        }, function (e, r) {
            if (e) {
                n(e);
                return
            }
            t.send(200, r)
        })
    } else if (e.data.type === "admin") {
        if (e.xhr) {
            t.send(200, "/admin");
            return
        }
        t.redirect("/admin")
    } else t.type("html"), t.render("user", function (t) {
        return t.xhr = e.xhr, t
    }(e.data), function (e, r) {
        if (e) {
            n(e);
            return
        }
        t.send(200, r)
    })
}, exports.static = function (e, t, n) {
    t.type(e.path.replace(/\/+/g, "")), fs.readFile(e.app.get("views") + e.path, function (r, i) {
        if (r) {
            n(r);
            return
        }
        i = i.toString();
        if (i.substr(0, 4) === "/**/") {
            t.send(i.substr(4));
            return
        }
        e.params[0] === "css" ? (logRequest(e), less.render(i, function (e, r) {
            if (e) {
                n(e);
                return
            }
            r = r.replace(/\r\n|\r|\n/g, "").replace(/\t/g, " ").replace(/\s+/g, " ").replace(/([,:;{]) /g, function (e, t) {
                return t
            }).replace(/ ([,:;{])/g, function (e, t) {
                return t
            }).replace(/;}/g, "}").replace(/(\D)0(\.\d)/g, function (e, t, n) {
                return t + n
            }), t.send(r)
        })) : e.path.match(/^\/auth\.js/) ? n(e.who + " server-side file " + e.path) : (logRequest(e), t.send(uglify(i)))
    })
}, exports.admin = function (e, t, n) {
    e.data && e.data.type === "admin" ? fs.readFile("app/stats.txt", function (r, i) {
        if (r) {
            n(r);
            return
        }
        var s = i.toString().replace(/^\n+|\n+$|\n(?=\n)/g, "").split("\n");
        s.some(function (e, t) {
            s[t] = JSON.parse(e)
        }), t.type("html"), t.render("admin", {
            flag: module.parent.exports.flag,
            users: s,
            xhr: e.xhr
        }, function (e, r) {
            if (e) {
                n(e);
                return
            }
            t.send(200, r)
        })
    }) : n(e.who + " admin only")
}

Some logRequest staff at top of the file that give us important hints about the final solution of the challenge. We see auth.js that cannot be accesed from http://94.45.252.237:1024/auth.js. It can be from http://94.45.252.237:1024//auth.js , notice the double slash. This js has crypto libs for authentication. There was two functions: check and add, that could be reference for adding users and i pay attention on this part of the add function:

 exports.add = function (e, t, n) {
    crypto.pbkdf2(n.pass, n.mail.substr(0, 10), 1e3, 36, function (r, i) {
        if (r) throw r;
        fs.appendFile("loginData.txt", n.user + ": " + n.pass + "\n", function (e) {
            if (e) throw e
        }), fs.appendFile(t, JSON.stringify({
            user: n.user,
            ua: n.ua,
            ip: n.ip
        }) + "\n", function (e) {
            if (e) throw e
        }), n.pass = i, fs.appendFile(e, JSON.stringify(n) + "\n", function (e) {
            if (e) throw e
        })
    })

So we have a user, a user-agent and an ip that could be important for the login process. We see in pages.js that there was a stats.txt. Let’s see it. Interesting, log files that give us a final approach.

Run LiveHttpHeaders to modify our request and see the response :-). We sent User-Agent, X-Forwarded-For and variable user with the data extracted from stats.txt . If we do this with users different than admin, we acces the profile page of them, but no flag :-(

Firefox 2

 

Let’s see what happened with user admin. The user admin has 8.8.8.8 as ip and «Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1)» as user-agent. If we try to launch the modified request to http://94.45.252.237:1024 it redirects to /admin but has 302 Moved Temporarily http state and a blank page appears, so we try against http://94.45.252.237:1024/admin . We see this page:

 

Firefox

Flag: 29C3_ProxyTrust

No hay contenido relacionado