昨天在推特上看到threedr3am师傅发的Spring Data MongoDB SpEL的环境,然后就自己去手动分析了一波,这里用的版本是Spring Data MongoDB 3.3.4
漏洞的原因是当使用@Query
或@Aggregation
注解进行数据库查询,且使用了占位符获取参数导致的Spel表达式注入
在diif上可以看到漏洞触发的点
在org.springframework.data.mongodb.util.json.ParameterBindingJsonReader#bindableValueFor
中进行绑定参数时触发漏洞,直接在327行打个断点调试,此时expression
的值就是构造好的payload,但此时并不会触发漏洞,会在下一次再到这个地方时才会触发漏洞,这里跟进到this.evaluateExpression
中看一下
此时this.expressionEvaluator
的值为ParameterBindingDocumentCodec
,此时会进入到ParameterBindingDocumentCodec
中对expression
进行处理,最后会返回一个空的对象
接下来比较了在同一个地方两个的堆栈,发现是在org.springframework.data.mongodb.repository.query.StringBasedMongoQuery#createQuery
中先进入到getBindingContext
进行了参数绑定,看到此时传入的codec
就是ParameterBindingDocumentCodec
导致第一次并没有触发漏洞,然后绑定参数后在进入到decode
中最后会再次进入bindableValueFor
中
先来看一下第一次进行参数绑定时进行了什么操作,在org.springframework.data.mongodb.util.json.ParameterBindingJsonReader#readBsonType
中,通过switch判断token的Type属性,进入到UNQUOTED_STRING中,在这里进行setCurrentName
操作,该值是在bindableValueFor
中通过一系列操作后获得,其为实体类中的id参数
接着继续往下走,会经过很多对value值进行equals的对比,此时value是:#{?0}
,肯定是false的,最后进入到bindableValueFor
中,首先是先把值传给了tokenValue,然后先后对其进行了PARAMETER_BINDING_PATTERN
和EXPRESSION_BINDING_PATTERN
规则匹配表达式,然后取出值交给binding,在通过substring取出占位符?0
,接下来通过for循环将一开始传进来的payload和占位符进行替换,然后执行this.evaluateExpression
,因为同时传入的codec
,只是返回了一个空的Object实例,最后将value和type进行set后返回bindableValue,这里感觉就是先对实体类的id参数进行了绑定
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.expressionEvaluator
为DefaultSpELExpressionEvaluator
,最后执行getValue触发SpEl表达式注入
这里引入的是Spring Data MongoDB 3.3.4
在新版本中额外增加了一个规则匹配表达式,并对binding
也就是:#{?0}
进行匹配,然后将传进来的payload放入到innerSpelVariables
的键值对里,key为特殊字符,最后和binding
一起传入到this.evaluateExpression
中
private static final Pattern SPEL_PARAMETER_BINDING_PATTERN = Pattern.compile("('\\?(\\d+)'|\\?(\\d+))");
其中进行了三元运算符判断,判断的是this.expressionEvaluator
是否为EvaluationContextExpressionEvaluator
的实例,然后会是false进入到evaluate
,此时传进来的是键值对中的key#__QVar0
然后无法触发SpEL表达式注入
public Object evaluateExpression(String expressionString, Map<String, Object>variables) { return this.expressionEvaluator instanceof EvaluationContextExpressionEvaluator ? ((EvaluationContextExpressionEvaluator)this.expressionEvaluator).evaluateExpression(expressionString, variables) : this.expressionEvaluator.evaluate(expressionString); }