SpringBoot Http请求分析

admin 2023年4月13日08:47:41评论67 views字数 19146阅读63分49秒阅读模式

SpringBoot Http请求分析

SpringBoot Http请求分析

本文来源自平安银河实验室

作者:荣生


0x01 概述

在进行Java代码审计的过程中经常会涉及到Java工程项目的环境部署及代码调试,本文主要针对Java项目常见的SpringBoot+Thymeleaf框架进行Http网络请求分析。

0x02 SpringBoot框架

2.1 Spring、SpringMVC、Springboot

先说说Spring、SpringMVC、Springboot三者之间的关系,Spring、Spring Boot 和 Spring MVC 都是与 Java 应用程序开发相关的框架。

* Spring 是一个开源的轻量级应用程序框架,它可以帮助我们构建企业级应用程序,并提供了许多用于处理各种企业级应用程序开发任务的功能,例如依赖注入、面向切面编程、事务管理等。* Spring MVC 是 Spring 框架中的一部分,它是一个基于模型-视图-控制器(MVC)模式的 Web 框架。它提供了一个模型-视图-控制器(MVC)架构,用于构建 Web 应用程序,并且可以与许多视图技术集成。* Spring Boot 是一个构建在 Spring 框架之上的框架,它通过自动配置和约定优于配置的方式简化了 Spring 应用程序的开发和部署。Spring Boot 可以帮助我们快速创建独立的、生产级别的 Spring 应用程序。

总的来说,Spring 是一个全面的应用程序框架,而 Spring MVC 是其中的一个 Web 框架,Spring Boot 则是一个构建在 Spring 基础上的开箱即用的框架。因此他们的关系大概就是这样:spring mvc < spring < springboot

2.2 SpringMVC架构图

MVC是Model(模型) + View(视图)+ Controller(控制层)的简称,其中Model层主要负责数据处理、View负责视图的渲染和加载、Controller负责处理用户请求及逻辑控制等。下图是用户发起网络请求后MVC框架层的流程图:

SpringBoot Http请求分析

0x03 Springboot http

3.1 Springboot http请求处理流程

Spring Boot 基于 Spring 框架,处理 HTTP 请求的流程也与 Spring 框架类似。下面是 Spring Boot 处理 HTTP 请求的大致流程:

1. 客户端发起 HTTP 请求,请求到达 Web 服务器。

2. Web 服务器将请求发送到 Spring Boot 应用程序中的 DispatcherServlet。

3. DispatcherServlet 通过 HandlerMapping 找到处理该请求的 Controller。

4. Controller 处理请求,生成 Model 数据,然后返回逻辑视图名。

5. DispatcherServlet 根据视图名查找 View,然后将 Model 数据传递给 View。

6. View 渲染 Model 数据,生成 HTML 响应。

7. DispatcherServlet 将 HTML 响应发送给客户端。

在上述过程中,Spring Boot 会涉及到许多组件,例如 DispatcherServlet、HandlerMapping、Controller、ViewResolver 等。此外,Spring Boot 还提供了许多注解和工具类,以简化开发人员的工作。例如,使用 @RestController 注解可以将 Controller 的所有方法都默认映射为 RESTful Web 服务,使用 RestTemplate 工具类可以方便地发送 HTTP 请求。

3.2 Springboot Http请求流程图

SpringBoot Http请求分析

3.3 具体实现

(1)FrameworkServlet

FrameworkServlet 继承自 HttpServletBean,而 HttpServletBean 继承自 HttpServlet,网络请求经过filter后,开始处理是在HttpServlet的service 方法。下面是它们之间的UML类图:

SpringBoot Http请求分析

我们先来看看 HttpServlet#service 方法:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {    HttpServletRequest request;    HttpServletResponse response;    try {        request = (HttpServletRequest)req;        response = (HttpServletResponse)res;    } catch (ClassCastException var6) {        throw new ServletException(lStrings.getString("http.non_http"));    }    this.service(request, response);}

子类FrameworkServlet重写了service请求:

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());    if (httpMethod != HttpMethod.PATCH && httpMethod != null) {        super.service(request, response);    } else {        this.processRequest(request, response);    }}

该方法中,首先获取到当前请求方法,如果是patch请求直接走processRequest,其他类型的请求全部通过 super.service 进行处理。

在FrameworkServlet父类HttpServlet 中的service 方法根据请求的method类型分别调用不同的处理,但在HttpServlet中doGet、doPost等并没有具体实现,在子类FrameworkServlet 中重写了各种请求对应的方法,如 doDelete、doGet、doOptions、doPost、doPut、doTrace 等。

