Ответ 1
ОБНОВЛЕНИЕ: Все это было сделано с использованием JEXL 2.0.1. Возможно, вам придется адаптировать это, чтобы заставить его работать с более новыми версиями.
Вот мой подход к решению каждого из этих случаев. Я создал модульные тесты для проверки каждого из этих случаев, и я убедился, что они работают.
-
JEXL делает это довольно легко. Просто создайте собственный ClassLoader. Переопределите два метода loadClass(). На JexlEngine вызовите setClassLoader().
-
Опять же, JEXL делает это довольно легко. Вы должны заблокировать как '.class', так и '.getClass()'. Создайте свой собственный класс Uberspect, который расширяет UberspectImpl. Переопределите getPropertyGet, если идентификатор равен "классу" и возвращает ноль. Переопределите getMethod, если метод равен "getClass" и возвращает ноль. При создании 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); } }
-
Вы делаете это с помощью механизма Java AccessController. Я сделаю это быстро. Запустите 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);