ОБНОВЛЕНИЕ: Все это было сделано с использованием JEXL 2.0.1. Возможно, вам придется адаптировать это, чтобы заставить его работать с более новыми версиями.
Вот мой подход к рассмотрению каждого из этих случаев. Я создал модульные тесты для проверки каждого из этих случаев и убедился, что они работают.
JEXL делает это довольно легко. Просто создайте собственный ClassLoader. Переопределите два метода loadClass (). При вызове JexlEngine setClassLoader ().
Опять же, JEXL делает это довольно легко. Вы должны заблокировать оба .class и .getClass (). Создайте свой собственный класс Uberspect, который расширяет UberspectImpl. Переопределите getPropertyGet, если идентификатор равен «классу» и возвращает ноль. Переопределите getMethod, если метод равен "getClass" и возвращает null. При создании JexlEngine передайте ссылку на вашу реализацию Uberspect.
class MyUberspectImpl extends UberspectImpl {
public MyUberspectImpl(Log jexlLog) {
super(jexlLog);
}
@Override
public JexlPropertyGet getPropertyGet(Object obj, Object identifier, JexlInfo info) {
// for security we do not allow access to .class property
if ("class".equals(identifier)) throw new RuntimeException("Access to getClass() method is not allowed");
JexlPropertyGet propertyGet = super.getPropertyGet(obj, identifier, info);
return propertyGet;
}
@Override
public JexlMethod getMethod(Object obj, String method, Object[] args, JexlInfo info) {
// for security we do not allow access to .getClass() method
if ("getClass".equals(method)) throw new RuntimeException("Access to getClass() method is not allowed");
return super.getMethod(obj, method, args, info);
}
}
Вы делаете это с помощью механизма AccessController в Java. Я сделаю это быстро. Запустите Java с -Djava.security.policy = policyfile. Создайте файл с именем policyfile, содержащий эту строку:
grant {разрешение java.security.AllPermission; };
Установите SecurityManager по умолчанию с помощью этого вызова: System.setSecurityManager (new SecurityManager ()); Теперь вы можете контролировать разрешения, и ваше приложение по умолчанию имеет все разрешения. Было бы лучше, если бы вы ограничивали разрешения вашего приложения только тем, что ему требуется. Затем создайте AccessControlContext, который ограничивает разрешения до минимума, и вызовите AccessController.doPrivileged () и передайте AccessControlContext, затем выполните сценарий JEXL внутри doPrivileged (). Вот небольшая программа, которая демонстрирует это. Сценарий JEXL вызывает System.exit (1), и если он не обернут в doPrivileged (), он успешно завершит JVM.
System.out.println("java.security.policy=" + System.getProperty("java.security.policy"));
System.setSecurityManager(new SecurityManager());
try {
Permissions perms = new Permissions();
perms.add(new RuntimePermission("accessDeclaredMembers"));
ProtectionDomain domain = new ProtectionDomain(new CodeSource( null, (Certificate[]) null ), perms );
AccessControlContext restrictedAccessControlContext = new AccessControlContext(new ProtectionDomain[] { domain } );
JexlEngine jexlEngine = new JexlEngine();
final Script finalExpression = jexlEngine.createScript(
"i = 0; intClazz = i.class; "
+ "clazz = intClazz.forName(\"java.lang.System\"); "
+ "m = clazz.methods; m[0].invoke(null, 1); c");
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
@Override
public Object run() throws Exception {
return finalExpression.execute(new MapContext());
}
}, restrictedAccessControlContext);
}
catch (Throwable ex) {
ex.printStackTrace();
}
Уловка с этим прерывает сценарий до его завершения. Один из способов сделать это - создать собственный класс JexlArithmetic. Затем переопределите каждый метод в этом классе и перед вызовом реального метода в суперклассе проверьте, должен ли скрипт прекратить выполнение. Я использую ExecutorService для создания потоков. Когда вызывается Future.get (), укажите время ожидания. Если выбрасывается TimeoutException, вызовите Future.cancel (), который прерывает поток, выполняющий сценарий. Внутри каждого переопределенного метода в новом классе JexlArithmetic проверяйте Thread.interrupted () и, если это правда, генерируйте java.util.concurrent.CancellationException.
Есть ли лучшее место для размещения кода, который будет выполняться регулярно при выполнении скрипта, чтобы его можно было прервать?
Вот выдержка из класса MyJexlArithmetic. Вы должны добавить все остальные методы:
public class MyJexlArithmetic extends JexlArithmetic {
public MyJexlArithmetic(boolean lenient) {
super(lenient);
}
private void checkInterrupted() {
if (Thread.interrupted()) throw new CancellationException();
}
@Override
public boolean equals(Object left, Object right) {
checkInterrupted();
return super.equals(left, right); //To change body of generated methods, choose Tools | Templates.
}
@Override
public Object add(Object left, Object right) {
checkInterrupted();
return super.add(left, right);
}
}
Вот как я запускаю JexlEngine:
Log jexlLog = LogFactory.getLog("JEXL");
Map <String, Object> functions = new HashMap();
jexlEngine = new JexlEngine(new MyUberspectImpl(jexlLog), new MyJexlArithmetic(false), functions, jexlLog);