MOB-SER-025: 앱 소스코드 내 운영정보 노출 | 위험도: 5 (매우 높음)
APK 디컴파일 준비 중...
// Java - Android App
public class DatabaseHelper {
// ❌ DB 정보 하드코딩!
private static final String DB_URL =
"jdbc:mysql://10.1.2.3:3306/bank_db";
private static final String DB_USER = "admin";
// ❌ 비밀번호 평문 노출!
private static final String DB_PASSWORD =
"Bank@1234!";
public Connection connect() {
return DriverManager.getConnection(
DB_URL, DB_USER, DB_PASSWORD
);
}
}
public class ApiClient {
// ❌ API 키 하드코딩!
private static final String API_KEY =
"sk_live_51HxJ9KLp2k3M4n5o6P7q8R9s0T";
// ❌ Secret 키도 노출!
private static final String API_SECRET =
"whsec_9a8b7c6d5e4f3g2h1i0j";
public void sendRequest() {
headers.put("Authorization",
"Bearer " + API_KEY);
}
}
public class CloudConfig {
// ❌ AWS 자격증명 하드코딩!
public static final String AWS_ACCESS_KEY =
"AKIAIOSFODNN7EXAMPLE";
public static final String AWS_SECRET_KEY =
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
public static final String S3_BUCKET =
"xxbank-customer-data";
}
jadx로 APK 역분석
// Java - 안전한 설정 관리
// ✅ 방법 1: 환경변수 사용
public class SecureDatabaseHelper {
private static final String DB_URL =
System.getenv("DATABASE_URL");
private static final String DB_USER =
System.getenv("DATABASE_USER");
// 환경변수에서 안전하게 로드
private static final String DB_PASSWORD =
System.getenv("DATABASE_PASSWORD");
public Connection connect() {
if (DB_URL == null || DB_PASSWORD == null) {
throw new IllegalStateException(
"Database credentials not configured"
);
}
return DriverManager.getConnection(
DB_URL, DB_USER, DB_PASSWORD
);
}
}
// ✅ 방법 2: 암호화된 설정 파일
public class EncryptedConfigManager {
private Properties config;
private static final String CONFIG_FILE =
"encrypted.properties";
public EncryptedConfigManager() {
loadEncryptedConfig();
}
private void loadEncryptedConfig() {
try {
// 암호화된 파일 읽기
InputStream is = getClass()
.getResourceAsStream(CONFIG_FILE);
// AES 복호화
byte[] encryptedData = is.readAllBytes();
byte[] decryptedData = decrypt(
encryptedData,
getDeviceKey()
);
// Properties 로드
config = new Properties();
config.load(new ByteArrayInputStream(
decryptedData
));
} catch (Exception e) {
throw new RuntimeException(
"Failed to load config", e
);
}
}
private byte[] decrypt(byte[] data, byte[] key) {
try {
Cipher cipher = Cipher.getInstance(
"AES/GCM/NoPadding"
);
SecretKeySpec keySpec =
new SecretKeySpec(key, "AES");
GCMParameterSpec gcmSpec =
new GCMParameterSpec(128,
Arrays.copyOf(data, 12));
cipher.init(Cipher.DECRYPT_MODE,
keySpec, gcmSpec);
return cipher.doFinal(data, 12,
data.length - 12);
} catch (Exception e) {
throw new RuntimeException(
"Decryption failed", e
);
}
}
private byte[] getDeviceKey() {
// Android KeyStore에서 키 가져오기
try {
KeyStore keyStore = KeyStore
.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyStore.SecretKeyEntry entry =
(KeyStore.SecretKeyEntry) keyStore
.getEntry("ConfigKey", null);
return entry.getSecretKey()
.getEncoded();
} catch (Exception e) {
throw new RuntimeException(
"Failed to get device key", e
);
}
}
public String getProperty(String key) {
return config.getProperty(key);
}
}
// ✅ 방법 3: Android KeyStore 활용
public class SecureApiClient {
private static final String KEY_ALIAS =
"api_key_alias";
public SecureApiClient() {
initializeKeyStore();
}
private void initializeKeyStore() {
try {
KeyStore keyStore = KeyStore
.getInstance("AndroidKeyStore");
keyStore.load(null);
// KeyStore에 키가 없으면 생성
if (!keyStore.containsAlias(KEY_ALIAS)) {
KeyGenerator keyGenerator =
KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
);
keyGenerator.init(
new KeyGenParameterSpec
.Builder(KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(
KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(
KeyProperties
.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(false)
.build()
);
keyGenerator.generateKey();
}
} catch (Exception e) {
throw new RuntimeException(
"KeyStore init failed", e
);
}
}
public String getApiKey() {
// 서버에서 안전하게 가져오기
try {
// 1. 디바이스 인증
String deviceToken = getDeviceToken();
// 2. 서버에 API 키 요청
Response response = apiService
.getApiKey(deviceToken);
// 3. KeyStore에 암호화 저장
String apiKey = response.getApiKey();
encryptAndStore(apiKey);
return apiKey;
} catch (Exception e) {
throw new RuntimeException(
"Failed to get API key", e
);
}
}
private void encryptAndStore(String data) {
try {
KeyStore keyStore = KeyStore
.getInstance("AndroidKeyStore");
keyStore.load(null);
SecretKey key = (SecretKey) keyStore
.getKey(KEY_ALIAS, null);
Cipher cipher = Cipher.getInstance(
"AES/GCM/NoPadding"
);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(
data.getBytes()
);
// SharedPreferences에 암호화된 데이터 저장
SharedPreferences prefs = context
.getSharedPreferences(
"secure_prefs",
Context.MODE_PRIVATE
);
prefs.edit()
.putString("encrypted_api_key",
Base64.encodeToString(
encrypted,
Base64.DEFAULT
))
.apply();
} catch (Exception e) {
throw new RuntimeException(
"Encryption failed", e
);
}
}
}
// ✅ 방법 4: 백엔드에서 키 관리
public class SecureCloudConfig {
// 하드코딩 대신 서버에서 가져오기
public CloudCredentials getCloudCredentials() {
try {
// 1. 사용자 인증 토큰
String authToken = getAuthToken();
// 2. 서버에 임시 자격증명 요청
Response response = cloudService
.getTemporaryCredentials(authToken);
// 3. STS (임시 자격증명) 반환
return new CloudCredentials(
response.getAccessKey(),
response.getSecretKey(),
response.getSessionToken(),
response.getExpiration()
);
} catch (Exception e) {
throw new RuntimeException(
"Failed to get credentials", e
);
}
}
}
// ProGuard 설정 (난독화)
# proguard-rules.pro
# 문자열 암호화
-keep class com.example.bank.** { *; }
-obfuscate
-repackageclasses 'o'
# 클래스/메서드 이름 난독화
-keepclassmembers class * {
native ;
}
# 리플렉션 방지
-keepattributes *Annotation*
-keepattributes Signature
-keepattributes Exceptions
// build.gradle 설정
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'
), 'proguard-rules.pro'
// 환경변수 주입
buildConfigField "String", "API_URL",
System.getenv("API_URL")
}
}
}