# 基本概念

# JVM

JVM 是 Java 平台的核心,以机器代码来实现,为程序执行提供了所需的所有基本功能,例如字节码解析器、JIT 编译器、垃圾收集器等。由于它是机器代码实现的,其同样受到二进制文件受到的攻击。

JCL 是 JVM 自带的一个标准库,含有数百个系统类。默认情况下,所有系统类都是可信任的,且拥有所有的特权。

# JDK

Java 开发工具包 (Java Development Kit,JDK) 是 Oracle 公司发布的 Java 平台,有标准版 (Standard Edition,Java SE)、企业版 (Enterprise Edition,Java EE) 等版本。

在最开始,JDK 以二进制形式发布,而后在 2006 年 11 月 17 日,Sun 以 GPL 许可证发布了 Java 的源代码,于是之后出现了 OpenJDK。

# JMX

JMX (Java Management Extensions,Java 管理扩展) 是一个为应用程序植入管理功能的框架,主要为管理和监视应用程序、系统对象、设备和面向服务的网络提供相应的工具。JMX 可以远程读取系统中的值、调用系统中的方法。在 JMX 未配置身份验证或 JDK 版本过低存在反序列化漏洞时,可能会导致远程代码执行。

# JNI

JNI (Java Native Interface) 是 Java 提供的和其他语言交互的接口。

# JNA

JNA (Java Native Access) 是在 JNI 上的框架,用于自动实现 Java 接口到 native function 的映射,而不需要另外编写 JNI 代码。

# OGNL

OGNL (Object-Graph Navigation Language,对象导航语言) 是一种功能强大的表达式语言,通过简单一致的表达式语法,提供了存取对象的任意属性、调用对象的方法、遍历整个对象的结构图、实现字段类型转化等功能。

Struts2 中使用了 OGNL,提供了一个 ValueStack 类。ValueStack 分为 root 和 context 两部分。root 中是当前的 action 对象,context 中是 ActionContext 里面所有的内容。

# IO 模型

Java 对操作系统的各种 IO 模型进行了封装,形成了不同的 API。

# BIO

BIO (Blocking I/O) 是同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。

# NIO

NIO (New I/O) 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。

# AIO

AIO (Asynchronous I/O) 在 Java 7 中引入,是 NIO 的改进版,是异步非阻塞的 IO 模型,基于事件和回调机制实现。

# 反射

# 简介

Java 反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能被称为语言的反射机制。

# 相关类

类名 用途
Class 类的实体
Field 类的成员变量
Method 类的方法
Constructor 类的构造方法

# Class 相关

  • asSubclass(Class<U> clazz)
    • 把传递的类的对象转换成代表其子类的对象
  • Cast
    • 把对象转换成代表类或是接口的对象
  • getClassLoader()
    • 获得类的加载器
  • getClasses()
    • 返回一个数组,数组中包含该类中所有公共类和接口类的对象
  • getDeclaredClasses()
    • 返回一个数组,数组中包含该类中所有类和接口类的对象
  • forName(String className)
    • 根据类名返回类的对象
  • getName():
    • 获得类的完整路径名字
  • newInstance()
    • 创建类的实例
  • getPackage()
    • 获得类的包
  • getSimpleName()
    • 获得类的名字
  • getSuperclass()
    • 获得当前类继承的父类的名字
  • getInterfaces()
    • 获得当前类实现的类或是接口
  • getField(String name)
    • 获得某个公有的属性对象
  • getFields()
    • 获得所有公有的属性对象
  • getDeclaredField(String name)
    • 获得某个属性对象
  • getDeclaredFields()
    • 获得所有属性对象
  • getAnnotation(Class<A> annotationClass)
    • 返回该类中与参数类型匹配的公有注解对象
  • getAnnotations()
    • 返回该类所有的公有注解对象
  • getDeclaredAnnotation(Class<A> annotationClass)
    • 返回该类中与参数类型匹配的所有注解对象
  • getDeclaredAnnotations()
    • 返回该类所有的注解对象
  • getConstructor(Class...<?> parameterTypes)
    • 获得该类中与参数类型匹配的公有构造方法
  • getConstructors()
    • 获得该类的所有公有构造方法
  • getDeclaredConstructor(Class...<?> parameterTypes)
    • 获得该类中与参数类型匹配的构造方法
  • getDeclaredConstructors()
    • 获得该类所有构造方法
  • getMethod(String name, Class...<?> parameterTypes)
    • 获得该类某个公有的方法
  • getMethods()
    • 获得该类所有公有的方法
  • getDeclaredMethod(String name, Class...<?> parameterTypes)
    • 获得该类某个方法
  • getDeclaredMethods()
    • 获得该类所有方法
  • isAnnotation()
    • 如果是注解类型则返回 true
  • isAnnotationPresent(Class<? extends Annotation> annotationClass)
    • 如果是指定类型注解类型则返回 true
  • isAnonymousClass()
    • 如果是匿名类则返回 true
  • isArray()
    • 如果是一个数组类则返回 true
  • isEnum()
    • 如果是枚举类则返回 true
  • isInstance(Object obj)
    • 如果 obj 是该类的实例则返回 true
  • isInterface()
    • 如果是接口类则返回 true
  • isLocalClass()
    • 如果是局部类则返回 true
  • isMemberClass()
    • 如果是内部类则返回 true

