Java安全攻防之Spring Cloud Gateway攻击Redis

admin 2024年4月2日08:22:13评论6 views字数 6678阅读22分15秒阅读模式

1

概述

案例来源于去年二月的一次攻防项目,在进入到目标公司外网nacos后,通过翻阅nacos中的配置文件发现存在gateway路由配置文件,以及内网redis地址、密码。
尝试在nacos中通过编辑路由配置文件,通过新增路由以及配置AddResponseHeader过滤器执行SPEL表达式实现RCE。
测试后发现由于目标是高版本的Spring Cloud修复了该漏洞只能执行简单SPEL表达式无法实现RCE,在最后我们通过Gateway攻击了内网的redis实现了RCE。
本文以spring-cloud-starter-gateway3.1.5测试。

2

漏洞利用

以actuator/gateway来介绍该利用方法(和nacos原理差不多,通过heapdump也能获取到redis地址密码)。

Java安全攻防之Spring Cloud Gateway攻击Redis

spring-cloud-starter-gateway#3.0.7、3.1.1修复了CVE-2022-22947,使用GatewayEvaluationContext替换了StandardEvaluationContext。

public static class GatewayEvaluationContext implements EvaluationContext {    private final BeanFactoryResolver beanFactoryResolver;    private final SimpleEvaluationContext delegate;    public GatewayEvaluationContext(BeanFactory beanFactory) {        this.beanFactoryResolver = new BeanFactoryResolver(beanFactory);        Environment env = (Environment)beanFactory.getBean(Environment.class);        boolean restrictive = (Boolean)env.getProperty("spring.cloud.gateway.restrictive-property-accessor.enabled", Boolean.class, true);        if (restrictive) {            this.delegate = SimpleEvaluationContext.forPropertyAccessors(new PropertyAccessor[]{new RestrictivePropertyAccessor()}).withMethodResolvers(new MethodResolver[]{(context, targetObject, name, argumentTypes) -> {                return null;            }}).build();        } else {            this.delegate = SimpleEvaluationContext.forReadOnlyDataBinding().build();        }    }

在GatewayEvaluationContext中,生成了SimpleEvaluationContext以及限制了属性的访问和方法的调用。导致从3.0.7开始只能执行一些简单的的SPEL表达式,无法再实现RCE。

题外话,在实战中,经常会遇到通过/actuator/gateway/routes能够发现一些其他攻击者新增的路由,但是自己新增后refresh一直不存在。这种通常是因为被其他攻击者插入了错误路由,导致在refresh的时候一直异常没法新增。

比如其他攻击者在之前已经新增了一个命令执行的路由(或者语法错误的),

Java安全攻防之Spring Cloud Gateway攻击Redis

然后此时我们去查看路由,是不存在spel这个恶意路由的。

Java安全攻防之Spring Cloud Gateway攻击Redis

因为版本比较高,漏洞已经修复的原因,导致refresh的时候直接出了异常,所以没法新增路由。

Java安全攻防之Spring Cloud Gateway攻击Redis

这个时候我们需要先将存在错误的路由给删除掉。

/actuator/gateway/routes 没法看到恶意的路由,通过/actuator/gateway/routedefinitions 接口可以查看到所有的。

Java安全攻防之Spring Cloud Gateway攻击Redis

然后将这个错误路由删除掉,即可正常新增路由了。

Java安全攻防之Spring Cloud Gateway攻击Redis

再回到漏洞利用,没法通过SPEL来实现RCE,并且已知了内网redis地址以及密码,很容易想到能否通过新增一个路由来指向redis地址,然后来攻击redis。

虽然gateway新增的路由仅支持http/https协议,但是因为我们能够完全的控制请求包,意味着可以随意的注入新行,按理也能正常攻击redis。

首先创建一个指向redis的路由,然后刷新,访问路由。

{      "id": "redis",      "predicates": [        "Path=/xxxxxxxx/**"    ],      "filters": [],      "uri": "http://localhost:6379/",      "order": 0    }

Java安全攻防之Spring Cloud Gateway攻击Redis

在请求gateway的路由后,gateway确实将完整的请求转发到了redis端口上。

Java安全攻防之Spring Cloud Gateway攻击Redis

然后正常来说,只需要在一个新行里面注入slaveof xx xx即可实现RCE,此时又有了新的问题。

io.netty.handler.codec.DefaultHeaders

public T addObject(K name, Object value) {    return this.add(name, this.valueConverter.convertObject(ObjectUtil.checkNotNull(value, "value")));}
public T add(K name, V value) {    this.nameValidator.validateName(name);    ObjectUtil.checkNotNull(value, "value");    int h = this.hashingStrategy.hashCode(name);    int i = this.index(h);    this.add0(h, i, name, value);    return this.thisT();}

在Spring Cloud Gateway解析重组request的header时,首先通过:分割得到header的name和value,然后调用addObject方法来添加请求头,在该方法中通过validateName方法来验证header name是否合法,this.valueConverter.convertObject方法来转换header value。

Java安全攻防之Spring Cloud Gateway攻击Redis

验证了header name中是否存在空白符,如果存在空白符就直接抛出了异常。所以没法在header中插入slaveof xxx来实现RCE。

虽然没法在header name中插入redis语句,但是又很容易想到request body里面肯定不会存在限制,可以随意的插入redis语句。

Java安全攻防之Spring Cloud Gateway攻击Redis

成功在请求包中插入了redis语句。

int processCommand(client *c) {    if (!scriptIsTimedout()) {        /* Both EXEC and scripts call call() directly so there should be         * no way in_exec or scriptIsRunning() is 1.         * That is unless lua_timedout, in which case client may run         * some commands. */        serverAssert(!server.in_exec);        serverAssert(!scriptIsRunning());    }    /* in case we are starting to ProcessCommand and we already have a command we assume     * this is a reprocessing of this command, so we do not want to perform some of the actions again. */    int client_reprocessing_command = c->cmd ? 1 : 0;    /* only run command filter if not reprocessing command */    if (!client_reprocessing_command) {        moduleCallCommandFilters(c);        reqresAppendRequest(c);    }    /* Handle possible security attacks. */    if (!strcasecmp(c->argv[0]->ptr,"host:") || !strcasecmp(c->argv[0]->ptr,"post")) {        securityWarningCommand(c);        return C_ERR;    }

但是很容易想到一个问题,redis在很久以前逐行处理命令的时候,就会判断该行中是否含有host: 或者 post关键字,如果含有则会直接返回异常不再继续处理后续的命令。

POST这个关键字没影响,因为我可以随意修改请求包,GET+request body,但是host:这个关键字经过测试,就算我在请求包中删除了host头,经过了gateway的解析重组后它会自动的添加上host头导致没法解决。

Java安全攻防之Spring Cloud Gateway攻击Redis

在这里卡了几十分钟,一直没法解决。后面想到,既然之前的spel漏洞利用是通过新增filter AddResponseHeader来实现的,那么有没有什么其他的filter能帮助我删除掉host头。

https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/gatewayfilter-factories/removerequestheader-factory.html

翻阅文档发现还真有一个removerequestheader的filter,最后经过测试发现并不能实现利用,在gateway解析重组的流程中,是先把filter链作用完后再添加了host header,导致无法实现利用。

然后又只能继续翻阅文档看看还有没有什么好玩的filter,

https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway-server-mvc/filters/addrequestheader.html

发现存在addrequestheader filter,能够想到如果在filter链中重组header时,如果gateway没有处理好crlf也可能利用。

addrequestheader组装header最后调用和之前提到的是一致的。

io.netty.handler.codec.http.DefaultHttpHeaders#addObject,

public T addObject(K name, Object value) {    return this.add(name, this.valueConverter.convertObject(ObjectUtil.checkNotNull(value, "value")));}

在this.valueConverter.convertObject中,HeaderValueConverterAndValidator对header value进行校验。

private static final class HeaderValueConverterAndValidator extends HeaderValueConverter {    static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();    private HeaderValueConverterAndValidator() {        super(null);    }    public CharSequence convertObject(Object value) {        CharSequence seq = super.convertObject(value);        int state = 0;        for(int index = 0; index < seq.length(); ++index) {            state = validateValueChar(seq, state, seq.charAt(index));        }        if (state != 0) {            throw new IllegalArgumentException("a header value must not end with '\r' or '\n':" + seq);        } else {            return seq;        }    }    private static int validateValueChar(CharSequence seq, int state, char character) {        if ((character & -16) == 0) {            switch (character) {                case 'u0000':                    throw new IllegalArgumentException("a header value contains a prohibited character 'u0000': " + seq);                case 'u000b':                    throw new IllegalArgumentException("a header value contains a prohibited character '\v': " + seq);                case 'f':                    throw new IllegalArgumentException("a header value contains a prohibited character '\f': " + seq);            }        }        switch (state) {            case 0:                switch (character) {                    case 'n':                        return 2;                    case 'r':                        return 1;                }            default:                return state;            case 1:                if (character == 'n') {                    return 2;                }                throw new IllegalArgumentException("only '\n' is allowed after '\r': " + seq);            case 2:                switch (character) {                    case 't':                    case ' ':                        return 0;                    default:                        throw new IllegalArgumentException("only ' ' and '\t' are allowed after '\n': " + seq);                }        }    }

从该方法中可以看出,如果header value中只n是不行的,但是只要t在n后面就可以,t不会影响redis命令的解析。

Java安全攻防之Spring Cloud Gateway攻击Redis

Java安全攻防之Spring Cloud Gateway攻击Redis

n抛出了异常,修改为 "value": "ntaaaa" 

Java安全攻防之Spring Cloud Gateway攻击Redis

成功注入了新行,并且在host之前。

Java安全攻防之Spring Cloud Gateway攻击Redis

成功执行了redis命令,最终RCE了目标系统。

3

总结

Spring Cloud Gateway 3.1.6修复了CRLF注入,在添加header的方法中新增了validateValue方法对header value进行校验,不再允许存在换行等空白符。

public T add(K name, V value) {    this.validateName(this.nameValidator, true, name);    this.validateValue(this.valueValidator, name, value);    ObjectUtil.checkNotNull(value, "value");    int h = this.hashingStrategy.hashCode(name);    int i = this.index(h);    this.add0(h, i, name, value);    return this.thisT();}

在攻击redis时,除了通过slaveof等方式rce(需要出网,以及版本不能太高),还可以尝试通过set token来利用。目前很多系统的鉴权都通过判断redis中是否含有对应的token实现,通过set token可以通过鉴权进入到目标系统中RCE或者配合fastjson等其他反序列化利用。

Java安全攻防之Spring Cloud Gateway攻击Redis

Java安全攻防之Spring Cloud Gateway攻击Redis

spring gateway处理{{}}此类数据时,会尝试解析,所以需要通过append实现。

当然除了攻击redis这种方式,也可以通过新增内网地址gateway尝试攻击内网http应用漏洞,但是由于不知道内网情况难度比较大。

往期推荐

Java安全攻防之Spring Cloud Gateway攻击Redis

Java安全攻防之Spring Cloud Gateway攻击Redis

原文始发于微信公众号(边界无限):Java安全攻防之Spring Cloud Gateway攻击Redis

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年4月2日08:22:13
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java安全攻防之Spring Cloud Gateway攻击Redishttps://cn-sec.com/archives/2621234.html

发表评论

匿名网友 填写信息