【静态分析】在springboot使用太阿(Tai-e)02

news2024/11/19 17:52:16

参考:使用太阿(Tai-e)进行静态代码安全分析(spring-boot篇二) - 先知社区

 本文章使用的被分析代码为GitHub - JoyChou93/java-sec-code: Java web common vulnerabilities and security code which is base on springboot and spring security

----------------------------------------------------------------------------------

1. 原理分析

1.1 控制反转

什么是控制反转?控制反转(IoC)是一种设计原则,其中应用程序的控制权被反转,由框架或容器负责管理对象的生命周期和控制对象之间的关系。这意味着应用程序不再负责直接实例化和管理对象,而是将这些任务委托给外部的框架或容器。
以如下代码为例:

@Configuration
public class AppConfig {

    @Bean
    public MyServiceImpl myService() {
        return new MyServiceImpl();
    }
}
@RestController
public class MovieRecommenderController {

    @Autowired
    private MyServiceImpl service;

    @GetMapping("/")
    public void doSomeAction(){
        service.action();
    }

    // ...
}

观察上面两段代码, 发现service的实例化和管理被Spring框架控制,而不是由应用程序直接控制。使用控制反转这样的技术可以使代码的对象的创建与使用解耦,降低组件之间的耦合度,使系统更易于维护、扩展和测试。而依赖注入是IoC的一种实现方式。它是指将一个对象的依赖关系通过构造函数、方法参数或属性注入到对象中,而不是在对象内部硬编码这些依赖关系。通过依赖注入,对象变得更加灵活和可配置。

1.2 指针分析如何处理函数调用?

在Tai-e,污点分析是作为指针分析的一个插件,在指针分析中完成了污点分析。所以在进一步分析控制反转前,我们需要先了解一下指针分析的基础。
指针分析就是计算指针(变量、字段)可以指向哪个对象的过程。指针分析首先需要对New语句进行分析,然后才能对assign、store、load语句进行分析,在此基础上进行指针分析传播,以及后续method call的处理。而对New语句初始化就需要用到堆抽象技术。

1.2.1 堆抽象

指针分析首先要进行堆抽象。对指针所代表的内存内容进行建模,Tai-e使用New Stmt创建一个新的对象(NewObj) 来实现堆抽象,即将指针所代表内容用对象构造时的Stmt代替。如下是构建一个堆抽象的案例。

A a = new A();
PT(a)={NewObj{<New: void main(java.lang.String[])>[0@L4] new A}}

这种堆抽象方法又叫做Allocation-Site abstraction,这是指针分析中最常见的堆抽象方法。

1.2.2 函数调用处理

在指针分析中会碰到许多函数调用,对象会沿着函数调用进行传播。在处理方法调用时时,需要分析出指针调用的具体方法。java的方法调用大概有以下四种,具体所调用方法的解析原理如下:

invokeinterface和invokevirtual
a.f(p1, p2)

当分析如上语句时,a.f所对应的函数就是指针a所对应Method即类A的方法。而在分析控制反转中,无法将service与其调用点关联起来,便无法分析出a.f实际所调用的函数
如上代码便是Tai-e中处理方法调用的相关代码:

private void processCall(CSVar recv, PointsToSet pts) {
    Context context = recv.getContext();
    Var var = recv.getVar();
    for (Invoke callSite : var.getInvokes()) {
        pts.forEach(recvObj -> {
            // resolve callee
            JMethod callee = CallGraphs.resolveCallee(
                    recvObj.getObject().getType(), callSite);
            if (callee != null) {
                // select context
                CSCallSite csCallSite = csManager.getCSCallSite(context, callSite);
                Context calleeContext = contextSelector.selectContext(
                        csCallSite, recvObj, callee);
                ......
                ......
            } else {
                plugin.onUnresolvedCall(recvObj, context, callSite);
            }
        });
    }
}

首先遍历pts(指针所指向堆抽象的集合),然后根据obj类型与当前函数调用callSite解析出具体调用的方法。