# Field 相关

  • equals(Object obj)
    • 属性与 obj 相等则返回 true
  • get(Object obj)
    • 获得 obj 中对应的属性值
  • set(Object obj, Object value)
    • 设置 obj 中对应属性值

# Method 相关

  • invoke(Object obj, Object... args)

    • 传递 object 对象及参数调用该对象对应的方法

# Constructor

  • newInstance(Object... initargs)

    • 根据传递的参数创建类的对象

#

# 生命周期

整体来说,Java 中类的生命周期如下:加载 (Loading) -> [ 连接 (Linking) : 验证 (Verification) -> 准备 (Perparation) -> 解析 (Resolutin) ] -> 初始化 (Initialization) -> 使用 (Using) -> 卸载 (Unloading) 。

加载过程分为三步:

  • 通过全限定类名来获取定义此类的二进制字节流
  • 将字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口

验证阶段主要用于确保 Class 文件的字节流符合当前虚拟机的要求,分为几步:

  • 判断文件格式:是否以 0xCAFEBABE 开始,主次版本号是否在处理范围内
  • 元数据验证
  • 字节码验证
  • 符号引用验证

# 部分运行选项与说明

  • -Xverify:none 关闭类加载时的验证措施

# 框架

# Servlet

# 简介

Servlet (Server Applet) 是 Java Servlet 的简称,称为小服务程序或服务连接器,是用 Java 编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态 Web 内容。

狭义的 Servlet 是指 Java 语言实现的一个接口,广义的 Servlet 是指任何实现了这个 Servlet 接口的类,一般情况下,人们将 Servlet 理解为后者。Servlet 运行于支持 Java 的应用服务器中。从原理上讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于 HTTP 协议的 Web 服务器。

