之前爆了一个大洞,据说堪比log4j,现在追溯一波看看情况;
还是老思路,从后往前推;漏洞是一个常规的spel注入;
看一下触发点,可以看到在ShortcutConfigurable这个接口类里getValue函数中,传入spelExpressionParser,第三个参数是entryValue String;然后下面进行解析,把在 #{}
之间的str拿出来做spel解析;接着这个函数往前追;
看到在normalize函数中触发,这个函数也在一个枚举类里,向上看一下,在shortcutType返回了这个枚举类,那么现在逻辑就是找到一个调用链,基本上是 shortcutType().normalize() 这样的;
在ConfigurableBuilder类下,normalizeProperties函数中找到了合理的调用;并且看到这里也是传入相关的参数,这里我们重点关注properties这个参数;也很简单,这个追一下参数的调用流程就会很清楚了;现在继续追normalizeProperties函数的调用点;发现在其父类里面;
在bind函数中进行触发;这里看到没有相关参数的传入,所以看一下参数的传入点;在ConfigurableBuilder的父类AbstractBuilder中
1 | public B properties(Map<String, String> properties) { |
所以看到相关的属性值是在其他的class中调用函数进行赋值的;所以这里就要考虑在什么class下调用bind函数,而且在调用之前进行了相关的赋值;向前追一下;
在RouteDefinitionRouteLocator类下;lookup方法里触发;下面那一段的逻辑我们可以看一下:
1 | this.configurationService.with(factory) |
configurationService属性追一下不难发现是ConfigurationService类,调用with函数返回一个InstanceBuilder对象;
1 | public <T> InstanceBuilder<T> with(T instance) { |
然后调用name方法;触发点是在其父类的name方法;
1 | public B name(String name) { |
然后接着返回InstanceBuilder对象;然后调用properties方法;这个方法很重要,追进去看一下:
1 | public B properties(Map<String, String> properties) { |
可以看到properties就是在此进行赋值,所以也满足我们之前说的要对属性赋值的条件;properties最后也就是进入spel解析;贴一下之前的调用点:
1 | //ConfigurableBuilder类 |
看到properties传入了args,然后在后续parser的时候会for遍历,将entry(map里的值)调用getValue函数拿到值之后进行解析;这个向上简单追一下就行了,不是很难;
上面已经追溯到了lookup函数,发现lookup函数里是可以满足调用逻辑的,现在就是再次向上追溯看看lookup在何处调用;
还是在lookup这个class里,在combinePredicates函数中触发;
继续追上去,发现在convertToRoute这个函数中触发,两个函数的处理数据都是RouteDefinition对象;重要的参数是predicates,可以看到predicate就是从predicates里调用get函数拿到的,然而predicates属性是在routeDefinition对象中调用getPredicates拿到的,具体可看上上个图;
梳理到这里已经很清楚了,最后的spel解析的str,会从routeDefinition这个封装的对象里的属性,那么也就是说如果我们可控routeDefinition里的属性将其赋值为一个spel恶意的表达式,那么就可最后触发spel注入;这就是一个很清晰的spel注入点;看到这里,其实通过函数名和参数名不难发现是和route相关的;这里继续向上追溯一下:
发现还是在相同的class下
getRoutes函数中调用lambda表达式的方式进行调用;
这个getRoutes函数已经在这个class里无法进行调用了,所以用全局的方法进行搜索一下;
直接发现可以在controller里直接调用,很幸运!!!发现了传入点;可以看到是调用了routeLocator属性下的getRoutes方法;上面的追溯不难发现,getRoutes方法就在 RouteDefinitionRouteLocator 类下,然而这个类是继承于RouteLocator的;
可以看到在controller里,直接在构造器中传入了,所以对于利用方式来说,直接在controller里进行请求攻击就行;可以看到是restful风格的写法,这个地方其实在官方的文档里也可看到,这个controller主要是用来检索特定路由的信息的;
可以看到检索之后会返回特定格式的内容;我们关注的一个点就出现了,那就是predicates这个属性,传进去的是一个数组,想来和后续的处理是符合的,数组后续处理程序会将其内容放到map里,所以这样就解释了为什么在追溯源码的时候会有如下的定义了:
1 | private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>(); |
看懂这个之后再去看一下追随过程中的源码就会很明显:
1 | private AsyncPredicate<ServerWebExchange> combinePredicates(RouteDefinition routeDefinition) { |
这个地方肯定是将整体的数据封装成为一个routeDefinition对象,然后调用getPredicates函数拿到prodicates这个属性的值,因为其是数组,所以要用LinkedHashMap来进行承载;然后向后传入lookup进入后续的流程,然后最后触发spel解析;
那么最后传入解析点的值自然就是如下代码所示:
1 | Object config = this.configurationService.with(factory) |
之前分析过调用properties进行设置properties,然后properties后续会进入spel解析,那么这里的参数值可以看到是predicate.getArgs()的结果,那么显然易见就是路由信息中的
1 | "predicates": [{ |
的args的值;所以如果可控这一部分就可造成最终的spel注入;
那么知道了利用点现在就是怎么去利用了,这个漏洞是发生在检索路由信息的地方,那么我们就可以考虑注册一个路由,这个对spring cloud有过了解的师傅们都知道,spring cloud是可以实现注册路由的;看下相关文档的利用;
意思就是我们创建路由的时候post提交的数据应该是json类型,而且相关的格式应该和检索返回的格式一样;
正如我们所意;那么这里就直接动态创建路由,然后refresh一下,然后再去检索自然就可触发漏洞利用;
1 | POST /actuator/gateway/routes/hacktest HTTP/1.1 |