resolveCallee对应的源码如下,如果callSite所调用的方法为interface或者virtual,就会根据obj的类型与方法引用派生出具体的方法。

public static JMethod resolveCallee(Type type, Invoke callSite) {
    MethodRef methodRef = callSite.getMethodRef();
    if (callSite.isInterface() || callSite.isVirtual()) {
        return World.get().getClassHierarchy()
                .dispatch(type, methodRef);
    } else if (callSite.isSpecial()) {
        return World.get().getClassHierarchy()
                .dispatch(methodRef.getDeclaringClass(), methodRef);
    } else if (callSite.isStatic()) {
        return methodRef.resolveNullable();
    } else {
        throw new AnalysisException("Cannot resolve Invoke: " + callSite);
    }
}

dispatch会调用lookupMethod,其核心逻辑如下:

  1. 遍历超类,如果超类含有该方法,则返回超类中的该方法
  2. 遍历超类的所实现的所有接口,返回接口中的该方法
private JMethod lookupMethod(JClass jclass, Subsignature subsignature,
                             boolean allowAbstract) {
    // JVM Spec. (11 Ed.), 5.4.3.3 Method Resolution
    // 1. If C is an interface, method resolution throws
    // an IncompatibleClassChangeError. TODO: what does this mean???

    // 2. Otherwise, method resolution attempts to locate the
    // referenced method in C and its superclasses
    for (JClass c = jclass; c != null; c = c.getSuperClass()) {
        JMethod method = c.getDeclaredMethod(subsignature);
        if (method != null && (allowAbstract || !method.isAbstract())) {
            return method;
        }
    }
    // 3. Otherwise, method resolution attempts to locate the
    // referenced method in the superinterfaces of the specified class C
    for (JClass c = jclass; c != null; c = c.getSuperClass()) {
        for (JClass iface : c.getInterfaces()) {
            JMethod method = lookupMethodFromSuperinterfaces(
                    iface, subsignature, allowAbstract);
            if (method != null) {
                return method;
            }
        }
    }
    return null;
    // TODO:
    //  1. check accessibility
    //  2. handle phantom methods
    //  3. double-check correctness
}

2. 问题分析

在使用控制反转时,类的实例化是由spring的IoC容器控制,而不是应用程序自身控制。这使得在指针分析中,无法通过传统的dispatch获取到callsite具体调用的方法,而无法解析控制反转所控制的实例。
如果我们可以分析出当前对象所对应的具体实现类,那便可以在指针分析处理call调用时获取到具体的方法调用。
代码的具体实现如下:

solver.addPointsTo(solver.getCSManager().getCSVar(csMethod.getContext(), var),
                                        solver.getHeapModel().getMockObj(() -> "DEPENDENCY_INJECTION", cls.getName(), cls.getType()));

问题的关键就是如何找出当前被注入者所对应的具体实现类

spring支持两种依赖注入的配置方式:

1. 通过注解配置

@Service
public class CustomizedService {
    @Resource
    private HttpService httpService;

    @GetMapping("/restTemplate/vuln1")
    public String RestTemplateUrlBanRedirects(String url){
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return httpService.RequestHttpBanRedirects(url, headers);
    }
}
@Service
public class HttpServiceImpl implements HttpService {

        public String RequestHttpBanRedirects(String url, HttpHeaders headers) {
        HttpEntity<String> entity = new HttpEntity<>(headers);
        ResponseEntity<String> re = restTemplateBanRedirects.exchange(url, HttpMethod.GET, entity, String.class);
        return re.getBody();
    }
}

如上代码会将字段httpService的配置为HttpServiceImpl的实例,无需在CustomizedService.java添加构造httpService的代码。

2. 通过xml文件配置

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}
<beans>
  <bean id="beanOne" class="x.y.ThingOne">
    <constructor-arg ref="beanTwo"/>
    <constructor-arg ref="beanThree"/>
  </bean>

  <bean id="beanTwo" class="x.y.ThingTwo"/>

  <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

