KoreanHackerTeam
Moderator
Java 反序列化漏洞系列-1
1 序列化与反序列化基础
직렬화는 Java 운영 환경과 Java 객체를 분리하는 수단으로 여러 플랫폼과 지속적인 객체 저장소 간의 통신을 효과적으로 실현할 수 있습니다.1.1 相关方法
ObjectOutputStream 클래스의 writeObject () 메소드는 직렬화를 구현할 수 있습니다. Java의 표준 협약에 따르면 파일에 .ser extension을 제공하십시오.ObjectInputStream 클래스의 readObject () 메소드는 사막화에 사용됩니다.
1.2 序列化前提
java.io.serializable 인터페이스를 구현할 수 있도록 구현되며 모든 속성은 직렬화 가능해야합니다 (일시적 키워드로 수정 된 속성 제외하고 직렬화 프로세스에 참여하지 않음).1.3 漏洞成因
직렬화 및 사막화 자체에 문제가 없습니다. 그러나, 입력 사형화 된 데이터를 사용자가 제어 할 수있는 경우, 공격자는 악의적 인 입력을 구성 할 수 있으므로 사막화가 예상치 못한 객체를 생성하고 프로세스에서 구성된 코드를 실행할 수 있습니다.사제화 페이로드 생성 도구 : https://github.com/frohoff/ysoserial/
2 漏洞基本原理
2.1 序列化
직렬화 된 데이터는 마법의 두 바이트로 시작합니다. 다음은 버전 번호 0005의 2 바이트 데이터입니다. 또한 클래스 이름, 멤버 변수의 유형 및 수 등도 포함됩니다.
직렬화 된 데이터 스트림은 마법 번호와 버전 번호로 시작합니다. 이 값은 ObjectOutputStream이 호출되면 WritesTreamEader 메소드에 의해 작성됩니다.
1
2
3
4
5
6
보호 된 void writestreamheader ()는 ioexception {
//stream_magic (2 바이트)0xaced
bout.writeshort (stream_magic);
//stream_version (2 바이트) 5
bout.writeshort (stream_version);
}
2.2 反序列化
Java 프로그램에서 Class ObjectInputStream의 readObject 메소드는 데이터 스트림을 객체로 사로화하는 데 사용됩니다.readObject () 메소드는 사막화 취약성에서 중요한 역할을합니다. readObject () 메소드가 다시 작성되면 클래스를 제조 할 때 readObject () 메소드가 호출됩니다. 메소드가 부적절하게 기록되면 악성 코드의 실행을 트리거 할 수 있습니다.
좋다:
1
2
3
4
5
6
공공 계급 악의는 직렬화 가능한 {
공개 문자열 CMD;
private void readObject (java.io.objectinputStream 스트림)는 예외 {{
stream.DefaultReadoBject ();
runtime.getRuntime (). exec (cmd);
}
그러나 사막화 취약점의 구성은 실제로는 비교적 복잡하며 Java 반사와 같은 일부 Java 기능의 도움이 필요합니다.
3 Java 反射
3.1 Java 反射定义
모든 클래스의 경우이 클래스의 모든 속성 및 방법을 얻을 수 있습니다. 모든 객체의 경우, 그 방법과 속성을 호출 할 수 있습니다. 정보를 동적으로 얻고 동적으로 호출하는 객체 방법을 Java 언어의 반사 메커니즘이라고합니다.반사는 대부분의 언어가 가지고있는 기능입니다. 물체는 반사를 통해 클래스를 얻을 수 있습니다. 클래스는 반사를 통해 모든 방법 (개인 포함)을 얻을 수 있으며, 얻은 방법을 직접 호출 할 수 있습니다. 요컨대, 반사를 통해动态特性을 Java에 부착 할 수 있습니다.
Java 언어에는 PHP와 같은 유연한动态特性이 많지 않지만 반사를 통해 특정 효과를 달성 할 수 있습니다. 예를 들어, 다음 코드에서, 들어오는 매개 변수 값이 불확실한 경우,이 함수의 특정 함수는 알 수 없습니다.
1
2
3
4
public void execute (string classname, String MethodName)는 예외 {
클래스 Clazz=class.forname (className);
clazz.getMethod (MethodName) .invoke (clazz.newinstance ());
}
Java로 정의 된 클래스는 그 자체로 객체, 즉 Java.lang.class 클래스의 인스턴스입니다. 이 인스턴스를 클래스 객체라고합니다
클래스 객체는 Java 응용 프로그램 실행의 클래스 및 인터페이스를 나타냅니다.
클래스 객체에는 공개 생성자 방법이 없으며 Java 가상 머신에 의해 자동으로 구성됩니다.
클래스 객체는 여러 생성자, 속성 수 및 일반 방법이있는 등급 자체에 대한 정보를 제공하는 데 사용됩니다.
클래스의 방법과 속성을 얻으려면 먼저 해당 클래스의 대상을 가져와야합니다.
3.2 获取类对象
이제 개인 수업이 있다고 가정합니다.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
스물 하나
스물 두 번째
공공 수업 담당자는 직렬화 가능한 {를 구현합니다.
개인 문자열 이름;
개인 정수 시대;
공개 사람 (문자열 이름, 정수 시대) {
this.name=이름;
this.age=age;
}
public void setName (문자열 이름) {
this.name=이름;
}
공개 문자열 getName () {
이 this.name;
}
공개 정수 getage () {
귀환 연령;
}
공개 무효 세트 (정수 시대) {
this.age=age;
}
}
일반적 으로이 클래스의 객체를 얻는 방법에는 세 가지가 있습니다.
class.forname ( 'com.geekby.person')
person.class
새로운 사람 (). getClass ()
가장 일반적으로 사용되는 것은 첫 번째 유형이며, 문자열, 즉 클래스의 전체 경로 이름을 사용하여 클래스 객체를 얻을 수 있습니다.
3.3 利用类对象创建对象
새로 객체를 직접 작성하는 것과 달리, 반사는 먼저 클래스 객체를 얻은 다음 클래스 객체를 통해 생성자 객체를 얻은 다음 생성자 객체를 통해 객체를 만듭니다.1
2
3
4
5
6
7
8
9
10
11
12
13
패키지 com.geekby;
import java.lang.reflect.*;
공개 클래스 CreateObject {
public static void main (string [] args)은 예외 {
클래스 personclass=class.forname ( 'com.geekby.person');
생성자 생성자=personclass.getConstructor (String.class, integer.class);
Person P=(사람) 생성자. Newinstance ( 'Geekby', 24);
System.out.println (p.getName ());
}
}
방법
설명
getConstructor (클래스… ParameterTypes)
이 클래스에서 매개 변수 유형과 일치하는公有구조 방법을 얻으십시오.
getConstructors ()
이 클래스의 모든 공개 생성자를 얻으십시오
getDeclaredConstructor (클래스… ParameterTypes)
이 클래스에서 매개 변수 유형과 일치하는 생성자 메소드를 얻습니다.
getDeclaredConstructors ()
이 클래스의 모든 생성자를 얻으십시오
3.4 利用反射调用方法
12
3
4
5
6
7
8
9
10
11
공개 클래스 CallMethod {
public static void main (string [] args)은 예외 {
클래스 personclass=class.forname ( 'com.geekby.person');
생성자 생성자=personclass.getConstructor (String.class, integer.class);
Person P=(사람) 생성자. Newinstance ( 'Geekby', 24);
메소드 m=personclass.getDeclaredMethod ( 'setName', String.class);
M.Invoke (P, 'Newgeekby');
System.out.println (p.getName ());
}
}
방법
설명
getMethod (문자열 이름, 클래스… ParameterTypes)
공개적으로 소유 한 클래스를 얻는 방법
getMethods ()
이 클래스의 모든 공개 방법을 얻으십시오
getDeclaredMethod (문자열 이름, 클래스… ParameterTypes)
이 클래스의 방법을 얻으십시오
getDeclaredMethods ()
이 클래스의 모든 방법을 얻으십시오
3.5 通过反射访问属性
12
3
4
5
6
7
8
9
10
11
12
13
공개 클래스 AccessIttribute {
public static void main (string [] args)은 예외 {
클래스 personclass=class.forname ( 'com.geekby.person');
생성자 생성자=personclass.getConstructor (String.class, integer.class);
Person P=(사람) 생성자. Newinstance ( 'Geekby', 24);
//이름은 사유지입니다. 먼저 액세스하도록 설정해야합니다.
필드 f=personclass.getDeclaredfield ( 'name');
f.setAccessible (true);
F. 세트 (p, 'newgeekby');
System.out.println (p.getName ());
}
}
방법
설명
getfield (문자열 이름)
공공 재산 대상을 얻으십시오
getfields ()
모든 공공 재산 대상을 얻습니다
getDeclaredfield (문자열 이름)
특정 속성 쌍을 얻습니다
getDeclaredFields ()
모든 속성 객체를 얻습니다
3.6 利用反射执行代码
12
3
4
5
6
7
8
9
10
공개 클래스 exec {
public static void main (string [] args)은 예외 {
//java.lang.runtime.getRuntime().exec('Calc ');
클래스 runtimeClass=class.forname ( 'java.lang.runtime');
//getRuntime은 정적 메소드이며 호출 할 때 호출 객체를 전달할 필요가 없습니다.
개체 런타임=runtimeClass.getMethod ( 'getRuntime'). 호출 (null);
runtimeClass.getMethod ( 'exec', string.class) .invoke (런타임, 'Open /system/applications/calculator.app');
}
}
위의 코드에서 Java의 반사 메커니즘은 코드 의도를 문자열 형태로 반영하는 데 사용되므로 문자열이어야 할 속성이 코드 실행을위한 논리가 되며이 메커니즘은 후속 취약점의 전제 조건입니다.
팁
Invoke의 기능은 메소드를 실행하는 것입니다. 첫 번째 매개 변수는 :입니다.
메소드가 일반 메소드 인 경우 첫 번째 매개 변수는 클래스 객체입니다.
메소드가 정적 메소드 인 경우 첫 번째 매개 변수는 클래스 또는 null입니다.
또한 일반적으로 사용되는 또 다른 명령을 실행하는 방법 인 ProcessBuilder는 반사를 통해 생성자를 얻은 다음 start ()을 호출하여 명령 :을 실행합니다.
1
2
클래스 Clazz=class.forname ( 'java.lang.processBuilder');
((ProcessBuilder) Clazz.getConstructor (list.class) .newinstance (Arrays.asList ( 'calc.exe')). start ();
ProcessBuilder에 두 개의 생성자가 있는지 확인하려면 문서를 확인하십시오.
공개 프로세스 빌더 (ListString 명령)
공개 프로세스 빌더 (문자열 . 명령)
위의 반사 방법은 첫 번째 형태의 생성자를 사용합니다.
그러나 위의 페이로드는 자바로 캐스팅을 사용합니다. 때로는 취약점을 악용 할 때 (표현의 맥락에서) 그러한 구문이 없습니다. 따라서 시작 방법은 여전히 반사를 사용하여 실행해야합니다.
1
2
3
클래스 clazz=class.forname ( 'java.lang.processBuilder');
clazz.getMethod ( 'start'). 호출 (clazz.getConstructor (list.class) .newinstance (arrays.aslist ( 'open', '/system/applications/calculator.app')));
위의 두 번째 생성자를 호출하는 방법은 무엇입니까?
가변 길이 매개 변수의 경우, 컴파일 할 때 Java가 배열로 컴파일됩니다. 즉, 다음 두 가지 쓰기 방법이 하단에서 동일 함을 의미합니다.
1
2
public void hello (string [] names) {}
public void hello (문자열 . 이름) {}
따라서 반사의 경우 목적 함수에 가변 길이 매개 변수가 포함 된 경우 배열로 전달하십시오.
1
2
classClazz=class.forname ( 'java.lang.processBuilder');
clazz.getConstructor (String []. class)
NewInstance를 호출 할 때 함수 자체는 변수 길이 매개 변수를 수신합니다.

ProcessBuilder에 전달 된 가변 길이 매개 변수는 또한 가변 길이 매개 변수이며,이 둘은 2 차원 배열로 겹쳐져 있으므로 전체 페이로드는 다음과 같습니다.
1
2
3
클래스 clazz=class.forname ( 'java.lang.processBuilder');
Clazz.getMethod ( 'start'). 호출
3.7 反序列化漏洞与反射
보안 연구에서 반사를 사용하는 주요 목적 중 하나는 특정 샌드 박스를 우회하는 것입니다. 예를 들어, 컨텍스트에 정수 번호 만있는 경우 명령을 실행할 수있는 런타임 클래스를 얻는 방법입니다.예를 들어, 다음과 같을 수 있습니다 (pseudocode) : 1. GetClass (). Forname ( 'java.lang.runtime').
4 DNSURL gadget 分析
4.1 调用链
12
3
4
* hashmap.readobject ()
* hashmap.putVal ()
* hashmap.hash ()
* url.hashcode ()
유효 탑재량:
1
2
3
4
5
6
7
8
9
10
Hashmap ht=new Hashmap ();
url u=new URL ( 'dnslog');
//직렬화 중에 요청이 전송되지 않으므로 사막화 감지시 잘못 판단을 방지하지 않습니다.
클래스 C=u.getClass ();
필드 f=c.getDeclaredfield ( 'Hashcode');
f.setAccessible (true);
F. 세트 (u, 1234);
ht.put (u, 'geekby');
//해시 코드를 -1로 변경하고 복원하십시오
F. 세트 (u, -1);
4.2 分析
먼저 해시 맵의 readObject 메소드를 확인하십시오
339 행 : Putval 메소드를 호출하기 전에 해시 메소드가 호출되고 소스 코드를 확인합니다.

줄 899-903 : key==null이면 해시 코드가 0에 할당됩니다. 키가 존재하면 키의 해시 코드 메소드가 호출됩니다.
이 가제트에서 키는 URL 객체입니다. 다음으로 URL의 해시 코드 방법에 대한 후속 조치.

URL 클래스의 해시 코드는 매우 간단합니다. 해시 코드가 -1이 아닌 경우 해시 코드를 반환합니다. 페이로드를 직렬화 할 때 해시 코드를 -1로 설정 해야하는 이유는 해시 코드 메소드에 들어가는 것을 방지 한 다음 판단에 영향을 미치는 DNS 요청을 보내는 것입니다.
Hashcode==-1 인 경우 해시 코드 핸들러 메소드가 호출됩니다. 이 클래스의 정의는 URL 생성자에 있으며 주로 체계를 기반으로 핸들러로 사용할 클래스를 결정합니다. URLStreamHandler 클래스는 다음과 같습니다. UrlstreamHandler의 해시 코드 방법을 후속 조치하십시오.

359 행에서 GethostAddress에 전화하여 도메인 이름에 해당하는 IP를 얻습니다.

DNSURL 체인은이 장소를 사용하여 DNSLOG 전송 요청을 트리거합니다.
参考
PHITH0N Java 채팅 시리즈Java Desorialization 취약성의 원리에 대한 분석
Java Desorialization 취약점은 처음부터 마감까지
0에서 Java Desorialization 취약성을 배우십시오
Java Desorialization 취약성을 이해하십시오
Java Desorialization 체인 완료 계획