[안드로이드 모의해킹] 디버그 로그 내 중요정보 노출

1. 개요

 

안드로이드 앱 개발 시, 디버그 로그는 앱 동작을 추적하고 개발 단계에서의 버그를 수정하는 데 사용한다. 디버그 로그는 앱의 실행 중에 발생하는 이벤트, 상태, 변수 값 등을 모니터링하고 기록할 수 있는 도구로 앱의 동작과 문제 해결에 도움을 준다.

 

 

안드로이드에서는 ‘android:debuggable’ 속성을 AndroidManifest.xml에서 ‘true’로 설정하여 앱을 디버깅 할 수 있도록 허용한다. 기본적으로 이 속성은 비활성화(false)되어 있지만, 앱 개발 중에 실행 중 데이터 확인 등 필요에 따라 설정을 변경할 수 있다. 그러나 이 속성을 ‘true’로 설정하게 되면, 앱 실행 중 세부 정보에 접근할 수 있기 때문에 소스코드를 직접 분석하지 않고도 앱을 디버깅할 수 있다.

 

디버그 허용 시 파일 시스템 접근

 

안드로이드 로깅은 로그캣(Logcat)에 결합된 여러 표준이 결합된 형태로 사용되기 때문에 복잡하다. 사용되는 주요 표준은 다음과 같다.

 

구현 출처 로깅 항목 예시 스택 레벨 사용 가이드
RFC 5424(syslog standard) 리눅스 커널 커널, 시스템 데몬
android.util.Log 안드로이드 프레임워크, 앱 로깅 안드로이드 프레임워크
java.util.logging.level 자바 환경에서 일반적 로깅 비 시스템 애플리케이션

 

각각의 표준은 유사한 로그 레벨 구성을 가지고 있지만, 애플리케이션 레벨에서 이번 절에서는 안드로이드 프레임워크와 앱 로깅에 대해서만 다룬다. 안드로이드는 앱이 로그 정보를 출력할 수 있도록 ‘android.util.Log’ 클래스를 제공한다. 클래스의 함수를 사용해서 개발중인 코드에 목적에 맞게 단계를 구분하여 로그를 남기도록 구성한다. 다음은 로그 클래스(Log)와 디버그 로그 함수를 사용하여 로그를 남기는 방법이다.

 

디버그 로그 함수 설명
Log.d(tag, msg) debugging, 디버그 목적으로 문제 발생 가능성에 기록
Log.e(tag, msg) error, 심각한 문제 발생시 기록
Log.v(tag, msg) verbose, 동작 여부를 자세히 살펴볼 목적으로 기록
Log.w(tag, msg) warning, 심각하지 않지만 문제의 소지가 있음을 기록
Log.i(tag, msg) information, 진행과정을 모니터링하기 위해 기록

 

각 디버그 로그 함수의 태그(tag)와 메시지(msg)에는 개발자가 직접 디버깅에 필요한 정보를 나타내는 문자열을 사용한다. 소스코드 내 디버그로그를 출력하는 함수가 있다면 컴파일 시점에는 함께 컴파일되지만, 런타임 시점에는 삭제된다. 하지만 오류(error), 경고(warning), 정보(information)로그는 항상 유지된다.

 

import android.util.Log
//(...생략...)
class LoginActivity : AppCompatActivity() {
    private lateinit var loginViewModel: LoginViewModel
    private lateinit var binding: ActivityLoginBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)
        val username = binding.username
        val password = binding.password
        val login = binding.login
        val loading = binding.loading
        loginViewModel = ViewModelProvider(this, LoginViewModelFactory())
            .get(LoginViewModel::class.java)
        loginViewModel.loginFormState.observe(this@LoginActivity, Observer {
            val loginState = it ?: return@Observer
            // disable login button unless both username / password is valid
            login.isEnabled = loginState.isDataValid
            if (loginState.usernameError != null) {
                username.error = getString(loginState.usernameError)
            }
            if (loginState.passwordError != null) {
                password.error = getString(loginState.passwordError)
            }
            Log.i("LOGIN ACTIVITY - username: ", username.text.toString())
            Log.i("LOGIN ACTIVITY - password: ", password.text.toString())
        })
	//(...생략...)
	}
}

 

로그인 액티비티에서 입력 폼에 값을 입력하여 폼의 상태(State)가 변경될 때마다 디버그 로그를 출력하도록 한 코드이다. 개발자는 입출력 처리 로직을 구성할 때 데이터가 잘 처리되는지 여부를 확인할 목적으로 위와 같이 디버그 로그를 구성할 수 있다.

하지만 앱 실행 중 로그캣에 기록된 로그는 개발자 도구나 명령어를 통해 접근이 가능하다. 따라서 악의적인 공격자가 해당 로그를 열람하거나 분석을 시도할 수 있다.

 

