Tags: bot bypass discord cowsay 

Rating:

# misc/electric-bovine @ DamCTF Oct. 2020

* challenge author: **Lyell Read**
* solves: **51**
* points: **468**

## Description

> Do androids dream of electric bovine? Find out on my new Discord server!

## Analysis

We are sent to a discord server named "Permissions pasture". The only
thing we see is the people in the server and a "general" channel, we
have no writing permissions on this channel though.

We also get DM'd by the server's bot: "Secure Permissions Bot".

### Secure Permissions Bot

![](https://i.imgur.com/IHk0lD9.png)

The only useful command at this point is the **about** command:

![](https://i.imgur.com/IWS7xj0.png)

It directs us to the bots source code! And a rick-roll if we try to
grab the bot token... ?‍♂️

### Source code

Available [here](https://gist.github.com/dunklastarn/d3e3ca30bb4f476221bf42faebb19a12).

### Getting higher permissions

We are made aware that there is a **botspam** channel, that we
currently cannot see in the server. This tells us that there are
other roles that we can potentially acquire.

By taking a look at the bot profile in the server, we find two new
roles:

![](https://i.imgur.com/ZzqyUBT.png)

The next step is to use the **send_msg** command to send a message
to the server through the bot. Since there is no checks on input, we
can chain commands and our intent is to use **role_add** as the bot
to give us higher permissions.

After a few unsuccessful tries, this attempt had a new result:

![](https://i.imgur.com/2hcWYTl.png)

Note here that what the **role_add** command wants is the user ID
and the role ID (which can be found in the source code, or by right
clicking the role in the bot profile and grabbing its ID).

This attempt didn't work perfectly though, because we need to pass
an authentication check:

```py

def authenticate(author, authorizer, role):

# Derive a variant to make auth requests resolve uniquely
variant = author.discriminator
variant = int(variant * 4)

# Add author's name to their credential set
author_credentials = [author.nick]
# Add perms
author_credentials.extend([str(int(y.id) + variant) for y in author.roles])
author_credentials.extend([y.name.lower() for y in author.roles])

# Add authorizer's name
authorizer_credentials = [authorizer.name]
# Add perms
authorizer_credentials.extend([str(int(y.id) + variant) for y in authorizer.roles])
authorizer_credentials.extend([y.name.lower() for y in authorizer.roles])

# Add role name
role_information = [role.name, str(int(role.id) + variant)]

permset = list(
(set(author_credentials) & set(authorizer_credentials)) & set(role_information)
)

if len(permset) >= 1:
return True
else:
return False
```

To simplify that:

First of all, the user's discriminator (the last 4 digits in the
discord username) is used to create a variant.

Three separate sets are created for user, bot, and role.

The user set contains the user's nickname on the server, the
user's role ID's + variant, and the user's role names.

The bot set contains the bot's name, the bot's role ID's +
variant, and the bot's role names.

The role set contains the role's name and the role's ID +
variant.

Finally, to pass the authentication check, the intersection of
the 3 sets has to have at least 1 element.

The intended solution here was to calculate the role ID + variant
number and set your nickname to that number. However, we were too
lazy for that, and instead just set our server nickname to
"private", since the "private" entry appears in the other 2 sets
already.

That being done, we retry our previous command:

![](https://i.imgur.com/YzOuLOz.png)

### Getting the flag

Now that we have higher permissions, we can use the **cowsay**
command.

Let's take a look at the important part of the cowsay command:

```py
for char in arg:
if char in " `1234567890-=~!@#$%^&*()_+[]\\{}|;':\",./?":
await message.author.send("Invalid character sent. Quitting.")
return

cow = "```\n" + os.popen("cowsay " + arg).read() + "\n```"
```

We notice immediately that what the command does is pass our input to the
cowsay command on the machine with the flag.

Many characters are disallowed, especially notable are `;` and `.` (dot).
This means that we can't really try to pass `cat flag.txt`, or even just
`flag.txt`. Armed with this knowledge, and noticing that the redirection
characters (`<` & `>`) are not disallowed, we deduce that the flag is in the
file `flag`, and we can direct the `flag` file's content to the cowsay command:

![](https://i.imgur.com/ejGz5Xb.png)

Original writeup (https://medium.com/@igotinfected/misc-electric-bovine-damctf-oct-2020-e9d51613a2d6).