EasyCTF 2017 writeups

I played EasyCTF 2017 with students and instructors of the Spring 2017 CS 161 (computer security) class and Hideaki Kawabata.

The competition lasted for a week (March 13–20), but we only played one evening for about two and a half hours. We were split across two teams:

We came in at places #640 and #649 out of 1938.

IRC 5 points

EasyCTF has an IRC channel! Check out #easyctf2017 on freenode to claim a free flag, and stick around to get on-the-fly updates during the competition.

Connect to the IRC channel and run /topic

Topic for #easyctf2017: EasyCTF 2017 - 
easyctf{irc_d0esn7_apist0rm_:)} - Fzz Buzz 2 now has a source 
verifier and solutions have been regraded - only ping admins for 
help (with @ or +) - FAQ(AKA what we know is broken): 

Flag: easyctf{irc_d0esn7_apist0rm_:)}

Hello, world! 10 points

Use your favorite programming language to print Hello, world! to stdout! Use the programming interface to do this!
print "Hello, world!"

Flip My Letters 20 points

I dropped my alphabet on its head, can you help me reassemble it? easyctf{r_wlmg_vevm_mvvw_zm_zhxrr_gzyov}

The hint suggests that it's a substitution cipher, where the key is the alphabet in reverse. Sure enough, that works.

$ echo "r_wlmg_vevm_mvvw_zm_zhxrr_gzyov" | tr abcdefghijklmnopqrstuvwxyz zyxwvutsrqponmlkjihgfedcba

Flag: easyctf{i_dont_even_need_an_ascii_table}

Hexable 25 points

I tried to hide a flag sneakily, can you find it? Download

The challenge file was a small executable of 235 bytes:

$ file 85b33e8a48a9e129d23c20483b4b12cd1d199708_hexable
85b33e8a48a9e129d23c20483b4b12cd1d199708_hexable: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, stripped

strings was lucky and found a flag in plaintext.

$ strings -a 85b33e8a48a9e129d23c20483b4b12cd1d199708_hexable
Can you find the flag?

Fizz Buzz 1 50 points

Write a program that takes an integer n as input.

Output the numbers 1 through n, in increasing order, one per line.

However, replace any line that is a multiple of 3 with Fizz and any that are a multiple of 5 with Buzz. Any line that is a multiple of 3 and 5 should be written as FizzBuzz.

The input will be the number of lines to write, n, followed by a linebreak.

Sample input:


Sample output:


There weren't any tricks to this one, though we had some initial difficulty figuring out how to provide n (through stdin, not in argv) and the Python 2/Python 3 language setting.

n = input()

for i in range(1, n+1):
    if i % 15 == 0:
        print "FizzBuzz"
    elif i % 3 == 0:
        print "Fizz"
    elif i % 5 == 0:
        print "Buzz"
        print i

RSA 1 50 points

I found somebody's notes on their private RSA! Help me crack this.

The challenge was an RSA ciphertext c, an exponent e=65537, and the factors p and q of N.

Knowing e and the factorization of N enables us to compute the private key d=e−1 mod (p−1)(q−1). Then the ciphertext is recovered as M=cd mod N.

Here is a Python 2 solution using some random modular inverse code from stackoverflow.

