目录

CVE-2022-22980 Spring Data MongoDB SpEL表达式注入

前言

昨天在推特上看到threedr3am师傅发的Spring Data MongoDB SpEL的环境,然后就自己去手动分析了一波,这里用的版本是Spring Data MongoDB 3.3.4

漏洞的原因是当使用@Query@Aggregation注解进行数据库查询,且使用了占位符获取参数导致的Spel表达式注入

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/220c868f-d07f-bb15-e722-5a6b8ea69fd2.png

漏洞分析

diif上可以看到漏洞触发的点

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/23c5607a-5851-3422-b057-5513b42d0383.png

org.springframework.data.mongodb.util.json.ParameterBindingJsonReader#bindableValueFor中进行绑定参数时触发漏洞,直接在327行打个断点调试,此时expression的值就是构造好的payload,但此时并不会触发漏洞,会在下一次再到这个地方时才会触发漏洞,这里跟进到this.evaluateExpression中看一下

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/296211f0-fc11-a664-0b7f-9da195ce3c3f.png

此时this.expressionEvaluator的值为ParameterBindingDocumentCodec,此时会进入到ParameterBindingDocumentCodec中对expression进行处理,最后会返回一个空的对象

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/d3eaeb62-ac76-a56b-8285-1add4b14e3cf.png

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/61c0cdf7-4784-82aa-cfa5-bf8bc156d668.png

接下来比较了在同一个地方两个的堆栈,发现是在org.springframework.data.mongodb.repository.query.StringBasedMongoQuery#createQuery中先进入到getBindingContext进行了参数绑定,看到此时传入的codec就是ParameterBindingDocumentCodec导致第一次并没有触发漏洞,然后绑定参数后在进入到decode中最后会再次进入bindableValueFor

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/91fb3e40-ef0f-8277-9bb6-ad97f68b7924.png

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/58e8580e-fa84-4b89-c6bc-c86112db5615.png

先来看一下第一次进行参数绑定时进行了什么操作,在org.springframework.data.mongodb.util.json.ParameterBindingJsonReader#readBsonType中,通过switch判断token的Type属性,进入到UNQUOTED_STRING中,在这里进行setCurrentName操作,该值是在bindableValueFor中通过一系列操作后获得,其为实体类中的id参数

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/be659d5c-ab0b-24e6-2d32-b97a843fc320.png

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/8173b86c-6065-e8ff-8fc0-0ea39c11aa27.png

接着继续往下走,会经过很多对value值进行equals的对比,此时value是:#0,肯定是false的,最后进入到bindableValueFor中,首先是先把值传给了tokenValue,然后先后对其进行了PARAMETER_BINDING_PATTERNEXPRESSION_BINDING_PATTERN规则匹配表达式,然后取出值交给binding,在通过substring取出占位符?0,接下来通过for循环将一开始传进来的payload和占位符进行替换,然后执行this.evaluateExpression,因为同时传入的codec,只是返回了一个空的Object实例,最后将value和type进行set后返回bindableValue,这里感觉就是先对实体类的id参数进行了绑定

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/d41fc3c8-ca45-f9c2-1a0c-ed612407f37e.png

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/b285d5cb-5b20-aee0-7828-b8145bfa3199.png

1
2
3
private static final Pattern PARAMETER_ONLY_BINDING_PATTERN = Pattern.compile("^\\(\\d+)$");
private static final Pattern PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+");
private static final Pattern EXPRESSION_BINDING_PATTERN = Pattern.compile("[\\?:#\\{.*\\}");

接着进行this.getBindingContext后,会进入到decode中,最后进行一样的操作,此时this.expressionEvaluatorDefaultSpELExpressionEvaluator,最后执行getValue触发SpEl表达式注入

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/58e8580e-fa84-4b89-c6bc-c86112db5615.png

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/9089cf15-9197-50ea-1571-d0b0d74f0c9b.png

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/2375dedb-a774-36be-fe9a-09c01d7945d4.png

漏洞防御

这里引入的是Spring Data MongoDB 3.3.4

在新版本中额外增加了一个规则匹配表达式,并对binding也就是:#0进行匹配,然后将传进来的payload放入到innerSpelVariables的键值对里,key为特殊字符,最后和binding一起传入到this.evaluateExpression

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/42f1c8c6-41d1-130b-c2e2-ddee12e64ebd.png

1
private static final Pattern SPEL_PARAMETER_BINDING_PATTERN = Pattern.compile("('\\?(\\d+)'|\\?(\\d+))");

其中进行了三元运算符判断,判断的是this.expressionEvaluator是否为EvaluationContextExpressionEvaluator的实例,然后会是false进入到evaluate,此时传进来的是键值对中的key#__QVar0然后无法触发SpEL表达式注入

1
2
3
public Object evaluateExpression(String expressionString, Map<String, Object>variables) {
    return this.expressionEvaluator instanceof EvaluationContextExpressionEvaluator ? ((EvaluationContextExpressionEvaluator)this.expressionEvaluator).evaluateExpression(expressionString, variables) : this.expressionEvaluator.evaluate(expressionString);
}

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513662/570f6384-0030-9822-f56f-3b7381c5ee25.png

参考链接

  1. https://github.com/threedr3am/learnjavabug/tree/master/spring/spring-data-mongodb-spel-CVE-2022-22980
  2. https://github.com/spring-projects/spring-data-mongodb/commit/7c5ac764b343d45e5d0abbaba4e82395b471b4c4?diff=split