Java openrasp记录-01

news2024/10/6 0:30:48

例子1

https://github.com/anbai-inc/javaweb-expression  一个hook ognl、spel、MVEL表达式注入的例子

用的是asm5进行字节码修改

采用premain进行插桩,重写transform方法

expClassList是要hook的类,这里定义在MethodHookDesc

这里判断hook点通过类名,具体其中的方法名,以及方法的描述符

其中expClassList中定义了具体要hook的类,就mvel、ognl、spel三种

 

 匹配到以上三种类后即重写visitMethod方法,匹配具体要hook的方法名和方法描述符,如果匹配到了,则重写MethodVisitor的visitCode方法,进行字节码修改,这里因为是表达式注入,因此这里涉及到string类型的表达式,因此获取传到hook函数处的表达式字符串压入操作数栈,并通过调用expression方法弹出该值进行检测,这里要涉及到操作数栈和局部变量表,因此要清楚原本的方法帧中局部变量表下标索引几代表的是输入的表达式:

ognl:

ognl对应的是parseExpression这个方法,其中expressoin参数是具体解析的表达式

其对应的字节码指令如下所示,Aload0即对应的即为表达式,通过invokeSpecial调用

也可以通过jclasslib来查看

 

spel:

这里的hook点时init方法,这里的expression即为表达式

其init方法中aload1对应赋值时的栈顶元素,所以其为表达式,因此下标对应的是1

 

 

mvel:

这个用的局部变量表的下标也是1,然而实际上取表达式值时用的为下标为0的this来取

 

根据局部变量表中的表达式的值传入expression方法进行处理

 

其中expression将打印出当前的函数调用栈,该例子只是一个插桩+hook方法字节码修改的例子,并没有最终的判断入侵的检测规则

 

例子2

https://toutiao.io/posts/4kt0al/preview 中给了一个例子,也是用asm进行字节码的修改

整体设计分析:

premain方式进行插桩,调用init方法,进一步调用Config.initConfig方法进行初始化配置

此时用到resources/main.config文件,读取其内容,从其格式来看其为json文件,以不同的模块名来区分不同的hook类别

{
	"module":
	[
		{
			"moduleName": "java/lang/ProcessBuilder",
			"loadClass": "xbear.javaopenrasp.visitors.rce.ProcessBuilderVisitor",
			"mode": "block",
			"whiteList":["javac"],
			"blackList": 
			[
			"calc", "etc", "var", "opt", "apache", "bin", "passwd", "login", "cshrc", "profile",
			"ifconfig", "tcpdump", "chmod", "cron", "sudo", "su", "rm", "wget", "sz", "kill", "apt-get",
			"find", "/applications/calculator.app/contents/macos/calculator"
			]
		},
		{
			"moduleName": "java/io/ObjectInputStream",
			"loadClass": "xbear.javaopenrasp.visitors.rce.DeserializationVisitor",
			"mode": "black", 
			"whiteList":[],
			"blackList":
			[
			"org.apache.commons.collections.functors.InvokerTransformer", 
			"org.apache.commons.collections.functors.InstantiateTransformer",
			"org.apache.commons.collections4.functors.InvokerTransformer",
			"org.apache.commons.collections4.functors.InstantiateTransformer",
			"org.codehaus.groovy.runtime.ConvertedClosure",
			"org.codehaus.groovy.runtime.MethodClosure",
			"org.springframework.beans.factory.ObjectFactory"
			]
		},
		{
			"moduleName": "ognl/Ognl",
			"loadClass": "xbear.javaopenrasp.visitors.rce.OgnlVisitor",
			"mode": "black",
			"whiteList":[],
			"blackList": 
			[
			"ognl.OgnlContext", 
			"ognl.TypeConverter",
			"ognl.MemberAccess",
			"_memberAccess",
			"ognl.ClassResolver",
			"java.lang.Runtime",
			"java.lang.Class",
			"java.lang.ClassLoader",
			"java.lang.System",
			"java.lang.ProcessBuilder",
			"java.lang.Object",
			"java.lang.Shutdown",
			"java.io.File",
			"javax.script.ScriptEngineManager",
			"com.opensymphony.xwork2.ActionContext",
			]
		},
		{
			"moduleName": "com/mysql/jdbc/StatementImpl",
			"loadClass": "xbear.javaopenrasp.visitors.sql.MySQLVisitor",
			"mode": "check", 
			"whiteList":[],
			"blackList":[]
		},
		{
			"moduleName": "com/microsoft/jdbc/base/BaseStatement",
			"loadClass": "xbear.javaopenrasp.visitors.sql.SQLServerVisitor",
			"mode": "check", 
			"whiteList":[],
			"blackList":[]
		}
	]
}