2. 진단 방법

 

로그 출력을 보려면 단말을 연결한 후 로그캣(Logcat) 명령을 실행하면 된다. 또는 안드로이드 스튜디오 하단의 메뉴에서 로그캣을 제공하고 있다.

 

디버그 로그 내 계정정보 평문 노출

 

로그캣의 출력 형식은 다음과 같다.

 

date time PID-TID packagename priority tag message
날짜 시간 프로세스,
스레드식별자
패지키명 로그레벨 태그 메시지
23-
08-14
20:42:58 9794-9794 com.gomguk
.secondlogin
I LOGIN ACTIVITY – username: myemail@email.com
23-
-08-14
20:42:58 9794-9794 com.gomguk
.secondlogin
I LOGIN ACTIVITY – password Mypassword123!

 

예제 코드의 실행 결과로 입력폼에 값을 입력할 때 로그캣에 입력한 데이터가 평문으로 노출되는 것을 확인할 수 있다.

ADB 명령으로 로그캣 실행을 통해 디버그 로그를 확인할 수 있다. 먼저, adb shell로 단말에 접속하여 실행중인 앱의 PID(Process ID)를 식별한다.

 

 


ADB$ ps -ef | grep [앱 패키지명]


실행중인 앱 PID 식별하기

식별한 PID를 인자로 전달하여 목적하는 앱의 디버그 로그를 분석한다.


PC > adb logcat --pid=[PID]

ADB를 이용한 로그캣 로그 출력


 

로그캣은 로그 수준 설정과 관계없이 모든 메시지를 계속 수집하기 때문에 분석에 불필요한 정보가 함께 기록되어 필요한 데이터를 분석하는 데 시간이 오래 걸릴 수 있다. 안드로이드 스튜디오 내 로그캣 기능에서 로그 필터 옵션을 제공한다. 또는 로그캣 수집 및 필터링 기능을 지원하는 서드 파티 GUI 도구인 로그 필터(LogFilter)를 사용할 수 있다.

 

로그 필터를 이용한 디버그 로그 분석

 

로그 필터 실행 후 연결된 단말 또는 에뮬레이터 선택 후 필터링할 단어, 로그레벨, PID 등을 선택 후 ‘Start’를 통해 로그를 수집한다. 수집한 로그는 로그필터를 실행한 경로 내 텍스트 파일(txt)로 저장되어 실행이후에도 다시 분석할 수 있다. 위와 같이 사용자 입력 내용이 디버그 로그에 남을 경우, 사용자의 행동 패턴이나 민감한 데이터가 노출될 수 있다.

 

Log.d("Facebook-authorize", "Login Success! access_token="       + getAccessToken() + " expires="       + getAccessExpires());

 

디버그 로그에 API Key, 토큰 값이 기록되는 경우, 공격자가 획득한 키를 이용하여 API호출, 권한 탈취 등 2차 공격에 악용할 수 있다.

 

💡 주의


로그캣에 기록되는 로그는 자바에 선언된 로그 클래스(android.util.Log)의 함수에 정의된 경우에만 출력된다. 자바에서 콘솔 출력을 위해 사용하는 함수(System.out.println())나 로그 클래스를 개발자가 별도로 로그 클래스를 정의해서 사용하는 경우 로그캣을 통한 디버그 로그에 출력되지 않을 수 있다.

코드 분석 및 동적 분석을 통해 별도 선언된 로그 클래스에서도 디버그 로그 내 민감정보가 노출되는 경우 취약으로 진단한다.

 

취약여부 설명
취약 디버그 로그 내 중요정보가 노출되는 경우
양호 디버그 로그 내 중요정보가 노출되지 않는 경우

 

3. 보안 대책

 

     배포 전 디버그 로그 제거

디버그 로그는 개발 과정에서 많은 도움을 주지만, 앱을 배포할 때는 로그를 제거하거나 최소화해야 한다. 로그에는 앱의 동작 정보뿐만 아니라 민감한 데이터가 포함될 수 있으므로, 특히 개인정보나 보안 키와 같은 민감한 정보는 등은 로깅하지 않도록 주의한다.

 

 

    안전한 Log 클래스 사용

안드로이드는 Log 클래스를 사용하여 디버그 로그를 생성한다. Log 클래스는 다양한 로그 레벨(Debug, Info, Warn, Error )을 제공하며, 이를 통해 개발자는 필요에 따라 처리를 다르게 지정할 수 있다. 개발 중에는 자세한 로그를 출력하여 디버깅에 도움을 받을 수 있지만, 배포 버전에는 로그의 수준을 낮추거나 제거해야 한다.

 

반응형