# 生命周期为

  • 客户端请求该 Servlet
  • 加载 Servlet 类到内存
  • 实例化并调用 init () 方法初始化该 Servlet
  • service ()(根据请求方法不同调用 doGet() / doPost() / ... / destroy()

# 接口

init()

在 Servlet 的生命期中,仅执行一次 init () 方法,在服务器装入 Servlet 时执行。

service()

service () 方法是 Servlet 的核心。每当一个客户请求一个 HttpServlet 对象,该对象的 service() 方法就要被调用,而且传递给这个方法一个 "请求"(ServletRequest) 对象和一个 "响应"(ServletResponse) 对象作为参数。

# Struts 2

# 简介

Struts2 是一个基于 MVC 设计模式的 Web 应用框架,它本质上相当于一个 servlet,在 MVC 设计模式中,Struts2 作为控制器 (Controller) 来建立模型与视图的数据交互。

# 请求流程

  • 客户端发送请求的 tomcat 服务器
  • 请求经过一系列过滤器
  • FilterDispatcher 调用 ActionMapper 来决定这个请求是否要调用某个 Action
  • ActionMppaer 决定调用某个 ActionFilterDispatcher 把请求给 ActionProxy
  • ActionProxy 通过 Configuration Manager 查看 structs.xml,找到对应的 Action 类
  • ActionProxy 创建一个 ActionInvocation 对象
  • ActionInvocation 对象回调 Action 的 execute 方法
  • Action 执行完毕后,ActionInvocation 根据返回的字符串,找到相应的 result,通过 HttpServletResponse 返回给服务器

# 相关 CVE

  • CVE-2016-3081 (S2-032)
  • CVE-2016-3687 (S2-033)
  • CVE-2016-4438 (S2-037)
  • CVE-2017-5638
  • CVE-2017-7672
  • CVE-2017-9787
  • CVE-2017-9793
  • CVE-2017-9804
  • CVE-2017-9805
  • CVE-2017-12611
  • CVE-2017-15707
  • CVE-2018-1327
  • CVE-2018-11776

# Spring

# 简介

Spring 一般指的是 Spring Framework,一个轻量级 Java 应用程序开源框架,提供了简易的开发方式。

# Spring MVC

Spring MVC 根据 Spring 的模式设计的 MVC 框架,主要用于开发 Web 应用,简化开发。

# Spring Boot

Spring 在推出之初方案较为繁琐,因此提供了 Spring Boot 作为自动化配置工具,降低项目搭建的复杂度。

# 请求流程

  • 用户发送请求给服务器
  • 服务器收到请求,使用 DispatchServlet 处理
  • Dispatch 使用 HandleMapping 检查 url 是否有对应的 Controller,如果有,执行
  • 如果 Controller 返回字符串,ViewResolver 将字符串转换成相应的视图对象
  • DispatchServlet 将视图对象中的数据,输出给服务器
  • 服务器将数据输出给客户端

# CVE 概览

  • CVE-2018-1270
    • Spring Websocket 远程代码执行漏洞
    • Spring Framework 5.0 - 5.0.5
    • Spring Framework 4.3 - 4.3.15
  • CVE-2018-1273
    • Spring Data 远程代码执行漏洞
    • Spring Data Commons 1.13 - 1.13.10
    • Spring Data Commons 2.0 - 2.0.5
    • Spring Data REST 2.6 - 2.6.10
    • Spring Data REST 3.0 - 3.0.5
  • CVE-2017-8046
    • Spring Data REST 远程代码执行漏洞
  • CVE-2017-4971
    • Spring Web Flow 远程代码执行漏洞

# Shiro

# 简介

Apache Shiro 是一个功能强大且易于使用的 Java 安全框架,功能包括身份验证,授权,加密和会话管理。

# CVE 概览

  • CVE-2020-13933
    • Apache Shiro < 1.6.0
    • 身份验证绕过漏洞
  • CVE-2020-11989
    • SHIRO-782
    • Apache Shiro < 1.5.3
    • 身份验证绕过漏洞
  • CVE-2020-1957
    • SHIRO-682
    • Apache Shiro < 1.5.2
    • 身份验证绕过漏洞
  • CVE-2019-12422
    • SHIRO-721
    • Apache Shiro < 1.4.2
    • Padding Oracle Attack 远程代码执行漏洞
  • CVE-2016-4437
    • SHIRO-550
    • Apache Shiro <= 1.2.4
    • 反序列化远程代码执行漏洞
  • CVE-2014-0074
    • SHIRO-460
    • Apache Shiro < 1.2.3
    • 身份验证绕过漏洞

# CVE-2020-13933

Apache Shiro 1.6.0 之前的版本,由于 Shiro 拦截器与 requestURI 的匹配流程与 Web 框架的拦截器的匹配流程有差异,攻击者构造一个特殊的 http 请求,可以绕过 Shiro 的认证,未授权访问敏感路径。

# CVE-2020-11989

Apache Shiro 1.5.3 之前的版本,由于 Shiro 拦截器与 requestURI 的匹配流程与 Web 框架的拦截器的匹配流程有差异,攻击者构造一个特殊的 http 请求,可以绕过 Shiro 的认证,未授权访问敏感路径。此漏洞存在两种攻击方式。

# CVE-2020-1957

Apache Shiro 1.5.2 之前的版本,由于 Shiro 拦截器与 requestURI 的匹配流程与 Web 框架的拦截器的匹配流程有差异,攻击者构造一个特殊的 http 请求,可以绕过 Shiro 的认证,未授权访问敏感路径。

# CVE-2019-12422

Apache Shiro 1.4.2 之前的版本默认使用 AES/CBC/PKCS5Padding 模式加密,开启 RememberMe 功能的 Shiro 组件将允许远程攻击者构造序列化数据,通过 Padding Oracle Attack 进行爆破,即使在秘钥未知的条件下,也可以在目标服务器上执行任意命令。

# CVE-2016-4437

Apache Shiro 1.2.5 之前的版本在 org.apache.shiro.mgt.AbstractRememberMeManager 中存在 AES 默认秘钥 kPH+bIxk5D2deZiIxcaaaA== ,开启 RememberMe 功能的 Shiro 组件将允许远程攻击者构造序列化数据,在目标服务器上执行任意命令。

# 容器

常见的 Java 服务器有 Tomcat、Weblogic、JBoss、GlassFish、Jetty、Resin、IBM Websphere 等,这里对部分框架做一个简单的说明。

# Tomcat

Tomcat 是一个轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,用于开发和调试 JSP 程序。

在收到请求后,Tomcat 的处理流程如下:

  • 客户端访问 Web 服务器,发送 HTTP 请求
  • Web 服务器接收到请求后,传递给 Servlet 容器
  • Servlet 容器加载 Servlet,产生 Servlet 实例后,向其传递表示请求和响应的对象
  • Servlet 实例使用请求对象得到客户端的请求信息,然后进行相应的处理
  • Servlet 实例将处理结果通过响应对象发送回客户端,容器负责确保响应正确送出,同时将控制返回给 Web 服务器

Tomcat 服务器是由一系列可配置的组件构成的,其中核心组件是 Catalina Servlet 容器,它是所有其他 Tomcat 组件的顶层容器。

# 相关 CVE

  • CVE-2020-1938
    • https://www.freebuf.com/vuls/228108.html
  • CVE-2019-0232
    • 远程代码执行
    • https://github.com/pyn3rd/CVE-2019-0232/
  • CVE-2017-12615
    • 任意文件写入
    • https://mp.weixin.qq.com/s?__biz=MzI1NDg4MTIxMw==&mid=2247483659&idx=1&sn=c23b3a3b3b43d70999bdbe644e79f7e5
  • CVE-2013-2067
  • CVE-2012-4534
  • CVE-2012-4431
  • CVE-2012-3546
  • CVE-2012-3544
  • CVE-2012-2733
  • CVE-2011-3375
  • CVE-2011-3190
  • CVE-2008-2938

# Weblogic

# 简介

WebLogic 是美国 Oracle 公司出品的一个 Application Server,是一个基于 Java EE 架构的中间件,WebLogic 是用于开发、集成、部署和管理大型分布式 Web 应用、网络应用和数据库应用的 Java 应用服务器。其将 Java 的动态功能和 Java Enterprise 标准的安全性引入大型网络应用的开发、集成、部署和管理之中。

WebLogic 对业内多种标准的全面支持,包括 EJB、JSP、Servlet、JMS、JDBC 等。

# 相关 CVE

  • CVE-2019-2725
    • wls-wsat 反序列化远程代码执行
  • CVE-2019-2658
  • CVE-2019-2650
  • CVE-2019-2649
  • CVE-2019-2648
  • CVE-2019-2647
  • CVE-2019-2646
  • CVE-2019-2645
  • CVE-2019-2618
    • https://github.com/jas502n/cve-2019-2618/
  • CVE-2019-2615
  • CVE-2019-2568
  • CVE-2018-3252
  • CVE-2018-3248
  • CVE-2018-3245
  • CVE-2018-3201
  • CVE-2018-3197
  • CVE-2018-3191
    • https://github.com/voidfyoo/CVE-2018-3191
    • https://github.com/Libraggbond/CVE-2018-3191
  • CVE-2018-2894
    • 任意文件上传
    • https://xz.aliyun.com/t/2458
  • CVE-2018-2893
    • 反序列化
    • https://www.freebuf.com/vuls/178105.html
  • CVE-2018-2628
    • https://mp.weixin.qq.com/s/nYY4zg2m2xsqT0GXa9pMGA
  • CVE-2018-1258
  • CVE-2017-10271
    • XMLDecoder 反序列化漏洞
    • http://webcache.googleusercontent.com/search?q=cache%3AsH7j8TF8uOIJ%3Awww.freebuf.com%2Fvuls%2F160367.html
  • CVE-2017-3248
  • CVE-2016-3510
  • CVE-2015-4852
    • https://github.com/roo7break/serialator

# JBoss

# 简介

JBoss 是一个基于 J2EE 的管理 EJB 的容器和服务器,但 JBoss 核心服务不包括支持 servlet/JSP 的 WEB 容器,一般与 Tomcat 或 Jetty 绑定使用。

# 相关 CVE

  • CVE-2017-12149
    • 反序列化漏洞
    • 访问 /invoker/readonly ,页面存在即有反序列化漏洞

# Jetty

# 简介

Jetty 是一个开源的 servlet 容器。

# 沙箱

# 简介

Java 实现了一套沙箱环境,使远程的非可信代码只能在受限的环境下执行。

# 相关 CVE

  • CVE-2012-0507
  • CVE-2012-4681
  • CVE-2017-3272
  • CVE-2017-3289

# 反序列化

# 简介

序列化就是把对象转换成字节流,便于保存在内存、文件、数据库中;反序列化即逆过程,由字节流还原成对象。一般用于远程调用、通过网络将对象传输至远程服务器、存储对象到数据库或本地等待重用等场景中。Java 中的 ObjectOutputStream 类的 writeObject() 方法可以实现序列化,类 ObjectInputStream 类的 readObject() 方法用于反序列化。如果要实现类的反序列化,则是对其实现 Serializable 接口。

当远程服务接受不可信的数据并进行反序列化且当前环境中存在可利用的类时,就认为存在反序列化漏洞。

# 序列数据结构

  • 0xaced 魔术头 / STREAM_MAGIC
  • 0x0005 版本号 / STREAM_VERSION / 参考 java.io.ObjectStreamConstants
  • 0x73 对象类型标识
  • 0x72 类描述符标识

# 序列化流程

  • ObjectOutputStream 实例初始化时,将魔术头和版本号写入 bout (BlockDataOutputStream 类型) 中
  • 调用 ObjectOutputStream.writeObject () 开始写对象数据
    • ObjectStreamClass.lookup () 封装待序列化的类描述 (返回 ObjectStreamClass 类型) ,获取包括类名、自定义 serialVersionUID、可序列化字段 (返回 ObjectStreamField 类型) 和构造方法,以及 writeObject、readObject 方法等
    • writeOrdinaryObject () 写入对象数据
      • 写入对象类型标识
      • writeClassDesc () 进入分支 writeNonProxyDesc () 写入类描述数据
        • 写入类描述符标识
        • 写入类名
        • 写入 SUID (当 SUID 为空时,会进行计算并赋值)
        • 计算并写入序列化属性标志位
        • 写入字段信息数据
        • 写入 Block Data 结束标识
        • 写入父类描述数据
      • writeSerialData () 写入对象的序列化数据
        • 若类自定义了 writeObject (),则调用该方法写对象,否则调用 defaultWriteFields () 写入对象的字段数据 (若是非原始类型,则递归处理子对象)

# 5.3.7.1.3. 反序列化流程

  • ObjectInputStream 实例初始化时,读取魔术头和版本号进行校验

  • 调用 ObjectInputStream.readObject () 开始读对象数据

    • 读取对象类型标识
    • readOrdinaryObject () 读取数据对象
      • readClassDesc () 读取类描述数据
        • 读取类描述符标识,进入分支 readNonProxyDesc ()
        • 读取类名
        • 读取 SUID
        • 读取并分解序列化属性标志位
        • 读取字段信息数据
        • resolveClass () 根据类名获取待反序列化的类的 Class 对象,如果获取失败,则抛出 ClassNotFoundException
        • skipCustomData () 循环读取字节直到 Block Data 结束标识为止
        • 读取父类描述数据
        • initNonProxy () 中判断对象与本地对象的 SUID 和类名 (不含包名) 是否相同,若不同,则抛出 InvalidClassException
      • ObjectStreamClass.newInstance () 获取并调用离对象最近的非 Serializable 的父类的无参构造方法 (若不存在,则返回 null) 创建对象实例
      • readSerialData () 读取对象的序列化数据
        • 若类自定义了 readObject (),则调用该方法读对象,否则调用 defaultReadFields () 读取并填充对象的字段数据

# 5.3.7.2. 漏洞利用

# 5.3.7.2.1. 存在危险的基础库

  • com.mchange:c3p0 0.9.5.2
  • com.mchange:mchange-commons-java 0.2.11
  • commons-beanutils 1.9.2
  • commons-collections 3.1
  • commons-fileupload 1.3.1
  • commons-io 2.4
  • commons-logging 1.2
  • org.apache.commons:commons-collections 4.0
  • org.beanshell:bsh 2.0b5
  • org.codehaus.groovy:groovy 2.3.9
  • org.slf4j:slf4j-api 1.7.21
  • org.springframework:spring-aop 4.1.4.RELEASE

# 5.3.7.2.2. 回显方式

  • 通过中间件特性回显
  • 通过抛出异常回显
  • 通过 OOB 回显
  • 通过写静态文件回显

# 5.3.7.3. 漏洞修复和防护

# 5.3.7.3.1. Hook resolveClass

在使用 readObject() 反序列化时会调用 resolveClass 方法读取反序列化的类名,可以通过 hook 该方法来校验反序列化的类,一个 Demo 如下

@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
    if (!desc.getName().equals(SerialObject.class.getName())) {
        throw new InvalidClassException(
                "Unauthorized deserialization attempt",
                desc.getName());
    }
    return super.resolveClass(desc);
}

