Recently, elttam released a series of CTF challenges that were created for BSides Canberra and Brucon a few years back. Justin Steven has been uploading a YouTube series containing solutions to some of these challenges. One of the challenges that was solved was a challenge called hideandseek. The challenge had a flag that was only accessible to the hideandseek user and required the user to find the suid nmap binary belonging to the hideandseek user. Justin quickly solved the hideandseek challenge using the intended solution of passing in flag.txt as a file containing list of hosts to scan and leaking it in an error message:
Justin spent some time trying to take it further by popping a shell without dropping privileges, however realised that this wasn’t as straight forward as originally hoped and decided to move onto the next challenge. This raised the question, how can we do it?
This blog will attempt to explore and solve the challenge of getting a shell through a setuid binary executing Lua scripts without dropping privileges. While this blog post will stick to using nmap for it’s examples, it’s important to note that this should affect most applications that run Lua scripts as a privileged user and is not limited to nmap.
Some readers might remember that nmap used to have an --interactive argument which allowed it to run arbitrary system commands. This could, in certain circumstances, allow privilege escalation if nmap had the setuid bit set (as seen in this challenge), or if a user was given access to run nmap with sudo:
The --interactive argument was removed from nmap in r17131 back in 2010, however another way of executing system commands is to use the Nmap Scripting Engine (NSE) that leverages Lua to perform variety of networking tasks which is able to be easily automated. Here’s an example nmap script that uses Lua to run an arbitrary system command:
As demonstrated, using nmap to run abitrary system commands is pretty easy. So lets set up nmap to run as root and see what user we are when Lua executes our system command:
You may have expected that running /usr/bin/id through Lua from the suid binary would have been executed as root, however it’s still being executed by the current user. If we take a look at the Lua source code to understand why, we can see that os.execute() takes a single argument which gets stored in the cmd variable, and then calls the underlying system function directly in C:
This is problematic in our case because the way system works under the hood is by calling execve() with ['/bin/sh', '-c', '<cmd>']. By default, sh will now drop privileges unless you provide a -p argument to tell it not to do so. As everything after -c is being treated as part of the command, we are unable to use any tricks like argument injection to introduce a -p argument. This means the expected behaviour is to have our privileges dropped before executing our command.
This behaviour of system can also be shown through the use of strace:
I decided to start going through Lua documentation to look for other potential functionality that may allow for another process to be executed without dropping privileges.
One idea was to use Lua’s file APIs to read and write files to privileged areas as the suid user. This could include dropping in your own SSH key to ~/.ssh/authorized_keys or modifying the user’s ~/.bashrc to do something when they login. While this could work, this method isn’t guaranteed to result in a shell, or, may have a delay in receiving a shell. It’d be better if it was possible to have an immediate shell.
Lua has some support to chmod files through the use of external packages. This led me down another path exploring the idea of dropping an ELF file and trying to chmod a binary to set the suid bit through Lua. Unfortunately, all of the packages explored use a system() call to run the /usr/bin/chmod binary, which as we know, drops privileges and leaves us in the same position.
Another interesting piece of functionality that I came across was the ability to write custom Lua functions in C. This seemed promising as this would allow native C functions to be executed. This means the execve system call could be called from a custom Lua module that has been written in C, which would bypass the shell and not drop privileges. To test this idea, I created the following C module:
The module was then compiled as a shared object and the following Lua script was created to use the new “inluaofaname” module:
Running nmap again and passing in the inluaofaname.nse Lua script, we can see that it was possible to successfully pop a shell without it dropping privileges:
While this certainly works, it does have a few caveats. Firstly, as it’s written for Lua 5.3, it would require modification to work with older versions of Lua. Secondly, not every box is guaranteed to have the Lua headers which would mean you would have to compile locally and uploading the shared object to the target. Daniel Hodson came up with a similar idea of using a shared object but using a constructor that which executes /bin/sh. This means when the shared object is loaded, the code would run immediately before anything else. The code is as follows:
I created the following Dockerfile that you can use if you want to test either payload yourself. It should be noted that the Docker environment differs slightly from the hideandseek challenge as the nmap binary is owned by root instead of the hideandseek user:
The final inluaofaname.nse payload contains the following:
Piecing everything together, we are able to still run /bin/sh without dropping privileges:
The next time you pop a shell on a pentest and see something with setuid privileges that runs Lua scripts, try giving this a shot.