GutHib Actions
This was a very simple Misc challenge with an obvious intended solution, but I solved it using a (relatively more complex) unintended solution. I thought I'd share it here for people to enjoy :)
Challenge Premise
The challenge revolves around the root
user running a build.sh
script every minute.
The script runs a build.py
file, which calls on pyinstaller
to build an executable from the /root/flag.py
file, and store it in the /root
directory.
Finally, the /tmp
directory is cleared with rm -r *
, which deletes all build files generated by pyinstaller
.
Unintended Solution
It might not be immediately obvious, but there is actually a vulnerability in the build.sh
file. The rm -r *
uses wildcard expansion, which is wildly dangerous.
In Unix, wildcards are expanded by the shell and any matching filenames are passed as arguments to the program being run. This means that if there is a file by the name -rf
, the rm *
command would automatically be expanded to rm -rf
- dangerous indeed!
In this case, we do not want everything from the /tmp
directory to be removed after the script is run, because the PyInstaller build files contain valuable information. We can place a file with the name -i
in the /tmp
directory to achieve this. According to the man page:
Therefore, by doing this:
we are effectively "hanging" the cronjob script at the rm -r *
command, since the script has no way of answering the prompt that appears:
After the script runs, we have access to PyInstaller's build directory, where PyInstaller creates the files necessary for building the final executable generated in the distpath
.
Now that we have access to the build
directory, let's take a look at what's inside.
As explained in the documentation, the flag.pkg
is a ZlibArchive containing compressed .pyc
files containing the bundled Python modules.
We can inspect its contents using pyi-archive_viewer
, which is installed together with PyInstaller.
We can then extract the flag
file using the X
command.
Although we could quite easily see the flag in the hexdump of the extracted file at this point, I was still slightly confused by the file format, as the magic bytes were not recognised by Python bytecode disassemblers.
The extracted file was in fact the original Python bytecode, and not a .pyc
file as was suggested by the PyInstaller documentation. The key difference is that a .pyc
file contains:
A four-byte magic number,
A four-byte modification timestamp, and
A marshalled code object.
And the extracted file contains only the marshalled code object (also see this issue). At this point we can just use Python's marshall
module to run the code.
Intended Solution
The intended solution was just to override the subprocess
module by writing to a subprocess.py
file. I actually thought of this halfway through, but I was already too far in with the unintended solution not to see it through.
Anyway, this was a fun challenge! At least I learnt a thing or two about PyInstaller.
Last updated