Skip to content

Commit 843b1c7

Browse files
committed
Improved: Prevent Freemarker interpolation in fields (OFBIZ-12594)
OFBIZ_12587 is a definitive solution to prevent any kind of Freemarker exploits. But it's hard to realise because OFBiz exposes objects, like attributes from the Servlet scopes. So in the meantime preventing Freemarker interpolation in fields is a pragmatic solution. This is an improvement but needs to be backported because it kinda affects security Conflicts handled by hand SeoContextFilter.java ControlFilter.java When I worked with Mathieu I did not measure how it will be hard sometimes to backport later :/
1 parent 56478e4 commit 843b1c7

4 files changed

Lines changed: 94 additions & 5 deletions

File tree

applications/product/src/main/java/org/apache/ofbiz/product/category/SeoContextFilter.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.apache.ofbiz.base.util.StringUtil;
4949
import org.apache.ofbiz.base.util.UtilHttp;
5050
import org.apache.ofbiz.base.util.UtilValidate;
51+
import org.apache.ofbiz.security.SecurityUtil;
5152
import org.apache.ofbiz.webapp.control.ConfigXMLReader;
5253
import org.apache.ofbiz.webapp.control.ConfigXMLReader.ControllerConfig;
5354
import org.apache.ofbiz.webapp.control.ControlFilter;
@@ -112,7 +113,11 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
112113
String queryString = URLEncodedUtils.format(params, Charset.forName("UTF-8"));
113114
uri = uri + "?" + queryString;
114115
}
115-
116+
117+
if (SecurityUtil.containsFreemarkerInterpolation(httpRequest, httpResponse, uri)) {
118+
return;
119+
}
120+
116121
boolean forwarded = forwardUri(httpResponse, uri);
117122
if (forwarded) {
118123
return;
@@ -167,7 +172,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
167172
if (pathItemList != null) {
168173
viewName = pathItemList.get(0);
169174
}
170-
175+
171176
String requestUri = UtilHttp.getRequestUriFromTarget(httpRequest.getRequestURI());
172177

173178
// check to make sure the requested url is allowed
@@ -231,7 +236,6 @@ public void destroy() {
231236

232237
/**
233238
* Forward a uri according to forward pattern regular expressions. Note: this is developed for Filter usage.
234-
*
235239
* @param uri String to reverse transform
236240
* @return String
237241
*/

framework/security/config/security.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,7 @@ allowedProtocols=localhost,127.0.0.1
253253
#-- By default (OOTB) OFBiz is protected against Large File Denial of Service because build.gradle defines -Xmx1024M
254254
#-- So you can at most upload a file around 500MB (see OFBIZ-11534 for more info)
255255
#-- If you need to upload larger files then follow https://nightlies.apache.org/ofbiz/trunk/readme/html5/#passing-jvm-runtime-options-to-ofbiz
256+
257+
#-- Prevent Freemarker exploits
258+
#-- eg: allowedURIsForFreemarkerInterpolation=createTextContentCms,updateTextContentCms,...
259+
allowedURIsForFreemarkerInterpolation=

framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,23 @@
1919
package org.apache.ofbiz.security;
2020

2121

22+
import java.io.IOException;
23+
import java.nio.charset.Charset;
2224
import java.util.ArrayList;
2325
import java.util.List;
2426
import java.util.Map;
2527
import java.util.stream.Collectors;
28+
29+
import javax.servlet.http.HttpServletRequest;
30+
import javax.servlet.http.HttpServletResponse;
31+
32+
import org.apache.http.client.utils.URLEncodedUtils;
33+
import org.apache.http.message.BasicNameValuePair;
2634
import org.apache.ofbiz.base.util.Debug;
2735
import org.apache.ofbiz.base.util.StringUtil;
36+
import org.apache.ofbiz.base.util.UtilHttp;
2837
import org.apache.ofbiz.base.util.UtilMisc;
38+
import org.apache.ofbiz.base.util.UtilProperties;
2939
import org.apache.ofbiz.base.util.UtilValidate;
3040
import org.apache.ofbiz.entity.Delegator;
3141
import org.apache.ofbiz.entity.GenericEntityException;
@@ -56,7 +66,7 @@ public final class SecurityUtil {
5666
*
5767
* @param delegator
5868
* @param userLoginId
59-
* @return
69+
* @return boolean
6070
*/
6171
public static boolean hasUserLoginAdminPermission(Delegator delegator, String userLoginId) {
6272
if (UtilValidate.isEmpty(userLoginId)) return false;
@@ -80,7 +90,7 @@ public static boolean hasUserLoginAdminPermission(Delegator delegator, String us
8090
* @param delegator
8191
* @param userLoginId
8292
* @param toUserLoginId
83-
* @return
93+
* @return List<String>
8494
*/
8595
public static List<String> hasUserLoginMorePermissionThan(Delegator delegator, String userLoginId, String toUserLoginId) {
8696
ArrayList<String> returnList = new ArrayList<>();
@@ -165,4 +175,68 @@ public static boolean authenticateUserLoginByJWT(Delegator delegator, String use
165175
}
166176
return false;
167177
}
178+
179+
/*
180+
* Prevents Freemarker exploits
181+
* @param req
182+
* @param resp
183+
* @param uri
184+
* @throws IOException
185+
*/
186+
public static boolean containsFreemarkerInterpolation(HttpServletRequest req, HttpServletResponse resp, String uri)
187+
throws IOException {
188+
String urisOkForFreemarker = UtilProperties.getPropertyValue("security", "allowedURIsForFreemarkerInterpolation");
189+
List<String> urisOK = UtilValidate.isNotEmpty(urisOkForFreemarker) ? StringUtil.split(urisOkForFreemarker, ",")
190+
: new ArrayList<>();
191+
String uriEnd = uri.substring(uri.lastIndexOf("/") + 1, uri.length());
192+
193+
if (!urisOK.contains(uriEnd)) {
194+
Map<String, String[]> parameterMap = req.getParameterMap();
195+
if (uri.contains("ecomseo")) { // SeoContextFilter call
196+
if (containsFreemarkerInterpolation(resp, uri)) {
197+
return true;
198+
}
199+
} else if (!parameterMap.isEmpty()) { // ControlFilter call
200+
List<BasicNameValuePair> params = new ArrayList<>();
201+
parameterMap.forEach((name, values) -> {
202+
for (String value : values) {
203+
params.add(new BasicNameValuePair(name, value));
204+
}
205+
});
206+
String queryString = URLEncodedUtils.format(params, Charset.forName("UTF-8"));
207+
uri = uri + "?" + queryString;
208+
if (SecurityUtil.containsFreemarkerInterpolation(resp, uri)) {
209+
return true;
210+
}
211+
} else if (!UtilHttp.getAttributeMap(req).isEmpty()) { // Call with Content-Type modified by a MITM attack (rare case)
212+
String attributeMap = UtilHttp.getAttributeMap(req).toString();
213+
if (containsFreemarkerInterpolation(resp, attributeMap)) {
214+
return true;
215+
}
216+
}
217+
}
218+
return false;
219+
}
220+
221+
/**
222+
* @param resp
223+
* @param stringToCheck
224+
* @throws IOException
225+
*/
226+
public static boolean containsFreemarkerInterpolation(HttpServletResponse resp, String stringToCheck) throws IOException {
227+
if (stringToCheck.contains("%24%7B") || stringToCheck.contains("${")
228+
|| stringToCheck.contains("%3C%23") || stringToCheck.contains("<#")
229+
|| stringToCheck.contains("%23%7B") || stringToCheck.contains("#{")
230+
|| stringToCheck.contains("%5B%3D") || stringToCheck.contains("[=")
231+
|| stringToCheck.contains("%5B%23") || stringToCheck.contains("[#")) { // not used OOTB in OFBiz, but possible
232+
233+
Debug.logError("===== Not saved for security reason, strings '${', '<#', '#{', '[=' or '[#' not accepted in fields! =====",
234+
MODULE);
235+
resp.sendError(HttpServletResponse.SC_FORBIDDEN,
236+
"Not saved for security reason, strings '${', '<#', '#{', '[=' or '[#' not accepted in fields!");
237+
return true;
238+
} else {
239+
return false;
240+
}
241+
}
168242
}

framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlFilter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import javax.servlet.http.HttpServletResponse;
3333

3434
import org.apache.ofbiz.base.util.Debug;
35+
import org.apache.ofbiz.entity.GenericValue;
36+
import org.apache.ofbiz.security.SecurityUtil;
3537

3638
/*
3739
* A Filter used to specify a whitelist of allowed paths to the OFBiz application.
@@ -132,6 +134,11 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
132134
if (offset == -1) {
133135
offset = requestUri.length();
134136
}
137+
if (!GenericValue.getStackTraceAsString().contains("ControlFilterTests")
138+
&& SecurityUtil.containsFreemarkerInterpolation(httpRequest, httpResponse, requestUri)) {
139+
return;
140+
}
141+
135142
while (!allowedPaths.contains(requestUri.substring(0, offset))) {
136143
offset = requestUri.indexOf("/", offset + 1);
137144
if (offset == -1) {

0 commit comments

Comments
 (0)