接着取到module中的值放入ConcurrentHashmap中,对于每一个moduleName都对应一个ConcurrentHashmap,那么后面运行过程中根据moudlename就能获取到每种hook点的信息

 对于jvm将要加载的类,如果module中包含该类名,则使用asm来进行字节码修改,这里创建ClassVisitor通过Reflections.createVisitorIns方法,因为通常在这里将需要设计具体如何对class进行检查,那么对于不同的需要进行hook的类处理逻辑不同,因此这里是一个分支点,例子1也是相同的。

 

 根据当前的类名得到其相对应的loadclass的类名然后利用反射进行实例化

 

 这里定义了rce和sql两个大类

 

 具体对应的hook的类名和具体的loadclass类名映射关系为:

java/lang/ProcessBuilder -> xbear.javaopenrasp.visitors.rce.ProcessBuilderVisitor   //命令执行
java/io/ObjectInputStream -> xbear.javaopenrasp.visitors.rce.DeserializationVisitor  //反序列化
ognl/Ognl -> xbear.javaopenrasp.visitors.rce.OgnlVisitor //ognl表达式注入
com/mysql/jdbc/StatementImpl -> xbear.javaopenrasp.visitors.sql.MySQLVisitor  //sql注入
com/microsoft/jdbc/base/BaseStatement -> xbear.javaopenrasp.visitors.sql.SQLServerVisitor  //sql注入

从大体上整个插桩过程分析结束,初始化的主要工作还是对各种hook点如何进行初始配置,方便后面hook进行中的具体细化操作。

hook点处理分析:

命令执行hook点:

java中命令执行一般常用的有两种,Runtime.exec和Processbuilder.start,但是Runtime.exec实际上也是利用的Processbuilder,而Processbuilder最终利用的是ProcessImpl来执行命令,那么实际上这里选择hook点,选择Processbuilder的start即可,因为只要执行命令,都将走到该类的start方法,在这里就能拿到具体要执行的命令。

具体的逻辑如下,这里重写了onMethodEnter方法,asm5中的,即进入start内部之前执行

    @Override
    protected void onMethodEnter() {
        mv.visitTypeInsn(NEW,
                "xbear/javaopenrasp/filters/rce/PrcessBuilderFilter"); //new一个命令执行过滤的对象压入栈
        mv.visitInsn(DUP); //再次压入该对象
        mv.visitMethodInsn(INVOKESPECIAL, 
                "xbear/javaopenrasp/filters/rce/PrcessBuilderFilter", "<init>", "()V", false); //弹出对象进行初始化,此时栈中大小为2-1=1
        mv.visitVarInsn(ASTORE, 1); //弹出存储该对象到局部变量表1处,此时栈的大小为1-1=0
        mv.visitVarInsn(ALOAD, 1);  //加载局部变量表1处的对象压入栈,此时栈的大小为0+1=1
        mv.visitVarInsn(ALOAD, 0); //加载this压入栈,此时栈大小为1+1=2
        mv.visitFieldInsn(GETFIELD, 
                "java/lang/ProcessBuilder", "command", "Ljava/util/List;"); //取this.command的值压入栈,栈大小为2
        mv.visitMethodInsn(INVOKEVIRTUAL,
                "xbear/javaopenrasp/filters/rce/PrcessBuilderFilter", "filter", //调用filer方法,弹出的值的数量为filter的方法参数大小1+1=2,栈顶的this.command的值作为参数,并将filter
方法的处理结果压入栈中,filter返回一个Boolean值,此时栈中大小为1
                "(Ljava/lang/Object;)Z", false);

        Label l92 = new Label();  //new一个label用来跳转
        mv.visitJumpInsn(IFNE, l92); //此时弹出filter处理的结果和0进行比较,如果不等与0,则跳到192lable,说明执行的当前的命令可以执行,则正常执行start方法,否则执行下一条指令,栈大小为0
        mv.visitTypeInsn(NEW, "java/io/IOException"); //new 一个io异常对象
        mv.visitInsn(DUP); //再次压入该对象,栈大小2
        mv.visitLdcInsn("invalid character in command because of security"); //压入该字符串,栈大小3
        mv.visitMethodInsn(INVOKESPECIAL,
                "java/io/IOException", "<init>", "(Ljava/lang/String;)V", false); //弹出1+1=2个值,初始化该异常对象,栈顶元素作为io异常的初始化参数,此时栈大小为1
        mv.visitInsn(ATHROW); //抛出该异常
        mv.visitLabel(l92);

    }

