セキュリティ系の勉強・その他開発メモとか雑談. Twitter, ブログカテゴリ一覧
本ブログはあくまでセキュリティに関する情報共有の一環として作成したものであり,公開されているシステム等に許可なく実行するなど、違法な行為を助長するものではありません.

angstromctf 2019 WriteUp

//

はじめに

angstromctfというワードがtwitterで流れてきたのでその勢いで登録しました。初心者にも解ける問題があるよって言われたので、、、ctfの勉強なんてしようと思っててずっとしてこなかった感じなので、これを機に始めようかななんて思っています。なので、このWriteupは初心者がたまたま解けた問題を乗っけているだけなので詳しいことを知りたい方は別の方のページに飛んでくださいねっ!

REV

One Bite

ctfのサーバに実行ファイルがあるのでその対話に答えてフラグ取得するやつ。elfファイルをもらえるので解析します。radare2を使いました。
f:id:thinline196:20190421012101p:plain f:id:thinline196:20190421011839p:plain ・1つ目の入力はstrcmpで比較されている文字列okrrrrrrr
・2つ目は足して0x88(136)、かけて0xec7(3783)、になる2つの数字の小さい順3997
actf{okrrrrrrr_39_97}

MISC

The Mueller Report

pdf

 string target_file.pdf | grep actf

actf{no0o0o0_col1l1l1luuuusiioooon}

Blank Paper

firefoxでそのままファイルを開いたら、普通に開けました。著者の部分にフラグがかいてあります。
actf{knot_very_interesting}

Paper Bin

.datファイルが入手できます。ストーリー仕立てみたいになっていて、先ほどのpdfファイルが消えたからリカバリしてくれみたいな感じです。GUIからダブルクリックでPDFは開けるのですが、著者の部分は空欄です。hexdump -C target.datで一度開いてみると先頭からしばらく00で埋まっていたので、データの一部分ごっそり抜いてきたみたいなファイルかと。
https://qiita.com/forestsource/items/15933888466ba9c3f048

ファイル抽出してみようと思いforemostで抜いてみると20個PDFファイルが出てきました。しかしstringsで"actf"をgrep回してみたけど見つからず..(これで見つかるなら元ファイルにgrepかけただけで見つかるはずだし..)
foremostの出力ファイルがあるのでそれをみると、411KBの若干他より重いファイルと、コメントがついたファイルに当たりをつけて開いてみると、後者の著者欄にフラグがありました。 f:id:thinline196:20190420233820p:plain actf{proof_by_triviality}

Paper Trail

pcapngファイルが入手できるので、TCPStreamをFollow. 「ランダムに生成するよ:(」って言ってますが、辿ってみるとa,c,t,fとなるのでそのまま追って行きました。
actf{fake_math_papers}

WEB

No Sequels

以下のフォームを通す。最初はJWTかなと思っていたのですが、MongoDBでした。

router.post('/login', verifyJwt, function (req, res) {
    // monk instance
    var db = req.db;

    var user = req.body.username;
    var pass = req.body.password;

    if (!user || !pass){
        res.send("One or more fields were not provided.");
    }
    var query = {
        username: user,
        password: pass
    }

    db.collection('users').findOne(query, function (err, user) {
        if (!user){
            res.send("Wrong username or password");
            return
        }

        res.cookie('token', jwt.sign({name: user.username, authenticated: true}, secret));
        res.redirect("/site");
    });
});

Burpをかませて、jsonを送信します。

-  Content-Type: application/x-www-form-urlencoded
+ Content-Type: application/json

- username=hoge&password=hoge
+ {
+    "username" : {"$gt": ""},
+    "password" : {"$gt": ""}
+}

ネットだとapplication/x-www-form-urlencodedでのPoCが多いですが、一度パラメータの存在を確認しているので、username[$ne]=のような事はできませんでした。
actf{no_sql_doesn't_mean_no_vuln}

CRYPTO

Classy Cipher

暗号苦手なので簡単ですが書きます。
問題文

from secret import flag, shift

def encrypt(d, s):
    e = ''
    for c in d:
        e += chr((ord(c)+s) % 0xff)
    return e

assert encrypt(flag, shift) == ':<M?TLH8<A:KFBG@V'

ord()は文字を単語に、chr()は数字を文字に変換します。なので、読み込んできているshiftの値が分かれば、比較している文字列を復号できそうです。

shift = 0
for i in range(257):
    e = ''
    e = chr((ord('a')+i) % 0xff)
    if (e==':'):
        shift = i

l = ':<M?TLH8<A:KFBG@V'
e = ''
for c in l:
    e += chr((ord(c) -shift) % 0xff )
    
print(e)

pythonfor文がわからなかった。。
actf{so_charming}

Really Secure Algorithm

RSAは相変わらず苦手ですが、過去のスクリプトを引っ張ってきてそのまま利用しました。

#python2


def exgcd(m, n):
  if n>0:
    y,x,d = exgcd(n, m%n)
    return x, y-m/n*x, d
  else:
    return 1, 0, m

def n_module(_p,_q):
    return _p*_q


E = 65537
C = 7022848098469230958320047471938217952907600532361296142412318653611729265921488278588086423574875352145477376594391159805651080223698576708934993951618464460109422377329972737876060167903857613763294932326619266281725900497427458047861973153012506595691389361443123047595975834017549312356282859235890330349

p = 8337989838551614633430029371803892077156162494012474856684174381868510024755832450406936717727195184311114937042673575494843631977970586746618123352329889
q = 7755060911995462151580541927524289685569492828780752345560845093073545403776129013139174889414744570087561926915046519199304042166351530778365529171009493
N = n_module(p,q)

d = exgcd(E, (p-1)*(q-1))[0] % ((p-1)*(q-1))
P = pow(C, d, N)
print ("%x"%P).decode("hex")

actf{really_securent_algorithm}

Half and Half

解けたのですが正攻法ではないと思うので詳しくは書きません...

from secret import flag

def xor(x, y):
    o = ''
    for i in range(len(x)):
        o += chr(ord(x[i])^ord(y[i]))
    return o

assert len(flag) % 2 == 0

half = len(flag)//2
milk = flag[:half]
cream = flag[half:]

assert xor(milk, cream) == '\x15\x02\x07\x12\x1e\x100\x01\t\n\x01"'

フラグ文字列を半分に分けて1文字ずつ取り出してXORしてます。先頭の文字列と末尾はわかっているので、actf{_taste}がわかります。あとは総当たりかなと思ったのですが、組み合わせパターン多すぎて諦めました。問題文がMm, coffee. Best served with half and half!だったので、coffeeかなと決め打ちでやってみたら当たりました。下は最後にフラグを出したコード。ちゃんとした答えが知りたい。

targets = '\x15\x02\x07\x12\x1e\x100\x01\t\n\x01"'

# 全部で24文字
# answer = 'actf{000000}'
# e = ''
# for i in range(len(targets)):
#     e += chr(ord(targets[i])^ord(answer[i]))
# print(e) => "taste 19:1_"
x = 'actf{coffee_'
mid11_16 = '_taste'

e = ''
for i in range(len(targets)):
    e+= chr(ord(targets[i])^ord(x[i]))
print(x + e)

actf{coffee_tastes_good}

最後に

いくつか省略していますが、簡単な問題なので。結果は 359/1570位となりました。意外と上位に感じるかもですが、上の方は桁が違うので全然上位ではないです。ひとまずweb問に強くなりたいので頑張ろうと思います。以上。