51
51
import static java .util .Collections .emptySet ;
52
52
import static java .util .Collections .singletonList ;
53
53
import static java .util .Collections .unmodifiableSet ;
54
+ import static org .apache .struts2 .StrutsConstants .STRUTS_ALLOWLIST_CLASSES ;
55
+ import static org .apache .struts2 .StrutsConstants .STRUTS_ALLOWLIST_PACKAGE_NAMES ;
54
56
55
57
/**
56
58
* Allows access decisions to be made on the basis of whether a member is static or not.
@@ -77,16 +79,28 @@ public class SecurityMemberAccess implements MemberAccess {
77
79
78
80
private final ProviderAllowlist providerAllowlist ;
79
81
private final ThreadAllowlist threadAllowlist ;
82
+
80
83
private boolean allowStaticFieldAccess = true ;
84
+
81
85
private Set <Pattern > excludeProperties = emptySet ();
82
86
private Set <Pattern > acceptProperties = emptySet ();
87
+
83
88
private Set <String > excludedClasses = unmodifiableSet (new HashSet <>(singletonList (Object .class .getName ())));
84
89
private Set <Pattern > excludedPackageNamePatterns = emptySet ();
85
90
private Set <String > excludedPackageNames = emptySet ();
86
91
private Set <String > excludedPackageExemptClasses = emptySet ();
92
+
93
+ private volatile boolean isDevModeInit ;
94
+ private boolean isDevMode ;
95
+ private Set <String > devModeExcludedClasses = unmodifiableSet (new HashSet <>(singletonList (Object .class .getName ())));
96
+ private Set <Pattern > devModeExcludedPackageNamePatterns = emptySet ();
97
+ private Set <String > devModeExcludedPackageNames = emptySet ();
98
+ private Set <String > devModeExcludedPackageExemptClasses = emptySet ();
99
+
87
100
private boolean enforceAllowlistEnabled = false ;
88
101
private Set <Class <?>> allowlistClasses = emptySet ();
89
102
private Set <String > allowlistPackageNames = emptySet ();
103
+
90
104
private boolean disallowProxyObjectAccess = false ;
91
105
private boolean disallowProxyMemberAccess = false ;
92
106
private boolean disallowDefaultPackageAccess = false ;
@@ -209,25 +223,71 @@ public boolean isAccessible(Map context, Object target, Member member, String pr
209
223
* @return {@code true} if member access is allowed
210
224
*/
211
225
protected boolean checkAllowlist (Object target , Member member ) {
212
- Class <?> memberClass = member .getDeclaringClass ();
213
226
if (!enforceAllowlistEnabled ) {
227
+ logAllowlistDisabled ();
214
228
return true ;
215
229
}
230
+
231
+ if (!disallowProxyObjectAccess && target != null && ProxyUtil .isProxy (target )) {
232
+ // If `disallowProxyObjectAccess` is not set, allow resolving Hibernate entities to their underlying
233
+ // classes/members. This allows the allowlist capability to continue working and offer some level of
234
+ // protection in applications where the developer has accepted the risk of allowing OGNL access to Hibernate
235
+ // entities. This is preferred to having to disable the allowlist capability entirely.
236
+ Object newTarget = ProxyUtil .getHibernateProxyTarget (target );
237
+ if (newTarget != target ) {
238
+ logAllowlistHibernateEntity (target , newTarget );
239
+ target = newTarget ;
240
+ member = ProxyUtil .resolveTargetMember (member , newTarget );
241
+ }
242
+ }
243
+
244
+ Class <?> memberClass = member .getDeclaringClass ();
216
245
if (!isClassAllowlisted (memberClass )) {
217
- LOG .warn (format ("Declaring class [{0}] of member type [{1}] is not allowlisted!" , memberClass , member ));
246
+ LOG .warn ("Declaring class [{}] of member type [{}] is not allowlisted! Add to '{}' or '{}' configuration." ,
247
+ memberClass , member , STRUTS_ALLOWLIST_CLASSES , STRUTS_ALLOWLIST_PACKAGE_NAMES );
218
248
return false ;
219
249
}
220
250
if (target == null || target .getClass () == memberClass ) {
221
251
return true ;
222
252
}
223
253
Class <?> targetClass = target .getClass ();
224
254
if (!isClassAllowlisted (targetClass )) {
225
- LOG .warn (format ("Target class [{0}] of target [{1}] is not allowlisted!" , targetClass , target ));
255
+ LOG .warn ("Target class [{}] of target [{}] is not allowlisted! Add to '{}' or '{}' configuration." ,
256
+ targetClass , target , STRUTS_ALLOWLIST_CLASSES , STRUTS_ALLOWLIST_PACKAGE_NAMES );
226
257
return false ;
227
258
}
228
259
return true ;
229
260
}
230
261
262
+ private void logAllowlistDisabled () {
263
+ if (!isDevMode && !LOG .isDebugEnabled ()) {
264
+ return ;
265
+ }
266
+ String msg = "OGNL allowlist is disabled!" +
267
+ " We strongly recommend keeping it enabled to protect against critical vulnerabilities." +
268
+ " Set the configuration `{0}=true` to enable it." ;
269
+ Object [] args = {StrutsConstants .STRUTS_ALLOWLIST_ENABLE };
270
+ if (isDevMode ) {
271
+ LOG .warn (msg , args );
272
+ } else {
273
+ LOG .debug (msg , args );
274
+ }
275
+ }
276
+
277
+ private void logAllowlistHibernateEntity (Object original , Object resolved ) {
278
+ if (!isDevMode && !LOG .isDebugEnabled ()) {
279
+ return ;
280
+ }
281
+ String msg = "Hibernate entity [{}] resolved to [{}] for purpose of OGNL allowlisting." +
282
+ " We don't recommend executing OGNL expressions against Hibernate entities, you may disallow this behaviour using the configuration `{}=true`." ;
283
+ Object [] args = {original , resolved , StrutsConstants .STRUTS_DISALLOW_PROXY_OBJECT_ACCESS };
284
+ if (isDevMode ) {
285
+ LOG .warn (msg , args );
286
+ } else {
287
+ LOG .debug (msg , args );
288
+ }
289
+ }
290
+
231
291
protected boolean isClassAllowlisted (Class <?> clazz ) {
232
292
return allowlistClasses .contains (clazz )
233
293
|| ALLOWLIST_REQUIRED_CLASSES .contains (clazz )
@@ -241,6 +301,7 @@ protected boolean isClassAllowlisted(Class<?> clazz) {
241
301
* @return {@code true} if member access is allowed
242
302
*/
243
303
protected boolean checkExclusionList (Object target , Member member ) {
304
+ useDevModeConfiguration ();
244
305
Class <?> memberClass = member .getDeclaringClass ();
245
306
if (isClassExcluded (memberClass )) {
246
307
LOG .warn ("Declaring class of member type [{}] is excluded!" , memberClass );
@@ -436,12 +497,12 @@ public void useEnforceAllowlistEnabled(String enforceAllowlistEnabled) {
436
497
this .enforceAllowlistEnabled = BooleanUtils .toBoolean (enforceAllowlistEnabled );
437
498
}
438
499
439
- @ Inject (value = StrutsConstants . STRUTS_ALLOWLIST_CLASSES , required = false )
500
+ @ Inject (value = STRUTS_ALLOWLIST_CLASSES , required = false )
440
501
public void useAllowlistClasses (String commaDelimitedClasses ) {
441
502
this .allowlistClasses = toClassObjectsSet (commaDelimitedClasses );
442
503
}
443
504
444
- @ Inject (value = StrutsConstants . STRUTS_ALLOWLIST_PACKAGE_NAMES , required = false )
505
+ @ Inject (value = STRUTS_ALLOWLIST_PACKAGE_NAMES , required = false )
445
506
public void useAllowlistPackageNames (String commaDelimitedPackageNames ) {
446
507
this .allowlistPackageNames = toPackageNamesSet (commaDelimitedPackageNames );
447
508
}
@@ -460,4 +521,41 @@ public void useDisallowProxyMemberAccess(String disallowProxyMemberAccess) {
460
521
public void useDisallowDefaultPackageAccess (String disallowDefaultPackageAccess ) {
461
522
this .disallowDefaultPackageAccess = BooleanUtils .toBoolean (disallowDefaultPackageAccess );
462
523
}
524
+
525
+ @ Inject (StrutsConstants .STRUTS_DEVMODE )
526
+ protected void useDevMode (String devMode ) {
527
+ this .isDevMode = BooleanUtils .toBoolean (devMode );
528
+ }
529
+
530
+ @ Inject (value = StrutsConstants .STRUTS_DEV_MODE_EXCLUDED_CLASSES , required = false )
531
+ public void useDevModeExcludedClasses (String commaDelimitedClasses ) {
532
+ this .devModeExcludedClasses = toNewClassesSet (devModeExcludedClasses , commaDelimitedClasses );
533
+ }
534
+
535
+ @ Inject (value = StrutsConstants .STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAME_PATTERNS , required = false )
536
+ public void useDevModeExcludedPackageNamePatterns (String commaDelimitedPackagePatterns ) {
537
+ this .devModeExcludedPackageNamePatterns = toNewPatternsSet (devModeExcludedPackageNamePatterns , commaDelimitedPackagePatterns );
538
+ }
539
+
540
+ @ Inject (value = StrutsConstants .STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAMES , required = false )
541
+ public void useDevModeExcludedPackageNames (String commaDelimitedPackageNames ) {
542
+ this .devModeExcludedPackageNames = toNewPackageNamesSet (devModeExcludedPackageNames , commaDelimitedPackageNames );
543
+ }
544
+
545
+ @ Inject (value = StrutsConstants .STRUTS_DEV_MODE_EXCLUDED_PACKAGE_EXEMPT_CLASSES , required = false )
546
+ public void useDevModeExcludedPackageExemptClasses (String commaDelimitedClasses ) {
547
+ this .devModeExcludedPackageExemptClasses = toClassesSet (commaDelimitedClasses );
548
+ }
549
+
550
+ private void useDevModeConfiguration () {
551
+ if (!isDevMode || isDevModeInit ) {
552
+ return ;
553
+ }
554
+ isDevModeInit = true ;
555
+ LOG .warn ("Working in devMode, using devMode excluded classes and packages!" );
556
+ excludedClasses = devModeExcludedClasses ;
557
+ excludedPackageNamePatterns = devModeExcludedPackageNamePatterns ;
558
+ excludedPackageNames = devModeExcludedPackageNames ;
559
+ excludedPackageExemptClasses = devModeExcludedPackageExemptClasses ;
560
+ }
463
561
}
0 commit comments