def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
        g, y, x = egcd(b % a, a)
        return (g, x - (b // a) * y, y)

def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception('modular inverse does not exist')
        return x % m

p = 34451230104112013463175823027282391407080573533708813498013454904757550436112679
q = 31306747900254159177088332852397756954066642701687034947060523309720714471241827
e = 65537
c = 724679268983899906738706846021472196023336446165782221418736175962186245052250536455942637480877569626887045938803963323130741588500132891175907722041588278225

N = p * q
d = modinv(e, (p-1)*(q-1))

M = pow(c, d, N)
print hex(M)[2:-1].decode("hex")

Flag: easyctf{wh3n_y0u_h4ve_p&q_RSA_iz_ez_eeba91df}

Useless Python 50 points

Boredom took over, so I wrote this python file! I didn't want anyone to see it though because it doesn't actually run, so I used the coolest base-16 encoding to keep it secret. python

The file looks like it's the hex encoding of something, with a lot of redundancy (66088 bytes):


Decoding the hex reveals what looks like 33044 bytes of obfuscated Python source code, made by concatenating one-byte strings:


Manually decoding that (by extracting the decimal digits, converting to bytes, and concatenating) reveals another, shorter, similarly encoded file of 4008 bytes:


Decoding that gives 486 bytes of a similar encoding:


Decoding a third time finally leads to something that resembles source code, and a key:

flag = 'easyctf{python_3x3c_exec_3xec_ex3c}'
priint flag

The typo priint means that we would not have been able to just run the code after removing the outer hex encoding: it would have raised the error NameError: name 'priint' is not defined without printing the key.

import re

FILENAME = "42552c587e13c09d2873cf20c4a2a558f60a3a46_useless.py"

with open(FILENAME) as f:
    x = f.read()

def dumb(s):
    z = []
    for m in re.finditer(r'[0-9]+', s):
    return "".join(z)

print x
x = x.decode("hex")
print x
x = dumb(x)
print x
x = dumb(x)
print x
x = dumb(x)
print x

Flag: easyctf{python_3x3c_exec_3xec_ex3c}

RSA 2 80 points

Some more RSA! This time, there's no P and Q... this.

This is like RSA 1, with the difference that this time we don't have the factorization of N. N is small enough, though, that it's easy to factor. For example, https://www.wolframalpha.com/input/?i=prime+factorization+of+360863815763347129786223223753677186553479 gives us p=595630980471473199937, q=605851320019829172167. Now we can just reuse our decryption code from RSA 1.

Flag: flag{l0w_n_ad80}

TinyEval 100 points

This page will evaluate anything you give it.

It was a web page with a text box and a submit button. The page would show an error if the input was longer than 11 characters.

We found pretty quickly than an input like print foo would cause "foo" to appear on the page. We found that echo would work as well as print, which suggested that the evaluator was PHP (PHP echo function). This was confirmed when we entered phpinfo(), which dumped an extensive table of PHP information.

We briefly followed a couple of false paths: looking for a flag in the PHP interpreter's variables and trying to get a directory listing in PHP code. We got on the right track by using backticks to execute a shell command. We got a directory listing with echo `ls`.

Dockerfile flag_pnvgx1Qco7gx0ApLCUhH index.php

(Amusingly, while testing various commands, we tripped the Cloudflare WAF on the challenge page a couple of times.) We now just needed a command to print the contents of the flag file while still being short enough.

<p>Give me something to eval!</p>

FROM tutum/lamp:latest
RUN sed -i 's/AllowOverride FileInfo/AllowOverride All/' /etc/apache2/sites-enabled/000-default.conf
RUN a2enmod rewrite
RUN rm -rf /app/*
COPY . /app/
RUN echo "Options -Indexes\n" > .htaccess
CMD '/run.sh'easyctf{it's_2017_anD_we're_still_using_PHP???}
<p>Give me something to eval!</p>

if (isset($_POST['cmd'])) {
    $cmd = $_POST['cmd'];
    if (strlen($cmd) > 11) {
        echo "sorry, your string is too long :(";
    } else {
        echo eval($cmd . ";");

<form method=post>
<input type=text name=cmd>
<input type=submit>
<form method=post>
<input type=text name=cmd>
<input type=submit>

Flag: easyctf{it's_2017_anD_we're_still_using_PHP???}

Edge 1 100 points

We found Edge inc's website! Take a look at it here.

The linked page, http://edge1.web.easyctf.com/, didn't expose a lot of obvious attack surface. It seemed to be mostly static HTML; what CSS and JavaScript there was, looked like standard Bootstrap and jQuery. We found directory listings at http://edge1.web.easyctf.com/css/ and http://edge1.web.easyctf.com/js/ but they weren't helpful. We inspected the headers and found nothing unusual.

We idly tried various URL paths, like /index.html.bak, /index.html.swp, /%, /%20, and /../../../../../../../../etc/passwd. In desperation we tried the Nmap http-enum script, which hit the jackpot:

$ nmap --script 'http-enum' -v edge1.web.easyctf.com -p80 -oN http-enum.nmap

Completed NSE at 19:41, 169.53s elapsed
Nmap scan report for edge1.web.easyctf.com (
Host is up (0.0038s latency).
Other addresses for edge1.web.easyctf.com (not scanned):
80/tcp open  http
| http-enum:
|   /.git/HEAD: Git folder
|   /css/: Potentially interesting directory w/ listing on 'apache/2.4.7 (ubuntu)'
|_  /js/: Potentially interesting directory w/ listing on 'apache/2.4.7 (ubuntu)'

The .git directory was probably where the flag was. We tried directly cloning the repo, but that didn't work, probably because the repo hadn't run git update-server-info, which is needed for cloning a repo over HTTP.

$ git clone -v http://edge1.web.easyctf.com/.git
Cloning into 'edge1.web.easyctf.com'...
fatal: repository 'http://edge1.web.easyctf.com/.git/' not found

Instead, we took the brute-force approach of recursively downloading the entire .git directory:

$ wget -r -np http://edge1.web.easyctf.com/.git

From there, all we had to do was enter the directory and inspect the revision history:

$ cd edge1.web.easyctf.com/
$ git log
commit ee9061b25d8a35bae8380339f187b44dc26f4999
Author: Michael <michael@easyctf.com>
Date:   Mon Mar 13 07:11:47 2017 +0000

    Whoops! Remove flag.

commit afdf86202dc8a3c3d671f2106d5cffa593f2b320
Author: Michael <michael@easyctf.com>
Date:   Mon Mar 13 07:11:45 2017 +0000


commit 15ca375e54f056a576905b41a417b413c57df6eb
Author: Fernando <fermayo@gmail.com>
Date:   Sat Dec 14 12:50:09 2013 -0300

    initial version

commit 8ac4f76df2ce8db696d75f5f146f4047a315af22
Author: Fernando Mayo <fermayo@gmail.com>
Date:   Sat Dec 14 07:36:18 2013 -0800

    Initial commit
$ git show
commit ee9061b25d8a35bae8380339f187b44dc26f4999
Author: Michael <michael@easyctf.com>
Date:   Mon Mar 13 07:11:47 2017 +0000

    Whoops! Remove flag.

diff --git a/flag.txt b/flag.txt
deleted file mode 100644
index a1009d9..0000000
--- a/flag.txt
+++ /dev/null
@@ -1 +0,0 @@

Flag: easyctf{w3_ev3n_u53_git}