先看start方法部分如下:

这里如果直接用asm字节码指令来写就要结合源码和bytecode字节码指令来写,可以看到0处放入的即为this,最终command.toArray的结果放到局部变量表1处,上面写指令码的时候也ASTORE_1了一次,这里并不一定直到1处是否有值,但是指令码这里直接ASTORE1,因此我们不需要担心1处是否有值

 

这样就完成了hook点的构造,取command的值调用filter进行过滤,命令执行的filter如下所示:

    public boolean filter(Object forCheck) {
        String moduleName = "java/lang/ProcessBuilder"; 
        List<String> commandList = (List<String>) forCheck;
        String command = StringUtils.join(commandList, " ").trim().toLowerCase();
        Console.log("即将执行命令:" + command);
        String mode = (String) Config.moduleMap.get(moduleName).get("mode"); //取对应的命令执行逻辑,mode为block,即阻断
        switch (mode) { 
            case "block":
                Console.log("> 阻止执行命令:" + command);
                return false;  //如果直接为block,那么所有命令都执行不了,也可以更改模式,用黑白名单过滤
            case "white":
                if (Config.isWhite(moduleName, command)) {
                    Console.log("> 允许执行命令:" + command);
                    return true;
                }
                Console.log("> 阻止执行命令:" + command);
                return false;
            case "black":
                if (Config.isBlack(moduleName, command)) {
                    Console.log("> 阻止执行命令:" + command);
                    return false;
                }
                Console.log("> 允许执行命令:" + command);
                return true;
            case "log":
            default:
                Console.log("> 允许执行命令:" + command);
                Console.log("> 输出打印调用栈\r\n" + StackTrace.getStackTrace());
                return true;
        }
    }

 asm感觉还是挺麻烦的,语句越复杂要用到的指令越多,稍微不熟练就会出错

反序列化hook点:

在java.io.ObjectInputStream处进行hook,这里定义了一些反序列化的黑名单

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
                                     String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        if ("resolveClass".equals(name) && "(Ljava/io/ObjectStreamClass;)Ljava/lang/Class;".equals(desc)) {
            mv = new DeserializationVisitorAdapter(mv, access, name, desc);
        }
        return mv;
    }

为什么选择resolveClass作为hook的方法?只要记住我们的目的是拿到将要反序列化的类名,那么实际上的反序列化过程中resolveClass的代码如下:

    protected Class<?> resolveClass(ObjectStreamClass desc)
        throws IOException, ClassNotFoundException
    {
        String name = desc.getName();
        try {
            return Class.forName(name, false, latestUserDefinedLoader());
        } catch (ClassNotFoundException ex) {
            Class<?> cl = primClasses.get(name);
            if (cl != null) {
                return cl;
            } else {
                throw ex;
            }
        }
    }

入口参数是ObjectStreamClass,那么在序列化过程中生成的序列化数据的过程中调用该类的lookup方法将生成类的描述信息,其中就包括的类名和SUID,那么调用该类的getName实际上就能拿到反序列化类的名字,所以只需拿到类描述符即可,从resolveClass的逻辑中将以类名通过反射进行类的加载获取反序列化类的class对象,以CommonsCollections2为例,涉及到PriorityQueue和InvokerTrasnformer和TransformingComparator,那么肯定要涉及到这两个类的反序列化

 比如如下图所示就能拿到反序列化的类名,然后再与黑名单进行匹配即可

