GlacierCTF2025 - gitresethard

Author: Ernesto Martínez García

Tags: 0014 ctf misc

gitresethard is a simple misc challenge I authored in GlacierCTF 2025. Hours before the CTF ends, the challenge had 166 solves.

In this challenge you receive a tarball containing the “disk” that hosted a git repository where a malicious employee did a git reset hard and a git push force. The task of the challenge is to recover a missing commit.

This challenge is not that security-oriented, initially it was something different and then it was re-purposed into a beginner git/misc challenge.

First we untar the file we got and we see it is a git barebone repo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
repo/objects/f9/
repo/objects/f9/18954e0094e965c5cce7e06837764258fc267f
repo/objects/f9/e055723842f05609b1754a9b8facb23dacea13
repo/objects/fa/
repo/objects/fa/455611d217077539410a514588cfc899dd5a17
repo/objects/fb/
repo/objects/fb/a80f10872080f0d01b095c2bf80189006b324c
repo/objects/fc/
repo/objects/fc/01fe4cdf875664f2cb4d8f1b91b4745d0e5493
repo/objects/fc/80e3eea1cc7f7cd46cc2ab0838d2bc45bb9562
repo/objects/fc/b7012c41376c02141982cb9cba560e439e20ce
repo/objects/ff/
repo/objects/ff/4f0ffb8da68f19ca727eb20c2f10a2b990773c
repo/objects/info/
repo/objects/pack/
repo/refs/
repo/refs/heads/
repo/refs/heads/main
repo/refs/tags/

As that is a git repo, one can query for dangling commits on the repo via git fsck --lost-found --full:

1
2
3
4
[ecomaikgolf@laptop ../gitresethard/repo/]$ git fsck --lost-found --full
Checking ref database: 100% (1/1), done.
Checking object directories: 100% (256/256), done.
dangling commit 6a81c76ebba614823433d7caf0ea7e523a998fcb

Them, with the danbling commit, you can see its diff:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[ecomaikgolf@laptop ../gitresethard/repo/]$ git fsck --lost-found --full
commit 6a81c76ebba614823433d7caf0ea7e523a998fcb
Author: Kevin Saiger <kevin.saiger@losfuzzys.net>
Date:   Wed Jul 23 20:39:34 2025 +0200

    leaving a shit in the carpet

diff --git a/carpet/shit b/carpet/shit
new file mode 100755
index 000000000000..8be5575f5ff1
--- /dev/null
+++ b/carpet/shit
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+openssl enc -d -aes-256-cbc -pbkdf2 -pass pass:tJnAQZQF2bKx4 -in <(base64 -d <(echo "U2FsdGVkX18liMZqk4AiqSRX5HZpfrnZAmrfRaS1UztVewZqjgX1wTHCNNj2H5crA/0VUhBXMk9bo/N/lKfFPQ==")) -A -out -
~

And we see a command on the matching commit, we run it and outputs

1
gctf{0113_wh0_g1t_r3s3t3d_th3_c4t_4789}

This was to avoid a simple grep or similar. I noted that the repo is totally safe on the description.

Maybe its known already to the reader, this happens as git is still holding the deleted commit as dangling. Git has a mechanism to clean this unneeded data, the git garbage collector.

When doing operations over the repo such as pulling,pushing, or bundling, git only takes consideration of the commits that are not dangling. This is the main reason we provided a tarfile of the “disk”.

Full Exploit

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
os.system("tar xvf repo.tar.gz")
os.system("git config --global --add safe.directory /app/repo")
os.chdir("repo/")
cmd = "git show $(git fsck --lost-found --full 2>&1 | tail -n 1 | cut -d' ' -f 3) | tail -n 1 | cut -d'+' -f 2 | bash"
out = subprocess.check_output(cmd, shell=True)

f = find_flag(out)
if f is not None:
    log.success(f)
    exit(0)
else:
    exit(1)