sanitizer: 防腐剂,消毒杀菌剂,洗手液 =》应该是指对js代码进行消杀
检查循环语句,并插入函数调用,用来当js执行引擎线程被interrupt中断时,break当前脚本的执行
版本:delight-nashorn-sandbox-0.2.0 (Java嵌入式js解析引擎的沙箱环境封装)
1. 流程
public方法只有secureJs,主要逻辑在secureJsImpl中
- 格式化js代码 =》 对应beautifyJs (TODO 格式化后,nashorn执行更快)
- 如果有循环js代码,则在循环中注入函数调用,(用来检测中断,抛出异常,进而中断当前js执行线程) =》 对应injectInterruptionCalls
2. 格式化js代码
在pom.xml文件中
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>js-beautify</artifactId>
<version>1.9.0</version>
</dependency>
依赖 org.webjars.bower:js-beautify:1.9.0
使用其中的格式化js库: /META-INF/resources/webjars/js-beautify/1.9.0/js/lib/beautifier.js
3. 注入函数调用
内部类PoisonPil: Poison即毒药,毒物,Pil,即Pills药丸,=》应该是指有危害的js代码片段,用正则来检测有危害的js代码,并替换对应的js代码replacement
比如:
new PoisonPil(Pattern.compile("(\\s*for\\s*\\([^\\{]+\\)\\s*\\{)"), JS_INTERRUPTED_FUNCTION + "();") // for with block
匹配 for ( ) {
替换 __if(); =》 实际上是把匹配上的内容,在后面添加该[替换内容],使用了matcher.appendReplacement(sb, ("$1" + pp.replacement)) $1表示匹配内容
上述替换,可以理解为js中的函数调用
如果有注入js代码,则在js代码开头再添加其他代码,getPreamble(preamble,序言,绪论,开场白):
var __it=Java.type('delight.nashornsandbox.internal.InterruptTest');
var __if=function(){__it.test();};
这里,首先把在js中引入Java类类型InterruptTest
然后,定义js函数__if,函数体即为调用InterruptTest的静态方法test,检测线程中断状态,并抛出异常
4. 例子
输入:
var x = 1;while (true) {x=x+1;}
格式化beautify:
var x = 1;
while (true) {
x = x + 1;
}
注入(loop):
var x = 1;
while (true) {__if();
x = x + 1;
}
注入(添加开头):
var __it=Java.type('delight.nashornsandbox.internal.InterruptTest');var __if=function(){__it.test();};
var x = 1;
while (true) {__if();
x = x + 1;
}
5. 作用
- 执行js代码时,是在线程池的线程中运行
- 同时,调用方,会执行ThreadMonitor中的run方法,监控js线程,比如CPU、内存,
- 如果超时,或者超出内存限制,则(在Java代码中)会向js线程发送interrupt中断信号(threadToMonitor.interrupt()) 相比thread.stop()终止线程,更优雅一些;TODO 并且,如果不在js中注入中断检测函数调用,在Java中使用thread.interrupt()时,js执行线程没有自动捕获或者检测线程中断状态,进而退出js执行
- 此时,js线程中注入的代码,会检测线程中断状态(在js代码中注入,执行的却是Java代码),并抛出异常
- 这样,js线程的调用方,JsEvaluator,会捕获到该异常,设置状态,并退出
- (实际上,应该不是js线程吧,JsEvaluator是Java线程,调用执行编译后的js代码)
ThreadMonitor
void run() {
try {
// wait, for threadToMonitor to be set in JS evaluator thread
synchronized (monitor) {
if (threadToMonitor == null) {
monitor.wait((maxCPUTime + 500) / MILI_TO_NANO);
}
if (threadToMonitor == null) {
timedOutWaitingForThreadToMonitor = true;
throw new IllegalStateException("Executor thread not set after " + maxCPUTime / MILI_TO_NANO + " ms");
}
}
final long startCPUTime = getCPUTime();
final long startMemory = getCurrentMemory();
while (!stop.get()) {
final long runtime = getCPUTime() - startCPUTime;
final long memory = getCurrentMemory() - startMemory;
if (isCpuTimeExided(runtime) || isMemoryExided(memory)) {
cpuLimitExceeded.set(isCpuTimeExided(runtime));
memoryLimitExceeded.set(isMemoryExided(memory));
threadToMonitor.interrupt();
synchronized (monitor) {
monitor.wait(50);
}
if (stop.get()) {
return;
}
if (!scriptFinished.get()) {
LOG.error(this.getClass().getSimpleName() + ": Thread hard shutdown!");
threadToMonitor.stop();
scriptKilled.set(true);
}
return;
} else {
}
synchronized (monitor) {
long waitTime = getCheckInterval(runtime);
if (waitTime == 0) {
waitTime = 1;
}
monitor.wait(waitTime);
}
}
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
JsEvaluator implements Runnable
@Override
public void run() {
try {
boolean registered = threadMonitor.registerThreadToMonitor(Thread.currentThread());
if (registered) {
result = operation.executeScriptEngineOperation(scriptEngine);
}
}
catch (final RuntimeException e) {
// InterruptedException means script was successfully interrupted,
// so no exception should be propagated
if(!(e.getCause() instanceof InterruptedException)) {
exception = e;
}
}
catch (final Exception e) {
exception = e;
}
finally {
threadMonitor.scriptFinished();
threadMonitor.stopMonitor();
}
}
参考: