제목 : Java 로컬 명령 실행 취약성

Java 本地命令执行漏洞​

背景​

JDK는 로컬 시스템 명령의 실행을위한 기본 기능을 제공하며 공격자는이 취약점을 통해 대상 서버에서 임의의 시스템 명령을 실행할 수 있습니다. Java에서 시스템 명령을 실행하는 데 사용할 수있는 방법에는 API가 포함됩니다.
java.lang.runtime
java.lang.processBuilder
java.lang.unixprocess/processimpl.

Runtime 命令执行​

exec(String command)​

Java에서 Java.lang.runtime 클래스의 Exec 방법은 일반적으로 로컬 시스템 명령을 실행하는 데 사용됩니다.
다음 프로그램 실행 명령을 예로 들어보십시오.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
패키지 com.geekby;
import java.io.bufferedReader;
import java.io.ioexception;
import java.io.inputstream;
import java.io.inputStreamReader;
공개 클래스 메인 {
public static void main (String [] args)은 ioexception {
문자열 cmd='';
프로세스 p=runtime.getRuntime (). exec ( 'ping 127.0.0.1' + cmd);
inputStream fis=p.getInputStream ();
bufferedReader br=new bufferedReader (new inputStreamReader (FIS));
문자열 라인=null;
while ((line=br.readline ())!=null) {
System.out.println (line);
}
}
}
위의 프로그램은 Ping 명령을 성공적으로 실행할 수 있습니다. 공격자가 이제 CMD 매개 변수를 제어하고 명령 스 플라이 싱을 통해 다른 명령을 실행할 수 있다고 가정합니다. CMD=';
202202131520032.png-water_print

명령 실행이 실패한 이유를 탐색하려면 먼저 프로그램 통화 스택을 추적하십시오.
1
2
3
4
5
6
7
8
Create:-1, ProcessImpl (java.lang)
init:386, processimpl (java.lang)
start:137, processimpl (java.lang)
Start:1029, ProcessBuilder (java.lang)
exec:620, 런타임 (java.lang)
exec:450, 런타임 (java.lang)
exec:347, 런타임 (java.lang)
main:8, main (com.geekby)
통화 체인을 따르면 EXEC 메소드가 마침내 과부하 된 함수 EXEC (String Command, String [] Envp, File Dir)으로 호출된다는 것을 알 수 있습니다.
202202131512055.png-water_print

명령이 문자열로 기능을 입력하고 종료 한 후 먼저 StringTokenizer를 통해 처리되고 \ t \ n \ r \ f에 따라 전달 된 명령을 나눕니다.
202202131527906.png-water_print

처리 후, ProcessBuilder는 마침내 들어오는 CMDarray를 처리하도록 인스턴스화됩니다. 여기에서 런타임의 기본 계층이 있습니다. getRuntime.exec ()는 실제로 ProcessBuilder입니다.
202202131528937.png-water_print

CMDARRY FIRST 매개 변수 CMDARRY [0]이 실행되는 명령으로 사용되는 ProcessBuilder 클래스의 시작 방법을 계속 추적하고 후속 CMDARRY [1:]을 명령 실행 매개 변수로 바이트 어레이 아르 블록으로 변환합니다.
202202131534214.png-water_print

현재 Prog는 실행되는 명령입니다. ArgBlock은 모든 매개 변수 127.0.0.1; PWD가 Ping에 전달되었습니다. StringTokenizer가 문자열을 처리 한 후 명령 실행의 의미가 변경됩니다. 세미콜론은 명령 분리기로 사용할 수 없으며 명령 주입이 구현됩니다.

exec(String cmdarray[])​

EXEC 기능의 과부하 기능은 Java 런타임 패키지에 존재하며 매개 변수 유형은 문자열 배열입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
패키지 com.geekby;
import java.io.bufferedReader;
import java.io.ioexception;
import java.io.inputstream;
import java.io.inputStreamReader;
공개 클래스 메인 {
public static void main (String [] args)은 ioexception {
문자열 cmd='; pwd';
프로세스 p=runtime.getRuntime (). exec (new String [] { '/bin/sh', '-c', cmd});
inputStream fis=p.getInputStream ();
bufferedReader br=new bufferedReader (new inputStreamReader (FIS));
문자열 라인=null;
while ((line=br.readline ())!=null) {
System.out.println (line);
}
}
}
EXEC 기능의 기본 코드에 대한 후속 조치. 배열이 직접 전달되므로 String은 StringTokenizer에 의해 처리되지 않습니다.
202202131613486.png-water_print

마지막으로 Unixprocess 방법에 대한 후속 조치
202202131617663.png-water_print

현재 Prog는 실행될 명령 /빈 /SH이며, ArgBlock은 Ping -C \ x00'ping 127.0.0.1; pwd '로 전달되는 모든 매개 변수입니다.
따라서 매개 변수를 제어 할 수있는 경우 명령 주입을 명령 분할 형태로 수행 할 수 없습니다. 특정 상황에 따라 Base64 인코딩을 수행 할 수 있습니다.

load()​

