PicoCTF caas Walkthrough

March 22 2024 - 3 Minutes
-

The caas entry in the PicoGym
The cowsay as a service entry in PicoGym

Hello! Today I wanted to make a new blog post, and decided to make it diffirent than what I normally do.
Over the past month or so, I have been obsessed with PicoCTF.
So I decided to write a blog post about one of my favorite CTFs "Cowsay as a service"!

Beginning

When we first click on the website we see this:

A simple website that says to make a request to /cowsay/{message} to get the cowsay of said text

This tells us that the endpoint /cowsay/ exists. Testing the endpoint like this: /cowsay/Minejerik.dev.
You get this as a result:

Cowsay saying 'Minejerik.dev'

This reveals a potential vulnerability with the website as cowsay is a terminal based program, unless they reimplemented it, they are probably using the terminal program. I will explore this more in the next section.

Exploration

We can assume that the command looks something like this:

cowsay Minejerik.dev

This exposes a vulerability, as they did not wrap the message in quotes, which allows us to run any linux commands on the hosting server.
To test this I changed the url from /cowsay/Minejerik.dev to /cowsay/Minejerik.dev;ls.

That extra ;ls will terminate the previous cowsay command and will run the ls command which will show all files in the folder.

When I actually change the payload to Minejerik.dev;ls on the caas website we get the result:

The website proving us correct!

That falg.txt looks a lot like where the flag is stored!
So to test this I change the payload a little more and and added an extra command:

/cowsay/Minejerik.dev;ls;cat falg.txt

That cat falg.txt will print the contents of the file, which will allow us to get the flag.
And when we test this, we get:

Proof that it works, flag is censored

SUCCESS! It works, and we get the flag!

Explanation and Mitigation

I want to explain now how this vulnerability came to be, and how to fix it.

PicoCTF has given us the code to this website, so lets look at it!

const express = require('express');
const app = express();
const { exec } = require('child_process');

app.use(express.static('public'));

app.get('/cowsay/:message', (req, res) => {
exec('/usr/games/cowsay ${req.params.message}', {timeout: 5000}, (error, stdout) => {
    if (error) return res.status(500).end();
    res.type('txt').send(stdout).end();
});
});

app.listen(3000, () => {
console.log('listening');
});

This is a lot of code, So lets cut out what isn't necessary.
What we are really worried about is this:

app.get('/cowsay/:message', (req, res) => {
exec('/usr/games/cowsay ${req.params.message}', {timeout: 5000}, (error, stdout) => {
    if (error) return res.status(500).end();
    res.type('txt').send(stdout).end();
});
});

Lets walk through it:

app.get('/cowsay/:message', (req, res) => {

This just defines the route as a get request route, and that the endpoint takes a message.

exec('/usr/games/cowsay ${req.params.message}', {timeout: 5000}, (error, stdout) => {

This is the most important part, it proves that we were correct.

The command is just a longer form of cowsay but what is really important is that the message doesn't have quotes around it.
If the programmer would have put quotes around the message, this whole vulerability would have been mitigated.
So if you are doing something like this, make sure that you add quotes around the paramaters of a command.

Thank you for reading this post, I hope you enjoyed.