对应的hook逻辑如下:

 @Override
    protected void onMethodEnter() {
        mv.visitTypeInsn(NEW, "xbear/javaopenrasp/filters/rce/DeserializationFilter"); //new一个反序列化过滤对象压入栈,栈大小1
        mv.visitInsn(DUP); //再次压入该对象,栈大小为2
        mv.visitMethodInsn(INVOKESPECIAL, "xbear/javaopenrasp/filters/rce/DeserializationFilter", "<init>", "()V", false); //弹出一个对象进行实例化,栈大小为1
        mv.visitVarInsn(ASTORE, 2); //存储该对象到局部变量表,栈大小为0
        mv.visitVarInsn(ALOAD, 2); //取出该对象到栈,栈大小为1
        mv.visitVarInsn(ALOAD, 1); //这里要涉及到取局部变量表的值, 所以又得去看该方法的字节码指令,取到的即为desc,压入操作数栈,栈大小为1+1=2
        mv.visitMethodInsn(INVOKEVIRTUAL, "xbear/javaopenrasp/filters/rce/DeserializationFilterr", "filter", "(Ljava/lang/Object;)Z", false); //调用反序列化过滤方法,弹出1+1=2个值,栈顶的desc作为参数

        Label l92 = new Label(); //new一个label
        mv.visitJumpInsn(IFNE, l92); //过滤的返回值和0比
        mv.visitTypeInsn(NEW, "java/io/IOException"); //如果等于0,则new一个异常对象
        mv.visitInsn(DUP); //再次压入
        mv.visitLdcInsn("invalid class in deserialization because of security"); //错误信息压栈
        mv.visitMethodInsn(INVOKESPECIAL, "java/io/IOException", "<init>", "(Ljava/lang/String;)V", false); //实例化异常
        mv.visitInsn(ATHROW); //抛出异常
        mv.visitLabel(l92); //不等于0,则说明反序列化的类不在黑名单中,进行正常反序列化过程

    }

从下图可以看到aload1,然后调用栈顶元素的getname方法,并把结果压入栈中,所以desc类描述符是在该方法的局部变量表1处存着,并且2处不管之前放什么元素,这里将被类名进行覆盖

 

在对应的过滤方法中再通过类描述符调用getName拿到类名,然后通过对应的mode为black,因此

 

 接着只要拿到预先配置好的黑名单来进行过滤即可

 

ognl的hook点:

hook的是ognl.Ognl的parseExpression这个方法,和第一个例子选择的hook点是相同的,因为该方法就能拿到要执行的表达式

那么对于对应的class文件直接看该方法的局部变量表就能看到表达式再局部变量表的0处,因此只要将该值传入过滤函数即可

 

 对应的hook处的逻辑:

    protected void onMethodEnter() {

        Label l30 = new Label(); //new一个label
        mv.visitLabel(l30); //访问该label(貌似没有意义)
        mv.visitVarInsn(ALOAD, 0); //加载局部表量表0处的表达式值到栈
        mv.visitMethodInsn(INVOKESTATIC, "xbear/javaopenrasp/filters/rce/OgnlFilter", "staticFilter", "(Ljava/lang/Object;)Z", false);//调用过滤函数,传入表达式的值,因为是static方法,所以只需要提供入口参数即可
        Label l31 = new Label(); //new一个label
        mv.visitJumpInsn(IFNE, l31); //如果过滤表达式不为0,则表达式正常执行
        Label l32 = new Label(); //new label,貌似没有
        mv.visitLabel(l32);
        mv.visitTypeInsn(NEW, "ognl/OgnlException"); //new一个异常对象
        mv.visitInsn(DUP); //再次压栈
        mv.visitLdcInsn("invalid class in ognl expression because of security"); //异常信息压栈
        mv.visitMethodInsn(INVOKESPECIAL, "ognl/OgnlException", "<init>", "(Ljava/lang/String;)V", false); //传入异常信息进行异常对象初始化
        mv.visitInsn(ATHROW); //抛出异常
        mv.visitLabel(l31);
    }

RASP绕过

1.多种姿势openrasp命令执行绕过-安全客 - 安全资讯平台

第一种是根据线程中rce,绕过了rasp对context url的判断,没有url则直接返回正常

第二种直接关掉了rasp的开关

两种措施都必须有代码执行的权限,也就是说必须有shell的前提下

2.de1ctf中的一道绕rasp的思路,思路虽然在园长的javaseccode中提到过,defineclass来绕过rasp检测,但是这种类的确不好找?

LandGrey's Blog

关于springboot为何能绕过rasp,首先defineclass,然后addclass说明已经添加到jvm中,然后class.forname再反射拿到该类时会进行类的链接从而执行static静态区的代码,不需要再重新loadclass

