This is a strategy for taking over a network socket or other file descriptor opened by a privileged process. I was directed to this reference by a user on the SmashTheStack Discord server after I was stuck on level 2 of the “amateria” CTF challenge.
The challenge involved a constantly running privileged server that would accept connections and spawn a child process, which would in turn prompt for a username and password. If they username matched “level3” it would spawn a “level3” privileged shell while the level2 username/password would spawn a level2 shell. There was also a hint in the README saying there was a script that mimiced a user with valid credentials using the service. The wording of this hint led me to believe that there was an existing privileged session that I was supposed to take over. But in fact, it was much simpler than that. The script in question would hit the service at a certain (frequent) interval and send the correct username and password for level 3. If we could take over the listening socket, we could intervene and capture this information when the script connects.
The idea is that child processes inherit any file descriptors opened by the parent process unless a special flag is set. So we open a session as “level2” (whose credentials are available). Then we run a python script that opens up the original listen socket using the file descriptor number, 3. This is 3 because 0, 1 and 2 correspond to stdin, stdout and stderr. We can use
socket.socket(fileno=3) to accomplish this (only in Python3). Afterwards, we can use it as if it is any other listen socket, accept connections, read incoming data and respond as if we are the original service.
The parent process must set the
FD_CLOEXEC flag on the privileged file descriptor to prevent the child processes from accessing them.
#include <fcntl.h> fcntl(fd,FD_CLOEXEC);