encrypted-command-executor

problem

A service that only executes encrypted commands. Regular users can only run echo or ls commands. The command execution output is encrypted too btw.

The server do not allow any outbound network connection, and the flag is located at /flag.

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from shlex import quote
from base64 import b64decode, b64encode
import os
from subprocess import Popen, PIPE, DEVNULL


def encrypt(key: bytes, ct: bytes) -> bytes:
    cipher = AES.new(key, AES.MODE_ECB)
    return cipher.encrypt(pad(ct, AES.block_size))


def decrypt(key: bytes, ct: bytes) -> bytes:
    cipher = AES.new(key, AES.MODE_ECB)
    return unpad(cipher.decrypt(ct), AES.block_size)


if __name__ == "__main__":
    print("=" * 40)
    print("Welcome to Encrypted Command Executor")
    print("=" * 40)
    key = os.urandom(AES.block_size)
    while True:
        print("1. Generate an echo command")
        print("2. Generate a ls command")
        print("3. Run an encrypted command")
        print("4. Exit")
        choice = input("> ")
        if choice == "1":
            msg = input("Message: ")
            cmd = "echo %s" % quote(msg)
            print("result:", b64encode(encrypt(key, cmd.encode())).decode())
        elif choice == "2":
            directory = input("Directory: ")
            cmd = "ls -- %s" % quote(directory)
            print("result:", b64encode(encrypt(key, cmd.encode())).decode())
        elif choice == "3":
            ct = input("Encrypted command: ")
            cmd = decrypt(key, b64decode(ct))
            proc = Popen(
                cmd.decode(), stdin=DEVNULL, stdout=PIPE, stderr=DEVNULL, shell=True
            )
            stdout, _ = proc.communicate()
            print("result:", b64encode(encrypt(key, stdout)).decode())
        elif choice == "4":
            exit()

solution

because this uses ECB, we can perform a chosen plaintext attack because unlike CBC, ECB encrypts the 2nd block with exactly the same starting state as the first.

see here

if we can input any data we want to AES ECB, we can easily encrypt any data after removing the first 16 bytes

solve script:

import socket
import string

HOST = "ictf2.maple3142.net"  # The server's hostname or IP address
PORT = 9999  # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.recv(1024)
    letters = "abcdefghijklmnopqrstuvwxyzQWERTYUIOPASDFGHJKLZXCVBNM1234567890!@#$%^&*()_{}"
    buffers = {}
    print(letters)
    newline = b"\n"
    for letter in letters:
        s.sendall(b"2\n")
        s.recv(1024)
        pload = b"abcdefghiqwertyuiopqwertyqwertyuiopqwer /" + letter.encode('ascii')
        # print(pload)
        s.sendall(pload + b"\n")
        buffers[s.recv(1024).split(newline)[0][8+64:].decode('ascii')] = letter
    for index in range(70):
        s.sendall(b"2\n")
        s.recv(1024)
        payload = b"abcdefghiqwertyuiopqwertyqwertyuiopqwertycat /flag | head -c " + str(index+1).encode('ascii') + b" | tail -c 1; echo Jw== | base64 -d #"
        # print(payload)
        s.sendall(payload + b"\n")
        cmd = s.recv(1024).split(newline)[0][8+64:]
        s.sendall(b"3\n")
        s.recv(1024)
        s.sendall(cmd + b"\n")
        thing = (s.recv(1024).split(newline)[0][8:].decode('ascii'))
        if thing in buffers:
            print(buffers[thing])
        else:
            print(f"MISSING ERROR AT INDEX {index+1} STARTING FROM 1")

this will print out the flag char by char

it is ictf{maybe_ecb_is_not_a_good_idea}