安全矩阵

 找回密码
 立即注册
搜索
查看: 2338|回复: 0

fastjson1224的TemplatesImpl链反序列化分析

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2022-1-7 18:35:57 | 显示全部楼层 |阅读模式
原文链接:fastjson1224的TemplatesImpl链反序列化分析

fastjson的序列化与反序列化
首先,分析fastjson的反序列化前我们先来了解一下fastjson,我们新建一个User类:
  1. public class User {
  2. public Long id;
  3. public String name;
  4. public Long getId() {
  5. System.out.println("getid="+this.id);
  6. return id;
  7. }
  8. public void setId(Long id) {
  9. this.id = id;
  10. System.out.println("setid="+this.id);
  11. }
  12. public String getName() {
  13. System.out.println("getname="+this.name);
  14. return name;
  15. }
  16. public void setName(String name) {
  17. System.out.println("setname="+this.name);
  18. this.name = name;
  19. }
  20. }
复制代码

fastjson提供了三种反序列化的方法,分别是:
  1. String jsonString = "{"id":2,"name":"guest"}\n";
  2. // 反序列化代码示例1
  3. Object ob1 = JSON.parse(jsonString);
  4. System.out.println("ob1="+ob1);
  5. // 反序列化代码示例2
  6. Object ob2 = JSON.parseObject(jsonString);
  7. System.out.println("ob2="+ob2);
  8. // 反序列化代码示例3
  9. Object ob3 = JSON.parseObject(jsonString,User.class);
  10. System.out.println("ob3="+ob3);
复制代码


那么这三个方法有什么区别呢,接下来,我们在代码中set一下参数的值,运行一遍看看:
  1. public class main {
  2. public static void main(String[] args) {
  3. User guestUser = new User();
  4. guestUser.setId(2L);
  5. guestUser.setName("guest");
  6. // fastjson提供toJsonString接口实现序列化
  7. // fastjson提供parseObject来分别实现反序列化
  8. // 序列化示例
  9. String jsonString = JSON.toJSONString(guestUser);
  10. System.out.println("序列化数据="+jsonString);
  11. ...
  12. }
  13. }
复制代码



运行后发现ob1和ob2返回的是JSONObject,而ob3则是实际的类对象,并且ob3调用了User类中全部set方法,ob1与ob2则是set、get方法均未调用,那么接下来我们就来看看他们在调用set与get方法的区别
我们新写一段代码,首先传入User类的一句序列化后的字符串,而后使用三种方法来反序列化它,看看分别调用了什么方法
  1. String parseString = "{"@type":"User","id":2,"name":"guest"}";
  2. System.out.println("parse方法的调用结果");
  3. Object ob11 = JSON.parse(parseString);
  4. System.out.println("ob11="+ob11);
  5. System.out.println("----------------------");
  6. System.out.println("parseObject方法的调用结果");
  7. Object ob12 = JSON.parseObject(parseString);
  8. System.out.println("ob12="+ob12);
  9. System.out.println("----------------------");
  10. System.out.println("parseObject(xx,class)方法的调用结果");
  11. Object ob13 = JSON.parseObject(parseString,User.class);
  12. System.out.println("ob13="+ob13);
复制代码

结果:

我们可以发现,parse和parseObject都返回了全部的set方法,并且parseObject返回了全部的get方法,parseObject(xx,class)返回了全部的set方法,那么我们是否可以得出结论,全部的方法都可以调用set方法呢?那么接下来我们就来看看调用到的set、get方法分别存在什么限制,这其实就是fastjson的特性
fastjson特性
首先,我们来看set与get方法的限制:
  1. // com/alibaba/fastjson/util/JavaBeanInfo.java
  2. public class JavaBeanInfo{
  3. public static JavaBeanInfo build(Class clazz, Type type, PropertyNamingStrategy propertyNamingStrategy) {
  4. /**
  5. * 判断set方法函数
  6. */
  7. for (Method method : methods) { //
  8. int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;
  9. String methodName = method.getName();
  10. // 长度比4大
  11. if (methodName.length() < 4) {
  12. continue;
  13. }
  14. // 非静态方法
  15. if (Modifier.isStatic(method.getModifiers())) {
  16. continue;
  17. }
  18. // 返回类型不能是void或者当前类
  19. // support builder set
  20. if (!(method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))) {
  21. continue;
  22. }
  23. Class[] types = method.getParameterTypes();
  24. // 不能有传入的参数
  25. if (types.length != 1) {
  26. continue;
  27. }
  28. // annotation = null,跳过
  29. JSONField annotation = method.getAnnotation(JSONField.class);
  30. if (annotation == null) {
  31. annotation = TypeUtils.getSuperMethodAnnotation(clazz, method);
  32. }
  33. if (annotation != null) {
  34. // ......略过
  35. }
  36. // 以set为开头
  37. if (!methodName.startsWith("set")) {
  38. continue;
  39. }
  40. char c3 = methodName.charAt(3);
  41. String propertyName;
  42. // 第4个字母大写
  43. if (Character.isUpperCase(c3) //
  44. || c3 > 512 // for unicode method name
  45. ) {
  46. if (TypeUtils.compatibleWithJavaBean) {
  47. propertyName = TypeUtils.decapitalize(methodName.substring(3));
  48. } else {
  49. propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
  50. }
  51. } else if (c3 == '_') {
  52. propertyName = methodName.substring(4);
  53. } else if (c3 == 'f') {
  54. propertyName = methodName.substring(3);
  55. } else if (methodName.length() >= 5 && Character.isUpperCase(methodName.charAt(4))) {
  56. propertyName = TypeUtils.decapitalize(methodName.substring(3));
  57. } else {
  58. continue;
  59. }
  60. Field field = TypeUtils.getField(clazz, propertyName, declaredFields);
  61. if (field == null && types[0] == boolean.class) {
  62. String isFieldName = "is" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
  63. field = TypeUtils.getField(clazz, isFieldName, declaredFields);
  64. }
  65. // filed = null,跳过
  66. JSONField fieldAnnotation = null;
  67. if (field != null) {
  68. // ......
  69. }
  70. // propertyNamingStrategy = null,跳过
  71. if (propertyNamingStrategy != null) {
  72. // ......
  73. }
  74. add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures,
  75. annotation, fieldAnnotation, null));
  76. }
  77. /**
  78. * 判断get方法函数体:
  79. */
  80. for (Method method : clazz.getMethods()) { // getter methods
  81. String methodName = method.getName();
  82. // 长度不能小于4
  83. if (methodName.length() < 4) {
  84. continue;
  85. }
  86. // 不能是静态方法
  87. if (Modifier.isStatic(method.getModifiers())) {
  88. continue;
  89. }
  90. // 以get开头并且第4位是个大写字母
  91. if (methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3))) {
  92. // 不能有传入的参数
  93. if (method.getParameterTypes().length != 0) {
  94. continue;
  95. }
  96. // 返回值继承于Collection Map AtomicBoolean AtomicInteger AtomicLong之一
  97. if (Collection.class.isAssignableFrom(method.getReturnType()) //
  98. || Map.class.isAssignableFrom(method.getReturnType()) //
  99. || AtomicBoolean.class == method.getReturnType() //
  100. || AtomicInteger.class == method.getReturnType() //
  101. || AtomicLong.class == method.getReturnType() //
  102. ) {
  103. // .........
  104. // 对get方法进行处理的函数,略过
  105. }
复制代码

所以,根据以上代码,我们可以总结出调用到set、get方法的限制:
  •         set方法条件

  •         方法名长度大于4且以set开头,且第四个字母要是大写
  •         非静态方法
  •         返回类型为void或当前类
  •         参数个数为1个

  •         get方法条件

  •         方法名长度大于等于4,且以get开头且第4个字母为大写
  •         非静态方法
  •         无传入参数
  •         返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

​​
并且,parseobject()这个方法中多了一步toJSON操作,会调用全部的get方法:

这样,上面调用到的set、get方法就全部说通了。
下面我们看看fastjson的另一种特性,就是@type这个参数,开发者本意是序列化时使用SerializerFeature.WriteClassName会写入类型信息,从而使反序列化时不会丢失类型信息,并且反序列化时自动进行类型识别,但是这就造成了一个问题,type参数可以指定反序列化任意的类,然后去调用set、get方法,我们来看看实际的代码是如何运作的

在序列化是写入类型信息,此时的序列化后的字符串就有@type参数:

我们在看看输入@type参数,他的反序列化结果:

看似这种情况很正常,反序列化使用到set、get方法恢复数据,但是在可以调用任意类的情况下,这就很恐怖了,如果set或者get方法中有可利用的点的情况下,就会造成RCE。
jdk7u21TemplatesImpl链
说到fastjson的TemplatesImpl链,就不得不提jdk7u21的TemplatesImpl链,fastjson的这种利用方法其实和jdk7u21的TemplatesImpl链很像,jdk7u21由以下代码出发RCE
  1. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

  2. public class jdk7u21_nekoc {
  3. public static void main(String[] args) throws Exception {
  4. TemplatesImpl calc = (TemplatesImpl) Gadgets.createTemplatesImpl("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
  5. calc.getOutputProperties();
  6. }
  7. }
复制代码

gadegets类是在ysoserial里拔出来的,跟进触发漏洞的calc.getOutputProperties();方法,发现最终由obj.newinstance触发恶意代码,由于newinstance示例化会默认触发static及构造方法,所以payload可以写在这两个中的其一,并且,这里注意,getOutputProperties方法正好符合fastjson中对get方法名字的限制。
我们这里不再具体分析jdk7u21,我们来跟进看一下该链子的一些限制条件,跟进代码:
return newTransformer().getOutputProperties();
首先,我们发现:
com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java中,存在两个限制条件:


  1. public class TemplatesImpl {
  2. private Translet getTransletInstance()
  3. throws TransformerConfigurationException {
  4. try {
  5. // 第一个条件 _name不为null
  6. if (_name == null) return null;

  7. // 第二个条件 _class不为null,进到defineTransletClasses里
  8. if (_class == null) defineTransletClasses();
  9. // ......
  10. }
  11. }
复制代码


第一个条件就是_name不为null,第二个条件是_class不为null,这样才能进到defineTransletClasses方法,我们继续来看看defineTransletClasses方法里边的判断:
  

  1. private void defineTransletClasses()
  2. throws TransformerConfigurationException {

  3. // 第三个条件:_bytecodes不为null
  4. if (_bytecodes == null) {
  5. ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
  6. throw new TransformerConfigurationException(err.toString());
  7. }

  8. // 7u21
  9. TransletClassLoader loader = (TransletClassLoader)
  10. AccessController.doPrivileged(new PrivilegedAction() {
  11. public Object run() {
  12. // 这里应该是第四个条件,这里7u21和高版本的7u80有区别
  13. return new TransletClassLoader(ObjectFactory.findClassLoader());
  14. }
  15. });
  16. // 7u80
  17. TransletClassLoader loader = (TransletClassLoader)
  18. AccessController.doPrivileged(new PrivilegedAction() {
  19. public Object run() {
  20. // 这里就是第四个限制条件,_tfactory参数存在一个getExternalExtensionsMap方法
  21. return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
  22. }
  23. });


  24. try {
  25. final int classCount = _bytecodes.length;
  26. _class = new Class[classCount];

  27. if (classCount > 1) {
  28. _auxClasses = new Hashtable();
  29. }

  30. for (int i = 0; i < classCount; i++) {
  31. _class[i] = loader.defineClass(_bytecodes[i]);
  32. final Class superClass = _class[i].getSuperclass();

  33. // Check if this is the main class
  34. // 第五个限制条件:_bytecodes必须是ABSTRACT_TRANSLET子类
  35. if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
  36. _transletIndex = i;
  37. }
  38. else {
  39. _auxClasses.put(_class[i].getName(), _class[i]);
  40. }
  41. }

  42. if (_transletIndex < 0) {
  43. ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
  44. throw new TransformerConfigurationException(err.toString());
  45. }
  46. }
  47. catch(Exception e){
  48. //...
  49. }
  50. }
  51. }
复制代码

我们跟进代码首先会发现第三个限制条件,即_bytecodes不为null,继续向下看,这里的代码版本不同,内容就不一样,在7u21中,不存在有关_tfactory的限制,在7u80中,就存在该限制,这里要求_tfactory参数存在一个getExternalExtensionsMap方法,为了版本通用,所以在poc里加上对tfactory的限制条件,继续向下看,我们可以看到第五个条件,即_bytecodes必须是ABSTRACT_TRANSLET子类,并且,payload要写在_bytecode对应的类的静态方法或者构造方法里,这样,我们就集齐了全部的限制条件,小结一下就是:
  •         name != null
  •         _class == null
  •         _bytecodes != null
  •         _tfactory需要有一个getExternalExtensionsMap方法
  •         _bytecodes的类必须是ABSTRACT_TRANSLET的子类
  •         payload要在_bytecode对应的类的静态方法或者构造方法里

1224 TemplatesImpl链
jdk7u21在fastjson1224的应用还需要一点点条件,用parseObject()时,必须用JSON.parseObject(jsonString, Feature.SupportNonPublicField);这样的格式,必须要有Feature.SupportNonPublicField参数才可以,payload中有部分参数是private属性,需要在这里设置一下才能被接受;使用parse()时,需要JSON.parse(jsonString,Feature.SupportNonPublicField); 这样的格式。
在编写payload时,需要用到javasist类,这里不再描述该类。
最终的部分payload:

看到最后的代码可能会有些疑问,这里的_tfactory为什么是空而不是一个有getExternalExtensionsMap方法的类对象?_OutputProperties是怎么调用到getOutputProperties方法的?
我们来看看这两个问题的解答。
首先是第一个,_tfactory为什么是空而不是一个有getExternalExtensionsMap方法的类对象呢,我们跟一下代码,不难发现,解析这几个参数的时候,如果发现参数值为空对象,就会新建一个该参数应有的格式的对象实例,将其赋值给该参数



那么,_tfactory应有的格式为啥是它呢?咱们看一下定义就知道了

所以,_tfactory其实本身就是TransformerFactoryImpl类的对象,赋值为空就系那个回家了一样,完全是可以的。
接下来我们看看第二个问题,_OutputProperties是怎么调用到getOutputProperties方法的呢,我们跟进代码就可以看到,在处理参数的时候,代码会将_OutputProperties前面的下划线替换为空,此时反序列化时就回去调用他的get方法,即getOutputProperties,所以其实poc生成中得_OutputProperties有没有下划线都是可以的,这样我们就在fastjson1224版本中完成了TemplatesImpl链的分析。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-5-3 05:08 , Processed in 0.022154 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表