以上的 Demo 就只允许序列化 SerialObject ,通过这种方式,就可以设置允许序列化的白名单,来防止反序列化漏洞被利用。SerialKiller/Jackson/Weblogic 等都使用了这种方式来防御。

# 5.3.7.3.2. ValidatingObjectInputStream

Apache Commons IO Serialization 包中的 ValidatingObjectInputStream 类提供了 accept 方法,可以通过该方法来实现反序列化类白 / 黑名单控制,一个 demo 如下

private static Object deserialize(byte[] buffer) throws IOException, ClassNotFoundException , ConfigurationException {
    Object obj;
    ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
    ValidatingObjectInputStream ois = new ValidatingObjectInputStream(bais);
    ois.accept(SerialObject.class);
    obj = ois.readObject();
    return obj;
}

# 5.3.7.3.3. ObjectInputFilter(JEP290)

Java 9 提供了支持序列化数据过滤的新特性,可以继承 java.io.ObjectInputFilter 类重写 checkInput 方法来实现自定义的过滤器,并使用 ObjectInputStream 对象的 setObjectInputFilter 设置过滤器来实现反序列化类白 / 黑名单控制。这个机制本身是针对 Java 9 的一个新特性,但是随后官方突然决定向下引进该增强机制,分别对 JDK 6,7,8 进行了支持。这个机制主要描述了如下的机制:

  • 提供一个限制反序列化类的机制,白名单或者黑名单
  • 限制反序列化的深度和复杂度
  • 为 RMI 远程调用对象提供了一个验证类的机制
  • 定义一个可配置的过滤机制,比如可以通过配置 properties 文件的形式来定义过滤器

