ForenseTrivia
FunsysDiscord
LockedCall me
MatchAlternative
AuthModified
Internal

Introducción

¡Hey! ¡Bienvenid@!

En esta ocasión vengo a traerles cómo se resolvieron algunos CTFs de la conferencia, desafíos creados por Null Life & GitHub Security Lab.

¡Estos desafíos fueron interesantes e incluso llegué al puesto 20! Por suerte pude completar algunos de ellos ya que soy alumno de la Hackademy, la academia para aprender Ciberseguridad de la Ekoparty!

¡Comencemos con los desafíos!

¿También quieres compartir cómo completaste los desafíos? ¡Haz click aquí!

Forense

Funsys

Comenzando con este nuevo desafío forense: Veamos qué tipo de archivo tenemos…

╰─ lanfran@parrot ❯ file funsys 
funsys: Linux rev 1.0 ext2 filesystem data (mounted or unclean), UUID=78f7e6f8-f21f-4553-95e4-1ac094918c51 (large files)

¡Excelente! Tenemos un filesystem, ¡así que montémoslo para ver qué hay dentro!

╰─ lanfran@parrot ❯ sudo mount funsys mnt/

╰─ lanfran@parrot ❯ ls -la mnt/
total 2072
drwxr-xr-x 4 root    root       1024 lis  5 17:21 .
drwxr-xr-x 1 lanfran lanfran      18 lis  8 08:14 ..
-rw-r--r-- 1 root    root         17 paź 31 02:33 .k
drwx------ 2 root    root      12288 paź 31 02:33 lost+found
-rw-r--r-- 1 root    root    2097152 paź 31 02:33 .RhYSrEm8d

╰─ lanfran@parrot ❯ cat .k 
1TxYs0r4ZRre0r3U

╰─ lanfran@parrot ❯ file .RhYSrEm8 
.RhYSrEm8: data

Un archivo con un nombre raro, .RhYSrEm8d, que tiene algunos “datos” y otro archivo, .k con texto plano dentro.

Entonces, si ejecutamos binwalk podemos extraer los datos del archivo “.RhYSrEm8d”.

╰─ lanfran@parrot ❯ sudo binwalk -e .RhYSrEm8 

╰─ lanfran@parrot ❯ ls -laR
.:
total 2072
drwxr-xr-x 4 root    root       1024 lis  8 08:18 .
drwxr-xr-x 1 lanfran lanfran      18 lis  8 08:14 ..
-rw-r--r-- 1 root    root         17 paź 31 02:33 .k
drwx------ 2 root    root      12288 paź 31 02:33 lost+found
-rw-r--r-- 1 root    root    2097152 paź 31 02:33 .RhYSrEm8
drwxr-xr-x 2 root    root       1024 lis  8 08:18 _.RhYSrEm8.extracted
ls: cannot open directory './lost+found': Permission denied

./_.RhYSrEm8.extracted:
total 1875
drwxr-xr-x 2 root root    1024 lis  8 08:18 .
drwxr-xr-x 4 root root    1024 lis  8 08:18 ..
-rw-r--r-- 1 root root 1908736 lis  8 08:18 5200.zip

Ahora tenemos un archivo .zip y dentro un archivo que solicita una contraseña para leerlo…

password

¡Usar la contraseña del archivo .k es la solución! ¡No necesitas crackearlo!

╰─ lanfran@parrot ❯ cat f.t 
EKO{th1s_1s_1ncr3d1bl3}

¡Finalizado!

Locked

Para este desafío, necesitamos encontrar la dirección IP y la máscara con la mayoría de los paquetes descartados(DROP), para esto usamos una palabra clave simple con grep. Usamos K DROP para filtrar los resultados, y en la lista encontramos la bandera:

╰─ lanfran@parrot ❯ cat locked.log | grep "K DROP" 
[...]
40690 3147K DROP       all  --  !lo    *       34.192.0.0/12        0.0.0.0/0           
14829 1166K DROP       all  --  !lo    *       34.208.0.0/12        0.0.0.0/0           
87870 6751K DROP       all  --  !lo    *       34.210.0.0/12        0.0.0.0/0            <-- La flag!
15610 1230K DROP       all  --  !lo    *       34.240.0.0/13        0.0.0.0/0
[...]

EKO{34.210.0.0/12}

Match

Teníamos 2 archivos, eko.local-access.log y eko.local-error.log, si usamos grep con el registro de acceso para las peticiones con respuesta HTTP 200 podemos encontrar solo 3 archivos:

╰─ lanfran@parrot ❯ cat eko.local-access.log | grep "HTTP/1.1\" 200"                                                               
10.0.3.1 - - [10/Oct/2021:04:32:31 +0000] "HEAD / HTTP/1.1" 200 258 "-" "Mozilla/5.00 (Nikto/2.1.6) (Evasions:None) (Test:Port Check)"
10.0.3.1 - - [10/Oct/2021:04:32:31 +0000] "GET / HTTP/1.1" 200 339 "-" "Mozilla/5.00 (Nikto/2.1.6) (Evasions:None) (Test:getinfo)"
10.0.3.1 - - [10/Oct/2021:04:32:31 +0000] "GET / HTTP/1.1" 200 339 "-" "Mozilla/5.00 (Nikto/2.1.6) (Evasions:None) (Test:map_codes)"
10.0.3.1 - - [10/Oct/2021:04:33:03 +0000] "GET /config.php HTTP/1.1" 200 296 "-" "Mozilla/5.00 (Nikto/2.1.6) (Evasions:None) (Test:000997)"
10.0.3.1 - - [10/Oct/2021:04:33:03 +0000] "GET /config.php.bak HTTP/1.1" 200 7411 "-" "Mozilla/5.00 (Nikto/2.1.6) (Evasions:None) (Test:006638)"

EKO{config.php.bak} era la flag.

Auth

Nuevamente para este desafío usamos otro filtro :)

Podemos empezar a filtrar la solicitud por las conexiones aceptadas con la contraseña correcta, luego de eso eliminamos las IPs duplicadas y limpiamos para obtener solo la IP, tenemos un archivo con 231 IPs, bastantes…

Pero… La última IP es diferente del resto y parece una IP 1337 (Leet). (Esta es una referencia al “lenguaje” h4x0r)

¡¡Y esa es la bandera !!

EKO{80.13.31.7}

Este es el comando bash completo para obtener la lista de direcciones IP:

─ lanfran@parrot ❯ cat auth.log | grep "Accepted" | grep -E -o "([0-9]{1,3}[\.]){3}[0-9]{1,3}" | sort | uniq
80.10.50.1
80.10.50.10
80.10.50.100
80.10.50.101
[...]
80.10.50.96
80.10.50.97
80.10.50.98
80.10.50.99
80.13.31.7 			<--- La ultima es la flag!

Internal

Tenemos un archivo .pcap, vamos a abrirlo con wirehark.

Podemos seguir la secuencia TCP y podemos ver una solicitud de un archivo .zip.

tcp

Podemos exportarlo con wirehark (Si no sabe cómo: vaya a Archivo> Exportar objetos …> HTTP)

Este zip tiene un archivo .log dentro, pero está protegido con contraseña, por lo que podemos conseguirla con fcrackzip.

╰─ lanfran@parrot ❯ fcrackzip -D -v -p /usr/share/wordlists/rockyou.txt -u exported.zip                                            ─╯
found file '.log', (size cp/uc     54/    42, flags 9, chk a839)


PASSWORD FOUND!!!!: pw == 2222

Después de descifrar la contraseña, podemos exportar el archivo, que tiene una “clave” y un nombre de usuario.

╰─ lanfran@parrot ❯ cat .log                                                                                                       ─╯
Hi Arno0x0x, your key is 4lzUySyIu9OEPv6E

Buscando en Google el nombre de usuario “Arno0x0x”, encontramos este repositorio: DNS Exfiltrator. Una herramienta para transferir (exfiltrar) un archivo a través de un canal encubierto de solicitud de DNS.

Y tenemos algunas solicitudes de DNS en el archivo .pcacp, aquí están:

init.MZWGCZZOOR4HI7BR.base64.ctf.ekoparty.org

0.h2x6Nstk5-xLWxkTIQVtXjm0WEDjbsY3b3ikP9aJcS8rdrDjQsp1-mlYMf4NNO7.dXMpSHmfQNrEhDHc9hPKC9c_ByTPE1R1IbC725RfqAZ32eggtHRc_KXVFLMC7iz.U4R3WRJnnXxRJ-FqvW1ZGRZ1HE-9apLYZUqnjAqsnFAMH5me0b4AdA7fBzgG2-F.uHWHh1s6cifvnE.ctf.ekoparty.org

Entonces, editando un poco el script de Arno0x0x, creé un decodificador para obtener la bandera con la información que tenemos.


#!/usr/bin/python
# Code created by Arno0x, Edited by Lanfran02 
# -*- coding: utf8 -*-
from base64 import b64decode
import sys

#======================================================================================================
#											HELPERS FUNCTIONS
#======================================================================================================