此时classforname时native方法直接加载加载该类,因此绕过了rasp对类加载机制的拦截

 

rasp的用途

1.代码审计

可以对一些漏洞,比如反序列化,ognl、spel等的关键函数处进行hook并记录,然后可以输出成类似日志的格式,结合其调用栈以及其入口参数提供给白盒代码审计工具进行自动化审计

2.0day捕获

对一些危险函数进行hook,并在执行时及时告警,比如Runtime.exec,Processs,但是个人感觉这样效率可能有点低,不如交给ids进行捕获效率更高

3.DevOps

因为进行hook时,asm中提供了大量有用的方法从而能够获得hook点处详细的信息:调用栈、代码行号、接口、父类等

rasp的缺陷

1.首先rasp拦截是侵入程序代码内部的,那么它实际上是和具体的语言强相关的,因此不同语言之间并不通用,需针对不同语言的特性进行开发

2.rasp是对关键函数进行hook,那么意味着无论攻击路径从哪条路走,最终都将汇集于某一个点,因此高效率的拦截要求设计rasp的hook规则时,开发者本身即必须对各种漏洞的利用方式以及一些关键函数点熟悉,因此存在遗漏的可能。

甲方如何应用rasp

1.直接根据开源的openrasp来进行二次开发,针对企业具体应用进行适配

问题:推广周期长,运维难度大,以及要保证现有的业务在布置rasp后仍旧能够正常运行,有一定的风险

2.在现有的APM程序上(cat,wiseapm)进行修改,弥补推广的周期,在稳定性也有一定的保证,只需要将rasp的一些想法加入到APM程序中,企业快速实践部署IAST/RASP的一种新思路 - FreeBuf网络安全行业门户这篇文章中介绍到平安银行是利用cat搜集的一些信息进行输出进行审计,比如apm本身就自带一些监控sql语句执行的功能

结合扫描器

如果能够得到具体的hook日志,则可以

1.流量设置标志位,对所有测试流量加某种标志位,如果hook的某个点有标志位进入,则认为该处可能存在漏洞(存在拼接且有入口)(例如sql注入,程序内部也可能有很多sql执行,这样能筛选出外部输入)

2.黑名单检测,检测hook点处函数入参是否在黑名单内,比如反序列化gadget的关键sink的黑名单或者sql注入的一些payload的黑名单(规则可以参考waf),sql注入还可以判断单引号的个数

3.判断request url中的参数和hook点处的参数是否相同,相同则为存在安全漏洞,hook点处的value是否包含一些敏感字符,比如sql注入的反斜杠 空格等关键payload

rasp和应用引入的第三方jar包之间版本问题

本地测试:

premain方式插桩,rasp引入fastjsonj1.2.48,应用引入fastjson1.2.47,使用1.2.47通杀payload进行尝试:

a.插桩之前:

b.插桩之后:

直接报autotype not support,说明此时理论上应用在使用fastjson时自动使用了rasp的1.2.48版本的fastjson

列出的jvm进程id来看,agent并非以独立进程运行,而是和应用统一jvm进程,用的同一个jvm虚拟机,那么类加载器都用的同一套,因此实际上类加载时,肯定不存在冲突问题,相同的类只会加载一次,此时主要就是看不同版本是加载哪里的,如下图所示:

此时可以看到应用在使用fastjson时将加载rasp指定目录下的fastjson,难道并不是智能地选择高版本????此时控制变量,改变rasp引入的fastjson为1.2.47,应用使用1.2.48,效果如下:

弹了。。。

因此结论为:

如果rasp中引入的第三方jar包的版本和应用引入的第三方jar包版本不同,此时应用在使用jar包时将优先加载rasp引入的对应版本的jar包。

参考

http://blog.nsfocus./rasp-tech/  已看

浅谈RASP技术攻防之基础篇 - FreeBuf网络安全行业门户 已看

https://www.03sec.com/3239.shtml 例子

https://toutiao.io/posts/4kt0al/preview 例子

https://paper.seebug.org/1041/

https://www.cnblogs.com/2014asm/p/10834818.html  有例子

Java RASP浅析--以百度OpenRASP为例 | c0d3p1ut0s  讲openrasp

多种姿势openrasp命令执行绕过-安全客 - 安全资讯平台 rasp绕过

OpenRASP梳理总结 - FreeBuf网络安全行业门户  openrasp梳理

OpenRASP 初探(二)之项目部署_openrasp部署-CSDN博客

IAST原理分析以及在SDL中的应用 - FreeBuf网络安全行业门户  rasp的应用

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

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

相关文章

ClickHouse 如何实现数据一致性

文章目录 ReplacingMegreTree 引擎数据一致性实现方式1.ReplacingMegreTree 引擎2.ReplacingMegreTree 引擎 手动合并3.ReplacingMegreTree 引擎 FINAL 查询4.ReplacingMegreTree 引擎 标记 GroupBy5.允许偏差 前言&#xff1a;在大数据中&#xff0c;基本上所有组件都要求…

【Python-Spark(大规模数据)】

Python-Spark&#xff08;大规模数据&#xff09; ■ Spark■ PySparl编程模型■ 基础准备■ 数据输入■ RDD的map成员方法的使用■ RDD的flatMap成员方法的使用■ RDD的reduceByKey成员方法的使用■ 单词计数统计■ RDD的filter成员方法的使用■ RDD的distinct成员方法的使用■…

【分布式通信】NPKit,NCCL的Profiling工具

NPKit介绍 NPKit (Networking Profiling Kit) is a profiling framework designed for popular collective communication libraries (CCLs), including Microsoft MSCCL, NVIDIA NCCL and AMD RCCL. It enables users to insert customized profiling events into different C…

【JavaScriptThreejs】判断路径在二维平面上投影的方向顺逆时针

原理分析 可以将路径每个连续的两点向量叉乘相加&#xff0c;根据正负性判断路径顺逆时针性 当我们计算两个向量的叉积时&#xff0c;结果是一个新的向量&#xff0c;其方向垂直于这两个向量所在的平面&#xff0c;并且其大小与这两个向量构成的平行四边形的面积成正比。这个新…

Android 组件提供的状态保存(saveInstanceState)与恢复(restoreInstanceState)

在Android的组件Activity中&#xff0c;有这样一对方法: onSaveInstanceeState 和 onRestoreInstanceState 这两对方法&#xff0c;可以让我在Activiy被异常销毁时&#xff0c;保存状态&#xff1b;以及在Activity重建时&#xff0c;恢复状态。 比如&#xff1a;当我们在输入…

MATLAB 2024a软件下载安装教程

1-首先下载Matlab&#xff0c;以下迅雷云链接&#xff0c;里面有全版本的matlab&#xff0c;根据自己的需要下载即可&#xff0c;建议下载最新版的&#xff0c;功能会更多&#xff0c;当然内存也会更大。 迅雷云盘迅雷云盘https://pan.xunlei.com/s/VNgH_6VFav8Kas-tRfxAb3XOA…

Linux I2C(二) - I2C软硬件架构

1&#xff0c;I2C的总线拓扑 2&#xff0c;I2C S/W topology linux kernel I2C framework使用如下的软件拓扑抽象I2C硬件&#xff08;我们可以一起领会一下其中的“设备模型”思想&#xff09;&#xff1a; 1&#xff09;platform bus&#xff08;/sys/bus/platform&#xff0…

Oracle导出导入dmp等文件类型的多表数据的常用方法、遇见的常见问题和解决办法(exp无效sql???)

使用PLSQL执行导出表数据的时候有两种方法 1、使用Oracle命令【imp--exp】【impdp--expdp】 但是如果你的本机没有安装有Oracle数据库&#xff0c;使用的instant client远程连接服务器上的Oracle数据库时候&#xff0c;你没有Oracle数据库带有的exp.exe、imp.exe等扩展文件&a…

如何高效跟进项目进度?试试禅道几个功能

禅道提供了一系列功能和工具&#xff0c;可实现项目进度的有效管理和跟进&#xff0c;极大提升项目管理效率。禅道中的项目进度来源于迭代进度&#xff0c;迭代的进度又来源于任务的消耗和剩余工时&#xff0c;可通过以下功能有效跟进项目进展。 一、燃尽图 在禅道里&#xf…

机器学习day1

一、人工智能三大概念 人工智能三大概念 人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&#xff09;和深度学习&#xff08;DL&#xff09; 人工智能&#xff1a;人工智能是研究计算代理的合成和分析的领域。人工智能是使用计算机来模拟&#xff0c;而不是人类…

