本文目录导读:

Java表达式注入案例
什么是表达式注入?
表达式注入是一种安全漏洞,当应用程序将用户输入直接传递给表达式求值引擎(如SpEL、OGNL、MVEL等)进行解析执行时,攻击者可以注入恶意表达式导致远程代码执行。
常见案例
Spring Expression Language (SpEL) 注入
漏洞代码示例:
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class SpELInjectionExample {
public static void main(String[] args) {
String userInput = "T(java.lang.Runtime).getRuntime().exec('calc.exe')";
ExpressionParser parser = new SpelExpressionParser();
// 危险:直接解析用户输入
Object result = parser.parseExpression(userInput).getValue();
System.out.println(result);
}
}
修复方案:
public class SpELFixExample {
public static void main(String[] args) {
String userInput = "T(java.lang.Runtime).getRuntime().exec('calc.exe')";
ExpressionParser parser = new SpelExpressionParser();
// 使用SimpleEvaluationContext限制可访问的类和对象
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// 或者直接在表达式中限制可访问的变量
try {
// 设置白名单,只允许访问特定对象
StandardEvaluationContext safeContext = new StandardEvaluationContext();
safeContext.setRootObject(new SafeObject());
Object result = parser.parseExpression(userInput).getValue(safeContext);
} catch (Exception e) {
// 安全的错误处理
System.err.println("非法表达式: " + e.getMessage());
}
}
static class SafeObject {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
OGNL (Object-Graph Navigation Language) 注入
常见于Struts2框架
漏洞代码示例:
import ognl.Ognl;
import ognl.OgnlContext;
public class OGNLInjectionExample {
public static void main(String[] args) {
String userInput = "#a=(new java.lang.ProcessBuilder(new java.lang.String[]{'cmd','/c','calc'})).start()";
OgnlContext context = new OgnlContext();
try {
// 危险:直接解析用户输入
Object expression = Ognl.parseExpression(userInput);
Ognl.getValue(expression, context, new Object());
} catch (Exception e) {
e.printStackTrace();
}
}
}
修复方案:
import ognl.ASTProperty;
import ognl.Node;
import ognl.Ognl;
import ognl.OgnlContext;
public class OGNLFixExample {
private static final String[] ALLOWED_PROPERTIES = {"name", "age", "email"};
public static boolean validateExpression(String expression) {
try {
Node node = Ognl.parseExpression(expression);
return validateNode(node);
} catch (Exception e) {
return false;
}
}
private static boolean validateNode(Node node) {
if (node instanceof ASTProperty) {
String property = ((ASTProperty) node).getProperty(null).toString();
return Arrays.asList(ALLOWED_PROPERTIES).contains(property);
}
// 递归验证子节点
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
if (!validateNode(node.jjtGetChild(i))) {
return false;
}
}
return true;
}
public static Object safeGetValue(String expression, Object root) {
if (!validateExpression(expression)) {
throw new SecurityException("非法表达式: " + expression);
}
OgnlContext context = new OgnlContext();
try {
Node node = Ognl.compileExpression(context, root, expression);
return Ognl.getValue(node, context, root);
} catch (Exception e) {
throw new RuntimeException("表达式执行失败", e);
}
}
}
MVEL (MVFLEX Expression Language) 注入
漏洞代码示例:
import org.mvel2.MVEL;
public class MVELInjectionExample {
public static void main(String[] args) {
String userInput = "Runtime.getRuntime().exec('calc')";
// 危险:直接执行用户输入
Object result = MVEL.eval(userInput);
System.out.println(result);
}
}
修复方案:
import org.mvel2.MVEL;
import org.mvel2.ParserContext;
public class MVELFixExample {
public static void main(String[] args) {
String userInput = "Runtime.getRuntime().exec('calc')";
// 创建安全的解析器上下文
ParserContext context = new ParserContext();
// 禁用危险的函数和类
context.addImport("Math", Math.class);
context.setAllowNakedMaptrixAccess(false);
context.setStrictTypeEnforcement(true);
// 定义白名单类
Set<String> allowedClasses = new HashSet<>();
allowedClasses.add("java.lang.Math");
allowedClasses.add("java.lang.String");
context.addClassImport("String", String.class);
// 使用白名单验证
if (isValidExpression(userInput, allowedClasses)) {
try {
Object result = MVEL.eval(userInput, context);
System.out.println("结果: " + result);
} catch (Exception e) {
System.err.println("表达式执行错误: " + e.getMessage());
}
} else {
System.err.println("包含不允许的类或方法");
}
}
private static boolean isValidExpression(String expression, Set<String> allowedClasses) {
// 检查是否包含危险的方法调用
String[] dangerousPatterns = {
"Runtime", "ProcessBuilder", "exec", "invoke",
"ClassLoader", "getClass", "forName"
};
for (String pattern : dangerousPatterns) {
if (expression.contains(pattern)) {
return false;
}
}
return true;
}
}
自定义表达式引擎注入
import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
public class ScriptEngineInjectionExample {
public static void main(String[] args) {
String userInput = "var proc = new java.lang.ProcessBuilder('calc').start()";
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");
try {
// 危险:直接执行用户输入的脚本
engine.eval(userInput);
} catch (ScriptException e) {
e.printStackTrace();
}
}
}
修复方案(使用沙箱环境):
import javax.script.*;
import java.util.*;
public class SecureScriptEngineExample {
public static void main(String[] args) {
String userInput = "var proc = new java.lang.ProcessBuilder('calc').start()";
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");
// 创建安全上下文
Bindings bindings = engine.createBindings();
bindings.put("safeMath", new MathHelper());
// 使用简单的数学运算白名单
SimpleScriptContext context = new SimpleScriptContext();
context.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
// 设置写入限制
StringWriter writer = new StringWriter();
context.setWriter(writer);
try {
// 使用预编译和验证
if (isMathExpression(userInput)) {
Object result = engine.eval(userInput, context);
System.out.println("结果: " + result);
} else {
System.err.println("只允许数学表达式");
}
} catch (ScriptException e) {
System.err.println("脚本执行错误: " + e.getMessage());
}
}
private static boolean isMathExpression(String input) {
// 只允许基本的数学运算
return input.matches("[0-9+\\-*/()\\s.]+");
}
// 提供安全的方法
static class MathHelper {
public double add(double a, double b) { return a + b; }
public double subtract(double a, double b) { return a - b; }
public double multiply(double a, double b) { return a * b; }
public double divide(double a, double b) {
if (b == 0) throw new ArithmeticException("除数不能为0");
return a / b;
}
}
}
- 输入验证:严格验证和过滤用户输入
- 使用安全上下文:使用限制性的求值上下文
- 白名单机制:只允许访问特定的类和方法
- 禁用动态执行:尽可能避免使用表达式引擎
- 最小权限原则:应用程序只拥有必要的权限
- 使用安全库:选择有安全特性支持的表达式引擎版本
- 代码审查:定期进行安全代码审查
- 日志记录:记录所有表达式执行操作
检测工具
- FindSecBugs:用于检测Java安全漏洞
- Snyk:开源组件安全扫描
- OWASP Dependency Check:检查依赖项漏洞
- 静态代码分析工具:如SonarQube、Checkmarx等
避免表达式注入的最佳实践是在设计阶段就考虑安全性,避免将用户输入直接用于表达式求值,如果必须使用,要实施严格的输入验证和执行环境限制。