通过如上配置,便可以将beanTwobeanThree 自动配置为ThingOne构造参数,不需要在ThingOne中添加beanTwobeanThree的构造过程。

3. 控制反转处理

如下为仓库GitHub - JoyChou93/java-sec-code: Java web common vulnerabilities and security code which is base on springboot and spring security中包含SSRF漏洞且利用了控制反转特性的代码片段

@RestController
@RequestMapping("/ssrf")
public class SSRF {

    private static final Logger logger = LoggerFactory.getLogger(SSRF.class);

    @Resource
    private HttpService httpService;

    @GetMapping("/restTemplate/vuln1")
    public String RestTemplateUrlBanRedirects(String url){
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return httpService.RequestHttpBanRedirects(url, headers);
    }

    @GetMapping("/restTemplate/vuln2")
    public String RestTemplateUrl(String url){
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return httpService.RequestHttp(url, headers);
    }

}

这个SSRF漏洞对应的调用链如下

org.joychou.controller.SSRF#RestTemplateUrlBanRedirects
org.joychou.impl.HttpServiceImpl#RequestHttpBanRedirects
org.springframework.web.client.RestTemplate#exchange

sink点的方法为org.springframework.web.client.RestTemplate#exchange,所以需要把该方法加入污点分析的配置文件。

另一个漏洞的sink点所调用方法与上个漏洞一致,不过调用链有所区别。

org.joychou.controller.SSRF#RestTemplateUrl
org.joychou.impl.HttpServiceImpl#RequestHttp
org.springframework.web.client.RestTemplate#exchange

4. 类CHA(Class Hierarchy Analysis)实现方式

根据原理分析一节中的描述,方法调用是通过堆抽象(用obj代表具体的类)所对应的类型解析出来的,当我们分析出当前指针所对应的实际类时,需要构造如下的MockObj,并将类型配置为我们解析出来的类型。

default Obj getMockObj(Descriptor desc, Object alloc, Type type) {
    return getMockObj(desc, alloc, type, null);
}
solver.addPointsTo(solver.getCSManager().getCSVar(csMethod.getContext(), var),
                                        solver.getHeapModel().getMockObj(() -> "DEPENDENCY_INJECTION", implementor.getName(), implementor.getType()));

实际代码如上,其中implementor为我们所分析出的具体的实现类。本质是将解析出的实际类所代表的MockObj添加至对应指针的指向集中。
具体实现思路如下:
遍历被分析代码中的所有方法,提取出Invoke语句即方法调用语句

具体实现思路如下:
遍历被分析代码中的所有方法,提取出Invoke语句即方法调用语句