# RMI

# 简介

RMI (Remote Method Invocation,远程方法调用) 能够让在客户端 Java 虚拟机上的对象像调用本地对象一样调用服务端 Java 虚拟机中的对象上的方法。其中 RMI 标准实现是 Java RMI,之外还有 Weblogic RMI、Spring RMI 等不同的实现。

RMI 中比较重要的两个概念是 Stub 和 Skeleton,Stub 和 Skeleton 对同一套接口进行实现,其中 Stub 由 Client 端调用,并不进行真正的实现,而是和 Server 端通信。Skeleton 是 Server 端,监听来自 Stub 的连接,根据 Stub 发送的数据进行真正的操作。

# 调用步骤

  • 客户调用客户端辅助对象 Stub 上的方法
  • 客户端辅助对象 Stub 打包调用信息 (变量,方法名),通过网络发送给服务端辅助对象 Skeleton
  • 服务端辅助对象 Skeleton 将客户端辅助对象发送来的信息解包,找出真正被调用的方法以及该方法所在对象
  • 调用真正服务对象上的真正方法,并将结果返回给服务端辅助对象 Skeleton
  • 服务端辅助对象将结果打包,发送给客户端辅助对象 Stub
  • 客户端辅助对象将返回值解包,返回给调用者
  • 客户获得返回值

