Coordinated Disclosure Timeline

Summary

Apache OFBiz up to version 18.12.05 is vulnerable to Regular Expression Denial of Service (ReDoS) in the way it handles URLs provided by external, unauthenticated users. Specially crafted URLs may cause catastrophic backtracking, taking exponential time to complete.

Product

Apache OFBiz

Tested Version

18.12.05

Details

Issue: Regular Expression Denial of Service (ReDoS) in UtilHttp.java. (GHSL-2022-025)

The Apache OFBiz framework uses the following regular expression in the method UtilHttp.extractUrls to identify URLs being passed as parameters in HTTP requests:

Pattern pattern = Pattern.compile(
    "\\b(((ht|f)tp(s?)\\:\\/\\/|~\\/|\\/)|www.)"
            + "(\\w+:\\w+@)?(([-\\w]+\\.)+(com|org|net|gov"
            + "|mil|biz|info|mobi|name|aero|jobs|museum"
            + "|travel|[a-z]{2}))(:[\\d]{1,5})?"
            + "(((\\/([-\\w~!$+|.,=]|%[a-f\\d]{2})+)+|\\/)+|\\?|#)?"
            + "((\\?([-\\w~!$+|.,*:]|%[a-f\\d{2}])+=?"
            + "([-\\w~!$+|.,*:=]|%[a-f\\d]{2})*)"
            + "(&(?:[-\\w~!$+|.,*:]|%[a-f\\d{2}])+=?"
            + "([-\\w~!$+|.,*:=]|%[a-f\\d]{2})*)*)*"
            + "(#([-\\w~!$+|.,*:=]|%[a-f\\d]{2})*)?\\b");

Note the nested repetitions in the regular expression, such as (((\\/([-\\w~!$+|.,=]|%[a-f\\d]{2})+)+|\\/)+ or ([-\\w~!$+|.,*:=]|%[a-f\\d]{2})*)*)*. A sufficiently complex payload can exploit this complexity to force the regex engine into catastrophic backtracking, taking exponential time to complete [1].

The pattern is used in the following way:

List<String> allowedProtocols = getAllowedProtocols();
for (String protocol : allowedProtocols) {
    if (input.contains(protocol)) {
        result.add(input);
    }
}

if (result.isEmpty()) {
    Matcher matcher = pattern.matcher(input);
    while (matcher.find()) {
        result.add(matcher.group());
    }
}

This means that if the user-provided URL doesn’t contain any of the allowedProtocols, the regular expression will be matched against it. Also, to reach extractUrls from UtilHttp.canonicalizeParameterMap, a parameter must look like an URL (or several) but it must not contain the string :// at indexes 3 or 4 (so, for example, protocols http://, ftp:// and https:// can’t be used):

public static Map<String, Object> canonicalizeParameterMap(Map<String, Object> paramMap) {
    for (Map.Entry<String, Object> paramEntry : paramMap.entrySet()) {
        if (paramEntry.getValue() instanceof String) {
            String paramEntries = (String) paramEntry.getValue();
            String[] stringValues = paramEntries.split(" ");
            String params = "";
            // Handles textareas, see OFBIZ-12249
            if (stringValues.length > 0 && !paramEntry.getKey().equals("DUMMYPAGE")) {
                for (String s : stringValues) {
                    // if the string contains only an URL beginning by http or ftp => no change to keep special chars
                    if (UtilValidate.isValidUrl(s) && (s.indexOf("://") == 4 || s.indexOf("://") == 3)) {
                        params = params + s + " ";
                    } else if (UtilValidate.isUrl(s) && !s.isEmpty()) {
                        // if the string contains not only an URL => concatenate possible canonicalized before and after, w/o changing the URL
                        String url = extractUrls(s).get(0); // There should be only 1 URL in a block, makes no sense else

So a parameter with the following form would reach extractUrls:

parameter=somethingelse://www.a.org

The method canonicalizeParameterMap is used to parse request parameters in several parts of the application. One of them is LoginWorker.extensionCheckLogin, which is an event called to check whether a user is logged in. This means that the vulnerable regex in extractUrls can be accessed by any unauthenticated user with a single HTTP request with a properly crafted parameter.

As an example, the following request would reproduce the denial of service due to exponential backtracking:

https://localhost:8443/partymgr/control/main?externalLoginKey=somethingelse://www.a.org/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!//!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/

Note that JDK 9 introduced important mitigations for this problem, so in order to reproduce the issue with the above example, Apache OFBiz must be run with JDK =< 8.

Impact

This issue may lead to a denial of service of the application server by resource consumption.

Resources

[1] https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
[2] https://github.com/google/re2j

CVE

Credit

This issue was discovered and reported by the CodeQL team members @atorralba (Tony Torralba) and @joefarebrother (Joseph Farebrother).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2022-025 in any communication regarding this issue.