#------------------------------------------------------------------------
# Class providing RC4 encryption/decryption functions
#------------------------------------------------------------------------
class RC4:
	def __init__(self, key = None):
		self.state = range(256) # initialisation de la table de permutation
		self.x = self.y = 0 # les index x et y, au lieu de i et j

		if key is not None:
			self.key = key
			self.init(key)

	# Key schedule
	def init(self, key):
		for i in range(256):
			self.x = (ord(key[i % len(key)]) + self.state[i] + self.x) & 0xFF
			self.state[i], self.state[self.x] = self.state[self.x], self.state[i]
		self.x = 0

	# Decrypt binary input data
	def binaryDecrypt(self, data):
		output = [None]*len(data)
		for i in xrange(len(data)):
			self.x = (self.x + 1) & 0xFF
			self.y = (self.state[self.x] + self.y) & 0xFF
			self.state[self.x], self.state[self.y] = self.state[self.y], self.state[self.x]
			output[i] = (data[i] ^ self.state[(self.state[self.x] + self.state[self.y]) & 0xFF])
		return bytearray(output)
		
#------------------------------------------------------------------------
def fromBase64URL(msg):
	msg = msg.replace('_','/').replace('-','+')
	if len(msg)%4 == 3:
		return b64decode(msg + '=')
	elif len(msg)%4 == 2:
		return b64decode(msg + '==')
	else:
		return b64decode(msg)


qname = str("0.h2x6Nstk5-xLWxkTIQVtXjm0WEDjbsY3b3ikP9aJcS8rdrDjQsp1-mlYMf4NNO7.dXMpSHmfQNrEhDHc9hPKC9c_ByTPE1R1IbC725RfqAZ32eggtHRc_KXVFLMC7iz.U4R3WRJnnXxRJ-FqvW1ZGRZ1HE-9apLYZUqnjAqsnFAMH5me0b4AdA7fBzgG2-F.uHWHh1s6cifvnE.ctf.ekoparty.org")

msg = qname[0:-(len("ctf.ekoparty.org")+2)] # Remove the top level domain name
chunkNumber, rawData = msg.split('.',1)
chunkIndex = 0
fileData = ''

#---- Is this the chunk of data we're expecting?
if (int(chunkNumber) == chunkIndex):
	fileData += rawData.replace('.','')
	chunkIndex += 1
	nbChunks = 1

#---- Have we received all chunks of data ?
if chunkIndex == nbChunks:
	try:
		# Create and initialize the RC4 decryptor object
		rc4Decryptor = RC4("4lzUySyIu9OEPv6E") # We use the key from the .log file
		
		# Save data to a file
		outputFileName = "Extracted.zip"
		print("[+] Decrypting using password [{}] and saving to output file [{}]".format("4lzUySyIu9OEPv6E",outputFileName))
		with open(outputFileName, 'wb+') as fileHandle:
			fileHandle.write(rc4Decryptor.binaryDecrypt(bytearray(fromBase64URL(fileData))))
			fileHandle.close()
			print("[+] Output file [{}] saved successfully".format(outputFileName))
	except IOError:
		print("[!] Could not write file [{}]".format(outputFileName))

Con este código en python2 podemos obtener la bandera:

╰─ lanfran@parrot ❯ python2 decode.py                                                                                              
[+] Decrypting using password [4lzUySyIu9OEPv6E] and saving to output file [Extracted.zip]
[+] Output file [Extracted.zip] saved successfully

╰─ lanfran@parrot ❯ cat flag.txt                                                                                                  
EKO{Are_you_filtering_DNS_over_UDP?}

Trivia

Alternative

En este desafío, necesitábamos usar el nombre DNS alternativo para la ekoparty, podemos obtenerlo viendo el Certificado y en los campos, buscar el SAN “Certificate Subject Alternative Name”(Nombre alternativo del sujeto(?)).

alternative

La flag es: EKO{noc.eko.party}

Discord

Este desafío fue muy divertido xD

Mucha gente comenzó a usar /flag en el canal “ctf-advanced” del servidor de Discord de Ekoparty (incluso yo lo usé … 🤡) pero no funcionó. discord_troll *tos* Trolleados *tos*…

La bandera estaba realmente en la descripción del canal.

discord

Modified

Esta vez necesitamos obtener la última fecha de modificación para la página “aboutus”, para esto podemos usar el sitemap.xml. Se encuentra aquí https://ekoparty.org/sitemap.xml

Yendo a esa dirección, podemos obtener la última fecha de modificación de la página http://eko.party/aboutus, que es 2019-12-22. Entonces la bandera es: EKO {2019-12-22}

Call me

Para este desafío necesitábamos conseguir un número de teléfono antiguo de la Ekoparty, utilicé una dork de Google muy mala pero efectiva: " +54 "inurl: ekoparty. Intentando con algunos de los números que google me dió, encontré la bandera.

The End

Esos son algunos de los CTF que pude completar, si tiene otro o quiere compartir conmigo cómo completaste otro desafío, puede contactarme a través de Telegram, Twitter, Linkedin o incluso crear un Pull Request para esta página. (¡Obviamente vas a ser referido y en las secciones de créditos!)