最近spring-cloud-gateway
出现RCE
漏洞,主要是SpEL
导致的
于是我随便找了下spring-cloud
其他组件可能的SpEL
漏洞
简单审计了几个框架,暂时写了三个组件的分析
可能利用点(1)
org/springframework/cloud/gateway/support/ShortcutConfigurable#getValue
xxxxxxxxxx
static 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
xxxxxxxxxx
private 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
xxxxxxxxxx
protected String getClusterName(Object object) {
StandardEvaluationContext context = new StandardEvaluationContext(object);
Object value = this.clusterNameExpression.getValue(context);
if (value != null) {
return value.toString();
}
return null;
}
初步结论
可能存在利用,因为使用了StandardEvaluationContext
允许执行命令
进阶分析
阅读源码,不难发现表达式来自于配置文件
xxxxxxxxxx
protected 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获取该实例名称时触发
xxxxxxxxxx
turbine
app-config test
cluster-name-expression T(java.lang.Runtime).getRuntime().exec(new String(new byte 0x63 0x61 0x6c 0x63 ))
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
定义如下,可以利用
xxxxxxxxxx
private StandardEvaluationContext context;
// init
this.context = new StandardEvaluationContext();
进阶分析
这是一个表名
xxxxxxxxxx
Expression 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
xxxxxxxxxx
private 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
类型,发现可能存在漏洞
xxxxxxxxxx
public <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
是怎么获取的
xxxxxxxxxx
private 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
解析并获取值会怎样呢?