我们先来看看 doDelete、doGet、doPost 以及 doPut 四个方法:

@Overrideprotected final void doGet(HttpServletRequest request, HttpServletResponse response)  throws ServletException, IOException { processRequest(request, response);}@Overrideprotected final void doPost(HttpServletRequest request, HttpServletResponse response)  throws ServletException, IOException { processRequest(request, response);}

可以看到,请求最后还是交给 processRequest 去处理了,在 processRequest 方法中则会进一步调用到 doService,对不同类型的请求分类处理。

(1.1)processRequest

processRequest 其实主要做了两件事,第一件事就是对 LocaleContext 和 RequestAttributes 的处理,第二件事就是发布事件publishRequestHandledEvent。

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {    long startTime = System.currentTimeMillis();    Throwable failureCause = null;    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();    LocaleContext localeContext = this.buildLocaleContext(request);    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();    ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());    this.initContextHolders(request, localeContext, requestAttributes);
    try {        this.doService(request, response);    } catch (IOException | ServletException var16) {        failureCause = var16;        throw var16;    } catch (Throwable var17) {        failureCause = var17;        throw new NestedServletException("Request processing failed", var17);    } finally {        this.resetContextHolders(request, previousLocaleContext, previousAttributes);        if (requestAttributes != null) {            requestAttributes.requestCompleted();        }
        this.logResult(request, response, (Throwable)failureCause, asyncManager);        this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);    }
}
(1.2)publishRequestHandledEvent

最后就是 processRequest 方法中的事件发布了。

在 finally 代码块中会调用 publishRequestHandledEvent 方法发送一个 ServletRequestHandledEvent 类型的事件,具体发送代码如下:

private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response, long startTime, @Nullable Throwable failureCause) {    if (this.publishEvents && this.webApplicationContext != null) {        long processingTime = System.currentTimeMillis() - startTime;        this.webApplicationContext.publishEvent(new ServletRequestHandledEvent(this, request.getRequestURI(), request.getRemoteAddr(), request.getMethod(), this.getServletConfig().getServletName(), WebUtils.getSessionId(request), this.getUsernameForRequest(request), processingTime, failureCause, response.getStatus()));    }}

以看到,事件的发送需要 publishEvents 为 true,而该变量默认就是 true。如果需要修改该变量的值,可以在 web.xml 中配置 DispatcherServlet 时,通过 init-param 节点顺便配置一下该变量的值。正常情况下,这个事件总是会被发送出去。

(2)DispatcherServlet

(2.1)doService

doService在FrameworkServlet是一个抽象方法,具体实现在子类DispatcherServlet中

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {    this.logRequest(request);    Map<String, Object> attributesSnapshot = null;    if (WebUtils.isIncludeRequest(request)) {        attributesSnapshot = new HashMap();        Enumeration<?> attrNames = request.getAttributeNames();
        label116:        while(true) {            String attrName;            do {                if (!attrNames.hasMoreElements()) {                    break label116;                }
                attrName = (String)attrNames.nextElement();            } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));
            attributesSnapshot.put(attrName, request.getAttribute(attrName));        }    }
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);    request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());    if (this.flashMapManager != null) {        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);        if (inputFlashMap != null) {            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));        }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);    }
    RequestPath previousRequestPath = null;    if (this.parseRequestPath) {        previousRequestPath = (RequestPath)request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);        ServletRequestPathUtils.parseAndCache(request);    }
    try {        this.doDispatch(request, response);    } finally {        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {            this.restoreAttributesAfterInclude(request, attributesSnapshot);        }
        if (this.parseRequestPath) {            ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);        }
    }
}

(2.2)doDispatch