Java 런타임 패키지에는 외부 라이브러리를로드하여 명령을 실행하는 다른 형태가 있습니다. Linux의 SO 파일 및 Windows 아래의 DLL 파일과 같은 동적 링크 라이브러리를로드함으로써.
1
msfvenom -p windows/x64/exec -Platform win -a x64 cmd=calc.exe exitfunc=스레드 -f dll calc.dll
테스트 코드 :
1
2
3
4
5
6
공개 클래스 rce {
public static void main (String [] args) {
런타임 rt=runtime.getRuntime ();
Rt.load ( 'D: \\ calc.dll');
}
}

ProcessBuilder​

ProcessBuilder 클래스를 사용하여 프로세스 생성, 프로세스 빌더 인스턴스를 작성하고 프로세스의 이름과 필요한 매개 변수를 지정합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
패키지 com.geekby;
import java.io.ioexception;
공개 클래스 메인 {
public static void main (String [] args)은 ioexception {
문자열 cmd='; pwd';
ProcessBuilder PB=새로운 프로세스 빌더 ( 'Ping', '127.0.0.1', CMD);
프로세스 프로세스=pb.start ();
inputStream fis=process.getInputStream ();
bufferedReader br=new bufferedReader (new inputStreamReader (FIS));
문자열 라인=null;
while ((line=br.readline ())!=null) {
System.out.println (line);
}
}
}
콜 스택 :
1
2
3
4
5
Create:-1, ProcessImpl (java.lang)
init:386, processimpl (java.lang)
start:137, processimpl (java.lang)
Start:1029, ProcessBuilder (java.lang)
main:8, main (com.geekby)
통화 스택을 분석하면 기본 레이어에서 호출 호출의 논리가 runtime.getRuntime.exec의 논리와 유사하므로 여기에서 자세히 설명하지 않을 것입니다.

ProcessImpl​

ProcessImpl의 생성자는 사유 속성이므로 정적 방법 시작을 반사로 호출해야합니다.
202202131653031.png-water_print

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
패키지 com.geekby;
import java.io.ioexception;
import java.lang.reflect.invocationTargetexception;
import java.lang.reflect.method;
java.util.map import;
공개 클래스 메인 {
public static void main (string [] args)은 ioexception, classnotfoundException, invocationTargetexception, 불법 행위, nosuchmethodexception을 던졌습니다.
클래스 clazz=class.forname ( 'java.lang.processimpl');
메소드 시작=clazz.getDeclaredMethod ( 'start', String []. class, map.class, string.class, processBuilder.Redirect []. class, boolean.class);
start.setAccessible (true);
start.invoke (null, (Object) new String [] { 'open', '-a', 'calculator'}, null, null, null, false);
}
}
콜 스택 :
1
2
3
4
5
6
7
8
Create:-1, ProcessImpl (java.lang)
init:386, processimpl (java.lang)
start:137, processimpl (java.lang)
invoke0:-1, nativeMethodaccessorimpl (sun.reflect)
Invoke:62, NativeMethodaccessorimpl (Sun.Reflect)
invoke:43, methodaccessorimpl 위임 (sun.reflect)
invoke:498, 메소드 (java.lang.reflect)
main:14, Main (com.geekby)

防御​

로컬 명령 실행은 매우 위험이 높은 취약점이며 항상 신중하게 사용해야합니다. 로컬 시스템 명령이 비즈니스에 사용되면 들어오는 매개 변수의 수신을 금지해야합니다. 대부분의 경우 공격자는 특정 취약점 (예 : Struts2, Desorialization 등)을 활용하여 비즈니스 시스템을 공격하고 궁극적으로 Java Local Command Execution을 사용하여 웹 서버를 제어하는 목적을 달성합니다. 이 경우 사용자가 실행 한 시스템 명령은 더 이상 제어되지 않습니다. 보안 관리자 규칙을 구성하여 명령 실행을 제한하는 것 외에도 RASP를 사용하여 로컬 명령 실행을 방어 할 수있어보다 편리하고 신뢰할 수 있습니다.

RASP 防御 Java 本地命令执行​

기본 Java에서 시스템 명령을 실행하기위한 API는 java.lang.unixProcess/ProcessImpl#ForkandExec 메소드입니다. ForkandExec은 기본 방법입니다. 연결하려면 에이전트 메커니즘에서 CAN-Set-Native-Method-Prefix를 사용하여 다음과 같은 ForkandExec의 별칭을 설정 한 다음 __rasp__forkandexec 메소드 로직을 다시 작성하여 원래 ForkandExec 메소드 후크를 구현해야합니다.
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
/**
* Hook Windows System ProcessImpl 클래스 생성자
*/
@raspmethodhook (
classname='java.lang.processimpl', methodname=constructor_init,
MethodArgsdesc='.*', MethodDescRegexp=true
))
public static class processimplhook 확장 raspmethodadvice {
@보수
공개 rasphookresult? OnMethodenter () {
노력하다 {
문자열 [] 명령=null;
//JDK9+의 API 매개 변수는 다릅니다!
if (getArg (0) string []) {
명령=getarg (0);
} else if (getArg (0) byte []) {
명령=new String [] {new String ((byte []) getArg (0))};
}
//실행 된 명령의 합법성을 감지합니다
LocalCommandHookHandler.processCommand를 반환합니다 (명령, getThisObject (), this);
} catch (예외 e) {
rasplogger.log (agent_name + 'processimpl Exception :' + e, e);
}
새로운 rasphookresult? (반환);
}
}
 
뒤로
상단