最近spring-cloud-gateway出现RCE漏洞,主要是SpEL导致的
于是我随便找了下spring-cloud其他组件可能的SpEL漏洞
简单审计了几个框架,暂时写了三个组件的分析
可能利用点(1)
org/springframework/cloud/gateway/support/ShortcutConfigurable#getValue
xxxxxxxxxxstatic Object getValue(SpelExpressionParser parser, BeanFactory beanFactory, String entryValue) { ... if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) { GatewayEvaluationContext context = new GatewayEvaluationContext(beanFactory); Expression expression = parser.parseExpression(entryValue, new TemplateParserContext()); value = expression.getValue(context); } ...}可能利用点(2)
org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator#getRouteDefinitions
xxxxxxxxxxprivate final SimpleEvaluationContext evalCtxt;...includePredicate = instance -> { Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class); if (include == null) { return false; } return include;};结论
显而易见,不可利用
因为SimpleEvaluationContext是安全的
新漏洞修复后加入的GatewayEvaluationContext也是安全的
可能利用点
org/springframework/cloud/netflix/turbine/CommonsInstanceDiscovery#getClusterName
xxxxxxxxxxprotected String getClusterName(Object object) { StandardEvaluationContext context = new StandardEvaluationContext(object); Object value = this.clusterNameExpression.getValue(context); if (value != null) { return value.toString(); } return null;}初步结论
可能存在利用,因为使用了StandardEvaluationContext允许执行命令
进阶分析
阅读源码,不难发现表达式来自于配置文件
xxxxxxxxxxprotected CommonsInstanceDiscovery(TurbineProperties turbineProperties, String defaultExpression) { // 配置文件属性 this.turbineProperties = turbineProperties; // 标准SPEL Parser SpelExpressionParser parser = new SpelExpressionParser(); // 从配置文件中取值 String clusterNameExpression = turbineProperties.getClusterNameExpression(); if (clusterNameExpression == null) { clusterNameExpression = defaultExpression; } this.clusterNameExpression = parser.parseExpression(clusterNameExpression); this.combineHostPort = turbineProperties.isCombineHostPort();}通过改配置文件即可实现RCE,在Eureka Server获取该实例名称时触发
xxxxxxxxxxturbine app-configtest cluster-name-expressionT(java.lang.Runtime).getRuntime().exec(new String(new byte0x630x610x6c0x63)) combine-host-porttrue
可能利用点(1)
com/google/cloud/spring/data/spanner/core/mapping/SpannerPersistentEntityImpl#tableName
xxxxxxxxxx...this.tableName = validateTableName( (this.tableNameExpression != null) ? this.tableNameExpression.getValue(this.context, String.class) : this.table.name());初步结论
阅读源码发现context定义如下,可以利用
xxxxxxxxxxprivate StandardEvaluationContext context;// initthis.context = new StandardEvaluationContext();进阶分析
这是一个表名
xxxxxxxxxxExpression expression =PARSER.parseExpression(this.table.name(), ParserContext.TEMPLATE_EXPRESSION);来自于Table注解
xxxxxxxxxx(ElementType.TYPE)(RetentionPolicy.RUNTIME)public @interface Table { String name() default "";}不可利用,因为表名在代码中写死,不可控
xxxxxxxxxx(name = "table_#{SPEL}")public class DemoTable {}可能利用点(2)
com/google/cloud/spring/data/spanner/repository/query/SqlSpannerQuery#resolveSpelTags
xxxxxxxxxxprivate void resolveSpelTags(QueryTagValue queryTagValue) { ... EvaluationContext evaluationContext = this.evaluationContextProvider.getEvaluationContext( this.queryMethod.getParameters(), queryTagValue.rawParams); for (Expression expression : expressions) { if (expression instanceof LiteralExpression) { sb.append(expression.getValue(String.class)); } ... }}初步结论
跟下代码寻找context类型,发现可能存在漏洞
xxxxxxxxxxpublic <T extends Parameters<?, ?>> EvaluationContext getEvaluationContext(T parameters, Object[] parameterValues) { StandardEvaluationContext evaluationContext = this.delegate.getEvaluationContext(parameterValues); evaluationContext.setVariables(collectVariables(parameters, parameterValues)); return evaluationContext;}进阶分析
阅读源码后,发现了类似于MyBatis那种在注解中写SQL的方式
xxxxxxxxxx("SELECT * FROM table WHERE xxx = #{SPEL} AND xxx > #{SPEL}")List<Demo> SQL(Params);因为只进行一次expression.getValue所以就算SQL语句中存在#{SPEL}也不会被解析,所以可以重点关注下其中for (Expression expression : expressions)中的expressions是怎么获取的
xxxxxxxxxxprivate Expression[] detectExpressions(String sql) { Expression expression = this.expressionParser.parseExpression(sql, ParserContext.TEMPLATE_EXPRESSION); if (expression instanceof LiteralExpression) { return new Expression[] {expression}; } else if (expression instanceof CompositeStringExpression) { return ((CompositeStringExpression) expression).getExpressions(); } ...}经过调试分析,不难发现这里会解析a #{b}这样的SPEL为[a,b]这样,并没有什么操作空间(假设有什么操作空间,也是由开发者自定义SQL语句的,所以没有意义做进一步分析和绕过)
但假如这里采用了递归或者多次的SPEL解析并获取值会怎样呢?