其中核心调用就是doDispatch方法:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {    HttpServletRequest processedRequest = request;    HandlerExecutionChain mappedHandler = null;    boolean multipartRequestParsed = false;    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {        try {            ModelAndView mv = null;            Exception dispatchException = null;
            try {                //1.判断是否文件上传                processedRequest = this.checkMultipart(request);                multipartRequestParsed = processedRequest != request;                //2.获取处理Handler                mappedHandler = this.getHandler(processedRequest);                if (mappedHandler == null) {                    this.noHandlerFound(processedRequest, response);                    return;                }
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());                String method = request.getMethod();                //3.判断是否get请求                boolean isGet = HttpMethod.GET.matches(method);                if (isGet || HttpMethod.HEAD.matches(method)) {                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());                    //4.如果资源文件没有过期,直接返回                    if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {                        return;                    }                }                //4.拦截器preHandle                if (!mappedHandler.applyPreHandle(processedRequest, response)) {                    return;                }                //5.获取ModelAndView视图                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());                if (asyncManager.isConcurrentHandlingStarted()) {                    return;                }
                this.applyDefaultViewName(processedRequest, mv);                //6.拦截器postHandle                mappedHandler.applyPostHandle(processedRequest, response, mv);            } catch (Exception var20) {                dispatchException = var20;            } catch (Throwable var21) {                dispatchException = new NestedServletException("Handler dispatch failed", var21);            }            //7.渲染视图            this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);        } catch (Exception var22) {            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);        } catch (Throwable var23) {            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));        }
    } finally {        if (asyncManager.isConcurrentHandlingStarted()) {            if (mappedHandler != null) {                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);            }        } else if (multipartRequestParsed) {            this.cleanupMultipart(processedRequest);        }
    }}

(2.3)processDispatchResult

处理请求结果

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {    boolean errorView = false;    if (exception != null) {        if (exception instanceof ModelAndViewDefiningException) {            this.logger.debug("ModelAndViewDefiningException encountered", exception);            mv = ((ModelAndViewDefiningException)exception).getModelAndView();        } else {            Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;            mv = this.processHandlerException(request, response, handler, exception);            errorView = mv != null;        }    }
    if (mv != null && !mv.wasCleared()) {        this.render(mv, request, response);        if (errorView) {            WebUtils.clearErrorRequestAttributes(request);        }    } else if (this.logger.isTraceEnabled()) {        this.logger.trace("No view rendering, null ModelAndView returned.");    }
    if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {        if (mappedHandler != null) {            mappedHandler.triggerAfterCompletion(request, response, (Exception)null);        }
    }}

exception 不为空时加载错误视图

调用view.render渲染视图view#render是一个接口类方法,具体有很多的实现类。

HtmlResourceViewStaticViewAbstractViewThymeleafViewAjaxThymeleafView

(3)View视图层

视图层会根据Http请求响应结果渲染视图,上面实现View#render接口方法的类都可以进行视图渲染。Springboot中会使用Thymeleaf作为视图解析器,在Controller控制器方法中所返回的视图名(无任何前后缀)会被解析,拼接上配置文件中Thymeleaf设置的前后缀,然后跳转到视图页面。其中ThymeleafView组件容易发生模板注入漏洞,已经存在多个CVE漏洞。

在Java工程项目的pom.xml依赖配置文件中会有Springboot和thymeleaf声明:

<dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <!-- thymeleaf 模版引擎 -->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-thymeleaf</artifactId>    </dependency></dependencies>

一般的控制器方法示例如下:

@Slf4j@Controller@RequestMapping("/admin/u")public class UserLoginController extends BaseController {@RequestMapping("toLogin")public ModelAndView toLogin() {    ModelAndView mv = new ModelAndView();    User user = (User) SecurityUtils.getSubject().getPrincipal();    mv.setViewName("admin/login");    return mv;}}

在上面的示例中,当用户访问http://xx.xx.xx/admin/u/toLogin时,控制器会返回ViewName为admin/login的 ModelAndView,视图解析器会根据视图名去自动查找src/main/resources/templtes/admin下的视图并加载。

SpringBoot Http请求分析下面分析下ThymeleafView加载视图流程:

public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {    this.renderFragment(this.markupSelectors, model, request, response);}protected void renderFragment(Set<String> markupSelectorsToRender, Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {    ServletContext servletContext = this.getServletContext();    String viewTemplateName = this.getTemplateName();    ISpringTemplateEngine viewTemplateEngine = this.getTemplateEngine();    if (viewTemplateName == null) {        throw new IllegalArgumentException("Property 'templateName' is required");    } else if (this.getLocale() == null) {        throw new IllegalArgumentException("Property 'locale' is required");    } else if (viewTemplateEngine == null) {        throw new IllegalArgumentException("Property 'templateEngine' is required");    } else {        Map<String, Object> mergedModel = new HashMap(30);        Map<String, Object> templateStaticVariables = this.getStaticVariables();        if (templateStaticVariables != null) {            mergedModel.putAll(templateStaticVariables);        }         if (pathVariablesSelector != null) {            Map<String, Object> pathVars = (Map)request.getAttribute(pathVariablesSelector);            if (pathVars != null) {                mergedModel.putAll(pathVars);            }        }         if (model != null) {            mergedModel.putAll(model);        }         ApplicationContext applicationContext = this.getApplicationContext();        RequestContext requestContext = new RequestContext(request, response, this.getServletContext(), mergedModel);        SpringWebMvcThymeleafRequestContext thymeleafRequestContext = new SpringWebMvcThymeleafRequestContext(requestContext, request);        addRequestContextAsVariable(mergedModel, "springRequestContext", requestContext);        addRequestContextAsVariable(mergedModel, "springMacroRequestContext", requestContext);        mergedModel.put("thymeleafRequestContext", thymeleafRequestContext);        ConversionService conversionService = (ConversionService)request.getAttribute(ConversionService.class.getName());        ThymeleafEvaluationContext evaluationContext = new ThymeleafEvaluationContext(applicationContext, conversionService);        mergedModel.put("thymeleaf::EvaluationContext", evaluationContext);        IEngineConfiguration configuration = viewTemplateEngine.getConfiguration();        WebExpressionContext context = new WebExpressionContext(configuration, request, response, servletContext, this.getLocale(), mergedModel);        String templateName;        Set markupSelectors;        if (!viewTemplateName.contains("::")) {            templateName = viewTemplateName;            markupSelectors = null;        } else {            SpringRequestUtils.checkViewNameNotInRequest(viewTemplateName, request);            IStandardExpressionParser parser = StandardExpressions.getExpressionParser(configuration);             FragmentExpression fragmentExpression;            try {                fragmentExpression = (FragmentExpression)parser.parseExpression(context, "~{" + viewTemplateName + "}");            } catch (TemplateProcessingException var25) {                throw new IllegalArgumentException("Invalid template name specification: '" + viewTemplateName + "'");            }             FragmentExpression.ExecutedFragmentExpression fragment = FragmentExpression.createExecutedFragmentExpression(context, fragmentExpression);            templateName = FragmentExpression.resolveTemplateName(fragment);            markupSelectors = FragmentExpression.resolveFragments(fragment);            Map<String, Object> nameFragmentParameters = fragment.getFragmentParameters();            if (nameFragmentParameters != null) {                if (fragment.hasSyntheticParameters()) {                    throw new IllegalArgumentException("Parameters in a view specification must be named (non-synthetic): '" + viewTemplateName + "'");                }                 context.setVariables(nameFragmentParameters);            }        }         String templateContentType = this.getContentType();        Locale templateLocale = this.getLocale();        String templateCharacterEncoding = this.getCharacterEncoding();        Set processMarkupSelectors;        if (markupSelectors != null && markupSelectors.size() > 0) {            if (markupSelectorsToRender != null && markupSelectorsToRender.size() > 0) {                throw new IllegalArgumentException("A markup selector has been specified (" + Arrays.asList(markupSelectors) + ") for a view that was already being executed as a fragment (" + Arrays.asList(markupSelectorsToRender) + "). Only one fragment selection is allowed.");            }             processMarkupSelectors = markupSelectors;        } else if (markupSelectorsToRender != null && markupSelectorsToRender.size() > 0) {            processMarkupSelectors = markupSelectorsToRender;        } else {            processMarkupSelectors = null;        }         response.setLocale(templateLocale);        if (!this.getForceContentType()) {            String computedContentType = SpringContentTypeUtils.computeViewContentType(request, templateContentType != null ? templateContentType : "text/html;charset=ISO-8859-1", templateCharacterEncoding != null ? Charset.forName(templateCharacterEncoding) : null);            response.setContentType(computedContentType);        } else {            if (templateContentType != null) {                response.setContentType(templateContentType);            } else {                response.setContentType("text/html;charset=ISO-8859-1");            }             if (templateCharacterEncoding != null) {                response.setCharacterEncoding(templateCharacterEncoding);            }        }         boolean producePartialOutputWhileProcessing = this.getProducePartialOutputWhileProcessing();        Writer templateWriter = producePartialOutputWhileProcessing ? response.getWriter() : new FastStringWriter(1024);        viewTemplateEngine.process(templateName, processMarkupSelectors, context, (Writer)templateWriter);        if (!producePartialOutputWhileProcessing) {            response.getWriter().write(templateWriter.toString());            response.getWriter().flush();        }     }}

其中条件判断viewTemplateName.contains("::")会检查viewTemplateName是否包含“::”,当包含“::”时会对viewTemplateName进行预处理调用StandardExpressions.getExpressionParser

static IStandardExpression parseExpression(IExpressionContext context, String input, boolean preprocess) {    IEngineConfiguration configuration = context.getConfiguration();    String preprocessedInput = preprocess ? StandardExpressionPreprocessor.preprocess(context, input) : input;    IStandardExpression cachedExpression = ExpressionCache.getExpressionFromCache(configuration, preprocessedInput);    if (cachedExpression != null) {        return cachedExpression;    } else {        Expression expression = Expression.parse(preprocessedInput.trim());        if (expression == null) {            throw new TemplateProcessingException("Could not parse as expression: "" + input + """);        } else {            ExpressionCache.putExpressionIntoCache(configuration, preprocessedInput, expression);            return expression;        }    }}
最后进入到StandardExpressionParser #preprocess方法中,调用了expression.execute,从而造成了ThymeleafView模板注入。
private static final char PREPROCESS_DELIMITER = '_';private static final String PREPROCESS_EVAL = "\_\_(.*?)\_\_";private static final Pattern PREPROCESS_EVAL_PATTERN = Pattern.compile("\_\_(.*?)\_\_", 32); static String preprocess(IExpressionContext context, String input) {    if (input.indexOf(95) == -1) {        return input;    } else {        IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(context.getConfiguration());        if (!(expressionParser instanceof StandardExpressionParser)) {            return input;        } else {            Matcher matcher = PREPROCESS_EVAL_PATTERN.matcher(input);            if (!matcher.find()) {                return checkPreprocessingMarkUnescaping(input);            } else {                StringBuilder strBuilder = new StringBuilder(input.length() + 24);                int curr = 0;                 String remaining;                do {                    remaining = checkPreprocessingMarkUnescaping(input.substring(curr, matcher.start(0)));                    String expressionText = checkPreprocessingMarkUnescaping(matcher.group(1));                    strBuilder.append(remaining);                    IStandardExpression expression = StandardExpressionParser.parseExpression(context, expressionText, false);                    if (expression == null) {                        return null;                    }                     Object result = expression.execute(context, StandardExpressionExecutionContext.RESTRICTED);                    strBuilder.append(result);                    curr = matcher.end(0);                } while(matcher.find());                 remaining = checkPreprocessingMarkUnescaping(input.substring(curr));                strBuilder.append(remaining);                return strBuilder.toString().trim();            }        }    }}
常见模板注入的payload:
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("whoami").getInputStream()).next()}__::.x
当不包含“::”会进入到默认的分支,返回templateName,然后调用TemplateManager去项目的/resources/template下寻找对应的模板资源文件返回浏览器进行加载。

