From 0b36e0c977b6ebba89e8c08f5cbdb1744b76a28e Mon Sep 17 00:00:00 2001 From: tatsu-no-otoshigo Date: Sun, 19 May 2024 11:40:28 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=BBJEXL=E3=82=92v2.1=E2=87=92v3.4?= =?UTF-8?q?=E3=81=B8=E3=82=A2=E3=83=83=E3=83=97=E3=83=87=E3=83=BC=E3=83=88?= =?UTF-8?q?=20=E3=83=BB=E3=82=AB=E3=82=B9=E3=82=BF=E3=83=A0=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E3=81=AE=E5=AE=9A=E7=BE=A9=E5=80=8B=E6=89=80=E3=82=92?= =?UTF-8?q?=E5=BC=8F=E8=A8=80=E8=AA=9E=E3=81=AE=E5=AE=9F=E8=A3=85=E5=80=8B?= =?UTF-8?q?=E6=89=80=E3=81=AB=E9=9B=86=E7=B4=84=E3=80=82=20=E3=83=BB?= =?UTF-8?q?=E5=BC=8F=E8=A8=80=E8=AA=9E=E3=81=AE=E6=8E=A5=E9=A0=AD=E8=AA=9E?= =?UTF-8?q?=E3=82=92x:=E2=87=92f:=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 22 +++- .../gh/mygreen/xlsmapper/Configuration.java | 18 +-- .../ExpressionLanguageJEXLImpl.java | 110 +++++++++++++++--- .../localization/MessageInterpolator.java | 30 +++-- .../com/gh/mygreen/xlsmapper/util/Utils.java | 20 +++- .../ExpressionLanguageJEXLImplTest.java | 27 +++-- .../fieldprocessor/AnnoFormulaTest.java | 2 +- 7 files changed, 164 insertions(+), 65 deletions(-) diff --git a/pom.xml b/pom.xml index 61d749858..18eb6073c 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ XlsMapper is Java Library for mapping Excel sheets to POJO. 1.8 5.1.0 - 1.7.32 + 3.2.11.RELEASE com.gh.mygreen.xlsmapper.* @@ -447,7 +447,7 @@ $(document).ready(function() { org.slf4j slf4j-api - ${slf4j.version} + 1.7.32 ch.qos.logback @@ -465,8 +465,18 @@ $(document).ready(function() { org.apache.commons - commons-jexl - 2.1.1 + commons-jexl3 + 3.4.0 + + + + commons-logging + commons-logging + + @@ -514,13 +524,13 @@ $(document).ready(function() { org.springframework spring-context - 3.2.11.RELEASE + ${spring.version} provided org.springframework spring-test - 3.2.11.RELEASE + ${spring.version} test diff --git a/src/main/java/com/gh/mygreen/xlsmapper/Configuration.java b/src/main/java/com/gh/mygreen/xlsmapper/Configuration.java index fd50560e1..6771bc352 100644 --- a/src/main/java/com/gh/mygreen/xlsmapper/Configuration.java +++ b/src/main/java/com/gh/mygreen/xlsmapper/Configuration.java @@ -1,12 +1,9 @@ package com.gh.mygreen.xlsmapper; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; import com.gh.mygreen.xlsmapper.annotation.XlsSheet; import com.gh.mygreen.xlsmapper.cellconverter.CellConverterRegistry; -import com.gh.mygreen.xlsmapper.expression.CustomFunctions; import com.gh.mygreen.xlsmapper.expression.ExpressionLanguageJEXLImpl; import com.gh.mygreen.xlsmapper.fieldprocessor.FieldProcessorRegistry; import com.gh.mygreen.xlsmapper.localization.MessageInterpolator; @@ -69,25 +66,14 @@ public class Configuration { private SheetFinder sheetFinder = new SheetFinder(); /** 数式をフォーマットするクラス */ - private MessageInterpolator formulaFormatter = new MessageInterpolator(); + private MessageInterpolator formulaFormatter = new MessageInterpolator(new ExpressionLanguageJEXLImpl()); /** セルコメントを操作するクラス */ private CellCommentOperator commentOperator = new CellCommentOperator(); /** Beanに対するアノテーションのマッピング情報 */ private AnnotationMappingInfo annotationMapping = null; - - public Configuration() { - - // 数式をフォーマットする際のEL関数を登録する。 - ExpressionLanguageJEXLImpl formulaEL = new ExpressionLanguageJEXLImpl(); - Map funcs = new HashMap<>(); - funcs.put("x", CustomFunctions.class); - formulaEL.getJexlEngine().setFunctions(funcs); - - formulaFormatter.setExpressionLanguage(formulaEL); - } - + /** * 指定したクラスタイプのインスタンスを作成する * @param clazz diff --git a/src/main/java/com/gh/mygreen/xlsmapper/expression/ExpressionLanguageJEXLImpl.java b/src/main/java/com/gh/mygreen/xlsmapper/expression/ExpressionLanguageJEXLImpl.java index a34c8655f..391457579 100644 --- a/src/main/java/com/gh/mygreen/xlsmapper/expression/ExpressionLanguageJEXLImpl.java +++ b/src/main/java/com/gh/mygreen/xlsmapper/expression/ExpressionLanguageJEXLImpl.java @@ -1,19 +1,31 @@ package com.gh.mygreen.xlsmapper.expression; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; -import org.apache.commons.jexl2.Expression; -import org.apache.commons.jexl2.JexlEngine; -import org.apache.commons.jexl2.MapContext; +import org.apache.commons.jexl3.JexlBuilder; +import org.apache.commons.jexl3.JexlEngine; +import org.apache.commons.jexl3.JexlExpression; +import org.apache.commons.jexl3.MapContext; +import org.apache.commons.jexl3.introspection.JexlPermissions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gh.mygreen.xlsmapper.util.ArgUtils; +import com.gh.mygreen.xlsmapper.util.Utils; /** * 式言語「JEXL」の実装。 - *

利用する際には、JEXL2.1のライブラリが必要です。 + *

利用する際には、JEXL v3.3以上のライブラリが必要です。 + *

JEXL v3.3から、ELインジェクション対策として、JexlPermissionsによるEL式中で参照/実行可能なクラスを制限されます。 + *

独自のCellConverter / FiledProcessosrなどを実装しているが場合は、システムプロパティ {@literal xlsmapper.jexlPermissions} で指定することができます。 + * 複数指定する場合はカンマ区切りで指定します。 + *

* + * @version 2.3 * @since 1.5 * @author T.TSUCHIE * @@ -22,16 +34,89 @@ public class ExpressionLanguageJEXLImpl implements ExpressionLanguage { private static final Logger logger = LoggerFactory.getLogger(ExpressionLanguageJEXLImpl.class); + /** + * 本ライブラリでJEXLからアクセス許可するパッケージ指定のパーミッション。 + */ + private static final String[] LIB_PERMISSIONS = + {"com.gh.mygreen.xlsmapper.*"}; + + /** + * 独自のJEXLのパーミッション。 + */ + private static final String[] USER_PERMISSIONS; + static { + String value = System.getProperty("xlsmapper.jexlPermissions"); + if(Utils.isNotEmpty(value)) { + USER_PERMISSIONS = Arrays.stream(value.split(",")) + .map(String::trim) + .filter(String::isEmpty) + .collect(Collectors.toList()) + .toArray(new String[0]); + + } else { + USER_PERMISSIONS = new String[] {}; + } + } + + /** + * JEXLのキャッシュサイズ。 + *

キャッシュする式の個数。 + */ + private static final int CACHE_SIZE = 256; + private final JexlEngine jexlEngine; - private final ObjectCache expressionCache = new ObjectCache<>(); + /** + * JEXLのパーミッションを指定するコンストラクタ。 + *

関数として{@link CustomFunctions}が登録されており、接頭語 {@literal x:}で呼び出し可能です。 + * + * @param userPermissions JEXLのパーミッション。 + * 詳細は、JexlPermissions を参照。 + */ + public ExpressionLanguageJEXLImpl(final String... userPermissions) { + this(Collections.emptyMap(), userPermissions); + + } - public ExpressionLanguageJEXLImpl() { - this(new JexlEngine()); - this.jexlEngine.setLenient(true); - this.jexlEngine.setSilent(true); + /** + * JEXLの独自のEL関数とパーミッションを指定するコンストラクタ。 + *

関数として{@link CustomFunctions}が登録されており、接頭語 {@literal f:}で呼び出し可能です。 + * + * @param userFunctions 独自のEL関数を指定します。keyは接頭語、valueはメソッドが定義されたクラス。 + * @param userPermissions JEXLのパーミッション。 + * 詳細は、JexlPermissions を参照。 + */ + public ExpressionLanguageJEXLImpl(final Map userFunctions, final String... userPermissions) { + + // EL式中で使用可能な関数の登録 + Map functions = new HashMap<>(); + functions.put("f", CustomFunctions.class); + + if (Utils.isNotEmpty(userFunctions)) { + functions.putAll(userFunctions); + } + + /* + * EL式で本ライブラリのクラス/メソッドのアクセスを許可する。 + * ・CustomFunctions以外にも、CellConverter / FieldProcessorでも参照するため。 + * ・JEXLv3からサーバーサイド・テンプレート・インジェクション、コマンドインジェクション対策のために、 + * 許可されたクラスしか参照できなくなったため、本ライブラリをEL式から参照可能に許可する。 + */ + String[] concateedUserPermission = Utils.concat(USER_PERMISSIONS, userPermissions); + JexlPermissions permissions = JexlPermissions.RESTRICTED + .compose(Utils.concat(LIB_PERMISSIONS, concateedUserPermission)); + + this.jexlEngine = new JexlBuilder() + .namespaces(functions) + .permissions(permissions) + .silent(true) + .strict(false) // JEXLv2相当の文法にする。 + .cache(CACHE_SIZE) + .create(); + } + /** * {@link JexlEngine}を指定するコンストラクタ。 * @param jexlEngine JEXLの処理エンジン。 @@ -52,12 +137,7 @@ public Object evaluate(final String expression, final Map values) { } try { - Expression expr = expressionCache.get(expression); - if (expr == null) { - expr = jexlEngine.createExpression(expression); - expressionCache.put(expression, expr); - } - + JexlExpression expr = jexlEngine.createExpression(expression); return expr.evaluate(new MapContext((Map) values)); } catch(Exception ex) { diff --git a/src/main/java/com/gh/mygreen/xlsmapper/localization/MessageInterpolator.java b/src/main/java/com/gh/mygreen/xlsmapper/localization/MessageInterpolator.java index 977d6c89b..846290214 100644 --- a/src/main/java/com/gh/mygreen/xlsmapper/localization/MessageInterpolator.java +++ b/src/main/java/com/gh/mygreen/xlsmapper/localization/MessageInterpolator.java @@ -1,16 +1,15 @@ package com.gh.mygreen.xlsmapper.localization; import java.util.Formatter; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.Map; +import java.util.Objects; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.gh.mygreen.xlsmapper.expression.CustomFunctions; import com.gh.mygreen.xlsmapper.expression.ExpressionEvaluationException; import com.gh.mygreen.xlsmapper.expression.ExpressionLanguage; import com.gh.mygreen.xlsmapper.expression.ExpressionLanguageJEXLImpl; @@ -39,16 +38,13 @@ public class MessageInterpolator { private ExpressionLanguage expressionLanguage; + /** + * デフォルトのコンストラクタ + *

式言語の処理実装として、JEXLの{@link ExpressionLanguageJEXLImpl} が設定されます。 + * + */ public MessageInterpolator() { - - // EL式中で使用可能な関数の登録 - ExpressionLanguageJEXLImpl el = new ExpressionLanguageJEXLImpl(); - - Map funcs = new HashMap<>(); - funcs.put("f", CustomFunctions.class); - el.getJexlEngine().setFunctions(funcs); - - setExpressionLanguage(el); + this.expressionLanguage = new ExpressionLanguageJEXLImpl(); } @@ -262,16 +258,16 @@ protected String evaluateExpression(final String expression, final Map new Formatter()); /* - * JEXLで存在しない変数名の場合、nullが帰ってくるため、null判定を行う。 + * 以下のケースの時、評価値はnullが返されるため、空文字に変換する。 + * ・JEXLで存在しない変数名のとき。 + * ・ELインジェクション対象の式のとき */ - Object eval = expressionLanguage.evaluate(expression, context); - String value = eval == null ? "" : eval.toString(); - + String evalValue = Objects.toString(expressionLanguage.evaluate(expression, context), ""); if(logger.isTraceEnabled()) { - logger.trace("evaluate expression language: expression='{}' ===> value='{}'", expression, value); + logger.trace("evaluate expression language: expression='{}' ===> value='{}'", expression, evalValue); } - return value; + return evalValue; } /** diff --git a/src/main/java/com/gh/mygreen/xlsmapper/util/Utils.java b/src/main/java/com/gh/mygreen/xlsmapper/util/Utils.java index 12431c160..c67ce67b6 100644 --- a/src/main/java/com/gh/mygreen/xlsmapper/util/Utils.java +++ b/src/main/java/com/gh/mygreen/xlsmapper/util/Utils.java @@ -10,6 +10,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.Queue; import java.util.Set; @@ -29,7 +30,7 @@ /** * ユーティリティクラス。 * - * @version 2.0 + * @version 2.3 * @author T.TSUCHIE * @author Naoki Takezoe * @author Mitsuyoshi Hasegawa @@ -308,6 +309,23 @@ public static boolean isEmpty(final Collection collection) { public static boolean isNotEmpty(final Collection collection) { return !isEmpty(collection); } + + /** + * Mapが空か判定する。 + * @param map + * @return nullまたはサイズが0のときにtrueを返す。 + */ + public static boolean isEmpty(final Map map) { + if(map == null || map.isEmpty()) { + return true; + } + + return false; + } + + public static boolean isNotEmpty(final Map map) { + return !isEmpty(map); + } /** * 配列がが空か判定する。 diff --git a/src/test/java/com/gh/mygreen/xlsmapper/expression/ExpressionLanguageJEXLImplTest.java b/src/test/java/com/gh/mygreen/xlsmapper/expression/ExpressionLanguageJEXLImplTest.java index 71c22dcf8..a60489460 100644 --- a/src/test/java/com/gh/mygreen/xlsmapper/expression/ExpressionLanguageJEXLImplTest.java +++ b/src/test/java/com/gh/mygreen/xlsmapper/expression/ExpressionLanguageJEXLImplTest.java @@ -1,11 +1,10 @@ package com.gh.mygreen.xlsmapper.expression; -import static org.junit.Assert.*; import static org.hamcrest.Matchers.*; -import static com.gh.mygreen.xlsmapper.TestUtils.*; - +import static org.junit.Assert.*; import java.sql.Timestamp; +import java.util.Collections; import java.util.Date; import java.util.Formatter; import java.util.HashMap; @@ -120,12 +119,7 @@ public void testEvalate_error_exp() { @Test public void testEvalate_function() { - // 関数の登録 - Map funcs = new HashMap<>(); - funcs.put("x", CustomFunctions.class); - el.getJexlEngine().setFunctions(funcs); - - String expression = "x:colToAlpha(columnNumber)"; + String expression = "f:colToAlpha(columnNumber)"; Map vars = new HashMap<>(); vars.put("columnNumber", 1); @@ -135,5 +129,20 @@ public void testEvalate_function() { } + /** + * ELインジェクション + */ + @Test + public void test_injection() { + + String expression = "''.getClass().forName('java.lang.Runtime').getRuntime().exec('notepad')"; + + Object eval = el.evaluate(expression, Collections.emptyMap()); + + // 評価に失敗しnullが返ってくる + assertThat(eval, nullValue()); + + } + } diff --git a/src/test/java/com/gh/mygreen/xlsmapper/fieldprocessor/AnnoFormulaTest.java b/src/test/java/com/gh/mygreen/xlsmapper/fieldprocessor/AnnoFormulaTest.java index cc4535ab7..da1460c0a 100644 --- a/src/test/java/com/gh/mygreen/xlsmapper/fieldprocessor/AnnoFormulaTest.java +++ b/src/test/java/com/gh/mygreen/xlsmapper/fieldprocessor/AnnoFormulaTest.java @@ -92,7 +92,7 @@ public void test_normal_formula() throws Exception { .field(createField("c1") .override(true) .annotation(createAnnotation(XlsFormula.class) - .attribute("value", "SUM(C2:${x:colToAlpha(columnNumber+2)}3)") + .attribute("value", "SUM(C2:${f:colToAlpha(columnNumber+2)}3)") .buildAnnotation()) .buildField()) .buildClass())