【办公类-22-14】周计划系列(5-5)“周计划-05 周计划表格内教案部分“节日”清空改成“节日“” (2024年调整版本)Win32

背景需求&#xff1a; 本学期19周&#xff0c;用了近10周的时间&#xff0c;终于把周计划教案部分的内容补全了&#xff08;把所有教案、反思的文字都撑满一个单元格&#xff09;&#xff0c; 一、原始教案 二、新模板内的教案 三、手动添加文字后的样式&#xff08;修改教案…

庐山研习班上介绍的25个LINUX工具

从2013年的第一届算起&#xff0c;庐山研习班走过十余个年头&#xff0c;办了十几次了。但每一次&#xff0c;都有很多不一样。即使是相同的主题&#xff0c;也有很大差异。 今年春季的庐山研习班是在上个周末。周四晚上我和大部分同学都到了五老峰脚下的训练基地。 除了周六下…

【C++ STL序列容器】list 双向链表

文章目录 【 1. 基本原理 】【 2. list 的创建 】2.1 创建1个空的 list2.2 创建一个包含 n 个元素的 list&#xff08;默认值&#xff09;2.3 创建一个包含 n 个元素的 list&#xff08;赋初值&#xff09;2.4 通过1个 list 初始化另一个 list2.5 拷贝其他类型容器的指定元素创…

HNCTF 2022 week1 题解

自由才是生活主旋律。 [HNCTF 2022 Week1] Interesting_include <?php //WEB手要懂得搜索 //flag in ./flag.phpif(isset($_GET[filter])){$file $_GET[filter];if(!preg_match("/flag/i", $file)){die("error");}include($file); }else{highlight_…

CentOS7安装并配置Yearning并实现无公网IP远程SQL审核与数据查询

目录 ​编辑 前言 1. Linux 部署Yearning 2. 本地访问Yearning 3. Linux 安装cpolar 4. 配置Yearning公网访问地址 5. 公网远程访问Yearning管理界面 6. 固定Yearning公网地址 结语 前言 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业应届生 今天给大家聊聊…

Docker 的数据管理 端口映射 容器互联 镜像的创建

目录 概念 概念 管理 Docker 容器中数据主要有两种方式&#xff1a;数据卷&#xff08;Data Volumes&#xff09;和数据卷容器&#xff08;DataVolumes Containers&#xff09;。总结&#xff1a;因为容器数据是临时保存的为了安全&#xff0c;就要让数据保持持久化。 1&#…

qt QTreeWidget 学习

树形控件的节点可以有多层、多个子节点&#xff0c; 如果将子节点全部展开&#xff0c;那么每一行都是一个数据条目。QTreeWidgetItem 比较特殊&#xff0c;一个条目内部可以有多列数据信息&#xff0c;相当于表格控件一整行的表格单元集成为一个条目。 默认情况下&#xff0c;…

Methoxy-PEG-PLGA,mPEG-PLGA是一种可生物降解的两亲性嵌段共聚物

【试剂详情】 英文名称 mPEG-PLGA&#xff0c;Methoxy-PEG-Poly(lactide-co-glycolide)&#xff0c;Methoxy-PEG-PLGA&#xff0c; mPEG-Poly(lactide-co-glycolide) 中文名称 聚乙二醇单甲醚聚乳酸&#xff0c;乙醇酸两嵌段共聚物 外观性状 由分子量决定&#xff0c;液体…

调试记录 Flash 芯片 GD25LQ128ESIG 的程序烧录问题

1. 烧录工具 工具型号&#xff1a; VS4000P 2. 烧录问题 1. 烧录器选择烧录型号过程中没有看见 Flash 芯片 GD25LQ128ESIG 的型号。其中有GD25Q128E &#xff0c;但是三个选项的封装不对。 3. 解决过程 1. 尝试别的类型的芯片型号烧录。 A.GD25LQ80E(SOP8_200) B.GD25LQ64E(SOP…

IDEA 2024.1 配置 AspectJ环境

最近Java课设在学习AspectJ&#xff0c;做PPT顺便写一个博客 下载包 首先去AspectJ官网下载一个JAR包并安装 安装完最后可以按照他的建议配置一下 然后找到AspectJ的安装位置的lib目录&#xff0c;把三个包拷到自己项目中的lib目录下 由于最新版的IDEA已经不支持AspectJ了 所…