# 样例

一份代码样例如下 (来自《Enterprise JavaBeans》):

# Person 接口定义

public interface Person {
    public int getAge() throws Throwable;
    public String getName() throws Throwable;
}

# 使用 PersonServer 实现 Person

public class PersonServer implements Person {
    private int age;
    private String name;
    public PersonServer(String name, int age) {
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }
}

# 使用 Person_Stub 实现 Person

import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.net.Socket;
public class Person_Stub implements Person {
    private Socket socket;
    public Person_Stub() throws Throwable {
        // connect to skeleton
        socket = new Socket("computer_name", 9000);
    }
    public int getAge() throws Throwable {
        // pass method name to skeleton
        ObjectOutputStream outStream =
            new ObjectOutputStream(socket.getOutputStream());
        outStream.writeObject("age");
        outStream.flush();
        ObjectInputStream inStream =
            new ObjectInputStream(socket.getInputStream());
        return inStream.readInt();
    }
    public String getName() throws Throwable {
        // pass method name to skeleton
        ObjectOutputStream outStream =
            new ObjectOutputStream(socket.getOutputStream());
        outStream.writeObject("name");
        outStream.flush();
        ObjectInputStream inStream =
            new ObjectInputStream(socket.getInputStream());
        return (String)inStream.readObject();
    }
}

# Skeleton 的实现

import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.net.Socket;
import java.net.ServerSocket;
public class Person_Skeleton extends Thread {
    private PersonServer myServer;
    public Person_Skeleton(PersonServer server) {
        // get reference of object server
        this.myServer = server;
    }
    public void run() {
        try {
            // new socket at port 9000
            ServerSocket serverSocket = new ServerSocket(9000);
            // accept stub's request
            Socket socket = serverSocket.accept();
            while (socket != null) {
                // get stub's request
                ObjectInputStream inStream =
                    new ObjectInputStream(socket.getInputStream());
                String method = (String)inStream.readObject();
                // check method name
                if (method.equals("age")) {
                    // execute object server's business method
                    int age = myServer.getAge();
                    ObjectOutputStream outStream =
                        new ObjectOutputStream(socket.getOutputStream());
                    // return result to stub
                    outStream.writeInt(age);
                    outStream.flush();
                }
                if(method.equals("name")) {
                    // execute object server's business method
                    String name = myServer.getName();
                    ObjectOutputStream outStream =
                        new ObjectOutputStream(socket.getOutputStream());
                    // return result to stub
                    outStream.writeObject(name);
                    outStream.flush();
                }
            }
        } catch(Throwable t) {
            t.printStackTrace();
            System.exit(0);
        }
    }
    public static void main(String args []) {
        // new object server
        PersonServer person = new PersonServer("Richard", 34);
        Person_Skeleton skel = new Person_Skeleton(person);
        skel.start();
    }
}

# Client 实现

public class PersonClient {
    public static void main(String [] args) {
        try {
            Person person = new Person_Stub();
            int age = person.getAge();
            String name = person.getName();
            System.out.println(name + " is " + age + " years old");
        } catch(Throwable t) {
            t.printStackTrace();
        }
    }
}

# T3 协议

T3 协议是用于在 WebLogic 服务器和其他类型的 Java 程序之间传输信息的协议,是 Weblogic 对 RMI 规范的实现。简单来说,可以把 T3 视为暴露 JDNI 给用户调用的接口。

# JRMP

Java 远程方法协议 (Java Remote Method Protocol,JRMP) 是特定于 Java 技术的、用于查找和引用远程对象的协议。这是运行在 Java 远程方法调用 (RMI) 之下、TCP/IP 之上的线路层协议。

JRMP 是一个 Java 特有的、适用于 Java 之间远程调用的基于流的协议,要求客户端和服务器上都使用 Java 对象。

# JNDI

# 简介

JNDI (Java Naming and Directory Interface,Java 命名和目录接口) 是为 Java 应用程序提供命名和目录访问服务的 API,允许客户端通过名称发现和查找数据、对象,用于提供基于配置的动态调用。这些对象可以存储在不同的命名或目录服务中,例如 RMI、CORBA、LDAP、DNS 等。

其中 Naming Service 类似于哈希表的 K/V 对,通过名称去获取对应的服务。Directory Service 是一种特殊的 Naming Service,用类似目录的方式来存取服务。

# JNDI 注入

JNDI 注入是 2016 年由 pentester 在 BlackHat USA 上的 A Journey From JNDI LDAP Manipulation To RCE 议题提出的。

