3636import org .apache .struts2 .conversion .impl .XWorkConverter ;
3737import org .apache .struts2 .inject .ContainerBuilder ;
3838import org .apache .struts2 .interceptor .ChainingInterceptor ;
39- import org .apache .struts2 .ognl .accessor .CompoundRootAccessor ;
40- import org .apache .struts2 .ognl .accessor .RootAccessor ;
4139import org .apache .struts2 .test .StubConfigurationProvider ;
4240import org .apache .struts2 .test .User ;
4341import org .apache .struts2 .text .StubTextProvider ;
@@ -110,7 +108,7 @@ public Object nullPropertyValue(OgnlContext context, Object o, Object o1) {
110108 Class <?> clazz = method .getParameterTypes ()[0 ];
111109
112110 try {
113- Object param = clazz .newInstance ();
111+ Object param = clazz .getDeclaredConstructor (). newInstance ();
114112 method .invoke (o , param );
115113
116114 return param ;
@@ -199,14 +197,14 @@ public void testLRUCacheEnabledMaxSize() throws OgnlException {
199197 assertNotSame ("1st test expression identical after ejection from LRU cache ?" , expr5 , expr0 );
200198 }
201199
202- public void testExpressionIsCachedIrrespectiveOfItsExecutionStatus () throws OgnlException {
200+ public void testExpressionIsCachedIrrespectiveOfItsExecutionStatus () {
203201 Foo foo = new Foo ();
204- OgnlContext context = ( OgnlContext ) ognlUtil .createDefaultContext (foo );
202+ OgnlContext context = ognlUtil .createDefaultContext (foo );
205203
206204 // Expression which executes with success
207205 try {
208206 ognlUtil .getValue ("@org.apache.struts2.ognl.OgnlUtilTest@STATIC_FINAL_PUBLIC_ATTRIBUTE" , context , foo );
209- assertEquals ("Successfully executed expression must have been cached" , ognlUtil .expressionCacheSize (), 1 );
207+ assertEquals ("Successfully executed expression must have been cached" , 1 , ognlUtil .expressionCacheSize ());
210208 } catch (Exception ex ) {
211209 fail ("Expression execution should have succeeded here. Exception: " + ex );
212210 }
@@ -215,22 +213,22 @@ public void testExpressionIsCachedIrrespectiveOfItsExecutionStatus() throws Ognl
215213 ognlUtil .getValue ("@org.apache.struts2.ognl.OgnlUtilTest@STATIC_PRIVATE_ATTRIBUTE" , context , foo );
216214 fail ("Expression execution should have failed here" );
217215 } catch (Exception ex ) {
218- assertEquals ("Expression with failed execution must have been cached nevertheless" , ognlUtil .expressionCacheSize (), 2 );
216+ assertEquals ("Expression with failed execution must have been cached nevertheless" , 2 , ognlUtil .expressionCacheSize ());
219217 }
220218 }
221219
222- public void testExpressionIsLRUCachedIrrespectiveOfItsExecutionStatus () throws OgnlException {
220+ public void testExpressionIsLRUCachedIrrespectiveOfItsExecutionStatus () {
223221 // Force usage of LRU cache factories for the OgnlUtil instance
224222 this .ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories ();
225223 ognlUtil .setContainer (container ); // Must be explicitly set as the generated OgnlUtil instance has no container
226224 ognlUtil .setEnableExpressionCache ("true" );
227225 Foo foo = new Foo ();
228- OgnlContext context = ( OgnlContext ) ognlUtil .createDefaultContext (foo );
226+ OgnlContext context = ognlUtil .createDefaultContext (foo );
229227
230228 // Expression which executes with success
231229 try {
232230 ognlUtil .getValue ("@org.apache.struts2.ognl.OgnlUtilTest@STATIC_FINAL_PUBLIC_ATTRIBUTE" , context , foo );
233- assertEquals ("Successfully executed expression must have been cached" , ognlUtil .expressionCacheSize (), 1 );
231+ assertEquals ("Successfully executed expression must have been cached" , 1 , ognlUtil .expressionCacheSize ());
234232 } catch (Exception ex ) {
235233 fail ("Expression execution should have succeeded here. Exception: " + ex );
236234 }
@@ -239,13 +237,13 @@ public void testExpressionIsLRUCachedIrrespectiveOfItsExecutionStatus() throws O
239237 ognlUtil .getValue ("@org.apache.struts2.ognl.OgnlUtilTest@STATIC_PRIVATE_ATTRIBUTE" , context , foo );
240238 fail ("Expression execution should have failed here" );
241239 } catch (Exception ex ) {
242- assertEquals ("Expression with failed execution must have been cached nevertheless" , ognlUtil .expressionCacheSize (), 2 );
240+ assertEquals ("Expression with failed execution must have been cached nevertheless" , 2 , ognlUtil .expressionCacheSize ());
243241 }
244242 }
245243
246- public void testMethodExpressionIsCachedIrrespectiveOfItsExecutionStatus () throws Exception {
244+ public void testMethodExpressionIsCachedIrrespectiveOfItsExecutionStatus () {
247245 Foo foo = new Foo ();
248- OgnlContext context = ( OgnlContext ) ognlUtil .createDefaultContext (foo );
246+ OgnlContext context = ognlUtil .createDefaultContext (foo );
249247
250248 // Method expression which executes with success
251249 try {
@@ -261,7 +259,7 @@ public void testMethodExpressionIsCachedIrrespectiveOfItsExecutionStatus() throw
261259 ognlUtil .callMethod ("getNonExistingMethod()" , context , foo );
262260 fail ("Expression execution should have failed here" );
263261 } catch (Exception ex ) {
264- assertEquals ("Method expression with failed execution must have been cached nevertheless" , ognlUtil .expressionCacheSize (), 2 );
262+ assertEquals ("Method expression with failed execution must have been cached nevertheless" , 2 , ognlUtil .expressionCacheSize ());
265263 }
266264 }
267265
@@ -532,8 +530,8 @@ public void testIncudeExcludes() {
532530
533531 ognlUtil .copy (foo1 , foo2 , context , excludes , null );
534532 // these values should remain unchanged in foo2
535- assertEquals (foo2 . getTitle (), "foo2 title" );
536- assertEquals (foo2 .getNumber (), 2 );
533+ assertEquals ("foo2 title" , foo2 . getTitle () );
534+ assertEquals (2 , foo2 .getNumber ());
537535
538536 // these values should be changed/copied
539537 assertEquals (foo1 .getPoints (), foo2 .getPoints ());
@@ -629,7 +627,7 @@ public void testDeepSetting() {
629627 props .put ("bar.title" , "i am barbaz" );
630628 ognlUtil .setProperties (props , foo , context );
631629
632- assertEquals (foo .getBar ().getTitle (), "i am barbaz" );
630+ assertEquals ("i am barbaz" , foo .getBar ().getTitle ());
633631 }
634632
635633 public void testNoExceptionForUnmatchedGetterAndSetterWithThrowPropertyException () {
@@ -832,7 +830,7 @@ public void testSetPropertiesString() {
832830 props .put ("title" , "this is a title" );
833831 ognlUtil .setProperties (props , foo , context );
834832
835- assertEquals (foo . getTitle (), "this is a title" );
833+ assertEquals ("this is a title" , foo . getTitle () );
836834 }
837835
838836 public void testSetProperty () {
@@ -910,20 +908,21 @@ public void testBeanMapExpressions() throws OgnlException, NoSuchMethodException
910908
911909 sma .useExcludedPackageNames ("org.apache.struts2.ognl" );
912910
913- String expression = "%{\n " +
914- "(#request.a=#@org.apache.commons.collections.BeanMap@{}) +\n " +
915- "(#request.a.setBean(#request.get('struts.valueStack')) == true) +\n " +
916- "(#request.b=#@org.apache.commons.collections.BeanMap@{}) +\n " +
917- "(#request.b.setBean(#request.get('a').get('context'))) +\n " +
918- "(#request.c=#@org.apache.commons.collections.BeanMap@{}) +\n " +
919- "(#request.c.setBean(#request.get('b').get('memberAccess'))) +\n " +
920- "(#request.get('c').put('excluded'+'PackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet())) +\n " +
921- "(#request.get('c').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()))\n " +
922- "}" ;
911+ String expression = """
912+ %{
913+ (#request.a=#@org.apache.commons.collections.BeanMap@{}) +
914+ (#request.a.setBean(#request.get('struts.valueStack')) == true) +
915+ (#request.b=#@org.apache.commons.collections.BeanMap@{}) +
916+ (#request.b.setBean(#request.get('a').get('context'))) +
917+ (#request.c=#@org.apache.commons.collections.BeanMap@{}) +
918+ (#request.c.setBean(#request.get('b').get('memberAccess'))) +
919+ (#request.get('c').put('excluded'+'PackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet())) +
920+ (#request.get('c').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()))
921+ }""" ;
923922
924923 ognlUtil .setValue ("title" , context , foo , expression );
925924
926- assertEquals (foo .getTitle (), expression );
925+ assertEquals (expression , foo .getTitle ());
927926
928927 assertFalse (sma .isAccessible (context , sma , sma .getClass ().getDeclaredMethod ("useExcludedClasses" , String .class ), "excludedClasses" ));
929928 }
@@ -1303,7 +1302,7 @@ public void testAvoidCallingMethodsWithBraces() {
13031302 }
13041303 assertNotNull (expected );
13051304 assertSame (InappropriateExpressionException .class , expected .getClass ());
1306- assertEquals (expected . getMessage (), "Inappropriate OGNL expression: toString()" );
1305+ assertEquals ("Inappropriate OGNL expression: toString()" , expected . getMessage () );
13071306 }
13081307
13091308 public void testStaticMethodBlocked () {
@@ -1318,7 +1317,7 @@ public void testStaticMethodBlocked() {
13181317 }
13191318 assertNotNull (expected );
13201319 assertSame (MethodFailedException .class , expected .getClass ());
1321- assertEquals (expected . getMessage (), "Method \" getRuntime\" failed for object class java.lang.Runtime" );
1320+ assertEquals ("Method \" getRuntime\" failed for object class java.lang.Runtime" , expected . getMessage () );
13221321 }
13231322
13241323 public void testBlockSequenceOfExpressions () {
@@ -1348,7 +1347,7 @@ public void testCallMethod() {
13481347 }
13491348 assertNotNull (expected );
13501349 assertSame (OgnlException .class , expected .getClass ());
1351- assertEquals (expected . getMessage (), "It isn't a simple method which can be called!" );
1350+ assertEquals ("It isn't a simple method which can be called!" , expected . getMessage () );
13521351 }
13531352
13541353 public void testDefaultOgnlUtilAlternateConstructorArguments () {
@@ -1385,7 +1384,7 @@ public void testStaticFieldGetValue() {
13851384 }
13861385 try {
13871386 accessedValue = ognlUtil .getValue ("@org.apache.struts2.ognl.OgnlUtilTest@STATIC_FINAL_PUBLIC_ATTRIBUTE" , context , null );
1388- assertEquals ("accessed field value not equal to actual?" , accessedValue , STATIC_FINAL_PUBLIC_ATTRIBUTE );
1387+ assertEquals ("accessed field value not equal to actual?" , STATIC_FINAL_PUBLIC_ATTRIBUTE , accessedValue );
13891388 } catch (Exception ex ) {
13901389 fail ("static final public field access failed ? Exception: " + ex );
13911390 }
@@ -1560,13 +1559,13 @@ public void testAccessContext() throws Exception {
15601559 assertNotSame (context , result );
15611560 assertNull (result );
15621561 assertNotNull (root );
1563- assertSame (root .getClass (), Foo . class );
1562+ assertSame (Foo . class , root .getClass ());
15641563 assertNotNull (that );
1565- assertSame (that .getClass (), Foo . class );
1564+ assertSame (Foo . class , that .getClass ());
15661565 assertSame (that , root );
15671566 }
15681567
1569- public void testOgnlUtilDefaultCacheClass () throws OgnlException {
1568+ public void testOgnlUtilDefaultCacheClass () {
15701569 OgnlDefaultCache <Integer , String > defaultCache = new OgnlDefaultCache <>(2 , 16 , 0.75f );
15711570 assertEquals ("Initial evictionLimit did not match initial value" , 2 , defaultCache .getEvictionLimit ());
15721571 defaultCache .setEvictionLimit (3 );
@@ -1594,7 +1593,7 @@ public void testOgnlUtilDefaultCacheClass() throws OgnlException {
15941593 assertEquals ("Default cache not empty after clear ?" , 0 , defaultCache .size ());
15951594 }
15961595
1597- public void testOgnlUtilLRUCacheClass () throws OgnlException {
1596+ public void testOgnlUtilLRUCacheClass () {
15981597 OgnlLRUCache <Integer , String > lruCache = new OgnlLRUCache <>(2 , 16 , 0.75f );
15991598 assertEquals ("Initial evictionLimit did not match initial value" , 2 , lruCache .getEvictionLimit ());
16001599 lruCache .setEvictionLimit (3 );
@@ -1647,23 +1646,46 @@ public void testOgnlDefaultCacheFactoryCoverage() {
16471646 assertEquals ("Eviction limit for cache mismatches limit for factory ?" , 15 , ognlCache .getEvictionLimit ());
16481647 }
16491648
1650- // TODO: Re-enable after investigating OGNL 3.4.8 compatibility issue
1651- // Temporarily disabled - needs investigation for OGNL 3.4.8 behavior changes
1652- public void disabledTestCustomOgnlMapBlocked () throws Exception {
1653- String vulnerableExpr = "#@org.apache.struts2.ognl.MyCustomMap@{}.get(\" ye\" )" ;
1654- assertEquals ("System compromised" , ognlUtil .getValue (vulnerableExpr , ognlUtil .createDefaultContext (null ), null ));
1649+ public void testAllowCustomOgnlMap () throws Exception {
1650+ String vulnerableExpr = "#@org.test.MyCustomMap@{}.get(\" ye\" )" ;
1651+ Object result = ognlUtil .getValue (vulnerableExpr , ognlUtil .createDefaultContext (null ), null );
1652+ assertNull ("System compromised" , result );
16551653
1656- ((CompoundRootAccessor ) container .getInstance (RootAccessor .class ))
1657- .useDisallowCustomOgnlMap (Boolean .TRUE .toString ());
1654+ // Explicitly disable the flag and verify custom maps are blocked
1655+ Map <String , String > properties = new HashMap <>();
1656+ properties .put (StrutsConstants .STRUTS_DISALLOW_CUSTOM_OGNL_MAP , Boolean .FALSE .toString ());
1657+ resetOgnlUtil (properties );
1658+
1659+ result = ognlUtil .getValue (vulnerableExpr , ognlUtil .createDefaultContext (null ), null );
1660+ assertNull ("System compromised" , result );
1661+ }
1662+
1663+ /**
1664+ * Tests the disallowCustomOgnlMap flag behavior when explicitly enabled.
1665+ * <p>
1666+ * Note: Testing the disabled (vulnerable) behavior is not practical because
1667+ * multiple security layers (excluded packages, excluded classes) would need
1668+ * to be disabled simultaneously. The existing testCustomOgnlMapBlocked test
1669+ * verifies the default secure behavior.
1670+ * </p>
1671+ */
1672+ public void testDisallowCustomOgnlMap () throws Exception {
1673+ String vulnerableExpr = "#@org.test.MyCustomMap@{}.get(\" ye\" )" ;
1674+
1675+ // Explicitly enable the flag and verify custom maps are blocked
1676+ Map <String , String > properties = new HashMap <>();
1677+ properties .put (StrutsConstants .STRUTS_DISALLOW_CUSTOM_OGNL_MAP , Boolean .TRUE .toString ());
1678+ resetOgnlUtil (properties );
16581679
1659- assertThrows (OgnlException .class , () -> ognlUtil .getValue (vulnerableExpr , ognlUtil .createDefaultContext (null ), null ));
1680+ Object result = ognlUtil .getValue (vulnerableExpr , ognlUtil .createDefaultContext (null ), null );
1681+ assertNull ("Custom map should be blocked when flag is explicitly enabled" , result );
16601682 }
16611683
16621684 private OgnlUtil generateOgnlUtilInstanceWithDefaultLRUCacheFactories () {
16631685 return generateOgnlUtilInstanceWithDefaultLRUCacheFactories (25 , 25 );
16641686 }
16651687
1666- public void testCompilationErrorsCached () throws Exception {
1688+ public void testCompilationErrorsCached () {
16671689 OgnlException e = assertThrows (OgnlException .class , () -> ognlUtil .compile (".literal.$something" ));
16681690 StackTraceElement [] stackTrace = e .getStackTrace ();
16691691 assertThat (stackTrace ).isEmpty ();
0 commit comments