BBGun06
3 minutos de lectura
Se nos proporciona un código fuente en Python que nos pregunta por una firma. Esta es la función main
:
def main(s):
rsa = RSA(2048)
user, data = parseEmail()
signature = rsa.sign(user)
rsa.verify(user, signature)
headers = generateHeaders(rsa, signature)
valid_email = headers + data
sendMessage(s, valid_email + "\n\n")
try:
forged_signature = recieveMessage(s, "Enter the signature as hex: ")
forged_signature = bytes.fromhex(forged_signature)
if not rsa.verify(user, forged_signature):
sendMessage(s, "Invalid signature")
if different(rsa, signature, forged_signature):
sendMessage(s, FLAG)
except:
sendMessage(s, "An error occured")
La función rsa.verify
hace esto:
def verify(self, message, signature):
keylength = len(long_to_bytes(self.n))
decrypted = self.encrypt(signature)
clearsig = decrypted.to_bytes(keylength, "big")
r = re.compile(b'\x00\x01\xff+?\x00(.{15})(.{20})', re.DOTALL)
m = r.match(clearsig)
if not m:
raise VerificationError('Verification failed')
if m.group(1) != self.asn1:
raise VerificationError('Verification failed')
if m.group(2) != sha1(message).digest():
raise VerificationError('Verification failed')
Ejecuta self.encrypt
, que será $s ^ e \mod{n}$, donde $s$ es la firma, $e$ es el exponente público y $n$ es el módulo público.
Tenemos $n$ y $e$ porque el servidor envía estos valores en formato PEM:
$ nc 127.0.0.1 1337
signature: 6d0367ad7e069a4eef22535a8fa5df13e9701b31c94d30db5905e7923535835463b8673ed13fe3b6eefcfdd6074d76bbb9ec95022f56049137282aa8ab01b25956884b18116d052fc16aed4af66bfda92ca64575f46c03fbc322de10fb8518edfe3d09020b3c109e438108edcdc7fbb33d34b6f4da745c0cec157b06ad7939e66edf89c6681aef841574dfccd98a803ba583e488b528df1501e717edcf4047ee8f2bc18556c1f41f89385fe68ef7af2ee2e132f7042e9aed724cc6659310db8153756d16f9da99662ac7d5abb8e00d64735ac544f8f57d5519e3edae3824db913aa67d8bfc64afbeb1787f56c455ffb1198fb2da5b40ea67cd0d810e8015ac4b
certificate:
-----BEGIN PUBLIC KEY-----
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAnxwOqoIroFTNdDdVS13C
I8wDj4H/3bI8T6wSMaL5GE4GLjgz33szj+u1XPxUb0vfdMxkfcTsvtYP9oZtPX3L
FMsi8KnrpvlqTzPNbKDO6Ga1viO3C6GPKIJwDyXzWI2IxnSrDHjty30dbKl1lO9E
XXWMj7GXFFVOBDnPfxWFwyMUnJc/oooyDF2vVpwvmIWFTfxRLMrmJpeHlA9Vg94A
Ype/CkbURdckwFJt0Yvp0wFFRrVhsQeFlIrq0GQtSif8YlIAjyANTAqNDoKS0fq5
Fc3388Gt2D04JNcny7qjmxWuPcnn+2PZaog10K4YrjK8ZVtptwl97f06CuktB14C
jQIBAw==
-----END PUBLIC KEY-----
From: IT Department <it@cloudcompany.com>
To: j.smith@sheesh.gov.edu
Subject: Confirm your identity
Hello there,
You have recently changed the password for your CloudCompany account.
Please verify your account using the link below to confirm your identity:
https://ccloud.cloudcompany.co/confirm-identity/5912031940529412
Below are some common reasons for unwanted or irregular activity:
- Unauthorized or unexpected resource activity - An unpatched CloudCompany
Elastic Compute Cloud (CCEC2) instance could be infected and become a botnet
agent.
- Exposed credentials or access keys.
- Unintentional misuse - An end user of a service provided by an CloudCompany
customer might post malware files to a public S1 bucket.
- False complaints - Sometimes Internet users falsely report legitimate
activities as misuse.
Thank you for your patience while we work on this case.
We appreciate your feedback. Please share your experience by rating this
correspondence using the CloudCompany Support Center link at the bottom of this
correspondence. Each correspondence can also be rated by selecting the stars in
the upper right corner of each correspondence in the CloudCompany Support
Center.
Best regards,
CloudCompany
<img width=396 height=129 id="1" src="cid:Logo.jpg">
Enter the signature as hex:
Además, una vez que se descifra la firma, el servidor usa una expresión regular para parsear los bytes descifrados.
Usando la siguiente instrucción en Python, podemos ver cuál es el payload que se está firmando:
print(f'{clearsig = }')
$ python3 server.py
clearsig = b'\x00\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x000!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p'
Nótese que la expresión regular no mira el número de bytes "\xff"
, pero mira que la cadena descifrara comienza por "\x00\x01\xff"
y su longitud es de 256 bytes. Por tanto, podemos pasar la expresión regular poniendo menos bytes "\xff"
y rellenando la cadena por el final. Por ejemplo:
$ python3 -q
>>> clearsig = b'\x00\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x000!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p'
>>> clearsig.count(b'\xff')
218
>>> len(clearsig)
256
>>> import re
>>> r = re.compile(b'\x00\x01\xff+?\x00(.{15})(.{20})', re.DOTALL)
>>> m = r.match( b'\x00\x01' + b'\xff' * 218 + b'\x000!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p')
>>> m.groups()
(b'0!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14', b'\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p')
>>> m = r.match((b'\x00\x01' + b'\xff' * 1 + b'\x000!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p').ljust(256, b'\0'))
>>> m.groups()
(b'0!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14', b'\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p')
Solo tenemos que encontrar un número $t$ de forma que $t ^ 3$ (recordemos que $e = 3$) pase la verificación de la expresión regular.
Entonces, con este código podemos obtener dicho valor $t$:
forged_min = int((b'\x00\x01' + b'\xff' * 1 + b'\x000!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p').ljust(256, b'\x00').hex(), 16)
forged_max = int((b'\x00\x01' + b'\xff' * 217 + b'\x000!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p').ljust(256, b'\xff').hex(), 16)
perfect_cube_range = range(iroot(forged_min, e)[0], iroot(forged_max, e)[0])
regex = re.compile(b'\x00\x01\xff+?\x00(.{15})(.{20})', re.DOTALL)
keylength = len(long_to_bytes(n))
for t in perfect_cube_range:
clearsig = (t ** e).to_bytes(keylength, 'big')
m = regex.match(clearsig)
if m and m.groups() == (b'0!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14', b'\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p'):
break
r.sendafter(b'Enter the signature as hex: ', hex(t)[2:].encode())
log.success(f'Flag: {r.recv().decode()}')
r.close()
Usando este script: solve.py
podemos obtener la flag:
$ python3 solve.py 178.62.23.240:30323
[+] Opening connection to 178.62.23.240 on port 30323: Done
[+] Flag: HTB{4_8131ch3n84ch32_254_vu1n}
[*] Closed connection to 178.62.23.240 port 30323