其攻击过程如下

  1. 攻击者将 Payload 绑定到攻击者的命名 / 目录服务中
  2. 攻击者将绝对 URL 注入易受攻击的 JNDI 查找方法
  3. 应用程序执行查找
  4. 应用程序连接到攻击者控制的 JNDI 服务并返回 Payload
  5. 应用程序解码响应并触发有效负载

# 攻击载荷

JDNI 主要有几种攻击载荷:

  • CORBA
  • IOR
  • JNDI Reference
  • LDAP
  • Remote Location
  • Remote Object
  • RMI
  • Serialized Object

# RMI Remote Object

攻击者实现一个 RMI 恶意远程对象并绑定到 RMI Registry 上,将编译后的 RMI 远程对象类放在 HTTP/FTP/SMB 等服务器上。其中 Codebase 地址由远程服务器的 java.rmi.server.codebase 属性设置,供受害者的 RMI 客户端远程加载。

利用条件如下:

  • RMI 客户端的上下文环境允许访问远程 Codebase。
  • 属性 java.rmi.server.useCodebaseOnly 的值为 false。

其中 JDK 6u45、7u21 后, java.rmi.server.useCodebaseOnly 的值默认为 true。

# RMI + JNDI Reference

攻击者通过 RMI 服务返回一个 JNDI Naming Reference,受害者解码 Reference 时会去攻击者指定的远程地址加载 Factory 类。这种方式原理上并非使用 RMI Class Loading 机制,因此不受 java.rmi.server.useCodebaseOnly 系统属性的限制。但是在 JDK 6u132, JDK 7u122, JDK 8u113 后限制了 Naming/Directory 服务中 JNDI Reference 远程加载 Object Factory 类的特性。系统属性 com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为 false,即默认不允许从远程的 Codebase 加载 Reference 工厂类。

# LDAP + JNDI Reference

Java 的 LDAP 可以在属性值中存储特定的 Java 对象,且 LDAP 服务的 Reference 远程加载 Factory 类不受 com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebase 等属性的限制,适用范围更广。

# JDK

# JDK 8

  • sun.net.www.protocol 不再支持 gopher 协议

# 8u251

  • com.sun.org.apache.bcel.internal.util.ClassLoader 类被删除

# 8u191

  • LDAP 远程 Reference 代码默认不信任,影响 LDAP 远程 Reference 代码攻击方式

# 8u121

  • RMI 加入了反序列化白名单机制
  • RMI 远程 Reference 代码默认不信任,影响 RMI 远程 Reference 代码攻击方式

# 8u113

  • com.sun.jndi.rmi.object.trustURLCodebase 默认为 false
  • com.sun.jndi.cosnaming.object.trustURLCodebase 默认为 false

# JDK 7

# 7u201

  • LDAP 远程 Reference 代码默认不信任,影响 LDAP 远程 Reference 代码攻击方式

# 7u122

  • com.sun.jndi.rmi.object.trustURLCodebase 默认为 false
  • com.sun.jndi.cosnaming.object.trustURLCodebase 默认为 false

# 7u40

  • java.io.File 类中添加了 isInvalid 方法,检测文件名中是否包含空字节

# JDK 6

# 6u211

  • LDAP 远程 Reference 代码默认不信任,影响 LDAP 远程 Reference 代码攻击方式

# 6u141

  • com.sun.jndi.rmi.object.trustURLCodebase 默认为 false
  • com.sun.jndi.cosnaming.object.trustURLCodebase 默认为 false

# 6u45

  • java.rmi.server.useCodebaseOnly 默认为 true,禁用自动加载远程类文件

# 常见 Sink

# 命令执行 / 注入

  • java.lang.Runtime.getRuntime().exec()
  • java.lang.ProcessBuilder

# XXE

  • java.net.bull.javamelody.PayloadNameRequestWrapper
  • javax.xml.bind.Unmarshaller
  • javax.xml.parsers.DocumentBuilderFactory
  • javax.xml.parsers.SAXParser
  • javax.xml.stream.XMLStreamReader
  • javax.xml.transform.sax.SAXSource
  • javax.xml.transform.sax.SAXTransformerFactory
  • javax.xml.transform.TransformerFactory
  • javax.xml.validation.SchemaFactory
  • javax.xml.validation.Validator
  • javax.xml.xpath.XpathExpression
  • org.apache.commons.digester3.Digester
  • org.apache.ofbiz.base.util.UtilXml
  • org.dom4j.io.SAXReader
  • org.jdom.input.SAXBuilder
  • org.jdom2.input.SAXBuilder
  • org.xml.sax.helpers.XMLReaderFactory
  • org.xml.sax.XMLReader

# SSRF

  • HttpClient.execute
  • HttpClients.execute
  • HttpURLConnection.getInputStream
  • ImageIO.read
  • OkHttpClient.newCall.execute
  • Request.Get.execute
  • Request.Post.execute
  • URL.openStream
  • URLConnection.getInputStream

# 反序列化

# 相关 Sink 函数

  • JSON.parseObject
  • ObjectInputStream.readObject
  • ObjectInputStream.readUnshared
  • ObjectMapper.readValue
  • XMLDecoder.readObject
  • XStream.fromXML
  • Yaml.load