solver.getCallGraph().reachableMethods().forEach(
    csMethod -> {
        if (csMethod.getMethod().getDeclaringClass().getName().matches("^(java\\.|sun\\.|javax\\.|com\\.sun\\.).+$")){
            return;
        }
        csMethod.getMethod().getIR().getStmts().forEach(
            stmt -> {
            }
    )
)

如果语句是静态方法调用或special调用,那不用分析

if(stmt instanceof Invoke invoke &&
        (invoke.isVirtual() || invoke.isInterface()) &&
        invoke.getRValue() instanceof InvokeInstanceExp invokeInstanceExp){
       //执行分析逻辑
}

如果接受者已经有了指向信息,代表接受者并不是由控制反转构造,不用分析

if(solver.getCSManager().getCSVar(context, var).getPointsToSet() != null &&
        !solver.getCSManager().getCSVar(context, var).getPointsToSet().isEmpty()) {
    return;
}

如果对象的类型为接口,那么可能的类就是所有实现类;
如果对象的类型为类,而非接口,那么可能的类就是所有子类。

JClass jClass = World.get().getClassHierarchy().getClass(var.getType().getName());
Collection<JClass> implementors = new ArrayList<>();
if(invoke.isInterface()){
    implementors.addAll(World.get().getClassHierarchy().getDirectImplementorsOf(jClass));
}else{
    implementors.add(jClass);
    implementors.addAll(World.get().getClassHierarchy().getDirectSubclassesOf(jClass));
}
System.out.printf("%s %s %s\n", var, jClass, implementors);

最后将可能的类加到对象的指向集中,指针分析会再分析方法调用,找到真实调用的方法

if(implementors.size() <= 3) {
    implementors.forEach(
        implementor -> {
            solver.addPointsTo(solver.getCSManager().getCSVar(csMethod.getContext(), var),
                    solver.getHeapModel().getMockObj(() -> "DEPENDENCY_INJECTION", implementor.getName(), implementor.getType()));
        }
    );
}

还有最后一个问题,对应的分析代码应该放在指针分析的哪个阶段?

根据DefaultSolver.javaanalyze方法的源码,指针分析会在完毕前调用onPhaseFinish,如果把代码放在此处,此时除控制反转外的其他分析都已经完成,会使我们的判断准确。

private void analyze() {
    while (!workList.isEmpty() && !isTimeout) {
        // phase starts
        while (!workList.isEmpty() && !isTimeout) {
            ......
            ......
        }
        plugin.onPhaseFinish();
    }
    if (!workList.isEmpty() && isTimeout) {
        logger.warn("Pointer analysis stops early as it reaches time limit ({} seconds)," +
                " and the result may be unsound!", timeLimit);
    } else if (timeLimiter != null) { // finish normally but time limiter is still running
        timeLimiter.stop();
    }
    plugin.onFinish();
}

完整代码如下

package pascal.taie.analysis.pta.plugin.taint;

import pascal.taie.World;
import pascal.taie.analysis.pta.core.cs.context.Context;
import pascal.taie.analysis.pta.core.solver.Solver;
import pascal.taie.analysis.pta.plugin.Plugin;
import pascal.taie.ir.exp.InvokeInstanceExp;
import pascal.taie.ir.exp.Var;
import pascal.taie.ir.stmt.Invoke;
import pascal.taie.language.classes.JClass;

import java.util.ArrayList;
import java.util.Collection;

public class DependencyInjectionHandler implements Plugin {
    private Solver solver;
    private boolean isCalled;

    public DependencyInjectionHandler(){
        isCalled = false;
    }

    @Override
    public void setSolver(Solver solver) {
        this.solver = solver;
    }

    @Override
    public void onPhaseFinish() {
        if(isCalled){
            return;
        }
        isCalled = true;
        solver.getCallGraph().reachableMethods().forEach(
            csMethod -> {
                if (csMethod.getMethod().getDeclaringClass().getName().matches("^(java\\.|sun\\.|javax\\.|com\\.sun\\.).+$")){
                    return;
                }
                csMethod.getMethod().getIR().getStmts().forEach(
                    stmt -> {
                        if(stmt instanceof Invoke invoke &&
                                (invoke.isVirtual() || invoke.isInterface()) &&
                                invoke.getRValue() instanceof InvokeInstanceExp invokeInstanceExp){
                            Var var = invokeInstanceExp.getBase();
                            Context context = csMethod.getContext();
                            if(solver.getCSManager().getCSVar(context, var).getPointsToSet() != null &&
                                    !solver.getCSManager().getCSVar(context, var).getPointsToSet().isEmpty()) {
                                return;
                            }
                            JClass jClass = World.get().getClassHierarchy().getClass(var.getType().getName());
                            Collection<JClass> implementors = new ArrayList<>();
                            if(invoke.isInterface()){
                                implementors.addAll(World.get().getClassHierarchy().getDirectImplementorsOf(jClass));
                            }else{
                                implementors.add(jClass);
                                implementors.addAll(World.get().getClassHierarchy().getDirectSubclassesOf(jClass));
                            }
                            if(invoke.toString().contains("RequestHttp")){
                                System.out.printf("%s %s %s\n", var, jClass, implementors);
                            }
                            if(implementors.size() <= 3) {
                                implementors.forEach(
                                    implementor -> {
                                        solver.addPointsTo(solver.getCSManager().getCSVar(csMethod.getContext(), var),
                                                solver.getHeapModel().getMockObj(() -> "DEPENDENCY_INJECTION", implementor.getName(), implementor.getType()));
                                    }
                                );
                            }
                        }
                    });
            });
    }
}

该方法可以扫描出依赖于控制反转的漏洞,但是上述方式缺乏准确性,如果一个接口有多个实现类,就无法准确识别出该指针所对应的实现类。

5. 基于注解的配置解析

在代码中,控制反转是通过xml、注解进行实际配置。因此通过解析xml、注解的实际含义,会使结果更加准确。

本文只分析基于注解的配置解析。
实现思路为识别注入点以及依赖实现类,然后将依赖实现类构造成的obj添加到注入点变量的的指向集中。

对于如下代码而言,需要识别出CustomizedService的注入点httpService,然后找到HttpService的实现类,最后把实现类HttpServiceImpl以obj的形式添加到变量的指向集中。

@Service
public class CustomizedService {
    @Resource
    private HttpService httpService;

    @GetMapping("/restTemplate/vuln1")
    public String RestTemplateUrlBanRedirects(String url){
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return httpService.RequestHttpBanRedirects(url, headers);
    }
}
@Service
public class HttpServiceImpl implements HttpService {

        public String RequestHttpBanRedirects(String url, HttpHeaders headers) {
        HttpEntity<String> entity = new HttpEntity<>(headers);
        ResponseEntity<String> re = restTemplateBanRedirects.exchange(url, HttpMethod.GET, entity, String.class);
        return re.getBody();
    }
}

实现方法如下:

5.1 注入点识别

List<JField> injectedFields = new ArrayList<>();
World.get().getClassHierarchy().allClasses()
        .map(JClass::getDeclaredFields)
        .flatMap(Collection::stream)
        .forEach(field -> {
            boolean isInjectedField = field.hasAnnotation("javax.annotation.Resource") ||
                    field.hasAnnotation("org.springframework.beans.factory.annotation.Autowired") ||
                    field.hasAnnotation("javax.inject.Inject");
            if(isInjectedField){
                injectedFields.add(field);
            }
        });

遍历所有类的所有字段,若字段包含以下注解,则说明该字段是注入点

  • javax.annotation.Resource
  • org.springframework.beans.factory.annotation.Autowired
  • javax.inject.Inject

5.2 寻找实现类

List<JClass> implementationClasses = new ArrayList<>();
implementationClasses.addAll(
    World.get().getClassHierarchy().allClasses()
            .filter(cls -> cls.hasAnnotation("org.springframework.stereotype.Service") ||
                    cls.hasAnnotation("org.springframework.stereotype.Component")).collect(Collectors.toSet())
);

遍历所有类,若类包含以下注解,则说明该类为实现类

  • org.springframework.stereotype.Service
  • org.springframework.stereotype.Component

5.3 将实现类与注入点关联起来

如果在方法中要使用当前类的字段,会从%this变量中载入该字段,然后使用。
[4@L281] $r4 = %this.<org.joychou.controller.SSRF: org.joychou.service.HttpService httpService>;

@org.springframework.web.bind.annotation.GetMapping({"/restTemplate/vuln1"})
public java.lang.String RestTemplateUrlBanRedirects(java.lang.String url) {
    org.springframework.http.HttpHeaders $r0;
    org.springframework.http.MediaType $r1;
    org.joychou.service.HttpService $r4;
    java.lang.String $r5;
    [0@L279] $r0 = new org.springframework.http.HttpHeaders;
    [1@L279] invokespecial $r0.<org.springframework.http.HttpHeaders: void <init>()>();
    [2@L280] $r1 = <org.springframework.http.MediaType: org.springframework.http.MediaType APPLICATION_JSON_UTF8>;
    [3@L280] invokevirtual $r0.<org.springframework.http.HttpHeaders: void setContentType(org.springframework.http.MediaType)>($r1);
    [4@L281] $r4 = %this.<org.joychou.controller.SSRF: org.joychou.service.HttpService httpService>;
    [5@L281] $r5 = invokeinterface $r4.<org.joychou.service.HttpService: java.lang.String RequestHttpBanRedirects(java.lang.String,org.springframework.http.HttpHeaders)>(url, $r0);
    [6@L281] return $r5;
}

如果要将实现类与注入点关联起来,就相当于把实现类与LoadField的返回值关联起来。具体关联代码如下:

injectedFields.stream().forEach(field -> {
    JClass jClass = field.getDeclaringClass();

    Collection<JClass> subClasses = World.get().getClassHierarchy().getAllSubclassesOf(
            World.get().getClassHierarchy().getClass(field.getType().getName())
    );
    List<JClass> implementors = new ArrayList<>(subClasses);
    implementors.retainAll(implementationClasses);
    System.out.printf("%s %s\n", field, implementors);

    Set<CSMethod> csMethodSet = solver.getCallGraph().reachableMethods()
            .filter(csMethod -> csMethod.getMethod().getDeclaringClass().equals(jClass))
            .collect(Collectors.toSet());
    csMethodSet.forEach(
            csMethod -> {
                List<Var> vars = csMethod.getMethod().getIR().getStmts().stream()
                        .filter(stmt -> stmt instanceof LoadField loadField &&
                                loadField.getFieldAccess().getFieldRef().resolve().equals(field))
                        .map(stmt -> (LoadField) stmt)
                        .map(AssignStmt::getLValue)
                        .toList();
                implementors.forEach(
                        implementor -> {
                            vars.forEach(
                                    var -> {
                                        solver.addPointsTo(solver.getCSManager().getCSVar(csMethod.getContext(), var),
                                                solver.getHeapModel().getMockObj(() -> "DEPENDENCY_INJECTION", implementor.getName(), implementor.getType()));
                                    }
                            );
                        }
                );
            }
    );
});
  1. 获取注入点字段的所属类,然后获取该类中所有上下文有关的方法;
  2. 获取字段类型的所有子类,如果是接口,则获取所有实现类。与之前所有的实现类取交集,这个交集就是控制反转中该字段的实现类;
  3. 遍历第一步获取到的方法的所有语句,若语句的类型是LoadField,则判断其FieldAccess的字段是否为注入点的字段,若是,则用第二步的实现类构造MockObj,将其添加到LoadField语句的返回值的指向集中。

6. 结果展示

lcark、keanu两位大佬更改的相关代码以及配置文件已经上传至github
具体食用方法如下:

 

1. 下载代码,并移动至spring-boot-2目录下

    git clone https://github.com/lcark/Tai-e-demo
    cd Tai-e-demo/spring-boot-2
    git submodule update --init

2. 将java-sec-code文件夹移至与Tai-e-demo文件夹相同目录下

3. 将DependencyInjectionHandler.java移动至Tai-e源码的src/main/java/pascal/taie/analysis/pta/plugin/taint/目录下,并重新编译打包

4. 使用如下命令运行tai-e便可以成功获取到扫描结果

java -cp xxx\tai-e-all-0.5.1-SNAPSHOT.jar pascal.taie.Main --options-file=options.yml

5. 如下图所示,两个测试案例都被检测到

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

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

相关文章

docxtemplater避坑!!! 前端导出word怎么插入本地图片或base64 有完整示例

用docxtemplater库实现前端通过模板导出word&#xff0c;遇到需求&#xff0c;要插图片并转成word并导出&#xff0c;在图片转换这块遇到了问题&#xff0c;网上查示例大多都跑不通&#xff0c;自己琢磨半天&#xff0c;总算搞明白了。 附上清晰完整示例&#xff0c;供参考。 …

医卫兰大药学试题及答案,分享几个实用搜题和学习工具 #媒体#微信

这些软件以其强大的搜索引擎和智能化的算法&#xff0c;为广大大学生提供了便捷、高效的解题方式。下面&#xff0c;让我们一起来了解几款备受大学生欢迎的搜题软件吧&#xff01; 1.彩虹搜题 这个是公众号 题库内容丰富全面&#xff0c;细致分类整理。找题再也不费力&#…

数据库|基于T-SQL添加默认约束、外键约束、内连接查询

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 前边学习了基于T-SQL48_47.基于T-SQL添加数据、CRUD操作、标识列详解&#xff1a;《数据库|基于T-SQL向数据库数据表中添加、修改、删除数据》 接下来接着学习基于T-SQL添加默认约束、外键约束、内连接查询&#xff0c…

多个文本如何一键导出二维码?在线批量生码的制作方法

当存在多条文本数据并且需要将每条数据生成单独的二维码来使用&#xff0c;很多小伙伴可能还在用一个一个来制作的方法&#xff0c;在二维码生成器上将文本转二维码。这种方式操作起来比较的繁琐&#xff0c;需要浪费大量的时间&#xff0c;那么有什么方法可以简化这个过程吗&a…

【Python】 如何将列表转换为字符串

基本原理 在Python中&#xff0c;列表&#xff08;list&#xff09;和字符串&#xff08;string&#xff09;是两种不同的数据类型。列表是一个有序的元素集合&#xff0c;可以包含不同类型的元素&#xff0c;而字符串则是一个字符序列。有时&#xff0c;我们可能需要将列表中…

记录一次cnvd事件型证书漏洞挖掘

事件起因是因为要搞毕设了&#xff0c;在为这个苦恼&#xff0c;突然负责毕设的老师说得到cnvd下发的证书结合你的漏洞挖掘的过程是可以当成毕设的&#xff0c;当时又学习了一段时间的web渗透方面的知识&#xff0c;于是踏上了废寝忘食的cnvd证书漏洞挖掘的日子。 前言&#x…

卷径计算(PID输出补偿法 SCL源代码)

卷径计算有很多方法,这里我们提供另一个思路,这里我们采用的是通过速度控制间接控制张力通过线速度和系统卷径我们可以计算出我们的速度前馈量(主速度)。具体收放卷前馈量计算可以参考下面文章链接: 收放卷前馈量计算FC(梯形图+SCL代码)-CSDN博客文章浏览阅读584次。这篇博…

Hexo最新实战:(一)Hexo7.0+GitHub Pages博客搭建

前言 很多平台都能写博客还有创作激励&#xff0c;为什么我又要搭一个&#xff1f;为什么这次要选择用Hexo框架&#xff1f; 对应的原因是流量自由和省钱&#xff0c;第一个&#xff0c;很多平台能写但不是都有收益&#xff0c;而且平台有自身的规则&#xff0c;比如会屏蔽一…

鸿蒙大厂目前政策变现沉淀思考

鸿蒙引擎定制优化 鸿蒙端hotfix&#xff1a; 技术栈太大了&#xff0c;但是鸿蒙需要学习什么呢&#xff1f; 什么最有价值&#xff1f; 这就是接下来需要表达下我的观点&#xff1a; 1、APP开发 2、应用市场技术专员 【游戏、电商重型APP性能的处理 SmartPerf、构建自己的工…

如果一个开发初学者从今天开始,他们应该学习什么?

What should a beginner developer learn if they were to start today? by Scott Hanselman 如果从今天才开始学习&#xff0c;新手开发者要学习什么&#xff1f; 新的开发人员今天应该从哪里开始&#xff1f; 他们应该学习什么来为自己的职业生涯做好准备&#xff1f;Sco…

做微火全域运营服务商要注意的风险有哪些?

随着全域运营赛道日渐兴起&#xff0c;微火全域运营服务商申请人不断增多&#xff0c;逐渐成为众多新兴职业中的一大热门。 所谓微火全域运营服务商&#xff0c;顾名思义&#xff0c;就是通过操作微火全域运营系统做全域运营的一类群体&#xff0c;其业务范围为公域和私域内所有…

IC开发——Verilator

1. 简介 Verilator 是一个开源的 Verilog 和 SystemVerilog 硬件描述语言 (HDL) 仿真器。它是一个高性能的仿真器,可以将 Verilog 和 SystemVerilog 代码转换为 C/SystemC 代码,并生成可执行的仿真模型。 Verilator 的主要特点包括: 高性能:Verilator 生成的仿真模型具有非常…

Bootstrap 3.x 版本基础引入指南

Bootstrap 是一款广受欢迎的前端框架&#xff0c;它简化了网页设计与开发流程&#xff0c;帮助开发者快速创建响应式布局和美观的网页界面。本文将向您介绍如何在项目中引入 Bootstrap 3.x 版本的基本步骤&#xff0c;包括 CSS 和 JavaScript 文件的引用&#xff0c;以及必要的…

关于Java Agent的使用、工作原理、及hotspot源码 解析

说明&#xff1a; 本文很长&#xff0c;长到超出了掘金编辑器的限制字符数 10万&#xff0c;所以我在最后边只是图解&#xff0c;没有更多的文字和代码描述了&#xff0c;本文知识点较多&#xff0c;如果没接触过agent那必然大概率会懵&#xff08;大部分知识点讲解完后&#x…

瑞_Windows环境下使用bat重启jar包等服务

文章目录 命令示例重启ray-project.jar重启redis服务 &#x1f64a; 前言&#xff1a;经验分享——Windows环境下使用.bat批处理文件重启 jar 包等服务。在学习或者工作日常中&#xff0c;有时候会需要在 Windows 系统环境下去启动 jar 包或其它服务&#xff0c;此时如果使用关…

百世慧入选第七届数字中国建设峰会“2024企业数字化转型典型应用案例”

5月24日-25日&#xff0c;第七届数字中国建设峰会在福州举行。本届峰会是国家数据工作体系优化调整后首次举办的数字中国建设峰会&#xff0c;主题为“释放数据要素价值&#xff0c;发展新质生产力”。 为了全方位展示各领域数字化最新成果&#xff0c;共创数字中国美好未来&a…

mail发送调用接口如何与三方服务无缝对接?

mail发送调用接口的性能怎么样&#xff1f;调用邮件接口的技巧&#xff1f; 为了提高效率和自动化水平&#xff0c;企业通常会选择使用mail发送调用接口。然而&#xff0c;仅仅使用这些接口还不够&#xff0c;如何与各种第三方服务无缝对接同样至关重要。AokSend将探讨如何有效…

Golang性能分析工具pprof--远程分析时无法定位源代码行数问题解决方案

场景 通过命令行模式的list命令&#xff0c;为了查看指标消耗在具体哪一行&#xff0c;需要源代码。但实际程序是部署在线上或者程序的源代码目录变了&#xff0c;则pprof从默认路径找不到代码&#xff0c;无法显示是哪一行的问题。 通过浏览器模式的source页面&#xff0c;有…

Linux java jni调用C++封装动态库

由于项目中java需要调用第三方提供的C动态库&#xff1b;由于第三方动态库传入的参数较多&#xff0c;还伴随着指针传入操作&#xff0c;导致java调用极为不便&#xff01;因此催生出对于第三方的C动态库进行二次封装。java调用只需按结构传入一个结构化的string即可。话不多说…

狂暴少帅短视频:成都科成博通文化传媒公司

狂暴少帅短视频&#xff1a;热血与激情的碰撞 在当下这个信息爆炸的时代&#xff0c;短视频以其独特的魅力迅速占领了人们的视线。而在众多短视频创作者中&#xff0c;一位名为“狂暴少帅”的创作者以其独特的风格和引人入胜的内容&#xff0c;赢得了广大网友的喜爱和追捧。今…