0x04 其他

在进行Java代码审计分析及调试时需要对Java常见的设计模式及类加载机制有所了解,特别是在寻找实现类及方法时中间可能涉及继承和接口实现,或者我们也可以在使用IDEA等工具进行调试时在上面Http请求流程中的任意一个方法中打断点,当进入断点时可以借助工具查看方法调用栈来分析代码的执行逻辑,甚至可以在debug时修改变量值进行调试。

IDEA下debug调用栈示例如下:SpringBoot Http请求分析调试时直接修改变量值:SpringBoot Http请求分析

本地环境可以使用Phpstudy等工具快速搭建,省去手动一个个安装WAMP等依赖项。
SpringBoot Http请求分析

0x05 参考文档

类加载机制:https://juejin.cn/post/6844903564804882445

设计模式:https://cloud.tencent.com/developer/article/1602270

小皮面板:https://www.xp.cn/download.html

银河实验室

SpringBoot Http请求分析

银河实验室(GalaxyLab)是平安集团信息安全部下一个相对独立的安全实验室,主要从事安全技术研究和安全测试工作。团队内现在覆盖逆向、物联网、Web、Android、iOS、云平台区块链安全等多个安全方向。
官网:http://galaxylab.pingan.com.cn/

JSP下的白魔法:JspEncounter
利用Cloudflare 零信任进行C2通信及防护
使用GitHub Actions进行红队自动化构建
使用VS-Code结合源码进行内核及内核模块远程调试的方法

点赞、分享,感谢你的阅读

▼ 点击阅读原文,进入官网

原文始发于微信公众号(平安集团安全应急响应中心):SpringBoot Http请求分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年4月13日08:47:41
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   SpringBoot Http请求分析https://cn-sec.com/archives/1669555.html

发表评论

匿名网友 填写信息