# Magic Call

以下的魔术方法都会在反序列化过程中被自动的调用。

  • readObject
  • readExternal
  • readResolve
  • readObjectNoData
  • validateObject
  • finalize

# 主流 JSON 库

主流的 JSON 库有 Gson、Jackson、Fastjson 等,因为 JSON 常在反序列化中使用,所以相关库都有较大的影响。

其中 Gson 默认只能反序列化基本类型,如果是复杂类型,需要程序员实现反序列化机制,相对比较安全。

Jackson 除非指明 @jsonAutoDetect,Jackson 不会反序列化非 public 属性。在防御时,可以不使用 enableDefaultTyping 方法。相关 CVE 有 CVE-2017-7525、CVE-2017-15095。

FastJson 是阿里巴巴的开源 JSON 解析库,支持将 Java Bean 序列化为 JSON 字符串,也支持从 JSON 字符串反序列化到 Java Bean,相关 CVE 有 CVE-2017-18349 等。

FastJson 常见的 Sink 点有:

  • JSON.toJSONString
  • JSON.parseObject
  • JSON.parse

# WebShell

# BCEL 字节码

String bcelCode = "...";
response.getOutputStream().write(String.valueOf(new ClassLoader().loadClass(bcelCode).getConstructor(String.class).newInstance(request.getParameter("cmd")).toString()).getBytes());

# 自定义类加载器

response.getOutputStream().write(new ClassLoader() {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (name.contains("shell")) {
            return findClass(name);
        }
        return super.loadClass(name);
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] bytes = Base64.getDecoder().decode("...");
            PermissionCollection pc = new Permissions();
            pc.add(new AllPermission());
            ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), pc, this, null);
            return this.defineClass(name, bytes, 0, bytes.length, protectionDomain);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }
}.loadClass("shell").getConstructor(String.class).newInstance(request.getParameter("cmd")).toString().getBytes());
%>

# 执行命令变式

  • java.lang.ProcessBuilder#start
  • java.lang.Runtime#exec
  • TemplatesImpl

# 基于反射

  • class.forName
  • MethodAccessor.invoke
  • Method.invoke

# 其他 Shell 变式

  • java.beans.Expression
  • java.lang.ClassLoader
  • java.net.URLClassLoader
  • jdk.nashorn.internal.runtime.ScriptLoader
  • ObjectInputStream.resolveClass
  • ScriptEngine.eval
  • ScriptEngineManager
  • ToolProvider.getSystemJavaCompiler

# Tomcat 容器

  • Servlet
  • Filter
  • Listener

# 参考链接

# 官方文档

  • ognl
  • Java SE Security Guide
  • Java RMI Release Notes for JDK 6
  • Java Release Notes for JDK 7

# 机制说明

  • 深入理解 Java 类加载

# 反序列化

# 标准

  • Java 序列化【草案一】
  • Java 14 Object Serialization Specification

# 利用与技巧

  • Marshalling Pickles how deserializing objects can ruin your day
  • AppSecCali 2015: Marshalling Pickles
  • More serialization hacks with AnnotationInvocationHandler
  • Pure JRE 8 RCE Deserialization gadget
  • Breaking Defensive Serialization
  • Java 反序列化漏洞从入门到深入
  • Java 反序列化漏洞通用利用分析
  • JRE8u20 反序列化漏洞分析
  • 浅析 Java 序列化和反序列化
  • Commons Collections Java 反序列化漏洞深入分析
  • FAR SIDES OF JAVA REMOTE PROTOCOLS
  • JDK8u20 反序列化漏洞新型 PoC 思路及具体实现
  • Pwn a CTF Platform with Java JRMP Gadget
  • 漫谈 JEP 290

# 框架

  • WebLogic 反序列化漏洞漫谈
  • 从 WebLogic 看反序列化漏洞的利用与防御
  • JSON 反序列化之殇
  • Shiro 组件漏洞与攻击链分析
  • Application Security With Apache Shiro
  • Shiro 安全框架【快速入门】
  • Shiro 实战 (四) - 过滤器机制

# 沙箱

  • Java Sandbox Escape

# 框架

  • Struts
  • Struts Examples
  • Eclipse Jetty
  • SpringBootVulExploit SpringBoot 相关漏洞学习资料,利用方法和技巧合集,黑盒安全评估 checklist

# 框架利用技巧

  • Spring Boot Fat Jar 写文件漏洞到稳定 RCE 的探索

# RMI

  • Java RMI 与 RPC 的区别
  • Remote Method Invocation (RMI)
  • Java 中 RMI、JNDI、LADP、JRMP、JMX、JMS 那些事儿
  • Oracle: Developing T3 Clients

# JNDI

  • Overview of JNDI
  • 关于 JNDI 注入
  • A Journey From JNDI LDAP Manipulation To RCE
  • 如何绕过高版本 JDK 的限制进行 JNDI 注入

# WebShell

  • 各种姿势 jsp webshell

# 其他漏洞

  • JAVA 常见的 XXE 漏洞写法和防御